Skip to content

QR Code Payments

Developer Integration

This guide is for developers building custom payment integrations.

Most merchants should use the Dberi POS system which handles QR codes automatically. Contact sales@dberi.com for hardware.

Overview

This guide shows how to programmatically generate and manage QR code payments via the Dberi API. Dberi automatically generates QR codes for you when you create a payment - you just need to display the qr_payload image.

Use cases for API integration:

  • Custom POS Systems - Build your own point-of-sale
  • Third-Party Integrations - Add Dberi to existing software
  • Web Applications - Generate QR codes for online checkouts
  • Mobile Apps - In-app payment QR generation

Quick Start

1. Create Static QR Code

For reusable checkout counter QR:

bash
curl -X POST https://api.dberi.com/v1/payments \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_mode": "STATIC_PAY",
    "description": "Checkout Counter 1"
  }'

Response:

json
{
  "id": "payment-static-abc123",
  "qr_payload": "dberi://pay/payment-static-abc123",
  "payment_mode": "STATIC_PAY"
}

2. Display QR Code

The API automatically generates a QR code for you. Just use the qr_payload from the response:

html
<!-- Display the auto-generated QR code -->
<img
  src="{{ qr_payload }}"
  alt="Scan to pay"
  width="300"
/>

The qr_payload is a ready-to-use base64 PNG image (data URL) - no external service needed!

3. Customer Scans & Pays

  1. Customer opens their mobile camera or Dberi app
  2. Scans QR code (redirects to checkout page)
  3. Reviews payment details
  4. Confirms payment with verification (PIN/Face ID if required)
  5. Payment complete!

Note: The QR code contains a link to your checkout page: {BASE_URL}/checkout/{payment_id}

Payment Modes

Static QR (Reusable)

Best for: Checkout counters, permanent locations

javascript
// Create reusable QR code
const qr = await createPayment({
  payment_mode: 'STATIC_PAY',
  description: 'Coffee Shop - Register 1'
})

// Use forever - never expires
// Customer enters amount each time

Features:

  • Never expires
  • Reusable unlimited times
  • Customer enters amount
  • Perfect for retail counters

Dynamic QR (One-Time)

Best for: Specific transactions, invoices

javascript
// Create one-time QR for specific amount
const qr = await createPayment({
  payment_mode: 'DYNAMIC_PAY',
  amount: 2500,
  description: 'Table 5 - Lunch'
})

// Expires after 24 hours
// Amount pre-filled
// Single use

Features:

  • 24-hour expiration
  • Pre-filled amount
  • Single use per session
  • Perfect for bills and invoices

Use Cases

Restaurant Payments

javascript
// Generate QR for a bill
async function generateBillQR(billAmount, tableNumber) {
  const payment = await createPayment({
    payment_mode: 'DYNAMIC_PAY',
    amount: billAmount,
    description: `Table ${tableNumber}`,
    metadata: {
      table: tableNumber
    }
  })

  // Display QR code on the bill
  return payment.qr_payload
}

Market Vendor Booth

javascript
// Permanent booth QR
const qr = await createPayment({
  payment_mode: 'STATIC_PAY',
  description: 'Island Crafts - Booth 42'
})

// Print and display at booth
// Customer scans and pays for items
// Works all day, every day

Event Ticket Scanning

javascript
// Generate ticket QR for each attendee
async function generateTicketQR(attendee) {
  const qr = await createPayment({
    payment_mode: 'DYNAMIC_PAY',
    amount: attendee.ticket_price,
    description: `${event.name} - ${attendee.ticket_type}`,
    metadata: {
      attendee_id: attendee.id,
      event_id: event.id,
      ticket_number: attendee.ticket_number
    }
  })

  // Email QR to attendee
  await sendTicketEmail(attendee.email, qr.qr_payload)
}

Mobile Service Provider

javascript
// On-site payment for service
async function collectPayment(service, amount) {
  const qr = await createPayment({
    payment_mode: 'DYNAMIC_PAY',
    amount,
    description: `${service.name} - ${service.location}`,
    metadata: {
      service_id: service.id,
      technician: service.technician,
      location: service.address
    }
  })

  // Show QR on mobile device
  displayQR(qr.qr_payload)

  // Wait for payment
  await waitForPayment(qr.id)

  console.log('Payment received!')
}

Implementation Examples

Point of Sale Terminal

javascript
// POS system integration
class POSTerminal {
  async checkout(items, total) {
    // Create payment QR
    const qr = await createPayment({
      payment_mode: 'DYNAMIC_PAY',
      amount: total,
      description: `POS Sale - ${items.length} items`,
      metadata: {
        items: items.map(i => i.name),
        terminal_id: this.terminalId,
        cashier: this.cashierId
      }
    })

    // Display QR on terminal screen
    this.displayQR(qr.qr_payload)

    // Poll for payment
    const payment = await this.waitForPayment(qr.id)

    if (payment.status === 'completed') {
      this.printReceipt(payment)
      this.openCashDrawer()
      return { success: true }
    }
  }

  async waitForPayment(paymentId, timeout = 120000) {
    const start = Date.now()

    while (Date.now() - start < timeout) {
      const payment = await fetchPayment(paymentId)

      if (payment.status === 'completed') {
        return payment
      }

      if (payment.status === 'failed') {
        throw new Error('Payment failed')
      }

      await sleep(2000) // Check every 2 seconds
    }

    throw new Error('Payment timeout')
  }
}

Retail Store Checkout

javascript
// In-store checkout with QR
async function processCheckout(items, total) {
  // Create payment for this transaction
  const payment = await createPayment({
    payment_mode: 'DYNAMIC_PAY',
    amount: total,
    description: `Purchase - ${items.length} items`,
    metadata: {
      items: items.map(i => i.name)
    }
  })

  // Display QR on register screen
  displayQR(payment.qr_payload)

  // Wait for customer to scan and pay
  const result = await waitForPayment(payment.id)

  if (result.status === 'completed') {
    console.log('Payment successful!')
    return { success: true }
  }
}

Service Providers

javascript
// On-site service payment
async function collectServicePayment(serviceName, amount) {
  const payment = await createPayment({
    payment_mode: 'DYNAMIC_PAY',
    amount,
    description: serviceName,
    metadata: {
      service_type: serviceName
    }
  })

  // Show QR code to customer
  console.log('Show this QR code to customer:')
  console.log(payment.qr_payload)

  // Wait for payment
  await waitForPayment(payment.id)

  console.log('Payment received! Service complete.')
}

Displaying QR Codes

Using the Auto-Generated QR Code

Dberi automatically generates QR codes for every payment. The qr_payload field contains a ready-to-use base64 PNG image:

javascript
// Create payment
const payment = await createPayment({
  payment_mode: 'STATIC_PAY',
  description: 'Checkout Counter 1'
})

// QR code is ready to use immediately
const qrImageSrc = payment.qr_payload
// Example: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."

// Display in HTML
document.getElementById('qr-image').src = qrImageSrc
html
<!DOCTYPE html>
<html>
<head>
  <style>
    @media print {
      .no-print { display: none; }
    }
    .qr-container {
      text-align: center;
      padding: 20px;
    }
  </style>
</head>
<body>
  <div class="qr-container">
    <h2>Scan to Pay</h2>
    <!-- Use the qr_payload from the API response -->
    <img src="{{ payment.qr_payload }}" alt="QR Code" width="300" />
    <p>Island Coffee Shop</p>
    <p>Checkout Counter 1</p>
    <button class="no-print" onclick="window.print()">Print QR Code</button>
  </div>
</body>
</html>

Display on Screen

javascript
// Show QR on POS terminal screen
function displayQR(payment, amount) {
  const qrImage = document.getElementById('qr-display')
  // Use the auto-generated QR code from the API
  qrImage.src = payment.qr_payload

  document.getElementById('amount-display').textContent = formatCurrency(amount)
  document.getElementById('qr-container').style.display = 'block'
}

Monitoring Payments

Real-Time Payment Detection

javascript
async function waitForPayment(paymentId, options = {}) {
  const {
    timeout = 120000, // 2 minutes
    pollInterval = 2000, // 2 seconds
    onUpdate = () => {}
  } = options

  const startTime = Date.now()

  while (Date.now() - startTime < timeout) {
    const payment = await fetchPayment(paymentId)

    onUpdate(payment)

    if (payment.status === 'completed') {
      return payment
    }

    if (payment.status === 'failed' || payment.status === 'canceled') {
      throw new Error(`Payment ${payment.status}`)
    }

    await sleep(pollInterval)
  }

  throw new Error('Payment timeout')
}

// Usage
await waitForPayment(paymentId, {
  timeout: 180000, // 3 minutes
  onUpdate: (payment) => {
    if (payment.status === 'pending') {
      console.log('Customer is paying...')
    }
  }
})

Webhook Notifications

javascript
// Receive instant notification when customer pays
app.post('/webhooks/dberi', (req, res) => {
  const event = req.body

  if (event.type === 'payment.completed') {
    const { payment_id, amount, metadata } = event.data

    // Notify POS terminal
    notifyTerminal(metadata.terminal_id, {
      status: 'completed',
      amount
    })

    // Print receipt
    printReceipt(payment_id)

    // Open cash drawer
    if (metadata.terminal_id) {
      openDrawer(metadata.terminal_id)
    }
  }

  res.status(200).send('OK')
})

Best Practices

1. Test Before Printing

Always test QR codes before printing:

javascript
// Generate test QR
const testQR = await createPayment({
  payment_mode: 'STATIC_PAY',
  description: 'TEST - Do Not Use'
})

// Scan with phone
// Verify it works
// Then generate production QR

2. Include Clear Instructions


                     
   [QR CODE HERE]    
                     
  Scan to Pay with   
   Dberi Mobile App  
                     
   Island Coffee     
   Counter 1

3. Use High-Quality Prints

  • Print at 300 DPI or higher
  • Laminate for durability
  • Use high-contrast colors (black on white)
  • Ensure adequate size (minimum 2" x 2")

4. Monitor Payment Times

javascript
const startTime = Date.now()

const payment = await waitForPayment(paymentId)

const duration = Date.now() - startTime

console.log(`Payment took ${duration}ms`)

// Alert if taking too long
if (duration > 30000) {
  console.warn('Payment took over 30 seconds')
}

Troubleshooting

QR Code Won't Scan

  1. Check image quality (needs high resolution)
  2. Ensure good lighting
  3. Verify QR payload is correct
  4. Test with multiple devices

Payment Not Detected

  1. Check internet connection
  2. Verify webhook endpoint is accessible
  3. Ensure polling interval is reasonable
  4. Check payment hasn't expired

Customer Confusion

  1. Add clear instructions near QR code
  2. Train staff to assist
  3. Offer alternative payment method (card reader)
  4. Provide customer support contact

Next Steps

Built for the Caribbean, powered by innovation