diff --git a/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java b/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java index 7bcf009..ee60fa5 100644 --- a/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java +++ b/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java @@ -21,6 +21,7 @@ import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; import org.json.JSONObject; @@ -35,6 +36,13 @@ public class HyperFragmentViewManager extends ViewGroupManager { private static final int COMMAND_PROCESS = 175; private final ReactApplicationContext reactContext; + // Track props for each view + private String currentNamespace = null; + private String currentPayload = null; + private FrameLayout currentView = null; + + // Architecture detection + private final Boolean newArchEnabled = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; public HyperFragmentViewManager(ReactApplicationContext reactContext) { this.reactContext = reactContext; @@ -58,6 +66,59 @@ protected FrameLayout createViewInstance(@NonNull ThemedReactContext context) { public Map getCommandsMap() { return MapBuilder.of("process", COMMAND_PROCESS); } + // Fabric-compatible props + @ReactProp(name = "namespace") + public void setNamespace(FrameLayout view, @Nullable String namespace) { + currentNamespace = namespace; + currentView = view; + tryProcessProps(); + } + + @ReactProp(name = "payload") + public void setPayload(FrameLayout view, @Nullable String payload) { + currentPayload = payload; + currentView = view; + tryProcessProps(); + } + + private void tryProcessProps() { + if (currentNamespace != null && currentPayload != null && currentView != null && newArchEnabled) { + currentView.post(() -> { + processWithProps(currentView, currentNamespace, currentPayload); + }); + } + } + + private void processWithProps(FrameLayout view, String namespace, String payload) { + try { + setupLayout(view); + + JSONObject fragments = new JSONObject(); + fragments.put(namespace, view); + + JSONObject payloadObj = new JSONObject(payload); + payloadObj.getJSONObject("payload").put("fragmentViewGroups", fragments); + + FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity(); + HyperServices hyperServices = HyperSdkReactModule.getHyperServices(); + + if (activity == null || hyperServices == null) { + return; + } + + hyperServices.process(activity, payloadObj); + + } catch (Exception e) { + SdkTracker.trackAndLogBootException( + NAME, + LogConstants.CATEGORY_LIFECYCLE, + LogConstants.SUBCATEGORY_HYPER_SDK, + LogConstants.SDK_TRACKER_LABEL, + "Exception in processWithProps", + e + ); + } + } @Override public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) { diff --git a/example/App.js b/example/App.js new file mode 100644 index 0000000..d1952c3 --- /dev/null +++ b/example/App.js @@ -0,0 +1,366 @@ +import React, {useEffect, useState} from 'react'; +import { + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, + Alert, + useColorScheme, +} from 'react-native'; + +// Import HyperSDK +import HyperSdkReact, { HyperFragmentView } from 'hyper-sdk-react'; +import { NativeEventEmitter, NativeModules } from 'react-native'; + +console.log('๐Ÿ” [App] HyperSdkReact imported:', !!HyperSdkReact); +console.log('๐Ÿ” [App] HyperFragmentView imported:', !!HyperFragmentView); + +function App() { + const isDarkMode = useColorScheme() === 'dark'; + const [sdkInitialized, setSdkInitialized] = useState(false); + const [paymentStatus, setPaymentStatus] = useState('Ready'); + const [processPayload, setProcessPayload] = useState(''); // Add this for HyperFragmentView + + console.log('๐Ÿ” [App] Component rendering - processPayload length:', processPayload.length); + console.log('๐Ÿ” [App] Component rendering - processPayload isEmpty:', processPayload === ''); + + const backgroundStyle = { + backgroundColor: isDarkMode ? '#000' : '#fff', + flex: 1, + }; + + // UUID generator function + const generateUUID = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + + useEffect(() => { + console.log('๐Ÿš€ App started - Initializing HyperSDK...'); + + // Check and log architecture info + try { + const archInfo = HyperSdkReact.getArchitectureInfo(); + console.log('๐Ÿ—๏ธ [Architecture Detection]', archInfo); + setPaymentStatus('Architecture: ' + archInfo.split(':')[1].trim()); + } catch (error) { + console.log('โŒ [Architecture Detection] Error:', error); + setPaymentStatus('Architecture detection failed'); + } + + // Set up event listener for HyperSDK events + const eventEmitter = new NativeEventEmitter(NativeModules.HyperSdkReact); + const eventListener = eventEmitter.addListener('HyperEvent', (resp) => { + console.log('๐ŸŽ‰ HyperEvent received:', resp); + try { + const data = JSON.parse(resp); + console.log('๐Ÿ“Š Parsed HyperEvent:', JSON.stringify(data, null, 2)); + + switch (data.event) { + case 'initiate_result': + if (data.error) { + console.error('โŒ HyperSDK initiate error:', data.errorMessage); + setSdkInitialized(false); + setPaymentStatus('Error: ' + data.errorMessage); + } else { + console.log('โœ… HyperSDK initiate successful'); + setSdkInitialized(true); + setPaymentStatus('SDK Initialized Successfully'); + + // Auto-create payment session with HyperFragmentView support + fetch('https://sandbox.juspay.in/session', { + method: 'POST', + headers: { + 'Authorization': 'Basic OTNBQTlFNzdCMzA0NzkwQUI5NUVBOEE4NjMwMDcxOg==', + 'x-merchantid': 'picasso', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + order_id: Date.now().toString(), + amount: '1.0', + customer_id: 'testing-customer-one', + customer_email: 'test@mail.com', + customer_phone: '9876543210', + payment_page_client_id: 'instaastro', + action: 'paymentPage', + return_url: 'https://youtube.com', + description: 'Complete your payment', + first_name: 'John', + last_name: 'wick', + features: { paymentWidget: { enable: true } }, // Enable payment widget + }), + }) + .then(response => response.json()) + .then(data => { + console.log('Session API response:', data.sdk_payload); + setProcessPayload(JSON.stringify(data.sdk_payload)); // Set payload for HyperFragmentView + }) + .catch(error => { + console.error('Session API error:', error); + }); + } + break; + case 'process_result': + console.log('๐Ÿ’ณ Payment process result:', data); + setPaymentStatus('Payment Completed'); + break; + default: + console.log('๐Ÿ“ฑ HyperSDK Event:', data.event, data); + } + } catch (error) { + console.error('โŒ Error parsing HyperEvent:', error); + setPaymentStatus('Error parsing event'); + } + }); + + // Initialize HyperSDK after a short delay + const initializeHyperSDK = async () => { + try { + console.log('๐Ÿ“ฑ Creating HyperSDK services...'); + setPaymentStatus('Creating services...'); + + // Create Hyper services first + await HyperSdkReact.createHyperServices(); + console.log('โœ… HyperSDK services created'); + + setPaymentStatus('Initializing SDK...'); + + const initiatePayload = { + requestId: generateUUID(), + service: 'in.juspay.hyperpay', + payload: { + action: 'initiate', + merchantId: 'picasso', // Updated to match your setup + clientId: 'instaastro', // Updated to match your setup + environment: 'sandbox', + }, + }; + + console.log('๐Ÿš€ HyperSDK initiate payload:', JSON.stringify(initiatePayload, null, 2)); + await HyperSdkReact.initiate(JSON.stringify(initiatePayload)); + console.log('โœ… HyperSDK initiate called'); + + } catch (error) { + console.error('โŒ HyperSDK initialization failed:', error); + setPaymentStatus('SDK Init Failed: ' + error.message); + } + }; + + // Initialize after component mounts + setTimeout(initializeHyperSDK, 2000); + + // Cleanup function + return () => { + eventListener.remove(); + console.log('๐Ÿงน HyperSDK event listener cleaned up'); + }; + }, []); + + const startPayment = async () => { + try { + console.log('๐Ÿ’ณ Starting payment process...'); + setPaymentStatus('Processing Payment...'); + + const payload = { + "requestId": generateUUID(), + "service": "in.juspay.hyperpay", + "payload": { + "action": "paymentPage", + "clientId": "instaastro", + "merchantId": "picasso", + "clientAuthToken": "tkn_6d39490435174c3b8c88ca331f954ce6", + "clientAuthTokenExpiry": "2025-12-31T23:59:59Z", + "environment": "sandbox", + "orderId": "order_" + Date.now(), + "amount": "100.0", + "currency": "INR", + "customerId": "testing-customer-one", + "customerEmail": "test@mail.com", + "customerPhone": "9876543210", + "firstName": "John", + "lastName": "Wick", + "returnUrl": "https://google.com", + "description": "Complete your payment" + } + }; + + console.log('๐Ÿ“‹ Payment payload:', JSON.stringify(payload, null, 2)); + await HyperSdkReact.process(JSON.stringify(payload)); + console.log('โœ… Payment process initiated'); + + } catch (error) { + console.error('โŒ Payment failed:', error); + setPaymentStatus('Payment Failed: ' + error.message); + Alert.alert('Payment Error', error.message); + } + }; + + return ( + + + + + HyperSDK React Native + + + Status: + + {paymentStatus} + + + + + โœ… React Native 0.81.4 + โœ… New Architecture (Fabric + TurboModules) + โœ… HyperSDK v4.0.6 + โœ… Hermes Engine + โœ… HyperFragmentView Support + + + + + {sdkInitialized ? 'Start Payment (โ‚น100)' : 'SDK Initializing...'} + + + + {/* HyperFragmentView - Shows when payment widget is ready */} + {processPayload !== '' && ( + + Payment Widget (With Payload): + + {console.log('๐Ÿ” [App] Rendering HyperFragmentView with payload length:', processPayload.length)} + + + + )} + + {/* TEST: Always render HyperFragmentView to debug */} + {/* + Test HyperFragmentView (Always Rendered): + + {console.log('๐Ÿ” [App] Rendering TEST HyperFragmentView')} + + + */} + + + Check console logs for detailed HyperSDK integration information + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 20, + marginTop: 50, + }, + title: { + fontSize: 24, + fontWeight: 'bold', + marginBottom: 10, + textAlign: 'center', + color: '#007AFF', + }, + subtitle: { + fontSize: 16, + marginBottom: 30, + textAlign: 'center', + color: '#666', + }, + statusContainer: { + marginBottom: 30, + alignItems: 'center', + }, + statusLabel: { + fontSize: 16, + fontWeight: '600', + marginBottom: 5, + }, + status: { + fontSize: 14, + textAlign: 'center', + fontWeight: '500', + }, + infoContainer: { + marginBottom: 30, + alignItems: 'center', + }, + infoText: { + fontSize: 14, + marginBottom: 5, + color: '#4CAF50', + }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 30, + paddingVertical: 15, + borderRadius: 8, + marginBottom: 20, + minWidth: 200, + }, + buttonDisabled: { + backgroundColor: '#ccc', + }, + buttonText: { + color: '#fff', + fontSize: 16, + fontWeight: '600', + textAlign: 'center', + }, + fragmentContainer: { + marginTop: 20, + marginBottom: 20, + alignItems: 'center', + }, + fragmentTitle: { + fontSize: 16, + fontWeight: '600', + marginBottom: 10, + color: '#007AFF', + }, + fragmentWrapper: { + backgroundColor: '#f0f0f0', + borderRadius: 8, + padding: 10, + borderWidth: 2, + borderColor: '#007AFF', + }, + note: { + fontSize: 12, + color: '#999', + textAlign: 'center', + fontStyle: 'italic', + }, +}); + +export default App; diff --git a/ios/HyperSdkReact.mm b/ios/HyperSdkReact.mm index ca3ad01..fbb9a14 100644 --- a/ios/HyperSdkReact.mm +++ b/ios/HyperSdkReact.mm @@ -439,6 +439,13 @@ + (NSString*)dictionaryToString:(id)dict{ @end @implementation HyperFragmentViewManagerIOS +{ + // Track props for each view + NSString *_currentNamespace; + NSString *_currentPayload; + UIView *_currentView; +} + RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue{ @@ -454,6 +461,66 @@ - (UIView *)view return [[UIView alloc] init]; } +// Fabric-compatible properties +RCT_EXPORT_VIEW_PROPERTY(namespace, NSString) +RCT_EXPORT_VIEW_PROPERTY(payload, NSString) +RCT_EXPORT_VIEW_PROPERTY(height, NSNumber) +RCT_EXPORT_VIEW_PROPERTY(width, NSNumber) + +- (void)setNamespace:(NSString *)namespace forView:(UIView *)view +{ + _currentNamespace = namespace; + _currentView = view; + [self tryProcessProps]; +} + +- (void)setPayload:(NSString *)payload forView:(UIView *)view +{ + _currentPayload = payload; + _currentView = view; + [self tryProcessProps]; +} + +- (void)tryProcessProps +{ + if (_currentNamespace && _currentPayload && _currentView && HAS_NEW_ARCH_SUPPORT) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self processWithPropsForView:_currentView namespace:_currentNamespace payload:_currentPayload]; + }); + } +} + +- (void)processWithPropsForView:(UIView *)view namespace:(NSString *)namespace payload:(NSString *)payload +{ + HyperServices *hyperServicesInstance = _hyperServicesReference; + if (payload && payload.length > 0) { + @try { + NSDictionary *jsonData = [HyperSdkReact stringToDictionary:payload]; + if (jsonData && [jsonData isKindOfClass:[NSDictionary class]] && jsonData.allKeys.count > 0) { + if (hyperServicesInstance.baseViewController == nil || hyperServicesInstance.baseViewController.view.window == nil) { + id baseViewController = RCTPresentedViewController(); + if ([baseViewController isMemberOfClass:RCTModalHostViewController.class] && [baseViewController presentingViewController]) { + [hyperServicesInstance setBaseViewController:[baseViewController presentingViewController]]; + } else { + [hyperServicesInstance setBaseViewController:baseViewController]; + } + } + + [self manuallyLayoutChildren:view]; + + NSMutableDictionary *nestedPayload = [jsonData[@"payload"] mutableCopy]; + NSDictionary *fragmentViewGroup = @{namespace: view}; + nestedPayload[@"fragmentViewGroups"] = fragmentViewGroup; + NSMutableDictionary *updatedJsonData = [jsonData mutableCopy]; + updatedJsonData[@"payload"] = nestedPayload; + [hyperServicesInstance process:[updatedJsonData copy]]; + } + } @catch (NSException *exception) { + // Handle exception silently + } + } +} + RCT_EXPORT_METHOD(process:(nonnull NSNumber *)viewTag nameSpace:(NSString *)nameSpace payload:(NSString *)payload) { HyperServices *hyperServicesInstance = _hyperServicesReference; @@ -495,5 +562,4 @@ - (void)manuallyLayoutChildren:(UIView *)view { view.frame = parent.bounds; } -@end - +@end \ No newline at end of file diff --git a/src/HyperFragmentView.tsx b/src/HyperFragmentView.tsx index 7861b57..26085e9 100644 --- a/src/HyperFragmentView.tsx +++ b/src/HyperFragmentView.tsx @@ -71,9 +71,16 @@ const HyperFragmentView: React.FC = ({ return ( - + ); }; -export default HyperFragmentView; +export default HyperFragmentView; \ No newline at end of file