2C2P Gateway Guide

Quick start

New to integrating 2C2P with Spreedly? Here's your setup guide:

  1. Retrieve your 2C2P credentials
    1. Log into your 2C2P merchant portal (or contact your 2C2P account manager)
    2. Obtain your merchant ID and secret key
    3. Download your 2C2P public certificate PEM and generate/upload your merchant private key PEM (and password if applicable)
  2. Get familiar with the Spreedly API
    1. Review Spreedly API basics
    2. Understand Gateways, Payment Methods, and Transactions
  3. Add a 2C2P gateway to Spreedly in sandbox mode
ℹ️

Review this documentation to understand more about how to test transactions.

Additional Information

2C2P uses two independent request-signing schemes depending on the operation. two_c_two_p_public_certificate_pem, merchant_private_key_pem, and merchant_jws_key_id are all required — the gateway will not initialize without them.

  • Payment token / inquiry / purchase are signed with symmetric JWT (HS256) using secret_key only.
  • Payment maintenance (capture, void, refund) uses asymmetric JWE + JWS (RSA-OAEP + A256GCM encryption, PS256 signing) with two_c_two_p_public_certificate_pem and merchant_private_key_pem.
  • Customer tokens (store, remove) use the same JWE + JWS scheme and place merchant_jws_key_id in the JWS kid header so 2C2P can select your registered public key.

Currency

The optional currency field sets the merchant account's default ISO currency (e.g. SGD, THB, VND, IDR). It is used as the fallback currency when a transaction does not specify currency_code.

Region

The optional region field controls endpoint routing and accepts two values:

ValueRoutes toUse for
standard2C2P standard PGW hostsThailand, Philippines, Vietnam
indonesia2C2P Indonesia (Alipay) PGW hostsIndonesia (IDR) merchants

If region is omitted or blank it defaults to standard. Any value other than standard or indonesia raises an ArgumentError at gateway creation. Indonesia (IDR) merchants must set region to indonesia explicitly — it is no longer inferred from the currency.

Supported onboarding countries: Thailand, Philippines, Vietnam, and Indonesia.

Supported card brands: Visa, Mastercard, American Express, JCB, Diners Club.

For stored credential (MIT) chaining, Spreedly maps 2C2P's paymentID (preferred) or schemePaymentID (fallback) as the network transaction identifier.

Certificates & keys

2C2P uses different credentials depending on the API being called. Understanding which credential signs which request avoids most 401 Unauthorized and gatewayCryptoError failures.

Credentials at a glance

CredentialWhat it isWho generates itUsed by
secret_key64-char hex HMAC-SHA256 shared secret2C2P (portal)JWT (HS256) signing on PGW JSON APIs
two_c_two_p_public_certificate_pem2C2P's X.509 cert (their RSA public key)2C2P (portal download)JWE encryption + JWS verification for PaymentAction & CustomerToken
merchant_private_key_pemYour 2048-bit RSA private key (PKCS#1 or PKCS#8)You (openssl)JWS signing + JWE decryption for PaymentAction & CustomerToken
merchant_jws_key_id32-char hex key identifier2C2P (on key upload)JWS kid header — CustomerToken only

Which API uses which scheme

OperationSigning schemeCredentials used
Payment token / inquiry / purchaseSymmetric JWT (HS256)secret_key only
Capture / void / refundAsymmetric JWE(JWS) — no kidtwo_c_two_p_public_certificate_pem + merchant_private_key_pem
Store / remove (customer token)Asymmetric JWE(JWS) — with kidBoth certs plus merchant_jws_key_id

The asymmetric flow is standard hybrid encrypt-then-sign: the request body is JWE-encrypted with 2C2P's public key (RSA-OAEP + A256GCM) so only 2C2P can read it, then JWS-signed with your private key (PS256) so 2C2P can verify authenticity. Responses invert the direction — 2C2P signs with their key and encrypts with yours; you verify with their cert and decrypt with your private key.

Adding a 2C2P gateway

Required Credentials

merchant_id: Your 2C2P merchant identifier
secret_key: Your 2C2P API secret key
two_c_two_p_public_certificate_pem: 2C2P public certificate PEM for JWE encryption and JWS verification
merchant_private_key_pem: Your merchant private key PEM for JWS signing and JWE decryption
merchant_jws_key_id: Key identifier placed in the JWS kid header so 2C2P can select your registered public key

Optional Credentials

currency (Optional): Default transaction currency (e.g. PHP, IDR, THB)
region (Optional): Regional routing profile (indonesia or standard). Defaults to standard when omitted. Indonesia (IDR) merchants must set this to indonesia explicitly — it is not inferred from the currency. Any value other than standard or indonesia is rejected at gateway creation.
merchant_private_key_pem_password (Optional): Password for the merchant private key PEM

Request

curl https://core.spreedly.com/v1/gateways.xml \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/xml' \
  -d '<gateway>
        <gateway_type>two_c_two_p</gateway_type>
        <merchant_id>your_merchant_id</merchant_id>
        <secret_key>your_secret_key</secret_key>
        <currency>PHP</currency>
        <region>standard</region>
        <merchant_jws_key_id>your_merchant_jws_key_id</merchant_jws_key_id>
        <two_c_two_p_public_certificate_pem>your_2c2p_public_certificate_pem</two_c_two_p_public_certificate_pem>
        <merchant_private_key_pem>your_merchant_private_key_pem</merchant_private_key_pem>
        <merchant_private_key_pem_password>your_merchant_private_key_password</merchant_private_key_pem_password>
      </gateway>'

Response

<gateway>
  <token>SampleTokenHere</token>
  <gateway_type>two_c_two_p</gateway_type>
  <name>2C2P</name>
  <description nil="true"/>
  <merchant_id>your_merchant_id</merchant_id>
  <merchant_jws_key_id>your_merchant_jws_key_id</merchant_jws_key_id>
  <currency>PHP</currency>
  <region>standard</region>
  <characteristics>
    <supports_purchase type="boolean">true</supports_purchase>
    <supports_authorize type="boolean">true</supports_authorize>
    <supports_capture type="boolean">true</supports_capture>
    <supports_credit type="boolean">true</supports_credit>
    <supports_general_credit type="boolean">false</supports_general_credit>
    <supports_void type="boolean">true</supports_void>
    <supports_adjust type="boolean">false</supports_adjust>
    <supports_verify type="boolean">false</supports_verify>
    <supports_reference_purchase type="boolean">false</supports_reference_purchase>
    <supports_offsite_purchase type="boolean">false</supports_offsite_purchase>
    <supports_offsite_authorize type="boolean">false</supports_offsite_authorize>
    <supports_offsite_synchronous_purchase type="boolean">false</supports_offsite_synchronous_purchase>
    <supports_offsite_synchronous_authorize type="boolean">false</supports_offsite_synchronous_authorize>
    <supports_3dsecure_purchase type="boolean">false</supports_3dsecure_purchase>
    <supports_3dsecure_authorize type="boolean">false</supports_3dsecure_authorize>
    <supports_3dsecure_2_purchase type="boolean">true</supports_3dsecure_2_purchase>
    <supports_3dsecure_2_authorize type="boolean">true</supports_3dsecure_2_authorize>
    <supports_3dsecure_2_mpi_purchase type="boolean">true</supports_3dsecure_2_mpi_purchase>
    <supports_3dsecure_2_mpi_authorize type="boolean">true</supports_3dsecure_2_mpi_authorize>
    <supports_purchase_via_preauthorization type="boolean">false</supports_purchase_via_preauthorization>
    <supports_store type="boolean">true</supports_store>
    <supports_remove type="boolean">true</supports_remove>
    <supports_fraud_review type="boolean">false</supports_fraud_review>
    <supports_network_tokenization type="boolean">true</supports_network_tokenization>
    <supports_partial_capture type="boolean">false</supports_partial_capture>
    <supports_partial_credit type="boolean">false</supports_partial_credit>
    <supports_stored_credentials type="boolean">true</supports_stored_credentials>
    <supports_stored_stored_credentials type="boolean">false</supports_stored_stored_credentials>
    <supports_inquire_by_gateway_transaction_id type="boolean">false</supports_inquire_by_gateway_transaction_id>
    <supports_inquire_by_order_id type="boolean">false</supports_inquire_by_order_id>
    <supports_transaction_retry type="boolean">false</supports_transaction_retry>
  </characteristics>
  <credentials>
    <credential>
      <name>merchant_id</name>
      <value>your_merchant_id</value>
    </credential>
    <credential>
      <name>merchant_jws_key_id</name>
      <value>your_merchant_jws_key_id</value>
    </credential>
    <credential>
      <name>currency</name>
      <value>PHP</value>
    </credential>
    <credential>
      <name>region</name>
      <value>standard</value>
    </credential>
  </credentials>
  <gateway_specific_fields>
    <gateway_specific_field>payment_channel</gateway_specific_field>
    <gateway_specific_field>channel_code</gateway_specific_field>
    <gateway_specific_field>locale</gateway_specific_field>
  </gateway_specific_fields>
  <payment_methods>
    <payment_method>credit_card</payment_method>
    <payment_method>third_party_network_token</payment_method>
  </payment_methods>
  <state>retained</state>
  <redacted type="boolean">false</redacted>
  <sandbox type="boolean">true</sandbox>
</gateway>

Purchase

Request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "ip": "203.0.113.1",
    "email": "[email protected]",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "token": "44k22fahmpfxsu6e0ry",
    "transaction_type": "Purchase",
    "amount": 500,
    "currency_code": "SGD",
    "gateway_transaction_id": "14888196|8738291",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success",
        "tranRef": "14888196",
        "referenceNo": "8738291",
        "invoiceNo": "PURCHASE_TEST_1718012345"
      }
    },
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Authorize

Request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/authorize.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 200,
    "currency_code": "SGD",
    "ip": "203.0.113.1",
    "email": "[email protected]",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "token": "7k33gbcnqpfxsu6e1rz",
    "transaction_type": "Authorization",
    "amount": 200,
    "currency_code": "SGD",
    "gateway_transaction_id": "AUTH-TR|AUTH-RN",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success",
        "tranRef": "AUTH-TR",
        "referenceNo": "AUTH-RN"
      }
    },
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Capture

Request (capture a prior authorization token; PHP gateway with PEM credentials):

curl https://core.spreedly.com/v1/transactions/[authorization_token]/capture.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Capture",
    "amount": 10000,
    "currency_code": "PHP",
    "gateway_transaction_id": "CAP-TR|CAP-RN",
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Void

Request:

curl https://core.spreedly.com/v1/transactions/[authorization_token]/void.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Void",
    "amount": 10000,
    "currency_code": "PHP",
    "gateway_transaction_id": "VOID-TR|VOID-RN",
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Credit (refund)

Request (partial refund of 50.00 PHP):

curl https://core.spreedly.com/v1/transactions/[purchase_token]/credit.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "amount": 5000
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Credit",
    "amount": 5000,
    "currency_code": "PHP",
    "gateway_transaction_id": "REF-TR|REF-RN",
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Customer tokens (store & unstore)

2C2P supports storing a card as a reusable customer token via the CustomerToken API (CustomerToken/add and CustomerToken/delete). This is the same JWE+JWS scheme as payment maintenance, but it additionally requires merchant_jws_key_id — 2C2P uses the kid in the JWS header to select which of your registered public keys to verify with. A store request against a gateway without merchant_jws_key_id is rejected.

Store

curl https://core.spreedly.com/v1/gateways/{gateway_token}/store.json \
  -u 'C7cRfNJGODKh4Iu53PToKjniY:4UIuWybmdythqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{
        "transaction": {
          "payment_method_token": "{payment_method_token}",
          "currency_code": "IDR",
          "email": "[email protected]"
        }
      }'

Unstore

Remove the stored token (via redact against the storing gateway):

curl https://core.spreedly.com/v1/payment_methods/{stored_payment_method_token}/redact.json \
  -u 'C7cRfNJGODKh4Iu53PToKjniY:4UIuWybmdythqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -X PUT \
  -d '{
        "transaction": {
          "remove_from_gateway": "{gateway_token}"
        }
      }'

🚧 A 401 Unauthorized on store almost always means the public key registered against merchant_jws_key_id on the portal does not match the merchant_private_key_pem on the gateway. Confirm the kid and key pair using the MD5 check above.

Gateway specific fields

<gateway_specific_fields>
  <two_c_two_p>
    <payment_channel>CC</payment_channel>
    <channel_code>VI</channel_code>
    <locale>en</locale>
  </two_c_two_p>
</gateway_specific_fields>
  • payment_channel: 2C2P payment channel. Use CC for card PAN; network tokens may omit unless explicitly set (avoids MID-specific errors).
  • channel_code: Channel code within the payment channel.
  • locale: Locale for 2C2P API requests.
Field NameTypeRequiredUse Case (Description)Example Value
payment_channelStringNo2C2P payment channel identifier (e.g. CC for card, CSTOKEN for network token)CC
channel_codeStringNoChannel code within the selected payment channelVI
localeStringNoLocale for 2C2P request/response formattingen

Gateway specific response fields

A response from 2C2P may contain additional fields which you can find in the gateway_specific_response_fields. These currently include respCode, respDesc, tranRef, referenceNo, invoiceNo, amount, currencyCode, paymentID, and schemePaymentID.

For example:

<gateway_specific_response_fields>
  <two_c_two_p>
    <respCode>0000</respCode>
    <respDesc>Success</respDesc>
    <tranRef>TRF1</tranRef>
    <referenceNo>REF1</referenceNo>
    <invoiceNo>ORD_P_FLOW_1</invoiceNo>
    <amount>100.0</amount>
    <currencyCode>PHP</currencyCode>
    <paymentID>PAY-12345678</paymentID>
    <schemePaymentID>SCH-9</schemePaymentID>
  </two_c_two_p>
</gateway_specific_response_fields>
Field NameTypeDescription
respCodeString2C2P result code. Success values: 0000 / 00 (approved), 0001 (accepted/pending). Other notable codes: 1001 (3DS required), 1005 (QR scan pending for PromptPay), 2000 (settlement success for maintenance ops), 12 (not refundable), 32 (auto-settlement)
respDescStringHuman-readable result description
tranRefString2C2P transaction reference
referenceNoString2C2P reference number
invoiceNoStringMerchant order/invoice ID; used for inquiry and maintenance
amountDecimalConfirmed amount from payment inquiry (major units)
currencyCodeStringConfirmed currency from payment inquiry
paymentIDStringPayment ID for stored-credential MIT chaining
schemePaymentIDStringFallback payment ID when paymentID is absent
refundReferenceNoStringReference on refund/credit maintenance responses; used as gateway_transaction_id for credit transactions

gateway_transaction_id is derived as invoiceNo|referenceNo (from inquiry) or tranRef|referenceNo (from do-payment). For credit transactions it falls back to refundReferenceNo. paymentID is also persisted on the payment method as stored_credential_usage.two_c_two_p.network_transaction_id.

3DS transactions

2C2P supports 3D Secure transactions via Spreedly's 3DS Global framework.

Request (authenticated 3DS2 purchase):

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "ip": "203.0.113.1",
    "email": "[email protected]",
    "three_ds_version": "2.2.0",
    "three_ds": {
      "ecommerce_indicator": "05",
      "authentication_value": "AAABB3NTYWxBYWFBQUFBQUFBQUE=",
      "directory_server_transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    },
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Purchase",
    "gateway_transaction_id": "3DS-TR|3DS-RN",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success"
      }
    },
    "message": "Succeeded!"
  }
}

Third-party 3DS2 auth data

2C2P supports third-party 3DS2 authentication data via Spreedly's MPI framework for 3dsecure_2_mpi_purchase and 3dsecure_2_mpi_authorize transactions.

Stored Credentials

For 2C2P, sending stored credential fields can be done using Spreedly's first class support. For any Authorize or Purchase request, include:

  • stored_credential_initiator
  • stored_credential_reason_type

Spreedly chains subsequent MIT transactions using 2C2P's paymentID (preferred) or schemePaymentID as the network transaction identifier.

CIT (initial) request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "stored_credential_initiator": "cardholder",
    "stored_credential_reason_type": "recurring",
    "gateway_specific_fields": {
      "two_c_two_p": { "locale": "en" }
    }
  }
}'

CIT response (note paymentID persisted as network_transaction_id):

{
  "transaction": {
    "succeeded": true,
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "paymentID": "PAY-12345678"
      }
    },
    "payment_method": {
      "stored_credential_usage": {
        "two_c_two_p": {
          "network_transaction_id": "PAY-12345678"
        }
      }
    }
  }
}

MIT (subsequent) request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 400,
    "currency_code": "SGD",
    "stored_credential_initiator": "merchant",
    "stored_credential_reason_type": "recurring",
    "gateway_specific_fields": {
      "two_c_two_p": { "locale": "en" }
    }
  }
}'

Learn more in our Stored Credentials guide.

Network Tokenization

While our Network tokenization framework provisions network tokens at Spreedly, your 2C2P merchant account must be correctly configured to use network tokens.

Please contact 2C2P support to enable network tokenization on your account.

Request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "network_token_payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "attempt_network_token": true,
    "gateway_specific_fields": {
      "two_c_two_p": { "locale": "en" }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "gateway_transaction_id": "NT-TR|NT-RN",
    "network_tokenization": {
      "attempted": true,
      "errors": []
    },
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success"
      }
    },
    "message": "Succeeded!"
  }
}

Local Payment Methods

Spreedly supports a variety of local payment methods through 2C2P. Each may require unique parameters and flows. Please expand each payment method's details to find implementation information.

BCA Installment (Indonesia)

BCA Installment (Indonesia)

BCA Installment is 2C2P's Installment Payment Plan (IPP) for Bank Central Asia cards in Indonesia. It uses the standard Spreedly credit card payment method on an inline Purchase or Authorize — not a separate local payment method type. When you pass installment_period in gateway specific fields, Spreedly maps doPayment to 2C2P's IPP channel (channelCode: IPP) with payment-token category GCARD.

Capabilities: Purchase, Authorize (inline card flow)
Flow: Inline synchronous (payment token → do payment → payment inquiry)
Region / currency: Indonesia (region: indonesia), IDR
Issuing bank: Determined by the card BIN (BCA sandbox card 4556330000000019)

Prerequisites

  • 2C2P gateway with currency: IDR, region: indonesia, and an IPP-enabled Indonesia MID
  • PEM credentials configured (two_c_two_p_public_certificate_pem, merchant_private_key_pem)
  • Card with CVV and billing country ID

Run a purchase

Use a standard credit card payment method token. Pass installment fields under gateway_specific_fields.two_c_two_p. installment_period triggers the IPP flow.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{credit_card_token}}",
    "amount": 80000,
    "currency_code": "IDR",
    "order_id": "BCA_IPP_INV_01",
    "description": "BCA Installment Purchase",
    "email": "[email protected]",
    "ip": "203.0.113.1",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "installment_period": 6,
        "interest_type": "M"
      }
    }
  }
}

On success the transaction returns state: succeeded inline (not pending).

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Purchase",
    "order_id": "BCA_IPP_INV_01",
    "amount": 80000,
    "currency_code": "IDR",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "BCA_IPP_INV_01",
        "resp_code": "0000",
        "reference_no": "10940866"
      }
    },
    "message": "Succeeded!"
  }
}

The same gateway specific fields apply on Authorize. Capture, void, and refund follow the standard 2C2P card paths after authorization.

Installment gateway specific fields

FieldTypeRequiredDescription
installment_periodIntegerYes (triggers IPP)Selected tenure in months (e.g. 6)
interest_typeStringNoA (all), C (customer pays), or M (merchant pays). Default A
installment_period_filterArrayNoRestrict offered tenures (e.g. [3, 6, 12])
installment_bank_filterArrayNoRestrict issuing bank (e.g. ["Bank Central Asia"] for BCA)

2C2P channel mapping

StepFieldIPP value
Payment TokenpaymentChannel["GCARD"]
Do PaymentchannelCodeIPP
Do PaymentinterestType / installmentPeriodFrom GSF

Notes

  • Not offsite: Unlike PromptPay, BCA IPP does not return pending, a QR code, or a redirect URL. Collect the buyer's installment period in your checkout UI before calling Spreedly.
  • 3DS: If your MID requires 3DS, use Spreedly 3DS Global or gateway-specific 3DS per the main 2C2P guide.
  • Sandbox: Confirm your Indonesia MID has IPP enabled with 2C2P before testing. Use BCA card 4556330000000019 (not 4111111111111111, which returns 2003 no IPP records on this MID).

DANA

DANA

DANA is an Indonesian e-wallet supported via 2C2P's DANA channel code. Merchants initiate offsite wallet payments that complete in the DANA app. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry. DANA refunds are asynchronous (REFUND_PENDING until final status).

Capabilities: Offsite purchase; asynchronous refunds subject to settlement
Flow: Offsite async (redirect/deep link → buyer approves in DANA app → callback / status inquiry)
Country / currency: ID / IDR

Create the payment method

Create a DANA payment method with payment_method_type set to dana and country set to ID.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "dana",
    "full_name": "Budi Santoso",
    "email": "[email protected]",
    "phone_number": "081234567890",
    "country": "ID"
  }
}

Run a purchase

Initiate an offsite purchase on your Indonesia 2C2P gateway (currency: IDR, region: indonesia). Include callback_url and redirect_url. Configure merchant_jws_key_id for refund maintenance calls.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 10000,
    "currency_code": "IDR",
    "order_id": "DANA-1789906849",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "id"
      }
    }
  }
}

The purchase returns pending. Redirect the buyer to gateway_specific_response_fields.two_c_two_p.redirect_url or checkout_url.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "DANA-1789906849",
        "resp_code": "1005",
        "channel_code": "DANA",
        "redirect_url": "https://sandbox-payment.2c2p.com/2c2pfrontend/redirect/DANA/..."
      }
    },
    "message": "Pending"
  }
}

Do not mark the order paid on browser return alone — wait for Spreedly callback/inquiry to move the transaction out of pending.

Refunds

DANA refunds may return REFUND_PENDING. Poll refund status or use notify_url on the credit request; do not treat the initial refund response as final.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 5000,
    "currency_code": "IDR",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

GCash

GCash

GCash is a Philippines e-wallet supported via 2C2P's Digital Payment (DPAY) channel and GCASH channel code. Merchants initiate offsite wallet payments that complete through the GCash app or web experience. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (redirect/deep link → buyer authorizes in GCash → callback / status inquiry)
Country / currency: PH / PHP

Create the payment method

Create a GCash payment method with payment_method_type set to gcash and country set to PH. full_name, email, and phone_number are required.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "gcash",
    "full_name": "Spreedly GWI",
    "email": "[email protected]",
    "phone_number": "8522847035",
    "country": "PH",
    "address1": "1234 test ave",
    "city": "Manila",
    "state": "MN",
    "zip": "12345",
    "retained": true
  }
}

Run a purchase

Initiate an offsite purchase on your Philippines 2C2P gateway (currency: PHP). Spreedly maps buyer contact fields from the payment method to 2C2P.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 5000,
    "currency_code": "PHP",
    "order_id": "ORDER-2026-06-30-0001",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}

The purchase returns pending. Redirect the buyer to checkout_url or gateway_specific_response_fields.two_c_two_p.payment_url.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "order_id": "ORDER-2026-06-30-0001",
    "amount": 5000,
    "currency_code": "PHP",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "ORDER-2026-06-30-0001",
        "resp_code": "1001",
        "channel_code": "GCASH",
        "payment_url": "https://uat.m.gcash.com/gcash-cashier-web/..."
      }
    },
    "checkout_url": "https://uat.m.gcash.com/gcash-cashier-web/...",
    "message": "Pending"
  }
}

Do not mark the order paid on browser return alone — wait for Spreedly callback/inquiry to move the transaction out of pending.

Refunds

After a succeeded GCash payment, issue a credit (refund) against the original transaction per your 2C2P merchant agreement.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 2500,
    "currency_code": "PHP",
    "notify_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

MoMo

MoMo

MoMo is a Vietnamese e-wallet supported via 2C2P's MOMO channel code. Merchants initiate offsite wallet payments that complete in the MoMo app. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (redirect/deep link → buyer approves in MoMo app → callback / status inquiry)
Country / currency: VN / VND

Create the payment method

Create a MoMo payment method with payment_method_type set to momo and country set to VN. Contact fields are forwarded to 2C2P when present.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "momo",
    "full_name": "Nguyen Van A",
    "email": "[email protected]",
    "phone_number": "0912345678",
    "country": "VN"
  }
}

Run a purchase

Initiate an offsite purchase on your Vietnam 2C2P gateway (currency: VND, region: standard). Include callback_url and redirect_url. Spreedly maps momo to an empty payment-token paymentChannel and channelCode: MOMO.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 10000,
    "currency_code": "VND",
    "order_id": "MOMO-1789906849",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "vi"
      }
    }
  }
}

The purchase returns pending. Redirect the buyer to gateway_specific_response_fields.two_c_two_p.redirect_url or checkout_url.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "MOMO-1789906849",
        "resp_code": "1005",
        "channel_code": "MOMO",
        "redirect_url": "https://sandbox-payment.2c2p.com/2c2pfrontend/redirect/MOMO/..."
      }
    },
    "message": "Pending"
  }
}

Do not mark the order paid on browser return alone — wait for Spreedly callback/inquiry to move the transaction out of pending.

Refunds

After a succeeded MoMo payment, issue a credit (refund) against the original transaction. Partial refunds may be supported per your 2C2P merchant agreement.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 5000,
    "currency_code": "VND",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

PromptPay

PromptPay

PromptPay is a QR-based bank transfer payment method in Thailand (THB). Through 2C2P's PPQR channel, merchants can initiate offsite purchases where the buyer scans a PromptPay QR code to complete payment. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (QR display → buyer scans → callback / status inquiry)

Create the payment method

Create a PromptPay payment method with payment_method_type set to prompt_pay and country set to TH. Optional identity fields are forwarded to 2C2P when present:

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "prompt_pay",
    "first_name": "Somchai",
    "last_name": "Jaidee",
    "email": "[email protected]",
    "phone_number": "0812345678",
    "country": "TH"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "2K061GY27B9V3BV029DMH0ZK33",
      "payment_method_type": "prompt_pay",
      "country": "TH",
      "full_name": "Somchai Jaidee",
      "email": "[email protected]",
      "phone_number": "0812345678"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your 2C2P gateway. Include callback_url and redirect_url so Spreedly can receive the final status and return the buyer to your site.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 15000,
    "currency_code": "THB",
    "order_id": "PP-1779906849",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "th"
      }
    }
  }
}

The transaction returns in pending state. Render the PromptPay QR code from gateway_specific_response_fields.two_c_two_p.qr_data:

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "order_id": "PP-1779906809",
    "amount": 15000,
    "currency_code": "THB",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "PP-1779906809",
        "resp_code": "1005",
        "qr_data": "00020101021230630016A00000067701011201150107537000017150208114004680308149655745204599953037645406150.005802TH59182C2P Test merchant6007Bangkok6241010811400468050811400468070110808Purchase63049D8D",
        "expiry_timer_ms": "600000"
      }
    },
    "response": {
      "success": true,
      "message": "Pending for user scan QR.",
      "pending": true
    },
    "message": "Pending"
  }
}

Render the QR code from qr_data for the buyer to scan. After payment, Spreedly receives the callback and runs payment inquiry to transition the transaction to succeeded or failed.

Refunds

After a PromptPay payment is captured and settled, issue a credit (refund) against the original transaction:

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 10000,
    "currency_code": "THB",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

Refund timing and partial refunds follow your 2C2P merchant agreement.

QRIS

QRIS

QRIS (Quick Response Code Indonesian Standard) is Indonesia's unified QR payment network (IDR). Through 2C2P's NOBUQRIS channel, merchants can initiate offsite purchases where the buyer scans a QRIS code to complete payment. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (QR display → buyer scans → callback / status inquiry)

Create the payment method

Create a QRIS payment method with payment_method_type set to qris and country set to ID. full_name, email, and phone_number are required.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "qris",
    "full_name": "Spreedly Test",
    "email": "[email protected]",
    "phone_number": "0812345678",
    "country": "ID",
    "address1": "123 Jalan Sudirman",
    "city": "Jakarta",
    "state": "DKI Jakarta",
    "zip": "10220"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "01KX3KST78KR9TSRF58ES4RQYS",
      "payment_method_type": "qris",
      "country": "ID",
      "full_name": "Spreedly Test",
      "email": "[email protected]",
      "phone_number": "0812345678"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your Indonesia 2C2P gateway (currency: IDR, region: indonesia). Include callback_url and redirect_url. Spreedly automatically maps qris to paymentChannel: ["QR"] and channelCode: NOBUQRIS, and sends qrType: URL on doPayment.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 6000,
    "currency_code": "IDR",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "redirect_url": "https://merchant.example.com/checkout/return"
  }
}

The purchase returns pending. Present the QR to the buyer from qr_data (sandbox returns a hosted QR image URL) or redirect to checkout_url.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "amount": 6000,
    "currency_code": "IDR",
    "checkout_url": "https://pgw-static-sandbox.s3.amazonaws.com/images/qr/15211426.png",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "01KX3KYP75DAZ0JEDHM7DNJFQC",
        "resp_code": "1005",
        "qr_data": "https://pgw-static-sandbox.s3.amazonaws.com/images/qr/15211426.png",
        "payment_url": "https://pgw-static-sandbox.s3.amazonaws.com/images/qr/15211426.png",
        "expiry_timestamp": "2026-07-09T14:29:31Z"
      }
    },
    "response": {
      "success": true,
      "message": "Pending for user scan QR.",
      "pending": true
    },
    "message": "Pending"
  }
}

Do not mark the order paid on browser return alone — wait for Spreedly callback/inquiry to move the transaction out of pending.

Refunds

After a succeeded QRIS payment, issue a credit (refund) against the original transaction. Partial refunds may be supported per your 2C2P merchant agreement. PEM credentials on the gateway are required for Payment Action refunds.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 3000,
    "currency_code": "IDR",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

True Money

True Money

True Money is a popular e-wallet and QR payment method in Thailand (THB). Through 2C2P's TRUEMONEYQR channel, merchants can initiate offsite purchases where the buyer scans a True Money QR code to complete payment. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (QR display → buyer scans → callback / status inquiry)

Create the payment method

Create a True Money payment method with payment_method_type set to true_money and country set to TH. full_name, email, and phone_number are required.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "true_money",
    "full_name": "Spreedly Test",
    "email": "[email protected]",
    "phone_number": "0812345678",
    "country": "TH",
    "document_id": "12345678909",
    "address1": "123 demo street bangkok",
    "city": "Bangkok",
    "state": "Bangkok",
    "zip": "10110"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "01KX09AEXAMPLETOKEN",
      "payment_method_type": "true_money",
      "country": "TH",
      "full_name": "Spreedly Test",
      "email": "[email protected]",
      "phone_number": "0812345678"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your Thailand 2C2P gateway (currency: THB). Include callback_url and redirect_url. Spreedly automatically maps true_money to paymentChannel: ["QR"] and channelCode: TRUEMONEYQR.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 5000,
    "currency_code": "THB",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}

The purchase returns pending. Present the QR from qr_data or redirect the buyer to checkout_url.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "amount": 5000,
    "currency_code": "THB",
    "checkout_url": "https://pgw-static-sandbox.s3.amazonaws.com/images/qr/15204037.png",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "01KX09ADC63ND5K6Y9VN1YBTW0",
        "resp_code": "1005",
        "qr_data": "https://pgw-static-sandbox.s3.amazonaws.com/images/qr/15204037.png",
        "payment_url": "https://pgw-static-sandbox.s3.amazonaws.com/images/qr/15204037.png",
        "expiry_timestamp": "2026-07-08T07:18:52Z"
      }
    },
    "response": {
      "success": true,
      "message": "Pending for user scan QR.",
      "pending": true
    },
    "message": "Pending"
  }
}

Do not mark the order paid on browser return alone — wait for Spreedly callback/inquiry to move the transaction out of pending.

Refunds

After a succeeded True Money payment, issue a credit (refund) against the original transaction. PEM credentials on the gateway are required for Payment Action refunds.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 2500,
    "currency_code": "THB",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

VietQR

VietQR

VietQR is a QR-based bank transfer payment method in Vietnam (VND). Through 2C2P's VNQR channel, merchants can initiate offsite purchases where the buyer scans a VietQR code or opens a hosted QR page to complete payment. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; partial and full refunds (requires a VND 2C2P gateway with Payment Action PEM credentials)
Flow: Offsite async (QR display or hosted checkout URL → buyer pays → callback / status inquiry)

Create the payment method

Create a VietQR payment method with payment_method_type set to vietqr and country set to VN. Identity fields are optional and are stored on the Spreedly payment method only (not forwarded to 2C2P for this channel).

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "vietqr",
    "full_name": "Nguyen Van A",
    "email": "[email protected]",
    "phone_number": "0901234567",
    "country": "VN"
  }
}

Run a purchase

Initiate an offsite purchase on your Vietnam 2C2P gateway. Spreedly maps order_id to 2C2P invoiceNo for callback and refund correlation.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 50000,
    "currency_code": "VND",
    "order_id": "VNQR-20260716-0001",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "vi"
      }
    }
  }
}

The transaction returns in pending state. VietQR exposes two ways to present the QR to the buyer:

  1. Render locally from gateway_specific_response_fields.two_c_two_p.qr_data (when present)
  2. Redirect the buyer to checkout_url (hosted QR page from 2C2P webPaymentUrl)
{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "order_id": "VNQR-20260716-0001",
    "amount": 50000,
    "currency_code": "VND",
    "checkout_url": "https://sandbox-pgw-ui.2c2p.com/payment/4.3/#/token/...",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "VNQR-20260716-0001",
        "resp_code": "2000",
        "channel_code": "VNQR",
        "payment_url": "https://sandbox-pgw-ui.2c2p.com/payment/4.3/#/token/..."
      }
    },
    "response": {
      "success": true,
      "message": "Transaction is completed, please do payment inquiry request for full payment information.",
      "pending": true
    },
    "message": "Pending"
  }
}

2C2P may send multiple callbacks while payment is in progress (resp_code 2001 — transaction in progress). These are treated as pending, not failure. Wait for Spreedly callback/inquiry to move the transaction to succeeded or failed; do not mark the order paid on browser return alone.

Refunds

After a VietQR payment succeeds, issue a credit (refund) against the original transaction. The refund uses the purchase reference (populated from invoice_no when needed). Partial refunds are supported.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 25000,
    "currency_code": "VND",
    "notify_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

Refund timing and settlement rules follow your 2C2P merchant agreement. Ensure the gateway PEM credentials match the key pair registered in the 2C2P merchant portal.

Virtual Account

Virtual Account

Virtual Account (VA) via 2C2P lets merchants accept bank-transfer payments in Indonesia (IDR). 2C2P issues a one-time virtual account number with optional payment-slip, QR, or barcode instructions. The customer pays through their bank (ATM, internet banking, or mobile banking). The flow is asynchronous: the purchase returns pending with VA instructions until 2C2P confirms payment via callback. VA payments auto-settle on success — do not attempt a separate capture.

Capabilities: Offsite purchase; full and partial refunds via Payment Action credit (PEM credentials required)
Flow: Offsite async (display VA instructions → customer pays → callback)
Country / currency: ID / IDR

Create the payment method

Create a Virtual Account payment method with payment_method_type set to virtual_account and country set to ID. Contact fields are forwarded to 2C2P when present.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "virtual_account",
    "country": "ID",
    "full_name": "Budi Santoso",
    "email": "[email protected]",
    "phone_number": "08996647676"
  }
}

Run a purchase

Initiate an offsite purchase on your Indonesia 2C2P gateway (currency: IDR, region: indonesia). Include callback_url and redirect_url. Bank selection is required via gateway specific fields: agent_code and agent_channel_code (confirm supported agents with 2C2P for your MID).

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 15000,
    "currency_code": "IDR",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "agent_code": "IDMANDIRI",
        "agent_channel_code": "ATM",
        "payment_expiry": "2026-12-31 23:59:59"
      }
    }
  }
}

Spreedly maps Virtual Account to channelCode: 123 and omits paymentChannel on the payment token (required by 2C2P). locale defaults to id; payment_expiry defaults to seven days from purchase time if omitted.

The purchase returns pending with VA payment instructions. Present the virtual account number, amount, and expiry to the customer. Optional fields include reference_no, qr_data, barcode_data, and a payment-slip URL via checkout_url or payment_url.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "amount": 15000,
    "currency_code": "IDR",
    "checkout_url": "https://uatpay123.2c2p.com/PaymentMerchant/PaymentSlip/ViewSlip/?paymentCode=...",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "01KY7GW1BJZ64ZE3R5FHHWR5GM",
        "resp_code": "1003",
        "channel_code": "123",
        "qr_data": "|010555303610000 0000008849391936 0000008849391936 15000",
        "barcode_data": "|010555303610000 0000008849391936 0000008849391936 15000",
        "reference_no": "8849391936",
        "payment_url": "https://uatpay123.2c2p.com/PaymentMerchant/PaymentSlip/ViewSlip/?paymentCode=..."
      }
    },
    "response": {
      "success": true,
      "message": "Transaction is pending for payment, please wait for backend notification.",
      "pending": true
    },
    "message": "Pending"
  }
}

resp_code 1003 indicates the VA is pending payment. Do not mark the order paid on browser return alone — wait for Spreedly callback to move the transaction to succeeded or failed.

Gateway specific fields (purchase)

FieldRequiredDescription
agent_codeYesBank agent code (e.g. IDMANDIRI)
agent_channel_codeYesChannel under the agent (e.g. ATM)
payment_expiryNoVA expiry (YYYY-MM-DD HH:MM:SS); defaults to now + 7 days
localeNoDefaults to id for Virtual Account

Refunds

After a succeeded Virtual Account payment, issue a credit (refund) against the original transaction. PEM credentials and merchant_jws_key_id on the gateway are required. Some MIDs require destination bank details on the credit request.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 1450,
    "currency_code": "IDR",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "bank_account_number": "1234567890123",
        "bank_account_name": "Budi Santoso",
        "bank_code": "BMRIIDJA"
      }
    }
  }
}

Refunds may return asynchronously (resp_code 42 / refund pending). Confirm final status via callback rather than the initial credit response alone.

Vietnamese Bank Transfer

Vietnamese Bank Transfer

Vietnamese Bank Transfer (VBT) is a redirect-based internet banking payment method in Vietnam (VND). Through 2C2P's WEBPAY channel, merchants can initiate offsite purchases where the buyer is redirected to their bank's payment page to authorize the transfer. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (redirect to bank page → buyer authorizes → callback / status inquiry)

Create the payment method

Create a Vietnamese Bank Transfer payment method with payment_method_type set to vietnamese_bank_transfer and country set to VN. full_name, email, and phone_number are required. Payer contact fields are forwarded to 2C2P on doPayment.

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "vietnamese_bank_transfer",
    "full_name": "Spreedly Test",
    "email": "[email protected]",
    "phone_number": "0909123456",
    "country": "VN",
    "address1": "123 Nguyen Hue",
    "city": "Ho Chi Minh City",
    "state": "Ho Chi Minh",
    "zip": "700000"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "01KX2NKEXAMPLETOKEN",
      "payment_method_type": "vietnamese_bank_transfer",
      "country": "VN",
      "full_name": "Spreedly Test",
      "email": "[email protected]",
      "phone_number": "0909123456"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your Vietnam 2C2P gateway (currency: VND). Include callback_url and redirect_url. Spreedly automatically maps vietnamese_bank_transfer to paymentChannel: ["WEBPAY"], channelCode: 123, and default agent routing VNVCB / WEBPAY. Confirm the agent code with your 2C2P MID configuration.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 100000,
    "currency_code": "VND",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "redirect_url": "https://merchant.example.com/checkout/return"
  }
}

The purchase returns pending with a bank redirect URL in checkout_url. Redirect the buyer to complete authorization on their bank's page.

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "amount": 100000,
    "currency_code": "VND",
    "checkout_url": "https://uatpay123.2c2p.com/PaymentMerchant/WebPayment/RedirectWebPay/?id=eJwyNDYzMDSzMDcys%2fL3cw1wjIx3dHcCCCDAADRxBTg%3d",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoice_no": "01KX2NKASQRF2K958TZQ0K6RCY",
        "resp_code": "1001",
        "qr_data": "https://uatpay123.2c2p.com/PaymentMerchant/WebPayment/RedirectWebPay/?id=eJwyNDYzMDSzMDcys%2fL3cw1wjIx3dHcCCCDAADRxBTg%3d",
        "payment_url": "https://uatpay123.2c2p.com/PaymentMerchant/WebPayment/RedirectWebPay/?id=eJwyNDYzMDSzMDcys%2fL3cw1wjIx3dHcCCCDAADRxBTg%3d"
      }
    },
    "response": {
      "success": true,
      "message": "Redirect to authenticate ACS bank page.",
      "pending": true
    },
    "message": "Pending"
  }
}

Do not mark the order paid on browser return alone — wait for Spreedly callback/inquiry to move the transaction out of pending.

Refunds

After a succeeded Vietnamese Bank Transfer payment, issue a credit (refund) against the original transaction. PEM credentials on the gateway are required for Payment Action refunds.

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 50000,
    "currency_code": "VND",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

Did this page help you?