# 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 }; }; ```