Skip to main content
QR Order Pay is a user-initiated payment flow in which the merchant generates a QR code containing an order or transaction request. The user scans this QR code using the Rebell SuperApp and completes the payment within the app. This flow works for both static QR codes (fixed merchant reference) and dynamic QR codes (unique order-specific QR). It is ideal for unattended or user-driven scenarios and enables seamless payments without POS hardware.

When to Use QR Order Pay

Ideal use cases:
  • Payment is initiated by the user, not the merchant
  • No POS hardware available
  • Hands-off checkout flow desired
  • Printed or digital QR codes
  • Desktop QR checkout support needed
Common scenarios:
  • Restaurants (table QR)
  • Cafés and takeaway counters
  • Parking machines / mobility services
  • Self-service kiosks without scanner hardware
  • Digital invoices or screens
  • E-commerce desktop checkout (scan-to-pay)

Payment Flow

1

Create Order

Merchant creates an order on their backend system
2

Generate QR Code

Merchant backend calls Rebell createQrOrder API
3

Display QR

Rebell responds with a QR code payload to be rendered on screen or printed
4

User Scans

User scans the QR code with the Rebell SuperApp
5

User Confirms

The app displays the order and requests confirmation from the user
6

Process Payment

Rebell processes the payment
7

Webhook Notification

Rebell sends webhook notification to the merchant backend
8

Complete Order

Merchant backend confirms order and completes the business flow

Sequence Diagram

API Specification

Endpoint

POST /v1/payments/createQrOrder

Request Headers

Include standard authentication headers:
Client-Id: your-client-id
Request-Time: 2024-01-10T12:22:30Z
Signature: algorithm=SHA256withRSA, keyVersion=1, signature=...
Content-Type: application/json

Request Body

productCode
string
required
Payment product type assigned by Rebell
paymentRequestId
string
required
Unique order identifier (idempotency key). Must be unique per transaction.
paymentAmount
object
required
Payment amount details
order
object
Order details
Example Request:
{
  "productCode": "51051000101000100040",
  "paymentRequestId": "order-20240321-001",
  "paymentAmount": {
    "currency": "EUR",
    "value": 2599
  },
  "order": {
    "orderDescription": "Parking - Zone 12",
    "merchant": {
      "store": {
        "externalStoreId": "STORE-123"
      }
    }
  }
}

Response Parameters

result
object
required
Result details
qrCode
string
required
QR code payload string (NOT an image). Must be rendered using a QR library.
Example Response:
{
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S"
  },
  "qrCode": "CGCP://bizType=PAY&version=1&...encoded payload..."
}
Important Notes:
  • qrCode is a string payload, NOT an image
  • Merchant must render it using a QR library (SVG/canvas/img)
  • Payload encoding is handled by Rebell and must not be altered

Rendering the QR Code

The API returns a string payload that you must render into a visual QR code:
import QRCode from 'qrcode';

async function renderQR(qrCodePayload) {
  // Render to canvas
  const canvas = document.getElementById('qr-canvas');
  await QRCode.toCanvas(canvas, qrCodePayload, {
    width: 300,
    margin: 2,
    errorCorrectionLevel: 'M'
  });

  // Or render to data URL
  const dataUrl = await QRCode.toDataURL(qrCodePayload);
  document.getElementById('qr-image').src = dataUrl;
}

Merchant Behavior Rules

Merchant MUST

Merchant MUST NOT

Critical Don’ts:
  • ❌ Reuse a previously generated QR code for a different order
  • ❌ Modify or shorten the QR payload
  • ❌ Generate QR codes client-side (always via backend)
  • ❌ Rely on the Rebell API response alone — webhook is the final authority

Handling Payment Completion

Upon Receiving Webhook SUCCESS

Payment completed successfullyActions to take:
  • Mark the order as paid in your system
  • Update frontend (web/mobile/table display)
  • Proceed with order fulfillment
  • Send confirmation to customer (email, SMS, etc.)

Upon Receiving Webhook FAIL

Error Scenarios & Recovery

ResultCode: QR_EXPIREDCause: User scans a QR long after creation (typically > 5 minutes)Recovery:
  • Generate a fresh QR code with a new API call
  • Display “QR code expired, please refresh” message
  • Implement auto-refresh mechanism for dynamic QR
ResultCode: INVALID_QR_CODECauses:
  • Wrong encoding used when rendering
  • Resizing too aggressively (QR became unreadable)
  • Using lossy image formats
Recovery:
  • Use error correction level ‘M’ or ‘H’
  • Ensure minimum size of 200x200px
  • Use PNG or SVG format (not JPEG)
  • Don’t modify the payload string
ResultCode: ORDER_AMOUNT_INVALIDCause: Merchant changes the amount after QR generationFix:
  • Always generate a new QR for updated amounts
  • Never reuse QR codes for different amounts
  • Implement order amount locking
ResultCode: PAYMENT_REJECTEDCause: User explicitly cancels in the appRecovery:
  • Display: “Payment not completed”
  • Offer option to generate new QR
  • Allow alternative payment methods
ResultCode: RISK_REJECTCause: Payment blocked by risk/fraud controlsRecovery:
  • Encourage user to try another payment method
  • Contact support if issue persists
  • Don’t retry automatically

Security Considerations

Security Best Practices:
  • ✅ QR codes should not expose sensitive merchant logic
  • ✅ Do not embed personal data in order description
  • ✅ Always generate QR codes server-side (never client-side)
  • ✅ Verify webhook signatures before updating order status
  • ✅ Keep QR validity time limited (recommended < 5 minutes for dynamic QR)
  • ✅ Implement rate limiting on QR generation
  • ✅ Log QR generation attempts for fraud monitoring

UX Recommendations

Create optimal user experiences for different QR types:
For order-specific QR codes:
  • Show a countdown timer (optional)
  • Auto-refresh QR every few minutes if needed
  • Display order amount prominently
  • Allow user to retry easily
  • Show “Scan with Rebell to pay” instruction
  • Update UI immediately on webhook receipt

Mobile Web Considerations

If the user is on mobile web, consider offering a direct app link instead of QR code:
<a href="rebellapp://pay?code=...">Pay with Rebell App</a>
This provides better UX than asking mobile users to scan a QR on their own device.

Testing Checklist

Test these scenarios in sandbox before going live:

Implementation Example

Here’s a complete implementation example:
Complete Flow Example
// Backend: Generate QR
app.post('/api/orders/:orderId/qr', async (req, res) => {
  const order = await getOrder(req.params.orderId);

  const response = await rebellAPI.createQrOrder({
    productCode: process.env.REBELL_PRODUCT_CODE,
    paymentRequestId: `order-${order.id}`,
    paymentAmount: {
      currency: 'EUR',
      value: order.totalCents
    },
    order: {
      orderDescription: order.description,
      merchant: {
        store: {
          externalStoreId: process.env.STORE_ID
        }
      }
    }
  });

  // Store QR code reference
  await updateOrder(order.id, {
    qrCode: response.qrCode,
    paymentRequestId: `order-${order.id}`,
    status: 'pending_payment'
  });

  res.json({ qrCode: response.qrCode });
});

// Frontend: Render QR
async function displayCheckout(orderId) {
  const { qrCode } = await fetch(`/api/orders/${orderId}/qr`).then(r => r.json());

  // Render QR code
  const canvas = document.getElementById('qr-canvas');
  await QRCode.toCanvas(canvas, qrCode, { width: 300 });

  // Poll for payment status (optional)
  const interval = setInterval(async () => {
    const order = await fetch(`/api/orders/${orderId}`).then(r => r.json());

    if (order.status === 'paid') {
      clearInterval(interval);
      showSuccess();
    } else if (order.status === 'failed') {
      clearInterval(interval);
      showError();
    }
  }, 3000);
}

// Webhook handler
app.post('/webhooks/rebell', async (req, res) => {
  // Verify signature
  if (!verifySignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  const { paymentRequestId, result } = req.body;
  const orderId = paymentRequestId.replace('order-', '');

  if (result.resultStatus === 'S') {
    await updateOrder(orderId, { status: 'paid' });
    await fulfillOrder(orderId);
  } else {
    await updateOrder(orderId, { status: 'failed' });
  }

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

Next Steps