UNPKG

@fenil265/fundly-payment-sdk

Version:

Fundly Payment SDK for seamless integration with Fundly Pay systems.

763 lines (589 loc) 22.5 kB
# Fundly Payment SDK A comprehensive React-based payment SDK for integrating Fundly's payment capabilities into your applications. Supports multiple payment modes including UPI, QR codes, cash, cheque, and Fundly Pay. --- ## 📦 Installation ```bash npm install @fenil265/fundly-payment-sdk ``` --- ## 🚀 Quick Start ### Basic Usage (React) ```tsx import { PaymentOptionPage, sdkConfig } from '@fenil265/fundly-payment-sdk'; import '@fenil265/fundly-payment-sdk/css'; function App() { // Configure SDK callbacks (optional but recommended) React.useEffect(() => { sdkConfig.configure({ onNavigateHome: () => { console.log('User wants to go back'); // Handle navigation in your app }, onShareText: (text) => { console.log('Share text:', text); // Handle text sharing (e.g., payment link) }, onShareImage: (base64Data, fileName, fileType) => { console.log('Share image:', fileName); // Handle image sharing (e.g., receipt) }, onOpenCamera: async () => { // Return captured image as base64 return { name: 'photo.jpg', type: 'image/jpeg', data: 'base64...' }; }, onOpenGallery: async () => { // Return selected image as base64 return { name: 'image.jpg', type: 'image/jpeg', data: 'base64...' }; } }); }, []); return ( <PaymentOptionPage config={{ sandbox: false, // Enable sandbox mode for testing (default: false) distributorIdnum: "YOUR_DISTRIBUTOR_ID", chemistId: "YOUR_CHEMIST_ID", typeOfApp: "FUNDLY_CUSTOMER_APP", // or "FUNDLY_COLLECT_APP" paymentInvoice: [ { invoiceId: "INV001", paidAmount: 1000 } ], payableValue: "1000", paymentType: "INVOICE_COLLECT", // or "DIRECT_COLLECT" // Optional: Customize button text successButtonText: "Close", // Default: "Home" failureButtonText: "Close", // Default: "Go Back" }} onPaymentCollected={(response) => { console.log('Payment successful!', response); // Handle successful payment }} onPaymentFailed={(response) => { console.log('Payment failed', response); // Handle failed payment }} /> ); } ``` --- ## 🔧 Configuration ### Payment Config ```typescript interface PaymentConfig { sandbox?: boolean; // Enable sandbox mode for testing (default: false) distributorIdnum: string; // Distributor business ID chemistId: string; // Chemist/Retailer business ID typeOfApp: 'FUNDLY_CUSTOMER_APP' | 'FUNDLY_COLLECT_APP'; paymentInvoice: Array<{ invoiceId: string; paidAmount: number; }>; payableValue: string; // Total amount as string paymentType: 'INVOICE_COLLECT' | 'DIRECT_COLLECT'; salesmanId?: number; // Optional: Salesman ID payerMobileNumber?: string; // Optional: Payer mobile number redirectionUrl?: string; // Optional: URL to redirect after UPI payment transactionId?: string; // Optional: Transaction ID to display result showResultOnly?: boolean; // Optional: Skip payment options, show result only successButtonText?: string; // Optional: Button text on success drawer (default: "Home") failureButtonText?: string; // Optional: Button text on failure drawer (default: "Go Back") } ``` ### Payment Callbacks The `PaymentOptionPage` component accepts optional callbacks to handle payment completion: ```typescript <PaymentOptionPage config={paymentConfig} onPaymentCollected={(response) => { // Called when payment is successful // For QR/UPI payments: called after transaction status becomes SUCCESS/COMPLETED console.log('Transaction ID:', response.transactionId); console.log('Payment Amount:', response.paymentAmount); console.log('Payment Mode:', response.paymentMode); // Navigate to success page, show receipt, etc. }} onPaymentFailed={(response) => { // Called when payment fails // Transaction status: FAILED, CANCELLED, or DECLINED console.log('Transaction ID:', response.transactionId); console.log('Status:', response.transactionStatus); // Show error message, retry option, etc. }} /> ``` **Note:** For **DYNAMIC_QR**, **PAYMENT_LINK**, and **UPI** payment modes, the SDK automatically polls the transaction status until the payment is completed or fails. --- ### SDK Callbacks Configure platform-specific behaviors using `sdkConfig.configure()`: ```typescript import { sdkConfig } from '@fenil265/fundly-payment-sdk'; sdkConfig.configure({ // Called when user wants to exit the SDK onNavigateHome: () => void; // Called when user wants to share text (payment link) onShareText: (text: string) => void; // Called when user wants to share image (receipt) onShareImage: (base64Data: string, fileName: string, fileType: string) => void; // Called when user wants to share image with byte data (Android WebView) onShareByteImage: (byteUrl: string, receiptText: string) => void; // Called when user wants to capture photo (cheque/cash proof) onOpenCamera: () => Promise<ImageFile | null>; // Called when user wants to select image from gallery onOpenGallery: () => Promise<ImageFile | null>; }); ``` **ImageFile Interface:** ```typescript interface ImageFile { name: string; // File name type: string; // MIME type (e.g., 'image/jpeg') data: string; // Base64 encoded image data } ``` --- ## 🌍 Environment Configuration The SDK supports switching between **production** and **sandbox/debug** environments using the `sandbox` parameter in the `PaymentConfig`. ### Production Environment (Default) By default, or when `sandbox: false`, the SDK uses production URLs: ```typescript <PaymentOptionPage config={{ sandbox: false, // or omit this parameter for production distributorIdnum: "YOUR_DISTRIBUTOR_ID", chemistId: "YOUR_CHEMIST_ID", // ... other config }} /> ``` ### Sandbox/Debug Environment For testing and development, enable sandbox mode: ```typescript <PaymentOptionPage config={{ sandbox: true, // Enable preprod/test environment distributorIdnum: "YOUR_DISTRIBUTOR_ID", chemistId: "YOUR_CHEMIST_ID", // ... other config }} /> ``` ### Example: Development vs Production ```typescript // Automatically switch based on your app's environment const isDevelopment = process.env.NODE_ENV === 'development'; <PaymentOptionPage config={{ sandbox: isDevelopment, // Use sandbox in dev, production in prod // ... other config }} /> ``` **Important Notes:** - Always use **sandbox mode** during development and testing - Switch to **production mode** (default) for production deployments - The environment affects all API calls throughout the SDK --- ## 💳 Supported Payment Modes The SDK automatically displays available payment options based on your configuration: 1. **Fundly Pay** - One-click payment with credit line 2. **Generate QR** - Dynamic UPI QR code generation for instant payments 3. **Payment Link** - Generate and share payment links 4. **UPI** - Direct UPI payment via payment gateway (supports all UPI apps) 5. **Scan QR** - Scan distributor's static QR code 6. **Cash** - Cash payment with OTP verification 7. **Cheque** - Cheque payment with image upload and verification ### UPI Payment Flow The UPI payment mode provides a seamless payment experience: 1. **User selects UPI option** - SDK displays UPI payment selection 2. **Redirect to payment gateway** - User is redirected to a secure payment page 3. **Complete payment** - User completes payment using any UPI app (Google Pay, PhonePe, Paytm, etc.) 4. **Automatic return** - After payment, user is automatically redirected back to your app 5. **Status polling** - SDK automatically polls for payment status and shows result **Key Features:** - Works on both web and mobile (including Flutter WebView) - Automatic transaction status tracking - Supports all major UPI apps - Zero additional charges for UPI payments - Secure payment gateway integration ### Display Payment Result Only Sometimes you may want to show only the payment result (success/failure) without displaying payment options. This is useful when: - User returns from a payment gateway redirect - You want to display a transaction's current status - You're handling payment initiation separately **Use Case Example:** User initiates UPI payment on `/scan-qr` page, gets redirected to payment gateway, completes payment, and returns to `/scan-qr?orderId=PTXN123`. Your app detects the orderId and opens the SDK to show only the result. ```typescript // Detect payment return in your app useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const orderIdFromUrl = urlParams.get("orderId") || urlParams.get("transactionId"); if (orderIdFromUrl) { // User returned from payment gateway - show SDK with result only setShowPaymentSDK(true); setTransactionId(orderIdFromUrl); } }, []); // Render SDK in result-only mode {showPaymentSDK && ( <PaymentOptionPage config={{ // ... your config transactionId: transactionId, // Transaction ID to fetch showResultOnly: true, // Skip payment options UI redirectionUrl: window.location.href // For consistency }} onPaymentCollected={(response) => { console.log('Payment successful!', response); // Handle success, navigate, etc. }} onPaymentFailed={(response) => { console.log('Payment failed', response); // Handle failure }} /> )} ``` **How it works:** 1. SDK fetches transaction status using the provided `transactionId` 2. If status is `SUCCESS`/`COMPLETED` Shows success drawer and calls `onPaymentCollected` 3. If status is `FAILED`/`CANCELLED`/`DECLINED` Shows failure drawer and calls `onPaymentFailed` 4. If status is still `PENDING`/`PROCESSING` Shows UPI drawer with polling until completion --- ## 🎨 UI Customization ### Custom Button Text You can customize the button text shown on success and failure drawers to better match your app's navigation behavior: ```typescript <PaymentOptionPage config={{ // ... your config successButtonText: "Close", // Custom text for success drawer button failureButtonText: "Close", // Custom text for failure drawer button }} /> ``` **Common Use Cases:** #### 1. Web Applications with Tab Closing When your app opens the SDK in a new tab that should be closed after payment: ```typescript config={{ successButtonText: "Close", failureButtonText: "Close", }} sdkConfig.configure({ onNavigateHome: () => { window.close(); // Close the tab/window } }); ``` #### 2. Single Page Applications (SPA) When navigating back to a specific page in your app: ```typescript config={{ successButtonText: "Back to Dashboard", failureButtonText: "Retry Payment", }} sdkConfig.configure({ onNavigateHome: () => { navigate('/dashboard'); // Use your router } }); ``` #### 3. Mobile Applications (React Native/Flutter) When you want native-like navigation: ```typescript config={{ successButtonText: "Done", failureButtonText: "Try Again", }} sdkConfig.configure({ onNavigateHome: () => { // React Native: navigation.goBack() // Flutter: Navigator.pop(context) } }); ``` #### 4. Default Behavior If you don't specify custom text, the defaults are used: - Success drawer: **"Home"** - Failure drawer: **"Go Back"** --- ## 🎨 Styling Import the CSS file in your application: ```tsx import '@fenil265/fundly-payment-sdk/css'; ``` The SDK uses Tailwind CSS internally and provides a clean, mobile-first UI. --- ## 📱 Platform Integration Examples ### Android WebView Integration For Android native apps using WebView, implement the `JavascriptInterface` to handle SDK callbacks: #### Step 1: Create Android JavascriptInterface ```kotlin class PaymentSDKInterface(private val context: Context, private val activity: Activity) { @JavascriptInterface fun shareByteImage(byteUrl: String, receiptText: String) { activity.runOnUiThread { try { // Parse data URL and convert to bitmap val base64String = byteUrl.substringAfter("base64,") val imageBytes = Base64.decode(base64String, Base64.DEFAULT) val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) // Save bitmap to cache directory val cachePath = File(context.cacheDir, "images") cachePath.mkdirs() val file = File(cachePath, "payment_receipt_${System.currentTimeMillis()}.png") val fileOutputStream = FileOutputStream(file) bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream) fileOutputStream.close() // Get content URI val contentUri = FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", file ) // Create share intent val shareIntent = Intent(Intent.ACTION_SEND).apply { type = "image/png" putExtra(Intent.EXTRA_STREAM, contentUri) putExtra(Intent.EXTRA_TEXT, receiptText) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } // Show share sheet activity.startActivity(Intent.createChooser(shareIntent, "Share Payment Receipt")) } catch (e: Exception) { Log.e("PaymentSDK", "Error sharing image", e) Toast.makeText(context, "Failed to share receipt", Toast.LENGTH_SHORT).show() } } } @JavascriptInterface fun shareText(text: String) { activity.runOnUiThread { val shareIntent = Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_TEXT, text) } activity.startActivity(Intent.createChooser(shareIntent, "Share Payment Link")) } } } ``` #### Step 2: Configure WebView ```kotlin class PaymentActivity : AppCompatActivity() { private lateinit var webView: WebView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) webView = WebView(this) setContentView(webView) // Enable JavaScript webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true } // Add JavaScript Interface webView.addJavascriptInterface( PaymentSDKInterface(this, this), "AndroidInterface" ) // Load your app webView.loadUrl("https://your-app-url.com") } } ``` #### Step 3: Configure SDK in JavaScript ```typescript import { sdkConfig } from '@fenil265/fundly-payment-sdk'; sdkConfig.configure({ onShareByteImage: (byteUrl: string, receiptText: string) => { // Call Android native method if (window.AndroidInterface?.shareByteImage) { window.AndroidInterface.shareByteImage(byteUrl, receiptText); } }, onShareText: (text: string) => { if (window.AndroidInterface?.shareText) { window.AndroidInterface.shareText(text); } } }); // TypeScript: Extend Window interface declare global { interface Window { AndroidInterface?: { shareByteImage: (byteUrl: string, receiptText: string) => void; shareText: (text: string) => void; }; } } ``` #### Step 4: Add FileProvider (AndroidManifest.xml) ```xml <application> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application> ``` Create `res/xml/file_paths.xml`: ```xml <?xml version="1.0" encoding="utf-8"?> <paths> <cache-path name="shared_images" path="images/" /> </paths> ``` **Important Notes:** - If `onShareByteImage` callback is **not implemented**, the SDK will **automatically fall back** to the native Web Share API or download functionality - The `byteUrl` parameter is a data URL (e.g., `data:image/png;base64,iVBORw0KG...`) - The SDK handles the fallback gracefully, so implementation is optional but recommended for better UX --- ### React Native / Flutter For mobile platforms, implement the callbacks to bridge native functionality: ```typescript sdkConfig.configure({ onNavigateHome: () => { // React Native: navigation.goBack() // Flutter: Navigator.pop(context) }, onOpenCamera: async () => { // Use native camera module // Return captured image as base64 const result = await NativeCamera.capture(); return { name: result.fileName, type: result.mimeType, data: result.base64 }; }, onShareText: (text) => { // Use native share module // React Native: Share.share({ message: text }) // Flutter: Share.share(text) } }); ``` ### Web Application For web apps, the SDK provides default implementations: - **onNavigateHome**: Uses `history.back()` or `window.close()` - **onShareText**: Uses Web Share API or clipboard - **onShareImage**: Uses Web Share API or downloads file - **onShareByteImage**: Uses Web Share API with file + text, or downloads file (falls back automatically) - **onOpenCamera/onOpenGallery**: Uses file input with camera/gallery access --- ## 🔐 Authentication The SDK requires a valid authentication token. Set it using: ```typescript import { setAccessToken } from '@fenil265/fundly-payment-sdk'; // Set token before rendering PaymentOptionPage setAccessToken('your-jwt-token'); ``` --- ## 📊 Analytics & Tracking The SDK automatically tracks payment events using CleverTap analytics. **No manual configuration needed** - analytics is initialized automatically when you render the `PaymentOptionPage` component. ### Automatic CleverTap Initialization When the SDK mounts, it automatically initializes CleverTap with the appropriate environment settings: - **Production**: Account ID `44K-ZR9-W57Z` with EU1 region - **Sandbox**: Account ID `TEST-54K-ZR9-W57Z` with EU1 region The environment is determined by the `sandbox` parameter in your `PaymentConfig`. ### Tracked Events The SDK automatically tracks these payment lifecycle events: 1. **Payment_Initiated** - When any payment is started 2. **Payment_Link_Generated** - When a payment link is created 3. **Payment_QR_Generated** - When a dynamic QR code is created 4. **Payment_Success** - When payment is successfully completed 5. **Payment_Failed** - When payment fails Each event includes: - Payment mode (CASH, CHEQUE, STATIC_QR, DYNAMIC_QR, PAYMENT_LINK) - Amount and invoice count - Salesman information (if provided) - Transaction ID and timestamp - Additional mode-specific data ### Custom Analytics Provider (Optional) If you want to use a different analytics provider or disable CleverTap: ```typescript import { analyticsService, IAnalyticsProvider } from '@fenil265/fundly-payment-sdk'; // Disable analytics analyticsService.configure({ enabled: false }); // Or use your custom provider class MyAnalyticsProvider implements IAnalyticsProvider { trackPaymentInitiated(data) { /* your implementation */ } trackPaymentLinkGenerated(data) { /* your implementation */ } trackPaymentQRGenerated(data) { /* your implementation */ } trackPaymentSuccess(data) { /* your implementation */ } trackPaymentFailed(data) { /* your implementation */ } } analyticsService.configure({ enabled: true, provider: new MyAnalyticsProvider() }); ``` **Note:** Custom configuration should be done before rendering the `PaymentOptionPage` component. --- ## 📚 API Reference ### Components #### `PaymentOptionPage` Main payment UI component with all payment options. **Props:** - `config: PaymentConfig` - Payment configuration object ### Utilities #### `sdkConfig.configure(callbacks: SDKCallbacks)` Configure platform-specific callbacks for SDK behavior. #### `setAccessToken(token: string)` Set authentication token for API calls. #### `getAccessToken(): string | null` Get current authentication token. --- ## 🛠️ Development ### Building the SDK ```bash npm run build ``` ### Local Testing ```bash # Link locally npm link # In your test project npm link @fenil265/fundly-payment-sdk ``` ### Unlink ```bash npm unlink @fenil265/fundly-payment-sdk ``` --- ## 📝 TypeScript Support The SDK is written in TypeScript and provides full type definitions. Import types: ```typescript import type { PaymentConfig, PaymentInvoice, SDKCallbacks, ImageFile } from '@fenil265/fundly-payment-sdk'; ``` --- ## 🐛 Troubleshooting ### Payment options not showing - Verify authentication token is set - Check that `typeOfApp` matches your use case - Ensure `paymentInvoice` array is properly formatted ### Camera/Gallery not working - Implement `onOpenCamera` and `onOpenGallery` callbacks - Return image data in the correct format (base64 string) ### SDK not closing - Implement `onNavigateHome` callback - Handle navigation in your application's routing system --- ## 📄 License MIT License © Fundly Developers --- ## 🤝 Support For issues and questions: - GitHub: [fundly/payment-sdk](https://github.com/fundly/payment-sdk) - Email: support@fundly.ai