Payment API
Payment API
TudadaSDK.payment.* is the payment API for purchasing in-game products. The game requests purchases by productId alone, without needing to know the payment method or rail. Payment method selection, price calculation, receipt verification, and grant processing are all handled by the Tudada platform.
Each action is split into a callback version (getProducts, etc. — success / fail / complete callbacks, returns nothing) and an *Async version (getProductsAsync, etc. — returns a Promise). The callback success value is identical to the resolve value of the corresponding *Async, and the required arguments of *Async are passed as positional arguments.
- Payment unsupported —
getProductsAsync()rejects withPAYMENT_UNAVAILABLE(the callback versiongetProducts()invokesfail). - Supported, no matching products — returns an empty array (a normal response). If empty, do not show the store UI.
- Communication failure — rejects with
NETWORK_ERROR.
The result of purchase() is reduced information for display and UX. Confirm the final grant on your game server. The transaction does not include amount or receipt; authoritative grant information is held by your game server and the Tudada server.
payment.getProducts(options?)
Retrieves the list of purchasable products (callback version, returns nothing).
Options:
| Parameter | Type | Required | Description |
|---|---|---|---|
success | function | - | Success callback — PaymentProduct[] |
fail | function | - | Failure callback — PaymentFailResult |
complete | function | - | Completion callback |
TudadaSDK.payment.getProducts({
success: (products) => {
if (products.length === 0) hideStore(); // empty array → do not show the store
else renderStore(products);
},
fail: (err) => console.warn('Product lookup failed:', err.code),
});
payment.getProductsAsync()
Promise version of getProducts(). Returns Promise<PaymentProduct[]>, which is an empty array if there are no products available for sale.
const products = await TudadaSDK.payment.getProductsAsync();
if (products.length === 0) {
hideStore(); // empty array → do not show the store
} else {
renderStore(products);
}
payment.getProduct(options)
Retrieves a single product (callback version). If the product does not exist or is not for sale, PRODUCT_NOT_FOUND is passed to the fail callback.
Options:
| Parameter | Type | Required | Description |
|---|---|---|---|
productId | string | ✅ | ID of the product to retrieve |
success | function | - | Success callback — PaymentProduct |
fail | function | - | Failure callback |
complete | function | - | Completion callback |
TudadaSDK.payment.getProduct({
productId: 'coin_100',
success: (product) => console.log(product.name, product.price),
fail: (err) => {
if (err.code === 'PRODUCT_NOT_FOUND') showToast('This product is not for sale.');
},
});
payment.getProductAsync(productId)
Promise version of getProduct(). Takes productId as a positional argument and returns Promise<PaymentProduct>, rejecting with PRODUCT_NOT_FOUND if the product does not exist or is not for sale.
try {
const product = await TudadaSDK.payment.getProductAsync('coin_100');
console.log(product.name, product.price);
} catch (err) {
if (err.code === 'PRODUCT_NOT_FOUND') showToast('This product is not for sale.');
}
payment.purchase(options)
Purchases a product (callback version). On success, the transaction is delivered with a GRANTED (grant complete) or PENDING (accepted, awaiting confirmation) status; otherwise the fail callback is invoked.
Options:
| Parameter | Type | Required | Description |
|---|---|---|---|
productId | string | ✅ | ID of the product to purchase |
passthroughPayload | string | - | Opaque echo string (≤1000 UTF-8 bytes). Returned verbatim during purchase verification. No secrets or personal data |
success | function | - | Success callback — PaymentTransaction |
fail | function | - | Failure callback |
complete | function | - | Completion callback |
TudadaSDK.payment.purchase({
productId: 'coin_100',
passthroughPayload: 'order-1234', // optional — returned verbatim on verification
success: (txn) => {
if (txn.status === 'GRANTED') {
// Grant complete — reflect in UX (final confirmation on the game server)
} else if (txn.status === 'PENDING') {
// Accepted — awaiting confirmation. Check later status with getTransaction
}
},
fail: (err) => {
if (err.code === 'USER_CANCELLED') return; // user closed the payment window
console.error('Payment failed:', err.code);
},
});
payment.purchaseAsync(productId, passthroughPayload?)
Promise version of purchase(). Takes productId (required) and passthroughPayload (optional) as positional arguments and returns Promise<PaymentTransaction> (GRANTED / PENDING).
try {
const txn = await TudadaSDK.payment.purchaseAsync('coin_100', 'order-1234');
if (txn.status === 'GRANTED') {
// Grant complete — reflect in UX (final confirmation on the game server)
} else if (txn.status === 'PENDING') {
// Accepted — awaiting confirmation. Check later status with getTransactionAsync
}
} catch (err) {
if (err.code === 'USER_CANCELLED') return; // user closed the payment window
console.error('Payment failed:', err.code);
}
payment.getTransaction(options)
Retrieves a transaction's status and result (callback version). Use it for follow-up checks when purchase ended in PENDING, etc. If the transaction does not exist or is not owned by the user, TRANSACTION_NOT_FOUND is passed to the fail callback.
Options:
| Parameter | Type | Required | Description |
|---|---|---|---|
txnKey | string | ✅ | Key of the transaction to retrieve |
success | function | - | Success callback — PaymentTransaction |
fail | function | - | Failure callback |
complete | function | - | Completion callback |
TudadaSDK.payment.getTransaction({
txnKey: prevTxn.txnKey,
success: (txn) => console.log(txn.status),
});
payment.getTransactionAsync(txnKey)
Promise version of getTransaction(). Takes txnKey as a positional argument and returns Promise<PaymentTransaction> (any of the 5 statuses possible), rejecting with TRANSACTION_NOT_FOUND if the transaction does not exist or is not owned by the user.
const txn = await TudadaSDK.payment.getTransactionAsync(prevTxn.txnKey);
console.log(txn.status); // 'PENDING' | 'GRANTED' | 'FAILED' | 'PARTIALLY_REFUNDED' | 'REFUNDED'
Data Types
PaymentProduct
| Field | Type | Description |
|---|---|---|
productId | string | Market-agnostic single product ID (specified at purchase) |
name | string | Registered product name |
price | string | Current price (with any discount applied) — display-ready string (e.g. "4,400", "4.99"). No arithmetic, display as-is |
currency | string | Currency identifier string (e.g. "KRW"). Display unrecognized values as the raw string |
originalPrice | string? | Pre-discount price (display-ready string). For strikethrough display, etc. |
discountRate | string? | Discount rate display string (e.g. "20%"). Marketing label — do not back-calculate from price |
All prices are display-ready completed strings. Thousands separators and decimal places are already applied, so output them as-is and do not use them in direct calculations.
PaymentTransaction
A reduced view for verification and UX (no amount or receipt).
| Field | Type | Description |
|---|---|---|
txnKey | string | Unique key — used for re-querying |
status | string | Transaction status (see table below) |
productId | string | Product of the transaction |
failReason | string | null | Detailed reason for diagnostics and logging (do not show to users, value subject to change). null if none |
passthroughPayload | string | null | The value passed at purchase, verbatim. null if omitted or an empty string |
requestedAt | number | Request time (Unix epoch ms) |
grantedAt | number | null | Time of first grant (epoch ms). null if not yet granted |
Transaction status (status):
| Value | Description |
|---|---|
PENDING | Accepted, grant not yet confirmed |
GRANTED | Grant complete |
FAILED | Failed |
PARTIALLY_REFUNDED | Partially refunded (a past grant exists) |
REFUNDED | Refunded (a past grant exists) |
purchaseonly resolves withGRANTED/PENDING, whilegetTransactionmay return any of the 5 statuses.
Error Handling
Failures are delivered via the fail callback (callback version) or Promise reject (*Async version), and branching is always done by code.
| Field | Type | Description |
|---|---|---|
code | string | Error code (see table below) — basis for branching |
errMsg | string | Message (do not show to users) |
txnKey | string? | Present only on failures where a transaction was recorded |
failReason | string? | Detailed reason for diagnostics and logging |
Error codes (code):
| Code | Description |
|---|---|
PAYMENT_UNAVAILABLE | Environment where payment is unavailable |
USER_CANCELLED | User closed the payment window |
PAYMENT_FAILED | Payment failed (card declined, insufficient balance, etc.) |
PRODUCT_NOT_FOUND | Unregistered productId |
INVALID_PARAM | Invalid argument (passthroughPayload exceeded, etc.) |
PURCHASE_REJECTED | Verification/grant rejected |
PAYMENT_IN_PROGRESS | A purchase is in progress, so the new call is rejected (one concurrent purchase) |
TRANSACTION_NOT_FOUND | Transaction does not exist or is not owned |
SESSION_EXPIRED | Session expired — re-login required |
NETWORK_ERROR | Communication failure |
try {
await TudadaSDK.payment.purchaseAsync('coin_100');
} catch (err) {
switch (err.code) {
case 'USER_CANCELLED': break; // silently ignore
case 'PAYMENT_IN_PROGRESS': showToast('Processing.'); break;
case 'SESSION_EXPIRED': requestRelogin(); break;
default: showToast('Payment failed.');
}
}