# Deuna App Wallet via Widget
## Prerequisites
Before implementing **Deuna App Wallet** payments, ensure you have:
* Enabled **Deuna App Wallet** in the Deuna Admin Panel
* Generated an **order token** via the Deuna API
* Integrated the **[Deuna SDK](https://docs.deuna.com/reference/deuna-sdks)** in your project
* Reviewed the **[Payment Widget documentation](https://docs.deuna.com/docs/payment-widget)** for your platform
* Selected your preferred **payment mode** (Numeric Code or QR) in the Admin Panel
## Payment Widget Configuration
### Basic Implementation
Initialize the Payment Widget with the `deuna_wallet` processor:
```typescript
DeunaSDK.initPaymentWidget({
orderToken: '',
paymentMethods: [
{
paymentMethod: 'voucher',
processors: ['deuna_wallet'],
},
],
callbacks: {
onSuccess: (data) => {
console.log('Payment successful:', data);
// Redirect to success page
},
onError: (error) => {
console.log('Payment failed:', error);
// Show error message to user
},
onPending: (data) => {
console.log('Payment pending confirmation:', data);
// Show waiting screen
},
},
});
```
### Complete Example with All Options
```typescript
DeunaSDK.initPaymentWidget({
orderToken: '',
paymentMethods: [
{
paymentMethod: 'voucher',
processors: ['deuna_wallet'],
metadata: {
// Optional: Pass additional data
orderId: 'your-internal-order-id',
},
},
],
callbacks: {
onSuccess: (response) => {
console.log('Payment confirmed:', {
orderId: response.order_id,
orderToken: response.order_token,
status: response.status,
amount: response.amount,
currency: response.currency,
});
// Verify payment on your backend
fetch('/api/verify-payment', {
method: 'POST',
body: JSON.stringify({
orderToken: response.order_token,
orderId: response.order_id,
}),
})
.then(res => res.json())
.then(data => {
if (data.verified) {
window.location.href = '/order-confirmation';
}
});
},
onError: (error) => {
console.error('Payment error:', {
code: error.code,
message: error.message,
});
// Show user-friendly error message
showErrorModal({
title: 'Payment Failed',
message: 'Unable to process your payment. Please try again.',
});
},
onPending: (response) => {
console.log('Payment pending user confirmation:', {
orderId: response.order_id,
expiresAt: response.expires_at,
});
},
onExpired: (response) => {
console.log('Payment code/QR expired:', {
orderId: response.order_id,
expiredAt: response.expired_at,
});
},
},
});
```
## Payment Modes
### Understanding the Two Modes
Your merchant account is configured with **one of these modes** in the Admin Panel:
#### Mode 1: Numeric Code (3-Minute Window)
**What the customer sees:**
* A 6-digit numeric code displayed on screen
* Countdown timer showing remaining time (0:00-3:00)
* Instructions to open Deuna App and enter the code
**Customer flow:**
1. Notes the 6-digit code
2. Opens Deuna App
3. Selects "Pay with Code"
4. Enters the 6-digit code
5. Confirms payment in app
6. Payment confirmed immediately via webhook
**Configuration:** Set in Admin Panel under Deuna App Wallet processor settings
#### Mode 2: QR Code (15-Minute Window)
**What the customer sees:**
* A dynamic QR code on screen
* Countdown timer showing remaining time (0:00-15:00)
* Two options to proceed:
* Scan QR with Deuna App camera
* Click QR to open app directly
**Customer flow:**
1. **Option A - Scan:**
* Opens Deuna App camera
* Scans the QR code
* Payment page opens automatically
* Confirms payment in app
* Payment confirmed via webhook
2. **Option B - Click:**
* Clicks QR code or button
* Opens Deuna App directly
* Shows payment confirmation screen
* Confirms payment
* Payment confirmed via webhook
**Configuration:** Set in Admin Panel under Deuna App Wallet processor settings
> **Note:** Mode selection happens **once during initial setup** in the Admin Panel. All subsequent orders will use the configured mode. Contact Deuna support to change modes.
## Handling Payment Confirmation
### Webhook-Based Confirmation (Primary)
The Deuna App Wallet sends a webhook notification to your backend when the customer confirms payment:
```typescript
// Your backend webhook endpoint
app.post('/webhooks/deuna-wallet', (req, res) => {
const { order_id, order_token, status, amount, currency } = req.body;
// Verify webhook signature (recommended)
const isValid = verifyWebhookSignature(req);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Handle confirmation
if (status === 'confirmed') {
// Update your database
updateOrder(order_id, {
status: 'paid',
paidAt: new Date(),
amount: amount,
currency: currency,
});
// Send confirmation email to customer
sendConfirmationEmail(order_id);
}
// Acknowledge receipt
res.json({ success: true });
});
```
### Polling Fallback (Secondary)
If webhook delivery is delayed or fails, the system polls the `/order` endpoint every 3 seconds:
```typescript
// Client-side polling (handled automatically by SDK)
// But you can also implement it manually if needed
const pollOrderStatus = async (orderToken) => {
const maxAttempts = 60; // 3 minutes for numeric code
let attempts = 0;
while (attempts < maxAttempts) {
try {
const response = await fetch(`/api/order/${orderToken}`);
const order = await response.json();
if (order.status === 'confirmed') {
handlePaymentSuccess(order);
return;
}
if (order.status === 'expired') {
handlePaymentExpired(order);
return;
}
// Wait 3 seconds before polling again
await new Promise(resolve => setTimeout(resolve, 3000));
attempts++;
} catch (error) {
console.error('Polling error:', error);
}
}
handlePaymentExpired();
};
```
## Response Objects
### Success Response
When payment is confirmed, `onSuccess` callback receives:
```typescript
{
order_id: "order_abc123def456",
order_token: "token_xyz789",
status: "confirmed",
payment_method: "deuna_wallet",
amount: 99.99,
currency: "USD",
timestamp: "2024-03-20T14:30:45Z",
metadata: {
// Any metadata passed during initialization
}
}
```
### Error Response
When payment fails, `onError` callback receives:
```typescript
{
code: "PAYMENT_FAILED",
message: "Unable to process payment",
order_id: "order_abc123def456",
details: {
reason: "Insufficient funds in Deuna wallet"
}
}
```
### Pending Response
When waiting for user confirmation, `onPending` callback receives:
```typescript
{
order_id: "order_abc123def456",
order_token: "token_xyz789",
status: "pending",
payment_method: "deuna_wallet",
code_or_qr: "123456", // For numeric code mode
expires_at: "2024-03-20T14:33:45Z",
expires_in_seconds: 180
}
```
### Expired Response
When code/QR expires, `onExpired` callback receives:
```typescript
{
order_id: "order_abc123def456",
order_token: "token_xyz789",
status: "expired",
payment_method: "deuna_wallet",
expired_at: "2024-03-20T14:33:45Z",
message: "Code has expired"
}
```
## UI Best Practices
### For Numeric Code Mode
* **Display the code prominently** - Large, clear font
* **Show countdown timer** - Update every second
* **Display clear instructions:**
* "Open the Deuna App"
* "Select Pay with Code"
* "Enter this 6-digit code"
* "Confirm payment"
* **Show expiration state** - When code expires after 3 minutes
* **Provide regenerate option** - "Generate new code" button
### For QR Code Mode
* **Display QR code prominently** - Centered, scalable
* **Show countdown timer** - Update every second
* **Provide two interaction options:**
* Scan QR with Deuna App
* Click QR to open app directly
* **Display clear instructions**
* **Show expiration state** - When QR expires after 15 minutes
* **Provide regenerate option** - "Generate new code" button
### General Guidelines
* **Keep modal/screen open** - Do not redirect or close until payment confirmed or expired
* **Disable other payment methods** - Prevent selecting another method while waiting
* **Mobile responsive** - Ensure readability on small screens
* **Accessibility** - Include alt text for QR, clear color contrast for timers
* **Loading states** - Show spinner while generating code/QR
## Common Implementation Patterns
### React Hook Pattern
```typescript
import { useEffect, useState } from 'react';
export const useDeunaWalletPayment = (orderToken) => {
const [status, setStatus] = useState('idle'); // idle | pending | success | error | expired
const [expiresAt, setExpiresAt] = useState(null);
useEffect(() => {
if (!orderToken) return;
DeunaSDK.initPaymentWidget({
orderToken,
paymentMethods: [
{
paymentMethod: 'voucher',
processors: ['deuna_wallet'],
},
],
callbacks: {
onPending: (data) => {
setStatus('pending');
setExpiresAt(data.expires_at);
},
onSuccess: (data) => {
setStatus('success');
},
onError: (error) => {
setStatus('error');
},
onExpired: () => {
setStatus('expired');
},
},
});
}, [orderToken]);
const regenerate = () => {
setStatus('idle');
window.location.reload();
};
return { status, expiresAt, regenerate };
};
```