ID: QRIS - Payments
Payment Method NamePlease use the following
paymentMethodNamevalue when creating theIntentand when completing thepaymentobject."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 Method | QRIS |
|---|---|
| Country | Indonesia |
| Processing Currencies | IDR |
| Payments | Yes |
| -- Min per transaction amount | 1 000 IDR |
| -- Max per transaction amount | 10 000 000 IDR |
| Refunds | Yes — full amount only |
| -- Partial refunds | No |
| Chargebacks | No |
Workflow: Payment
For QRIS the payer always pays by scanning the QR — you render
qrBase64(or build the QR fromurl). 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:urlis the QR payload, not a hosted checkout page (see the note onurlbelow).
Possible Payment Scenarios
QRIS has a single integration flow: you generate a QR at
Intentcreation, 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 = ERRORalready in the synchronous response — no QR is issued and no webhook follows.
| Name | Steps |
|---|---|
| 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 thansessionTimeoutMinOnce the QR's lifetime (
sessionTimeoutMin) elapses, the payment is moved toCANCELLEDand 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
| Request | Endpoint | Description |
|---|---|---|
| Intent Creation | POST /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 Creation | POST /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,DECLINEDorCANCELLED) 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
Intentand aPaymentfor 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:
paymentMethodNamemust be set toQRISauthCurrencyCodemust be set toIDR
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
| Parameter | Type | Required | Description |
|---|---|---|---|
clientReferenceId | String | Required | Order number in the merchant system. |
payments | Array of Objects | Required | List of payment objects to be created within the Intent. |
payment
| Parameter | Type | Required | Description |
|---|---|---|---|
payer | Object | Optional | Payer details. The processor requires payer contact data to create a QRIS transaction. |
paymentInstrument | Object | Required | Payment method and payment instrument details used to process the payment. |
submittedAmount | Object | Required | Amount and currency submitted for the payment. |
authCurrencyCode | String | Required | Authorisation currency code to be used for the payment. Always IDR for this method. If FX conversion is not required, it should match submittedAmount.currency. |
description | String | Optional | Payment description. |
webhookUrl | String | Required | URL to which payment status change webhooks will be sent. |
billingAddress | Object | Optional | Payer billing address. |
totalTaxAmount | Object | Optional | Total tax amount included in the payment. |
payer
| Parameter | Type | Required | Description |
|---|---|---|---|
firstname | String | Optional | Payer first name. |
lastname | String | Optional | Payer last name. |
phone | String | Optional | Payer mobile number in international format. |
email | String | Optional | Payer email address. |
countryIsoCode | String | Optional | Payer country in ISO 3166-1 alpha-3 format. |
locale | String | Optional | Payer locale. |
taxIdentification | String | Optional | Payer tax identification number. |
merchantPayerReference | String | Optional | Your own identifier of the payer. Useful for linking repeat payers on your side. |
paymentInstrument
| Parameter | Type | Required | Description |
|---|---|---|---|
paymentMethodName | String | Required | Payment method name. For this scenario, use QRIS. |
incomingDetails | Object | Required | Payment method-specific data required to initiate the payment. |
incomingDetails
| Parameter | Type | Required | Description |
|---|---|---|---|
sessionTimeoutMin | Integer | Optional | QR 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
| Parameter | Type | Required | Description |
|---|---|---|---|
value | Number | Required | Submitted 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. |
currency | String | Required | Submitted order currency in ISO 4217 alpha-3 format. May differ from the authorised currency if FX conversion is applied. |
Response specifics
- The
Intentcreation response is generally consistent across all supported payment methods.- Common fields describe the
Intent, thePayment, and the current transaction status.- Method-specific data is returned in
payments[].additionalData.details. Both fields represent the same QR — use one of the two:
qrBase64— the QR as a Base64-encoded PNG image. Display it to the payer as-is.url— the raw EMVCo payload the QR encodes. Use it only if you render the QR image yourself.
Intent Response: Example
{
"intentId": "232510340021612544",
"payments": [
{
"id": "232510340646563840",
"submittedAmount": {
"value": 150000,
"currency": "IDR"
},
"authAmount": {
"value": 150000,
"currency": "IDR"
},
"paymentInstrument": {
"id": "PI204264628196626432",
"paymentMethodId": "PM199507277233246209",
"paymentMethodName": "QRIS",
"storedDetails": {
"sessionTimeoutMin": 20
}
},
"status": "AUTHORIZATION_REQUESTED",
"additionalData": {
"details": {
"qrBase64": "iVBORw0KGgoAAAANSUhEUgAA... (Base64-encoded QR image)",
"url": "00020101021226610014COM.GO-JEK.WWW01189360091430096736560210G0967365650303UMI51440014ID.CO.QRIS.WWW0215ID10200160245310303UMI5204581253033605802ID5908Merchant6013Jakarta Pusat61051034062070703A016304D11E"
}
}
}
],
"disbursements": [],
"clientReferenceIntentId": "1234",
"paymentCreationErrors": [],
"disbursementCreationErrors": [],
"intentStatus": "IN_PROGRESS"
}Response Description
Top-Level Response Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intentId | String | Required | Unique identifier of the Intent in our platform. |
payments | Array of Objects | Required | List of Payment objects created within the Intent. |
disbursements | Array | Optional | List of disbursement objects created within the Intent. Empty if no disbursements were created. |
clientReferenceIntentId | String | Required | Order number in the merchant system. Returned from the request. |
paymentCreationErrors | Array | Required | List of payment creation errors, if any. Empty if no errors occurred. |
disbursementCreationErrors | Array | Required | List of disbursement creation errors, if any. Empty if no errors occurred. |
intentStatus | String | Required | Current Intent status. See the dedicated section of this API guide for possible values and the status model. |
payment
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Required | Unique identifier of the Payment in our platform. |
submittedAmount | Object | Required | Submitted payment amount and currency. Returned from the request. |
authAmount | Object | Required | Authorised amount and currency used for the payment. |
paymentInstrument | Object | Required | Payment method information associated with the payment. |
status | String | Required | Current payment status. See the dedicated section of this API guide for possible values and the status model. |
additionalData | Object | Optional | Method-specific data required to continue or complete the payment flow. |
submittedAmount
| Parameter | Type | Required | Description |
|---|---|---|---|
value | Number | Required | Submitted order amount. Returned from the request. May differ from the authorised amount if FX conversion is applied. |
currency | String | Required | Submitted order currency. Returned from the request. May differ from the authorised currency if FX conversion is applied. |
authAmount
| Parameter | Type | Required | Description |
|---|---|---|---|
value | Number | Required | Authorised amount used for the payment. May differ from the submitted amount if FX conversion is applied. |
currency | String | Required | Authorisation currency used for the payment. Always IDR for this method. |
paymentInstrument
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Optional | Unique identifier of the payment instrument in our platform. |
paymentMethodId | String | Optional | Unique identifier of the payment method in our platform. |
paymentMethodName | String | Optional | Payment method name. Returned from the request. |
storedDetails | Object | Optional | Stored payment instrument details. Returned when applicable. |
storedDetails
| Parameter | Type | Required | Description |
|---|---|---|---|
sessionTimeoutMin | Integer | Optional | QR lifetime in minutes. Returned from the request. |
additionalData
| Parameter | Type | Required | Description |
|---|---|---|---|
details | Object | Required | Method-specific details required to continue or complete the payment flow. |
details
| Parameter | Type | Required | Description |
|---|---|---|---|
qrBase64 | String | Required | QRIS code rendered as a PNG image and encoded in Base64. Render it and display it to the payer. |
url | String | Required | The raw EMVCo payload encoded in the QR. This is not a link — it is the QR content itself, and opening it in a browser will not work. Use it only if you generate the QR image yourself. |
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,DECLINEDorCANCELLED), 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
CAPTUREDstatus. Only one refund per payment is possible.
How it works
| Step | What happens |
|---|---|
| 1 | The payer asks you for a refund. |
| 2 | You send a refund request via POST /processing/api/v1/refunds, referencing the original paymentId. |
| 3 | Our platform validates the request and creates a new Refund transaction. A partial refund is rejected immediately with an error. |
| 4 | Our platform returns the refund creation acceptance status (ACCEPTED) in the synchronous response. |
| 5 | Our platform submits the refund request to the processor. |
| 6 | The processor returns the refund result. |
| 7 | Our platform changes the refund status to REFUNDED (success) or DECLINED (failure). |
| 8 | Our platform sends a webhook with the latest status update. |
Refund Request/Response
Request specifics
- Endpoint:
POST /processing/api/v1/refunds- Purpose: Creates a
Refundfor an existingPaymentwithin anIntent.- For this method:
partialmust befalseor omitted;paymentSubmittedAmountandpaymentAuthAmountare 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
| Parameter | Type | Required | Description |
|---|---|---|---|
paymentId | Integer | Required | Identifier of the original payment to be refunded. |
partial | Boolean | Optional | Whether this is a partial refund. Defaults to false. Partial refunds are not supported for this method — true is rejected. |
reason | String | Optional | Reason for the refund. |
clientReferenceId | String | Optional | Your own unique reference for the refund. |
webhookUrl | String | Optional | URL that will receive refund status update notifications. The final refund status is delivered only via webhook. |
Response specifics
- The synchronous response confirms that the refund request has been accepted for processing, not that the money has been returned.
statusisACCEPTEDat this point. The final status arrives via webhook.
Refund Response: Example
{
"id": "344115098234117632",
"intentId": "344115072451633152",
"paymentId": "344115072812343296",
"refundSubmittedAmount": {
"value": 150000,
"currency": "IDR"
},
"refundAuthAmount": {
"value": 150000,
"currency": "IDR"
},
"reason": "Customer requested a refund",
"status": "ACCEPTED",
"createdAt": "2026-09-02T10:15:12.318Z",
"clientReferenceTransactionId": "070826-1-refund"
}Response Description
Top-Level Response Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Required | Unique identifier of the Refund in our platform. |
intentId | String | Required | Identifier of the Intent the original payment belongs to. |
paymentId | String | Required | Identifier of the refunded payment. Returned from the request. |
refundSubmittedAmount | Object | Required | Submitted refund amount and currency. Always equal to the original payment. |
refundAuthAmount | Object | Required | Authorised refund amount and currency. |
reason | String | Optional | Reason for the refund. Returned from the request. |
status | String | Required | Current refund status. See the status table below. |
createdAt | String | Required | Refund creation time. |
clientReferenceTransactionId | String | Optional | Your own reference for the refund. Returned from the request. |
Refund Statuses
| Status | Meaning |
|---|---|
ACCEPTED | The refund request has been accepted by the platform and is about to be sent to the processor. |
REFUND_REQUESTED | The processor has accepted the request. The result is not final yet. |
REFUNDED | The funds have been returned to the payer. Final status. |
DECLINED | The 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 balanceA 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 (
REFUNDEDorDECLINED) 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
| Case | Scenario Keyword (intent.description) | Final Payment Status |
|---|---|---|
| -- | CAPTURED |
| -- | CANCELLED |
| DeclinedPayment | DECLINED |
| LinkCreationError | ERROR |
Case "Successful payment"
Steps:
- The client creates an intent via
POST /processing/api/v1/intents. - A synchronous response is returned with
status = AUTHORIZATION_REQUESTED. - Render the QR from
payments[].additionalData.details.qrBase64. - A webhook is received with
status = CAPTURED.
Case "QR Expired"
Steps:
- The client creates an intent via
POST /processing/api/v1/intents(use thesessionTimeoutMinparameter to shorten the expiration time). - A synchronous response is returned with
status = AUTHORIZATION_REQUESTED. - Wait for the expiration time.
- A webhook is received with
status = CANCELLED.
Case "Payment Declined"
Steps:
- The client creates an intent via
POST /processing/api/v1/intentsusing theintent.descriptionparameter with the value"DeclinedPayment". - A synchronous response is returned with
status = AUTHORIZATION_REQUESTED. - Render the QR from
payments[].additionalData.details.qrBase64. - A webhook is received with
status = DECLINED.
Case "Failure"
Steps:
- The client creates an intent via
POST /processing/api/v1/intentsusing theintent.descriptionparameter with the value"LinkCreationError". - A synchronous response is returned with
status = ERROR.
Updated about 1 hour ago
