Flows

Escrow Payment

End-to-end guide for buyer-protected escrow payments with merchant acceptance and customer confirmation.

Overview

Escrow payment provides buyer protection by holding funds in escrow until the customer confirms receipt of goods or services. This flow requires multiple API calls throughout the transaction lifecycle: payment initiation, merchant acceptance, and customer confirmation (or void/cancel if issues arise).

The escrow flow ensures that:

  • Funds are held securely until the merchant fulfills the order
  • Merchants must explicitly accept orders before fulfillment
  • Customers can confirm receipt to release funds
  • Merchants can void/cancel if fulfillment cannot be completed
Escrow payment uses the special product code 51051000101000100008. Standard cashier payments use a different product code and complete immediately without the acceptance/confirmation steps.

Complete Escrow Flow

1. Initial Payment (Cashier Mode)

The flow starts like a standard cashier payment, but uses the escrow product code.

Steps 1-18: Follow the standard cashier payment flow:

  1. Mini Program gets authorization via my.getAuthCode
  2. Merchant backend exchanges authCode for accessToken via applyToken
  3. Mini Program creates order and sends to merchant backend
  4. Merchant backend calls pay API with escrow product code
  5. Customer completes payment in wallet cashier
  6. Funds are held in escrow (not released to merchant yet)

Request to /v1/payments/pay:

{
  "productCode": "51051000101000100008",
  "paymentRequestId": "escrow_order_20240101_123456",
  "paymentAmount": {
    "currency": "IQD",
    "value": "500000"
  },
  "order": {
    "orderDescription": "iPhone 15 Pro - Space Black",
    "buyer": {
      "referenceBuyerId": "216610000000003660xxxx"
    }
  },
  "paymentNotifyUrl": "https://www.merchant.com/notify/payment",
  "paymentRedirectUrl": "https://www.merchant.com/order/return"
}

Response:

{
  "result": {
    "resultCode": "ACCEPT",
    "resultStatus": "A",
    "resultMessage": "accept"
  },
  "paymentId": "2024010111121280010016600900000xxxx",
  "redirectActionForm": {
    "redirectionUrl": "https://www.wallet.com/cashier?orderId=xxxxxxx"
  }
}

2. Merchant Accepts Order

After payment is completed, the merchant reviews the order (checks inventory, validates delivery address, etc.) and must explicitly accept it to proceed with fulfillment.

API: POST /v1/payments/merchantAccept

Request:

{
  "paymentId": "2024010111121280010016600900000xxxx"
}

Response:

{
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "Success."
  }
}
Why Accept? The acceptance step commits the merchant to fulfilling the order. If the merchant cannot fulfill (out of stock, delivery issue, etc.), they should not accept and should instead use the cancel API.

3. Fulfillment & Delivery

After acceptance, the merchant:

  • Prepares and ships the goods
  • Provides tracking information to customer
  • Delivers the product/service

During this phase, the funds remain in escrow.

4. Customer Confirms Receipt

Once the customer receives and verifies the goods/services are satisfactory, they confirm receipt through the Mini Program. This releases the escrowed funds to the merchant.

API: POST /v1/payments/confirm

Request:

{
  "paymentId": "2024010111121280010016600900000xxxx",
  "confirmRequestId": "confirm_20240115_789012"
}

Response:

{
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "Success."
  },
  "confirmId": "202401151112128001016xxxx",
  "confirmTime": "2024-01-15T14:30:22+03:00"
}
Irreversible Action: Once confirmed, funds are immediately released to the merchant and cannot be reversed. Customers should only confirm after verifying goods are satisfactory.

Transaction Complete

The escrow payment is now complete and the merchant receives the funds.

Alternative Flows

Cancel Before Acceptance

If the merchant decides not to accept the order (e.g., out of stock, cannot deliver), they can cancel the payment before calling merchantAccept. This immediately returns the full amount to the customer.

API: POST /v1/payments/cancel

Use when:

  • Merchant is out of stock
  • Cannot deliver to customer's location
  • Pricing error on the order
  • Payment result delayed and merchant wants to cancel

Request:

{
  "paymentId": "2024010111121280010016600900000xxxx"
}

Response:

{
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "Success."
  },
  "paymentId": "2024010111121280010016600900000xxxx",
  "cancelTime": "2024-01-01T15:20:10+03:00"
}
Important: After calling merchantAccept, you cannot use cancel. Use void instead.

Void After Acceptance

If issues arise after the merchant has accepted the order but before/during delivery, the merchant can initiate a void to return funds to the customer.

API: POST /v1/payments/void

Use when:

  • Merchant accepted but cannot fulfill (supplier issue, damaged goods, etc.)
  • Delivery failed or goods cannot be delivered
  • Goods were damaged and cannot be replaced
  • Any other fulfillment issue preventing order completion

Full Void Request:

{
  "voidRequestId": "void_20240105_345678",
  "paymentId": "2024010111121280010016600900000xxxx"
}

Partial Void Request:

{
  "voidRequestId": "void_20240105_345678",
  "paymentId": "2024010111121280010016600900000xxxx",
  "voidAmount": {
    "value": "200000",
    "currency": "IQD"
  }
}

Response:

{
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "Success."
  },
  "voidRequestId": "void_20240105_345678",
  "voidId": "202401051112128017xxxx"
}
Partial vs Full Void:
  • Full void: Returns entire amount and closes the order
  • Partial void: Returns part of the amount; order remains open with timeout for remaining funds

Refund After Completion

If the customer already confirmed receipt but later discovers issues (defective product, wrong item, etc.), the merchant can issue a refund.

API: POST /v1/payments/refund

Use when:

  • Customer confirmed but goods were defective
  • Wrong item was delivered
  • Customer changed mind after confirmation (merchant's discretion)
  • Warranty claim or return processing

Request:

{
  "refundRequestId": "refund_20240120_901234",
  "paymentId": "2024010111121280010016600900000xxxx",
  "refundAmount": {
    "value": "500000",
    "currency": "IQD"
  }
}

Key Differences: Escrow vs Standard Payment

AspectStandard Cashier PaymentEscrow Payment
Product Code5105100010100000001151051000101000100008
Fund ReleaseImmediateAfter customer confirmation
Merchant ActionNone requiredMust call merchantAccept
Customer ActionNone requiredMust call confirm to release funds
Buyer ProtectionStandard wallet policiesExtended protection with escrow hold
After AcceptanceUse refund after completionUse void before completion, refund after

Polling Payment Status

Since escrow payments involve multiple steps, use inquiryPayment to check the current transaction status:

API: POST /v1/payments/inquiryPayment

Request:

{
  "paymentId": "2024010111121280010016600900000xxxx"
}

Response includes:

  • paymentStatus: Current payment status (PROCESSING, SUCCESS, FAIL, etc.)
  • transactions: Array of all transaction operations (PAY, VOID, REFUND, etc.)

Common Error Codes

merchantAccept Errors

resultCodeCauseAction
ORDER_STATUS_INVALIDPayment not completed or already acceptedVerify payment status via inquiryPayment
ORDER_IS_MERCHANT_ACCEPTEDAlready acceptedNo action needed
ORDER_NOT_EXISTInvalid payment IDVerify paymentId

void Errors

resultCodeCauseAction
ORDER_STATUS_INVALIDOrder cannot be voided in current statusCheck if already voided/cancelled/confirmed
VOID_AMOUNT_EXCEEDS_AUTH_LIMITVoid amount exceeds payment amountReduce void amount
MUTEX_OPERATION_IN_PROCESSINGAnother operation in progressWait and retry
ORDER_UNSUPPORTED_OPERATIONNon-escrow paymentOnly escrow payments support void

confirm Errors

resultCodeCauseAction
ORDER_STATUS_INVALIDMerchant hasn't accepted yetWait for merchant acceptance
MUTEX_OPERATION_IN_PROCESSINGVoid/refund in progressWait and retry
ORDER_NOT_EXISTInvalid payment IDVerify paymentId

cancel Errors

resultCodeCauseAction
ORDER_HAS_BEEN_CAPTUREDAlready confirmed/completedUse refund instead
ORDER_HAS_BEEN_VOIDEDAlready voidedNo action needed
CANCEL_WINDOW_EXCEEDToo late to cancelUse void or refund

Best Practices

  1. Always check payment status before calling merchantAccept to ensure payment was successful
  2. Set reasonable timeouts for acceptance and confirmation to avoid funds being held indefinitely
  3. Implement notifications for all payment events (payment, acceptance, confirmation, void, etc.)
  4. Validate inventory before accepting orders to avoid accepting orders you cannot fulfill
  5. Provide tracking information to customers after acceptance to reduce disputes
  6. Handle async notifications properly as some operations may complete asynchronously
  7. Use idempotent IDs (voidRequestId, confirmRequestId) to safely retry failed operations
  8. Monitor for UNKNOWN_EXCEPTION status and poll inquiryPayment rather than assuming failure