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 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:

  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:

// 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:

// 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:

{
  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 };
};