Payment API
Payment API
TudadaSDK.Instance.payment.* is the payment API for purchasing in-game products. The game requests
purchases by productId only, without needing to know the payment method or rail. Payment method
selection, pricing, receipt verification, and granting are all handled by the Tudada platform.
All four methods are callback-based — they take onSuccess (a result-specific type) and onFail
(Action<PaymentFailResult>). Always branch on failure using PaymentFailResult.code.
- Payment-unsupported environment —
GetProducts'sonFailis called withPAYMENT_UNAVAILABLE. - Supported environment, no matching products —
productsis an empty array (a normal response). When it's empty, do not show the store UI. - Communication failure — fails with
NETWORK_ERROR.
The result of Purchase is reduced information for display/UX. Confirm the final grant on your game server.
Transactions do not include amounts or receipts; authoritative grant information is held by the game server and the Tudada server.
Note: Payment works only in WebGL builds. In the Unity Editor simulation,
onFailis called withPAYMENT_UNAVAILABLE(see Editor Simulation).
payment.GetProducts(onSuccess, onFail)
Retrieves the list of purchasable products. If there are no products available for sale, products is an empty array.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
onSuccess | Action<GetProductsResult> | - | Success callback |
onFail | Action<PaymentFailResult> | - | Failure callback |
Success Response (GetProductsResult):
| Field | Type | Description |
|---|---|---|
products | PaymentProduct[] | List of purchasable products (empty array if none) |
errMsg | string | Result message |
TudadaSDK.Instance.payment.GetProducts(
onSuccess: (res) => {
if (res.products.Length == 0) { HideStore(); return; } // empty array → hide store
foreach (var p in res.products)
Debug.Log($"{p.productId} / {p.name} / {p.price} {p.currency}");
RenderStore(res.products);
},
onFail: (err) => Debug.LogError("Product query failed: " + err.code)
);
payment.GetProduct(productId, onSuccess, onFail)
Retrieves a single product. If it does not exist or is not for sale, onFail is called with PRODUCT_NOT_FOUND.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
productId | string | ✅ | ID of the product to query |
onSuccess | Action<GetProductResult> | - | Success callback |
onFail | Action<PaymentFailResult> | - | Failure callback |
Success Response (GetProductResult):
| Field | Type | Description |
|---|---|---|
product | PaymentProduct | Product information |
errMsg | string | Result message |
TudadaSDK.Instance.payment.GetProduct("coin_100",
onSuccess: (res) => Debug.Log($"{res.product.name} / {res.product.price}"),
onFail: (err) => {
if (err.code == PaymentErrorCode.ProductNotFound) ShowToast("This product is not for sale.");
}
);
payment.Purchase(productId, passthroughPayload, onSuccess, onFail)
Purchases a product. On success, the transaction is delivered in the GRANTED (granted) or PENDING
(accepted, awaiting confirmation) state; otherwise onFail is called.
Parameters:
| 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 |
onSuccess | Action<PurchaseResult> | - | Success callback |
onFail | Action<PaymentFailResult> | - | Failure callback |
Success Response (PurchaseResult):
| Field | Type | Description |
|---|---|---|
transaction | PaymentTransaction | Transaction (GRANTED / PENDING) |
errMsg | string | Result message |
TudadaSDK.Instance.payment.Purchase("coin_100", "order-1234",
onSuccess: (res) => {
var txn = res.transaction;
if (txn.status == PaymentTxnStatus.Granted) {
// Granted — reflect in UX (confirm the final grant on the game server)
} else if (txn.status == PaymentTxnStatus.Pending) {
// Accepted — awaiting confirmation. You can check the later status with GetTransaction
}
},
onFail: (err) => {
if (err.code == PaymentErrorCode.UserCancelled) return; // user closed the payment window
Debug.LogError("Payment failed: " + err.code);
}
);
payment.GetTransaction(txnKey, onSuccess, onFail)
Retrieves a transaction's status and result. Use it, for example, to follow up when Purchase ended in PENDING.
If it does not exist or is not owned, onFail is called with TRANSACTION_NOT_FOUND.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
txnKey | string | ✅ | Key of the transaction to query |
onSuccess | Action<GetTransactionResult> | - | Success callback |
onFail | Action<PaymentFailResult> | - | Failure callback |
Success Response (GetTransactionResult):
| Field | Type | Description |
|---|---|---|
transaction | PaymentTransaction | Transaction (all 5 statuses possible) |
errMsg | string | Result message |
TudadaSDK.Instance.payment.GetTransaction(prevTxnKey,
onSuccess: (res) => Debug.Log("Status: " + res.transaction.status),
onFail: (err) => Debug.LogError("Query failed: " + err.code)
);
Data Types
PaymentProduct
| Field | Type | Description |
|---|---|---|
productId | string | Market-agnostic single product ID (specified when purchasing) |
name | string | Registered product name |
price | string | Current price (with discount applied) — a 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). Empty string if there is no discount. For strikethrough display, etc. |
discountRate | string | Discount rate display string (e.g. "20%"). A marketing label — do not back-calculate from the price |
All prices are display-ready strings. Thousands separators and decimal places are already applied, so output them as-is and do not use them in calculations.
PaymentTransaction
A reduced view for verification/UX (no amount or receipt).
| Field | Type | Description |
|---|---|---|
txnKey | string | Unique key — used for re-querying |
status | string | Transaction status (table below, PaymentTxnStatus constants) |
productId | string | Transaction product |
failReason | string | Detailed reason for diagnostics/logging (do not expose to users, value may change). Empty string if absent |
passthroughPayload | string | The value passed at purchase, verbatim. Empty string if omitted or empty |
requestedAt | long | Request time (Unix epoch ms) |
grantedAt | long | First grant time (epoch ms). 0 if not granted (check existence with hasGrantedAt) |
Transaction status (status):
| Value | Description |
|---|---|
PENDING | Accepted, grant not yet confirmed |
GRANTED | Granted |
FAILED | Failed |
PARTIALLY_REFUNDED | Partially refunded (a prior grant exists) |
REFUNDED | Refunded (a prior grant exists) |
Purchasedelivers onlyGRANTED/PENDING, whileGetTransactioncan return all 5 statuses.
Error Handling
Failures are delivered via PaymentFailResult in onFail, and you always branch on code.
| Field | Type | Description |
|---|---|---|
code | string | Error code (table below) — the branching criterion. PaymentErrorCode constants |
errMsg | string | Message (do not expose to users) |
txnKey | string | Present only on failures where a transaction was recorded. Empty string if absent |
failReason | string | Detailed reason for diagnostics/logging. Empty string if absent |
Error codes (code, PaymentErrorCode constants):
| Code | Description |
|---|---|
PAYMENT_UNAVAILABLE | Payment-unavailable environment |
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 over limit, etc.) |
PURCHASE_REJECTED | Verification/grant rejected |
PAYMENT_IN_PROGRESS | A new call is rejected because a purchase is in progress (one at a time) |
TRANSACTION_NOT_FOUND | Transaction does not exist or is not owned |
SESSION_EXPIRED | Session expired — re-login required |
NETWORK_ERROR | Communication failure |
TudadaSDK.Instance.payment.Purchase("coin_100", null,
onSuccess: (res) => { /* ... */ },
onFail: (err) => {
switch (err.code) {
case PaymentErrorCode.UserCancelled: break; // silently ignore
case PaymentErrorCode.PaymentInProgress: ShowToast("Processing..."); break;
case PaymentErrorCode.SessionExpired: RequestRelogin(); break;
default: ShowToast("Payment failed.");
}
}
);