@dingpay/react-native
v1.0.3
Published
DingPay in-app payments for React Native
Maintainers
Readme
@dingpay/react-native
Accept payments in your React Native app with DingPay. Users set up once in the Ding Wallet app, then pay anywhere with one tap. Secured with biometric authentication (Face ID / Touch ID / Fingerprint).
How it works
- User enables "In-App Checkout" in their Ding Wallet app
- Your app calls
pay()with an amount - DingPay shows a bottom sheet with the user's saved payment methods (cards, bank accounts, wallet)
- User taps Pay, confirms with Face ID or fingerprint
- Your app receives a success or failure callback
No sign-up flow. No card entry. No OTP. The user's payment methods are already in their Ding Wallet.
Installation
npm install @dingpay/react-nativePeer dependencies
npm install react-native-webview @react-native-async-storage/async-storage react-native-inappbrowser-reborn react-native-biometricsPlatform setup
DingPay uses a callback scheme to complete authentication. Choose a unique scheme for your app (e.g. your bundle ID) and configure it on both platforms. The same scheme must be passed to useDingPay().
iOS
Run pod install:
cd ios && pod installAdd your callback scheme and Face ID usage description to ios/<YourApp>/Info.plist. Place both entries inside the top-level <dict> tag:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.yourapp.dingpay</string>
</array>
</dict>
</array>
<key>NSFaceIDUsageDescription</key>
<string>Confirm your payment</string>Android
Add an intent filter with your callback scheme to android/app/src/main/AndroidManifest.xml. Place it inside your main <activity> tag (the one with android.intent.action.MAIN):
<activity
android:name=".MainActivity"
...>
<!-- Existing launcher intent filter -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- DingPay callback -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.yourapp.dingpay" />
</intent-filter>
</activity>Replace com.yourapp.dingpay with your own unique scheme on both platforms.
Quick start
import React from 'react';
import { View, Button, Alert } from 'react-native';
import { useDingPay } from '@dingpay/react-native';
export default function CheckoutScreen() {
const { pay, dingPaySheet } = useDingPay({
callbackScheme: 'com.yourapp.dingpay',
apiKey: 'your_merchant_api_key',
});
const handlePay = () => {
pay({
amount: 5000,
onSuccess: () => {
Alert.alert('Payment successful');
},
onFailure: (message) => {
Alert.alert('Payment failed', message);
},
});
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Button title="Pay ₦5,000" onPress={handlePay} />
{dingPaySheet}
</View>
);
}Important: {dingPaySheet} must be included in your TSX. It is invisible until pay() is called. Place it at the root level of your component, not inside buttons or other interactive elements.
API
useDingPay(config)
React hook that returns { pay, dingPaySheet }.
config
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| callbackScheme | string | Yes | A unique URL scheme for your app (must match the scheme registered in Info.plist and AndroidManifest.xml) |
| apiKey | string | Yes | Your merchant API key from the DingPay dashboard |
pay(options)
Opens the DingPay payment sheet.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| amount | number | Yes | Amount in Naira (e.g. 5000 for ₦5,000) |
| metadata | Record<string, any> | No | Additional data to attach to the transaction |
| onSuccess | () => void | No | Called when payment succeeds |
| onFailure | (message: string) => void | No | Called when payment fails, with an error message |
| onCancel | () => void | No | Called when the user dismisses the payment sheet |
dingPaySheet
TSX element that renders the payment bottom sheet. Include it at the root level of your component's return statement. It manages its own visibility internally.
return (
<View>
{/* your UI */}
{dingPaySheet}
</View>
);clearDingPaySession()
Clears the locally cached session. Call this when the user logs out of your app.
import { clearDingPaySession } from '@dingpay/react-native';
const handleLogout = async () => {
await clearDingPaySession();
};Full example
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
Alert,
StyleSheet,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import { useDingPay } from '@dingpay/react-native';
const MERCHANT_API_KEY = 'pk_live_xxxxxxxxxxxx';
const CALLBACK_SCHEME = 'com.myapp.dingpay';
export default function PaymentScreen() {
const [amount, setAmount] = useState('');
const { pay, dingPaySheet } = useDingPay({
callbackScheme: CALLBACK_SCHEME,
apiKey: MERCHANT_API_KEY,
});
const handlePay = () => {
const num = parseFloat(amount);
if (!num || num <= 0) {
Alert.alert('Enter a valid amount');
return;
}
pay({
amount: num,
metadata: {
orderId: 'order_12345',
customerEmail: '[email protected]',
},
onSuccess: () => {
Alert.alert('Success', 'Your payment was processed.');
setAmount('');
},
onFailure: (message: string) => {
Alert.alert('Payment Failed', message);
},
onCancel: () => {
console.log('User cancelled');
},
});
};
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<View style={styles.content}>
<Text style={styles.title}>Checkout</Text>
<TextInput
style={styles.input}
placeholder="Amount (NGN)"
keyboardType="numeric"
value={amount}
onChangeText={setAmount}
/>
<TouchableOpacity style={styles.button} onPress={handlePay}>
<Text style={styles.buttonText}>Pay with DingPay</Text>
</TouchableOpacity>
</View>
{dingPaySheet}
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
content: { flex: 1, justifyContent: 'center', padding: 24 },
title: { fontSize: 24, fontWeight: '700', marginBottom: 24 },
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 10,
padding: 16,
fontSize: 16,
marginBottom: 16,
},
button: {
backgroundColor: '#1a1a1a',
borderRadius: 100,
padding: 18,
alignItems: 'center',
},
buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});Security
DingPay is built with multiple layers of security:
- Biometric authentication: Every payment requires Face ID, Touch ID, or fingerprint confirmation before processing.
- One-time sessions: Each checkout page creates a unique session that is burned after a single use. Intercepted session IDs cannot be replayed.
- Server-side rendering: Payment assets and charge logic are handled entirely on the server. No sensitive data (card details, authorization codes) is ever exposed to the client.
- Encrypted signatures: The user's payment session is encrypted and validated server-side on every request.
- API key validation: Every SSO and checkout request is validated against your merchant API key.
- No card entry: Users never enter card details in your app. Payment methods are managed securely in the Ding Wallet app.
Payment methods supported
- Cards: Visa, Mastercard, Verve cards saved in Ding Wallet
- Bank accounts: Direct debit mandates from Nigerian banks
- Wallet: Ding Wallet balance
Requirements
- React Native 0.60+
- iOS 12+ / Android 5+
- User must have the Ding Wallet app with "In-App Checkout" enabled
Troubleshooting
"In-app payments not enabled"
The user has not set up In-App Checkout in their Ding Wallet app. They need to open Ding Wallet and enable it in settings.
"Session expired. Please try again."
The cached session was invalidated. The SDK automatically clears it. The next payment attempt will re-authenticate.
Payment sheet not appearing
Make sure {dingPaySheet} is included at the root level of your component's TSX, not inside buttons or other interactive elements. It must be rendered before pay() is called.
Biometric prompt not showing
On iOS, ensure NSFaceIDUsageDescription is set in Info.plist. On devices without biometrics, payment proceeds without the prompt.
Android: SSO redirect not working
Ensure you added an intent filter with your callbackScheme to AndroidManifest.xml inside the main <activity> tag. The scheme in the manifest must exactly match the callbackScheme passed to useDingPay().
iOS: SSO redirect not working
Ensure you added your callbackScheme to the CFBundleURLSchemes array in Info.plist. The scheme must exactly match the callbackScheme passed to useDingPay().
iOS: App freezes after SSO
This is a known timing issue between the SSO browser closing and the payment sheet opening. The SDK handles this with an internal delay. If you still experience it, ensure you're on the latest version.
Android: "Continue with" prompt appears
This means another app on the device has registered the same URL scheme. Choose a unique scheme for your app, such as your bundle ID followed by .dingpay (e.g. com.chowdeck.dingpay).
License
MIT
