Payment

01. Payment Flow

Flowchart

Polling

Webhook

1. Triggering createPayment API from Mini Dapp

When a user requests a purchase, the Mini Dapp Client sends the item information to the Mini Dapp Server. The Mini Dapp Server then calls the createPayment API on the Dapp Portal Payment Server. The createPayment API responds with a payment ID in the format: { id: <payment_id> }.

Request

curl --location 'https://payment.dappportal.io/api/payment-v1/payment/create' \
--header 'X-Client-Id: {your_client_id}' \
--header 'X-Client-Secret: {your_client_secret}' \
--header 'Content-Type: application/json' \
--data '{
    "buyerDappPortalAddress": "{user_wallet_address}", // user_wallet_address should be achieved through walletProvider.
    "pgType": "{pg_type}",
    "currencyCode": "{currency_code}",
    "price": "{price}",
    "paymentStatusChangeCallbackUrl": "{url_to_get_confirm_callback}",
    "lockUrl": "{url_to_get_item_lock_callback}",
    "unlockUrl": "{url_to_get_item_unlock_callback}",
    "items": [
        {
            "itemIdentifier": "{your_item_identifier}",
            "name": "{your_item_name}",
            "imageUrl": "{your_item_image_url}",
            "price": "{price}",
            "currencyCode": "{currencyCode}"
        }
    ],
    "testMode": {true | false}
}'

Response

{
    "id": {payment_id}
}

2. Get paymentProvider from sdk instance.

The Mini Dapp Client retrieves the PaymentProvider instance from sdk with getPaymentProvider method.

const paymentProvider = sdk.getPaymentProvider()

3. Start payment process via paymentProvider.

Then, call the startPayment method of the paymentProvider, passing the paymentId received from step 1 as a parameter.

await paymentProvider.startPayment(paymentId)

4. Check payment status and finalize payment.

There are two ways to check the payment status:

Client-side: Wait for the promise returned by the startPayment method to resolve. When the promise resolves, the payment status is updated to FINALIZED

Server-side: Handle the payment status via a webhook event on the server. When using webhooks, the Dapp Portal Payment Server sends an update to the paymentStatusChangeCallbackUrl specified in the parameters of the createPayment API whenever the payment status changes. The update request is sent as an HTTP POST , and the body includes the updated payment status in the following format:

{
  "paymentId": "{payment_id}",
  "status": "CONFIRMED"
}

If the received status is CONFIRMED, the Mini Dapp Server should send a request to finalize the payment by calling the following API:

curl --location --request 
POST 'https://payment.dappportal.io/api/payment-v1/payment/finalize' \
--header 'Content-Type: application/json' \
--data '{
    "id": "{payment_id}"
}'

5. In case of payment canceled by system.

If an HTTP 200 OK status is not returned in response to the webhook event, the event will be retried up to four times at exponential intervals: 1, 2, 4, and 8 seconds.

After all retry attempts have failed, if a lockUrl was specified when the payment was created, the Dapp Portal Payment Server will send an unlock request as an HTTP POST . The body includes the paymentId and itemIdentifiers.

{
    "paymentId": "{payment_id}",
    "itemIdentifiers": ["{your_item_identifier}"]
}

This ensures that any reserved resources (e.g., NFT items, inventory) are released properly in case the payment status could not be confirmed via webhook.

6. You can open payment history page via paymentProvider. Promise will be completed if payment history page opens successfully.

await paymentProvider.openPaymentHistory()

02. Payment API

baseUrl for Payment API: https://payment.dappportal.io

1. create payment

post /api/payment-v1/payment/create Parameters

Name
Description

X-Client-Id *required string (header)

client id obtained from support team

X-Client-Secret *required string (header)

client secret obtained from support team

Request Body

Example Value
Schema
{
    "buyerDappPortalAddress": "{user_wallet_address}",
    "pgType": "{pg_type}",
    "currencyCode": "{currency_code}",
    "price": "{price}",
    "paymentStatusChangeCallbackUrl": "{url_to_get_status_change_callback_using_webhook}",
    "lockUrl": "{url_to_get_item_lock_callback}",
    "unlockUrl": "{url_to_get_item_unlock_callback}",
    "items": [
        {
            "itemIdentifier": "{your_item_identifier}",
            "name": "{your_item_name}",
            "imageUrl": "{your_item_image_url}",
            "price": "{price}",
            "currencyCode": "{currencyCode}"
        }
    ],
    "testMode": {true | false}
}
{
    buyerDappPortalAddress*: String,
    pgType*: String(Enum: [STRIPE,CRYPTO]),
    currencyCode*: String(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
    price*: String,
    paymentStatusChangeCallbackUrl: String,
    lockUrl: String,
    unlockUrl: String,
    items*: [Item {
           itemIdentifier: String,
           name: String,
           imageUrl: String,
           price: String,
           currencyCode: String(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
        }],
    testMode*: Boolean,
}
field
limitation

buyerDappPortalAddress

Max length : 42

pgType

  • STRIPE

  • CRYPTO

currencyCode

  • USD

  • KRW

  • JPY

  • TWD

  • THB

  • KAIA

It should be equal to Item's currencyCode

price

  • STRIPE

    • Put minimum unit following here

      • 100(cent) should be put if price is $1

    • Case1. Minumum unit equals to price unit

      • 10,000 KRW = 10000

      • 10,000 JPY = 10000

    • Case2. Minimum unit no equals to price unit

      • 10,000 USD = 1000000 (10,000 * 100)

      • 10,000 THB = 1000000 (10,000 * 100)

      • 10,000 TWD = 1000000 (10,000 * 100)

  • CRYPTO

    • Put price as KAIA unit

    • 1 $KAIA = 1.0

It should be equal to sum of each price of Items. - Decimal Policy STRIPE : no CRYPTO : Up to 4 decimal places

paymentStatusChangeCallbackUrl

Max length : 512 You can receive webhook whenever payment status was changed if you set paymentStatusChangerCallbackUrl.

Please use port 443 for entire connection. Webhooks cannot be received if a port other than 443 is used.

lockUrl

Max length : 512

unlockUrl

Max length : 512

items(*)

Only the purchase of single item is supported under current version

testMode

The payment methods according to testMode are as follows. - testMode : false stripe : realmode crypto : kaia - testMode : true stripe : testmode crypto : kairos

Items(*)

field
limitation

itemIdentifier

Max length : 256

name

Max length : 256

imageUrl

Max length : 512

price

Put minimum unit following here

For example, put 100(cent) if price is 1 Dollar.

currencyCode

  • USD

  • KRW

  • JPY

  • TWD

  • THB

  • KAIA

Responses

HTTP Status Code
description

200

Example Value

{
    "payment_id": "{payment_id}"
}

Schema

{
    payment_id*: String
}

400

Example Value

{
    "code": 1001,
    "detail": "Invalid argument",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

401

Example Value

{
    "code": 1007,
    "detail": "Invalid X-Client-Id or X-Client-Secret",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

403

Example Value

{
    "code": 1007,
    "detail": "Access denied due to country restrictions.",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

500

Example Value

{
    "code": 500,
    "detail": "Internal server error",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

Request Example


curl --location 'https://payment.dappportal.io/api/payment-v1/payment/create' \
--header 'X-Client-Id: {your_client_id}' \
--header 'X-Client-Secret: {your_client_secret}' \
--header 'Content-Type: application/json' \
--data '{
    "buyerDappPortalAddress": "{user_wallet_address}",
    "pgType": "{pg_type}",
    "currencyCode": "{currency_code}",
    "price": "{price}",
    "paymentStatusChangeCallbackUrl": "{url_to_get_confirm_callback}",
    "lockUrl": "{url_to_get_item_lock_callback}",
    "unlockUrl": "{url_to_get_item_unlock_callback}",
    "items": [
        {
            "itemIdentifier": "{your_item_identifier}",
            "name": "{your_item_name}",
            "imageUrl": "{your_item_image_url}",
            "price": "{price}",
            "currencyCode": "{currencyCode}"
        }
    ],
    "testMode": {true | false}
}'

2. get payment information

get /api/payment-v1/payment/info

Parameters

Name
Description

X-Client-Id *required string (header)

client id obtained from support team

X-Client-Secret *required string (header)

client secret obtained from support team

id *required

string (query)

payment id

responses

HTTP Status Code
Description

200

Example Value

{
    "id":"{payment id}",
    "buyerDappPortalAddress": "{user_wallet_address}",
    "pgType": "{pg_type}",
    "status": "{status}",
    "currencyCode": "{currency_code}",
    "price": "{price}",
    "usdExchangeRate":"{Fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED.}",
    "usdExchangePrice":"{USD Price applied fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED."}
    "items": [
        {
            "itemIdentifier": "{your_item_identifier}",
            "name": "{your_item_name}",
            "imageUrl": "{your_item_image_url}",
            "price": "{price}",
            "currencyCode": "{currencyCode}"
        }
    ],
    "testMode": {true | false},
    "refund": {
      type: "REFUND",
      amount: "1000.0000000000000000000000000000", //Price policy follows . 
    }
}

Schema

{
    id*: String,     
    buyerDappPortalAddress*: string(maxLength: 42),
    pgType*: string(Enum: [STRIPE,CRYPTO]),
    status*: string(Enum: [CREATED,STARTED,REGISTERED_ON_PG,CAPTURED,CONFIRMED,CONFIRM_FAILED,FINALIZED,CANCELED])
    currencyCode*: string(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
    price*: string,
    usdExchangeRate: string,
    usdExchangePrice: string,
    items*: [Item {
           itemIdentifier: string(maxLength: 256),
           name: string(maxLength: 256),
           imageUrl: string(maxLength: 512),
           price: string,
           currencyCode: string(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
        }]    
    testMode*: Boolean,
    refund: {
           type*: string(Enum:[CHARGEBACK, REFUND]),
           amount*: string,
           chargebackStatus: string(Enum:[NEEDS_RESPONSE, UNDER_REVIEW, WON, LOSE]), 
    }
}

404

Example Value

{
    "code": 1002,
    "detail": "Not found payment",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}

401

Example Value

{
    "code": 1007,
    "detail": "Invalid X-Client-Id or X-Client-Secret Header is not included when payment status is CONFIRMED or FINALIZED.",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}

403

Example Value

{
    "code": 4030,
    "detail": "Access denied due to country restrictions.",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}

500

Example Value

{
    "code": 500,
    "detail": "Internal server error",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}
field
limitation

id

buyerDappPortalAddress

Max length : 42

pgType

  • STRIPE

  • CRYPTO

status

Payment status >

  • CREATED

  • STARTED

  • REGISTERED_ON_PG

  • CAPTURED

  • CONFIRMED

  • CONFIRM_FAILED

  • FINALIZED

  • CANCELED

  • REFUNDED

  • CHARGEBACK

currencyCode

  • USD

  • KRW

  • JPY

  • TWD

  • THB

  • KAIA

It should be equal to Item's currencyCode

price

Put minimum unit as here. For example, put 100(cent) if price is 1 Dollar. It should be equal to sum of each price of Items. - Decimal Policy STRIPE : no CRYPTO : Up to 4 decimal places

usdExchangeRate

Fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED.

usdExchangePrice

USD Price applied fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED.

items(*)

Only the purchase of single item is supported under current version

testMode

The payment methos according to testMode are as follows. - testMode : false stripe : realmode crypto : kaia - testMode : true stripe : testmode crypto : kairos

refund

refund.type

  • CHARGEBACK

  • REFUND

refund.amount

Price policy follows here.

refund.chargebackStatus

  • NEEDS_RESPONSE: A CHARGEBACK has occurred, but it is in the state before contesting or challenging it.

  • UNDER_REVIEW: The dispute has been filed and is currently under review by STRIPE.

  • WON: If the dispute is accepted.

  • LOST: If the dispute result is not accepted

Items(*)

field
limitation

itemIdentifier

Max length : 256

name

Max length : 256

imageUrl

Max length : 512

price

For example, put 100(cent) if price is 1 Dollar.

currencyCode

  • USD

  • KRW

  • JPY

  • TWD

  • THB

  • KAIA

Request Example

curl --location 'https://payment.dappportal.io/api/payment-v1/payment/info?id={payment_id}' \
--header 'X-Client-Id: {your_client_id}' \
--header 'X-Client-Secret: {your_client_secret}'

3. get payment status

get /api/payment-v1/payment/status Parameters

Name
Description

id *required

string (query)

payment id

Responses

HTTP Status Code
Description

200

Example Value

{
    "status": "{status}"
}

Schema

{
    status*: String (Enum:[CREATED, STARTED, REGISTERED_ON_PG, CAPTURED, CONFIRMED_FAILED,FINALIZED,CANCELED]
}

403

Example Value

{
    "code": 4030,
    "detail": "Access denied due to country restrictions.",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}

404

Example Value

{
    "code": 1002,
    "detail": "Not found payment",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}

500

Example Value

{
    "code": 500,
    "detail": "Internal server error",
    "cause": null
}

Schema

{
    code*: number,
    detail*: String,
    cause: String
}
field
limitation

status

Payment status >

  • CREATED

  • STARTED

  • REGISTERED_ON_PG

  • CAPTURED

  • CONFIRMED

  • CONFIRMED_FAILED

  • FINALIZED

  • CANCELED

  • REFUNDED

  • CHARGEBACK

Request Example

curl --location 'https://payment.dappportal.io/api/payment-v1/payment/status?id={payment_id}'

4. finalized payment

It will be ready to be settlement after requesting finalize payment API.

You can't get settlement for STRIPE transaction if you did not request finalize payment API. For CRYPTO, We recomment to request finalize payment API too. Parameters N/A

Request Body

Example Value
Schema

{
    id: "{payment_id}" 
}

{
    id*: String
}

Responses

HTTP Status Code
Description

200

N/A

403

Example Value

{
    "code": 1004,
    "detail": "Invalid payment status.",//Current payment status cannot complete payment finalization
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

403

Example Value

{
    "code": 4030,
    "detail": "Access denied due to country restrictions.",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

404

Example Value

{
    "code": 1002,
    "detail": "Not found payment",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String
}

500

Example Value

{
    "code": 500,
    "detail": "Internal server error",
    "cause": null
}

Schema

{    
    code*: number,
    detail*: String,
    cause: String

Request Example

curl --location 'https://payment.dappportal.io/api/payment-v1/payment/finalize
--header 'Content-Type: application/json' \
  --data '{
    "id": "{payment_id}"
  }

03. PaymentProvider

sdk.getPaymentProvider()

Initializes the paymentProvider, allowing developers to use various payment features.

const walletProvider = sdk.getWalletProvider();

Parameters

N/A

Responses

PaymentProvider

paymentProvider.startPayment()

Calling paymentProvider.startPayment() displays a transaction window to the user and begins the payment process.

Parameters

payment_id *required · string

Responses

Promise<unknown>

Error

Code
Description

-31001

{
code: -31001,
message:'Payment is canceled by user or timeout'
}

-31002

{
code: -31002,
message:'Payment is failed'
}

paymentProvider.openPaymentHistory()

This method opens a payment history window. In order to complete the operation, a signature from the user is required.

Parameters

N/A

Responses

Promise<void>

04. Payment Webhook

Each Webhook's callback URL should be set differently.

If there are any unused Webhooks, please enter null.

1. lock event

If the 'lockUrl' parameter is included in the create payment request, a POST method request for the lock webhook event will be made.

When the payment is initiated using the SDK’s startPayment function, the webhook event is requested just before starting the user’s payment flow if it is determined that the payment can proceed normally.

If a request is made with the 'lockUrl' but a 200 response is not received, the payment will be immediately canceled and marked as failed automatically.

{
    "paymentId": "{payment_id}",
    "itemIdentifiers": [
        {
            "{your_item_identifier}"
        }
    ]
}

2. unlock event

If the 'unlockUrl' parameter is included in the create payment request, a POST method request for the unlock webhook event will be made.

If the payment is automatically canceled by the system, a webhook event will be requested.

If a request is made with the 'unlockUrl' but a 200 response is not received, it will be retried up to 5 times at intervals of 1, 2, 4 and 8 seconds. If a 200 response is still not received after retries of 5 times, the payment cancellation will proceed.

{
    "paymentId": "{payment_id}",
    "itemIdentifiers": [
        {
            "{your_item_identifier}"
        }
    ]
}

3. payment status change event

When creating a payment request, a payment status change webhook event will be sent to the paymentStatusChangeCallbackUrl you've provided.

An event occurs whenever the payment status changes, except when the status is CREATED.

If the status of the received webhook is CONFIRMED, you can call the finalize payment API.

If a request to the paymentStatusChangeCallbackUrl does not receive a 200 response, a maximum of 5 retries will be made at intervals of 1, 2, 4, and 8 seconds. Even if a 200 response is not received after 5 retries, the payment cancellation will not proceed.

{
    "paymentId": "{payment_id}"
    "status": "STARTED|REGISTERED_ON_PG|CAPTURED_CONFIRMED_CONFIRM_FAILED|FINALIZED|CANCELED|REFUNDED|CHARGEBACK"
}

05. Payment Status

Status
Description

CREATED

Hosted create payment API but not host startPayment of SDK

STARTED

Hosted startPayment but await payment approval from user (STRIPE/CRYPTO)

REGISTERED_ON_PG

(Only for pgType = CRYPTO) Transaction has been approved but await for enough block confirmation to prevent chain re-organization. - at least after 10 block confirmation from user's request for KAIA

CAPTURED

(Only for pgType = CRYPTO) Checking validity of the transaction after 10 blocks have confirmed from the user's transaction request

CONFIRMED

Payment approval is completed and await payment finalization from Mini Dapp

CONFIRM_FAILED

(Only for pgTye = CRYPTO) Failed to check validity of the transaction after 10 blocks have confirmed from user's transaction request

FINALIZED

Payment process is done with paymet approval and payment finialization from Mini Dapp - In case of pgType is CRYPTO, transactions will be automatically finalized after 5 minutes once it reachs the CONFIRMED status if the finalize payment API is not hosted.

CANCELED

Payment is cancelled as Policy for payment cancellation

REFUNDED

Payment has been refunded

CHARGEBACK

Users has claimed chargeback directly.

This Webhook message will be sent at each stage of the process, including the occurrence of a CHARGEBACK.

(*) This status can only be queried through the get payment information API.

Last updated