UNPKG

quivio-transaction-processor

Version:

React Native hook for Card payment integration with DataCap

703 lines (589 loc) 22.3 kB
# Quivio Transaction Processor A comprehensive React Native library for EMV payment integration with DataCap terminals. This package provides a robust, TypeScript-first approach to integrating EMV card reader functionality into React Native Android applications. ## 🚀 Features - **🔌 Easy Integration**: Simple provider-based API for React Native - **💳 Full EMV Support**: Complete EMV card reading and processing capabilities - **🤖 Android Native**: Optimized native Android implementation with EMV libraries - **🔄 Real-time Events**: Comprehensive event system for payment status updates - **📊 Built-in Logging**: Detailed transaction logging for debugging and monitoring - **⚡ TypeScript Support**: Full TypeScript definitions and type safety - **🔄 Recurring Payments**: Support for recurring payment transactions - **🎯 Multiple Payment Types**: Support for sale, in-house, recurring, and card replacement transactions - **🛡️ Error Handling**: Robust error handling and recovery mechanisms - **🎯 Context-Based State Management**: Centralized state management with React Context ## 📦 Installation ```bash npm install quivio-transaction-processor # or yarn add quivio-transaction-processor ``` ## 🏗️ Architecture Overview ### Context-Based State Management The package uses React Context for state management, providing: - **Centralized State**: All payment state managed in one place - **Automatic Initialization**: EMV manager initialized on provider mount - **Event Management**: Built-in event subscription and cleanup - **Type Safety**: Full TypeScript support with proper typing ### Provider Pattern ```tsx // Wrap your app or component with PaymentProvider <PaymentProvider config={emvConfig}> <YourPaymentComponent /> </PaymentProvider> // Use the hook in any child component const { handleCardPayment, isDeviceConnected } = useEMVPayment(); ``` ## 🔧 Android Setup This package includes native Android libraries that require proper configuration. Follow these steps carefully: ### 1. Add Native Libraries First, ensure the `dsiEMVAndroid.aar` file is present in your `android/app/libs/` folder. This file contains the core EMV functionality. **Important**: Create the `libs` folder if it doesn't exist in your `android/app/` directory. ### 2. Update settings.gradle Add the following lines to your `android/settings.gradle`: ```gradle include ':emvCardReaderLib' project(':emvCardReaderLib').projectDir = file('../node_modules/quivio-transaction-processor/libs/emvCardReaderLib') include ':emvNative' project(':emvNative').projectDir = file('../node_modules/quivio-transaction-processor/libs/emvNative') ``` ### 3. Update app/build.gradle Add the following dependencies to your `android/app/build.gradle`: ```gradle dependencies { // ... other dependencies implementation files("libs/dsiEMVAndroid.aar") implementation project(":emvCardReaderLib") implementation project(":emvNative") } ``` **Critical**: The `implementation files("libs/dsiEMVAndroid.aar")` line is essential for EMV functionality. ### 4. Update MainApplication.kt Add the import and package registration to your `android/app/src/main/java/com/your-app/MainApplication.kt`: ```kotlin import com.quivio_transaction_processor.EMVPaymentPackage class MainApplication : Application(), ReactApplication { private val mReactNativeHost = object : ReactNativeHost(this) { override fun getPackages(): List<ReactPackage> { return PackageList(this).packages.apply { // Add the EMV payment package add(EMVPaymentPackage()) } } // ... rest of your MainApplication code } } ``` ## 🎯 Quick Start ### Basic Implementation with PaymentProvider ```tsx import React from 'react'; import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; import { PaymentProvider, useEMVPayment, EMVConfig } from 'quivio-transaction-processor'; const emvConfig: EMVConfig = { merchantID: "YOUR_MERCHANT_ID", onlineMerchantID: "YOUR_ONLINE_MERCHANT_ID", isSandBox: true, // true for testing, false for production secureDeviceName: "YOUR_DEVICE_NAME", operatorID: "YOUR_OPERATOR_ID", posPackageID: "com.your_app:1.0" // App Bundle ID and version }; const PaymentScreen = () => { return ( <PaymentProvider config={emvConfig}> <PaymentContent /> </PaymentProvider> ); }; const PaymentContent = () => { const { logs, isDeviceConnected, loading, isInitialized, handleCardPayment, setupConfig, EVENTS } = useEMVPayment(); return ( <View style={{ flex: 1, padding: 16 }}> <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}> Status: {isInitialized ? '✅ Initialized' : '❌ Not Initialized'} </Text> <Text style={{ fontSize: 16, marginBottom: 10 }}> Device: {isDeviceConnected ? '✅ Connected' : '❌ Not Connected'} </Text> <TouchableOpacity style={{ backgroundColor: isDeviceConnected ? '#4CAF50' : '#FF9800', padding: 12, borderRadius: 8, marginBottom: 10 }} onPress={setupConfig} disabled={loading} > <Text style={{ color: 'white', textAlign: 'center' }}> {isDeviceConnected ? 'Configuration Ready' : 'Setup Configuration'} </Text> </TouchableOpacity> <TouchableOpacity style={{ backgroundColor: (loading || !isDeviceConnected) ? '#ccc' : '#2196F3', padding: 12, borderRadius: 8, marginBottom: 10 }} onPress={() => handleCardPayment('5.00')} disabled={loading || !isDeviceConnected} > <Text style={{ color: 'white', textAlign: 'center' }}> Process Payment ($5.00) </Text> </TouchableOpacity> {loading && ( <Text style={{ textAlign: 'center', color: '#FF9800', marginBottom: 10 }}> Processing... </Text> )} <ScrollView style={{ flex: 1 }}> {logs.map((log, index) => ( <View key={index} style={{ backgroundColor: '#f5f5f5', padding: 8, marginBottom: 5, borderRadius: 4 }}> <Text style={{ fontWeight: 'bold' }}>{log.type}</Text> <Text>{JSON.stringify(log.payload, null, 2)}</Text> </View> ))} </ScrollView> </View> ); }; ``` ### Advanced Implementation with Event Handling ```tsx import React, { useEffect } from 'react'; import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; import { PaymentProvider, useEMVPayment, EMVConfig } from 'quivio-transaction-processor'; const emvConfig: EMVConfig = { merchantID: "YOUR_MERCHANT_ID", onlineMerchantID: "YOUR_ONLINE_MERCHANT_ID", isSandBox: true, secureDeviceName: "YOUR_DEVICE_NAME", operatorID: "YOUR_OPERATOR_ID", posPackageID: "com.your_app:1.0" }; const AdvancedPaymentScreen = () => { return ( <PaymentProvider config={emvConfig}> <AdvancedPaymentContent /> </PaymentProvider> ); }; const AdvancedPaymentContent = () => { const { logs, isDeviceConnected, loading, isInitialized, handleCardPayment, handleInHousePayment, runRecurringTransaction, replaceCardInRecurring, setupConfig, pingConfig, clearAllTransactions, cancelOperation, subscribeToEvent, unsubscribeFromEvent, EVENTS } = useEMVPayment(); useEffect(() => { // Subscribe to payment events const saleListener = subscribeToEvent(EVENTS.onSaleTransactionCompleted, (payload) => { console.log('Sale completed:', payload); // Handle successful sale transaction }); const cardReadListener = subscribeToEvent(EVENTS.onCardReadSuccessfully, (payload) => { console.log('Card read successfully:', payload); // Handle card read event }); const errorListener = subscribeToEvent(EVENTS.onError, (payload) => { console.error('Payment error:', payload); // Handle error events }); const configSuccessListener = subscribeToEvent(EVENTS.onConfigCompleted, (payload) => { console.log('Configuration completed:', payload); // Handle successful configuration }); const deviceConnectedListener = subscribeToEvent(EVENTS.onConfigPingSuccess, (payload) => { console.log('Device connected:', payload); // Handle device connection success }); // Cleanup on unmount return () => { if (saleListener) unsubscribeFromEvent(EVENTS.onSaleTransactionCompleted, saleListener.listener); if (cardReadListener) unsubscribeFromEvent(EVENTS.onCardReadSuccessfully, cardReadListener.listener); if (errorListener) unsubscribeFromEvent(EVENTS.onError, errorListener.listener); if (configSuccessListener) unsubscribeFromEvent(EVENTS.onConfigCompleted, configSuccessListener.listener); if (deviceConnectedListener) unsubscribeFromEvent(EVENTS.onConfigPingSuccess, deviceConnectedListener.listener); }; }, [subscribeToEvent, unsubscribeFromEvent, EVENTS]); return ( <View style={{ flex: 1, padding: 16 }}> <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}> Status: {isInitialized ? '✅ Initialized' : '❌ Not Initialized'} </Text> <Text style={{ fontSize: 16, marginBottom: 10 }}> Device: {isDeviceConnected ? '✅ Connected' : '❌ Not Connected'} </Text> <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 10 }}> <TouchableOpacity style={{ backgroundColor: '#4CAF50', padding: 10, borderRadius: 8, flex: 1, minWidth: 120 }} onPress={setupConfig} disabled={loading} > <Text style={{ color: 'white', textAlign: 'center', fontSize: 12 }}> Setup Config </Text> </TouchableOpacity> <TouchableOpacity style={{ backgroundColor: '#2196F3', padding: 10, borderRadius: 8, flex: 1, minWidth: 120 }} onPress={pingConfig} disabled={loading} > <Text style={{ color: 'white', textAlign: 'center', fontSize: 12 }}> Ping Config </Text> </TouchableOpacity> </View> <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 10 }}> <TouchableOpacity style={{ backgroundColor: (loading || !isDeviceConnected) ? '#ccc' : '#9C27B0', padding: 10, borderRadius: 8, flex: 1, minWidth: 120 }} onPress={() => handleCardPayment('5.00')} disabled={loading || !isDeviceConnected} > <Text style={{ color: 'white', textAlign: 'center', fontSize: 12 }}> EMV Sale </Text> </TouchableOpacity> <TouchableOpacity style={{ backgroundColor: (loading || !isDeviceConnected) ? '#ccc' : '#FF5722', padding: 10, borderRadius: 8, flex: 1, minWidth: 120 }} onPress={handleInHousePayment} disabled={loading || !isDeviceConnected} > <Text style={{ color: 'white', textAlign: 'center', fontSize: 12 }}> In-House </Text> </TouchableOpacity> </View> <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 10 }}> <TouchableOpacity style={{ backgroundColor: (loading || !isDeviceConnected) ? '#ccc' : '#607D8B', padding: 10, borderRadius: 8, flex: 1, minWidth: 120 }} onPress={() => runRecurringTransaction('5.00')} disabled={loading || !isDeviceConnected} > <Text style={{ color: 'white', textAlign: 'center', fontSize: 12 }}> Recurring </Text> </TouchableOpacity> <TouchableOpacity style={{ backgroundColor: (loading || !isDeviceConnected) ? '#ccc' : '#795548', padding: 10, borderRadius: 8, flex: 1, minWidth: 120 }} onPress={replaceCardInRecurring} disabled={loading || !isDeviceConnected} > <Text style={{ color: 'white', textAlign: 'center', fontSize: 12 }}> Replace Card </Text> </TouchableOpacity> </View> <TouchableOpacity style={{ backgroundColor: '#F44336', padding: 10, borderRadius: 8, marginBottom: 10 }} onPress={clearAllTransactions} disabled={loading} > <Text style={{ color: 'white', textAlign: 'center' }}> Clear Logs </Text> </TouchableOpacity> {loading && ( <Text style={{ textAlign: 'center', color: '#FF9800', marginBottom: 10 }}> Processing... </Text> )} <ScrollView style={{ flex: 1 }}> {logs.map((log, index) => ( <View key={index} style={{ backgroundColor: '#f5f5f5', padding: 8, marginBottom: 5, borderRadius: 4 }}> <Text style={{ fontWeight: 'bold' }}>{log.type}</Text> <Text>{JSON.stringify(log.payload, null, 2)}</Text> </View> ))} </ScrollView> </View> ); }; ``` ## 📚 API Reference ### PaymentProvider Component The main provider component that manages EMV payment state and initialization. #### Props | Prop | Type | Required | Description | |------|------|----------|-------------| | `config` | `EMVConfig` | Yes | EMV configuration object | | `children` | `React.ReactNode` | Yes | Child components | #### Configuration Object ```typescript interface EMVConfig { merchantID: string; // Your merchant ID onlineMerchantID: string; // Your online merchant ID isSandBox: boolean; // true for testing, false for production secureDeviceName: string; // Terminal device name operatorID: string; // Employee/operator ID posPackageID: string; // App Bundle ID and version } ``` ### useEMVPayment Hook The main hook that provides all EMV payment functionality. Must be used within a `PaymentProvider`. #### Parameters None - the hook automatically uses the configuration from the `PaymentProvider`. #### Returns | Property | Type | Description | |----------|------|-------------| | `logs` | `CallbackLog[]` | Array of transaction logs for debugging | | `isDeviceConnected` | `boolean` | Device connection status | | `isInitialized` | `boolean` | EMV initialization status | | `loading` | `boolean` | Loading state for operations | | `handleCardPayment` | `(amount: string) => void` | Process EMV card payment | | `handleInHousePayment` | `() => void` | Process in-house payment | | `runRecurringTransaction` | `(amount: string) => void` | Process recurring payment | | `replaceCardInRecurring` | `() => void` | Replace card in recurring setup | | `setupConfig` | `() => void` | Setup device configuration | | `pingConfig` | `() => void` | Ping device configuration | | `clearTransactionListener` | `() => void` | Clear transaction listener | | `clearAllTransactions` | `() => void` | Clear all transaction logs | | `cancelOperation` | `() => void` | Cancel current operation | | `initializeEMV` | `() => void` | Manually initialize EMV | | `subscribeToEvent` | `(eventName, callback) => Listener` | Subscribe to events | | `unsubscribeFromEvent` | `(eventName, callback) => void` | Unsubscribe from events | | `EVENTS` | `Record<EMVEventName, EMVEventName>` | Available event names | ### Available Events The following events are available for subscription: | Event | Description | Payload | |-------|-------------|---------| | `onError` | Payment or device errors | Error message | | `onCardReadSuccessfully` | Card successfully read | Card data with BIN | | `onSaleTransactionCompleted` | Sale transaction completed | Transaction details | | `onRecurringSaleCompleted` | Recurring sale completed | Recurring transaction details | | `onShowMessage` | Display messages from device | Message content | | `onConfigError` | Configuration errors | Error details | | `onConfigPingFailed` | Configuration ping failed | Failure details | | `onConfigPingSuccess` | Configuration ping successful | Success details | | `onConfigCompleted` | Configuration setup completed | Configuration details | ### Transaction Response Types #### Sale Transaction Response ```typescript interface SaleTransactionResponse { cmdStatus: string; textResponse: string; sequenceNo: string; userTrace: string; acctNo: string; cardType: string; authCode: string; captureStatus: string; refNo: string; invoiceNo: string; amount: { purchase: string }; acqRefData: string; entryMethod: string; date: string; time: string; } ``` #### Recurring Transaction Response ```typescript interface RecurringTransactionResponse { cmdStatus: string; textResponse: string; sequenceNo: string; userTrace: string; captureStatus: string; refNo: string; invoiceNo: string; amount: { purchase: string }; cardholderName: string; acctNo: string; cardType: string; authCode: string; entryMethod: string; recordNo: string; recurringData: string; acqRefData: string; date: string; time: string; payAPIId: string; } ``` ## 🛠️ Troubleshooting ### Common Issues and Solutions #### 1. Device Not Connecting **Symptoms**: `isDeviceConnected` remains false **Solutions**: - Ensure device is powered on and in pairing mode - Check Bluetooth permissions in app settings - Verify device compatibility with your terminal - Try restarting the device and re-pairing #### 2. Payment Processing Fails **Symptoms**: Transactions fail or timeout **Solutions**: - Verify device connection status before processing - Ensure card is properly inserted/swiped - Check amount format (use "10.00" not "10") - Verify merchant configuration is correct #### 3. Events Not Firing **Symptoms**: No event callbacks received **Solutions**: - Ensure proper event subscription - Check that device is properly configured - Verify event names match exactly - Check for JavaScript errors in console #### 4. Initialization Issues **Symptoms**: `isInitialized` remains false **Solutions**: - Verify all configuration parameters are provided - Check that `posPackageID` matches your app's bundle ID - Ensure native modules are properly linked - Check Android build for compilation errors ### Debug Mode The hook provides detailed logging through the `logs` array. Display these logs to debug issues: ```tsx {logs.map((log, index) => ( <View key={index} style={styles.logItem}> <Text style={styles.logType}>{log.type}</Text> <Text style={styles.logPayload}> {typeof log.payload === 'object' ? JSON.stringify(log.payload, null, 2) : String(log.payload)} </Text> <Text style={styles.logTime}> {new Date(log.timestamp).toLocaleTimeString()} </Text> </View> ))} ``` ### Error Handling Best Practices 1. **Always check device status** before processing payments 2. **Subscribe to error events** to handle failures gracefully 3. **Implement timeout handling** for long-running operations 4. **Provide user feedback** during loading states 5. **Log all transactions** for debugging and audit trails ## 📱 Example Implementation The package includes a complete example implementation that demonstrates: - Complete UI implementation with modern styling - Event handling with proper cleanup - Error management and user feedback - Loading states and disabled buttons - Transaction logging with formatted display - Multiple payment types (sale, in-house, recurring, card replacement) ### Using the Complete Example Component ```tsx import React from 'react'; import { EMVPaymentScreenExample, EMVConfig } from 'quivio-transaction-processor'; const App = () => { const emvConfig: EMVConfig = { merchantID: "YOUR_MERCHANT_ID", onlineMerchantID: "YOUR_ONLINE_MERCHANT_ID", isSandBox: true, secureDeviceName: "YOUR_DEVICE_NAME", operatorID: "YOUR_OPERATOR_ID", posPackageID: "com.your_app:1.0" }; return <EMVPaymentScreenExample config={emvConfig} />; }; ``` ### Example Features The example component includes: - **Device Status Display**: Real-time connection status with visual indicators - **Configuration Management**: Setup and ping device configuration - **Payment Operations**: - Credit card payments - In-house payment collection - Recurring payment setup - Card replacement in recurring setups - **Transaction Logging**: Detailed logs with timestamps and formatted display - **Error Handling**: Comprehensive error management with user feedback - **Loading States**: Visual feedback during operations - **Modern UI**: Styled buttons and responsive layout ## 🤝 Contributing We welcome contributions! Please follow these steps: 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request ### Development Setup ```bash # Clone the repository git clone https://github.com/RohanAppinventiv/RN-Bridge-App.git # Install dependencies npm install # Build the package npm run build # Run tests npm test ``` ## 📄 License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## 🆘 Support For support and questions: - 📧 **Email**: Contact the maintainers - 🐛 **Issues**: Open an issue on [GitHub](https://github.com/RohanAppinventiv/RN-Bridge-App) - 📖 **Documentation**: Check the example implementation