Integration GuideAPI Reference
Integration Guide
📓

Payment Method Name

Please use the following paymentMethodName value when creating the Intent and when completing the payment object.

"paymentMethodName": "QRIS"

📘

QRIS (Quick Response Code Indonesian Standard) is the national QR payment standard of Indonesia, regulated by Bank Indonesia. It is a QR-based push payment method: you create an Intent, receive a dynamic QR in the synchronous response, and display it to the payer. The payer scans it with any Indonesian banking or e-wallet application, and we notify you of the final status via a webhook.

A single QR is accepted by every QRIS-enabled bank and wallet in Indonesia — you do not select an issuer.

Payment Method Features

Payment MethodQRIS
CountryIndonesia
Processing CurrenciesIDR
PaymentsYes
-- Min per transaction amount1 000 IDR
-- Max per transaction amount10 000 000 IDR
RefundsYes — full amount only
-- Partial refundsNo
ChargebacksNo

Workflow: Payment

📘

For QRIS the payer always pays by scanning the QR — you render qrBase64 (or build the QR from url). The generic "redirect the payer to the provided url" branch shown in the diagram above is part of the shared H2H template and does not apply to QRIS: url is the QR payload, not a hosted checkout page (see the note on url below).


Possible Payment Scenarios

📘

QRIS has a single integration flow: you generate a QR at Intent creation, then wait for the final status via webhook. The QR resolves to one of three terminal outcomes — Successful Payment (CAPTURED), Declined Payment (DECLINED) or Expired QR (CANCELLED).

If QR generation itself fails, the payment is returned with status = ERROR already in the synchronous response — no QR is issued and no webhook follows.

NameSteps
QR Generation[1.1] The payer initiates a payment on your side.
[1.2] You send an Intent creation request via the API (POST /processing/api/v1/intents), including a Payment transaction for QRIS.
[1.3] Our platform validates the incoming data, creates the Intent, and creates a separate Payment.
[1.4] Our platform creates an order and requests a dynamic QR from the processor.
[1.5] The processor returns the created QR.
[1.6] Our platform returns the created Intent & Payment info with the latest transaction status in the synchronous response (payments[].additionalData.details.qrBase64 and url).
[1.7] You render the QR so the payer can complete the payment.
Successful Payment[2.1] The payer scans the QR in a banking or e-wallet app, reviews the details, and confirms the payment.
[2.5] The processor sends a callback with the payment result to our platform.
[2.6] Our platform changes the payment status to CAPTURED.
[2.7] Our platform sends a webhook with the latest status update.
Declined Payment[2.1] The payer scans and confirms the QR, but the payment is rejected by the payer's bank or wallet (e.g. insufficient funds or a limit exceeded).
[2.5] The processor sends a callback with the failure to our platform.
[2.6] Our platform changes the payment status to DECLINED.
[2.7] Our platform sends a webhook with the latest status update.
Expired QR[3.1] The payer does not complete the payment before the QR expires; our platform registers the expiration.
[3.2] Our platform changes the payment status to CANCELLED.
[3.3] Our platform sends a webhook with the latest status update.
⚠️

Do not display the QR longer than sessionTimeoutMin

Once the QR's lifetime (sessionTimeoutMin) elapses, the payment is moved to CANCELLED and can no longer be paid. An expired QR left on screen only results in failed scan attempts in the payer's banking app.


List of used API Requests

RequestEndpointDescription
Intent CreationPOST /processing/api/v1/intents• Creates a new Intent with a QRIS Payment transaction.
• The platform validates the request, creates the internal payment record, requests a dynamic QR from the processor, and returns the QR in the synchronous response.
Refund CreationPOST /processing/api/v1/refunds• Creates a full refund for a CAPTURED QRIS payment.
• Partial refunds are not supported by the scheme.
🔗

The final transaction status (CAPTURED, DECLINED or CANCELLED) is always delivered asynchronously via a webhook. More on the dedicated page.


Intent Creation Request/Response

📓

Request specifics

  • Endpoint: POST /processing/api/v1/intents
  • Purpose: Creates an Intent and a Payment for the QRIS payment method.
  • Structure: A unified request structure is used for all supported payment methods, but some fields are method-specific.
  • For this method:
    • paymentMethodName must be set to QRIS
    • authCurrencyCode must be set to IDR

Intent Request: Example

{
  "clientReferenceId": "1234",
  "payments": [
    {
      "paymentInstrument": {
        "paymentMethodName": "QRIS",
        "incomingDetails": {
          "sessionTimeoutMin": 20
        }
      },
      "submittedAmount": {
        "value": 150000,
        "currency": "IDR"
      },
      "authCurrencyCode": "IDR",
      "webhookUrl": "https://merchant.example.com/webhooks/payments"
    }
  ]
}

Intent Request: Fields Description

Top-Level Request Parameters
ParameterTypeRequiredDescription
clientReferenceIdStringRequiredOrder number in the merchant system.
paymentsArray of ObjectsRequiredList of payment objects to be created within the Intent.
payment
ParameterTypeRequiredDescription
payerObjectOptionalPayer details. The processor requires payer contact data to create a QRIS transaction.
paymentInstrumentObjectRequiredPayment method and payment instrument details used to process the payment.
submittedAmountObjectRequiredAmount and currency submitted for the payment.
authCurrencyCodeStringRequiredAuthorisation currency code to be used for the payment. Always IDR for this method.
If FX conversion is not required, it should match submittedAmount.currency.
descriptionStringOptionalPayment description.
webhookUrlStringRequiredURL to which payment status change webhooks will be sent.
billingAddressObjectOptionalPayer billing address.
totalTaxAmountObjectOptionalTotal tax amount included in the payment.
payer
ParameterTypeRequiredDescription
firstnameStringOptionalPayer first name.
lastnameStringOptionalPayer last name.
phoneStringOptionalPayer mobile number in international format.
emailStringOptionalPayer email address.
countryIsoCodeStringOptionalPayer country in ISO 3166-1 alpha-3 format.
localeStringOptionalPayer locale.
taxIdentificationStringOptionalPayer tax identification number.
merchantPayerReferenceStringOptionalYour own identifier of the payer. Useful for linking repeat payers on your side.
paymentInstrument
ParameterTypeRequiredDescription
paymentMethodNameStringRequiredPayment method name.
For this scenario, use QRIS.
incomingDetailsObjectRequiredPayment method-specific data required to initiate the payment.
incomingDetails
ParameterTypeRequiredDescription
sessionTimeoutMinIntegerOptionalQR lifetime in minutes. If not provided, the default of 20 minutes is applied.
Min: 5 minutes. Max: 1440 minutes (24 hours). A value of 0 is not allowed.
submittedAmount
ParameterTypeRequiredDescription
valueNumberRequiredSubmitted order amount.
IDR is a zero-decimal currency, so amounts must be whole numbers (e.g. 150000).
May differ from the authorised amount if FX conversion is applied.
currencyStringRequiredSubmitted order currency in ISO 4217 alpha-3 format.
May differ from the authorised currency if FX conversion is applied.


Webhooks

🔗

More about Payment Webhooks on the dedicated page.

You will receive a webhook on each status change of any transaction you created.

  • After the transaction is processed and reaches its final status (CAPTURED, DECLINED or CANCELLED), the platform notifies you of the latest status change via the webhook.
  • In some cases the processor may return extra information about the transaction. In that case we include additional data in the webhook.

Workflow: Refunds

📘

Full refunds only

QRIS supports native refunds, but only for the full payment amount. A refund request for less than the original amount is rejected.

A refund is available only for a payment in the CAPTURED status. Only one refund per payment is possible.


How it works

StepWhat happens
1The payer asks you for a refund.
2You send a refund request via POST /processing/api/v1/refunds, referencing the original paymentId.
3Our platform validates the request and creates a new Refund transaction. A partial refund is rejected immediately with an error.
4Our platform returns the refund creation acceptance status (ACCEPTED) in the synchronous response.
5Our platform submits the refund request to the processor.
6The processor returns the refund result.
7Our platform changes the refund status to REFUNDED (success) or DECLINED (failure).
8Our platform sends a webhook with the latest status update.

Refund Request/Response

📓

Request specifics

  • Endpoint: POST /processing/api/v1/refunds
  • Purpose: Creates a Refund for an existing Payment within an Intent.
  • For this method: partial must be false or omitted; paymentSubmittedAmount and paymentAuthAmount are not used — the refund always covers the full amount of the original payment.

Refund Request: Example

    {
      "paymentId": 344115072812343296,
      "reason": "Customer requested a refund",
      "clientReferenceId": "070826-1-refund",
      "webhookUrl": "https://merchant.example.com/webhooks/refunds"
    }

Refund Request: Fields Description

Top-Level Request Parameters
ParameterTypeRequiredDescription
paymentIdIntegerRequiredIdentifier of the original payment to be refunded.
partialBooleanOptionalWhether this is a partial refund. Defaults to false.
Partial refunds are not supported for this method — true is rejected.
reasonStringOptionalReason for the refund.
clientReferenceIdStringOptionalYour own unique reference for the refund.
webhookUrlStringOptionalURL that will receive refund status update notifications.
The final refund status is delivered only via webhook.

Refund Statuses

StatusMeaning
ACCEPTEDThe refund request has been accepted by the platform and is about to be sent to the processor.
REFUND_REQUESTEDThe processor has accepted the request. The result is not final yet.
REFUNDEDThe funds have been returned to the payer. Final status.
DECLINEDThe refund was rejected — by validation or by the processor. Final status.

Other values of RefundStatusEnum are not used for this payment method.

⚠️

Refunds and your settlement balance

A refund is paid out of your remaining settlement balance at the processor. If the balance is insufficient, the refund request is declined; you can create a new refund request once the balance is replenished by incoming payments.

🔗

The final refund status (REFUNDED or DECLINED) is always delivered asynchronously via a webhook. More on the dedicated page.



Testing in Sandbox

Our system allows emulating different test cases in the sandbox environment.

To simulate specific scenarios and receive different results, use the intent.description parameter.

QRIS Payment Test flow

List of Supported Cases

CaseScenario Keyword
(intent.description)
Final Payment Status
  1. Successful payment
--CAPTURED
  1. QR Expired
--CANCELLED
  1. Payment Declined
DeclinedPaymentDECLINED
  1. Failure
LinkCreationErrorERROR

Case "Successful payment"

Steps:

  1. The client creates an intent via POST /processing/api/v1/intents.
  2. A synchronous response is returned with status = AUTHORIZATION_REQUESTED.
  3. Render the QR from payments[].additionalData.details.qrBase64.
  4. A webhook is received with status = CAPTURED.

Did this page help you?