@fenil265/fundly-payment-sdk
Version:
Fundly Payment SDK for seamless integration with Fundly Pay systems.
763 lines (589 loc) • 22.5 kB
Markdown
# 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