Deuna (Banco Pichincha) via Payment Widget
Integrate the Banco Pichincha-backed Deuna wallet with the DEUNA Payment Widget.
On this page
Prerequisites#
Before implementing Deuna (Banco Pichincha) payments, ensure you have:
- Enabled Deuna (Banco Pichincha) in the DEUNA Admin Panel
- Generated an order token via the DEUNA API
- Integrated the DEUNA SDK in your project
- Reviewed the Payment Widget documentation 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:
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
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:
- Notes the 6-digit code
- Opens Deuna app
- Selects "Pay with Code"
- Enters the 6-digit code
- Confirms payment in app
- DEUNA processes the final payment state asynchronously
Configuration: Set in Admin Panel under Deuna (Banco Pichincha) 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:
-
Option A - Scan:
- Opens Deuna app camera
- Scans the QR code
- Payment page opens automatically
- Confirms payment in app
- DEUNA processes the final payment state asynchronously
-
Option B - Click:
- Clicks QR code or button
- Opens Deuna app directly
- Shows payment confirmation screen
- Confirms payment
- DEUNA processes the final payment state asynchronously
Configuration: Set in Admin Panel under Deuna (Banco Pichincha) 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#
Deuna (Banco Pichincha) does not define a separate merchant webhook payload. It follows the standard asynchronous payment-status processing model.
Use the widget callback to update the customer interface, then retrieve the original order and make fulfillment decisions from its final payment status. If the result remains unresolved, poll the order endpoint with a bounded cadence:
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:
{
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:
{
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:
{
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:
{
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
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 };
};