Skip to main content
Version: v4

Server Development Guide

tip

For general service rules, please refer to Request Rules and Signature Calculation.

Main Query Services

Request Domain:

  • https://cloud-payment.tapapis.com
EndpointMethodDescription
/order/v1/info?client_id={{client_id}}&order_id={{order_id}}GETQuery order information
/order/v1/unconfirmed?client_id={{client_id}}GETQuery unconfirmed orders list
/order/v1/verify?client_id={{client_id}}POSTVerify order

Query Order Information

Service URL

  • https://{{domain}}/order/v1/info?client_id={{client_id}}&order_id={{order_id}}

Request Method

  • GET

Request Signature Calculation

Query detailed order information and payment status by order ID

curl -X GET \
-H 'X-Tap-Sign: {{signature}}' \
-H 'X-Tap-Ts: {{unix timestamp}}' \
-H 'X-Tap-Nonce: {{random nonce}}' \
https://{{domain}}/order/v1/info?client_id={{client_id}}&order_id={{order_id}}

The data.order object structure can be found in Order Information.

{
"data": {
"order": {}
},
"success": true
}

Query Unconfirmed Orders List

Service URL

  • https://{{domain}}/order/v1/unconfirmed?client_id={{client_id}}

Request Method

  • GET

Request Signature Calculation

Query the current list of unconfirmed orders. Normally, after a user successfully pays, you should verify the order through the verify interface and ensure successful delivery to the user. If verification was not completed due to an exception, you can query it through this interface and re-verify and complete delivery.

curl -X GET \
-H 'X-Tap-Sign: {{signature}}' \
-H 'X-Tap-Ts: {{unix timestamp}}' \
-H 'X-Tap-Nonce: {{random nonce}}' \
https://{{domain}}/order/v1/unconfirmed?client_id={{client_id}}

The data.list array object structure can be found in Order Information.

{
"data": {
"list": [
{}
]
},
"success": true
}

Verify Order

Service URL

  • https://{{domain}}/order/v1/verify?client_id={{client_id}}

Request Method

  • POST[application/json; charset=utf-8]

Request Body

Parameter NameRequiredFormatDescription
order_idYstringUnique order ID
purchase_tokenYstringToken used for order verification

Request Signature Calculation

After a successful payment, verifying the order indicates that the payment result has been confirmed and the goods have been delivered to the buyer. The order status will change from charge.succeeded to charge.confirmed.

curl -X POST \
-H 'X-Tap-Sign: {{signature}}' \
-H 'X-Tap-Ts: {{unix timestamp}}' \
-H 'X-Tap-Nonce: {{random nonce}}' \
-H 'Content-Type: application/json; charset=utf-8'
-d '{"order_id":"{{order_id}}","purchase_token":"{{purchase_token}}"}'
https://{{domain}}/order/v1/verify?client_id={{client_id}}

The data.order object structure can be found in Order Information.

{
"data": {
"order": {}
},
"success": true
}

Webhook Callback

tip

The same notification may be sent multiple times, and the merchant system must correctly handle duplicate notifications.

The recommended practice is: when the merchant system receives a notification, first perform signature verification, then check the status of the corresponding business data. If it is unprocessed, process it; if it has been processed, directly return success.

It is recommended to use data locks for concurrency control when processing business data to avoid potential data anomalies.

Webhook Description

Currently, Webhook supports listening to "Recharge Successful", "Refund Successful", and "Refund Failed" events. It is recommended to actively verify orders for "Recharge Successful" and complete delivery based on the order status.

  1. Navigate to TapTap Developer Center > Your Game > Game Services > TapPayment > Products and Orders > API Keys to check if there is an active key. If not, add a new key.
  2. Navigate to TapTap Developer Center > Your Game > Game Services > TapPayment > Products and Orders > Webhooks Settings > Add to add a valid Recharge Successful URL.

Webhook Request

Service URL

  • Provided by the developer, added in Webhooks Settings

Request Method

  • POST[application/json; charset=utf-8]

Request Body

Parameter NameRequiredFormatDescription
orderYobjectObject structure can be found in Order Information
event_typeYstringEvent enumeration can be found in Webhook Event Enumeration

Request Signature Calculation

curl -X POST \
-H 'X-Tap-Sign: {{signature}}' \
-H 'X-Tap-Ts: {{unix timestamp}}' \
-H 'X-Tap-Nonce: {{random nonce}}' \
-H 'Content-Type: application/json; charset=utf-8'
-d '{"order":{},"event_type":"charge.succeeded"}'
{{your webhook url}}

Webhook Response

{
"code": "SUCCESS",
"msg": ""
}

Field Description

FieldTypeRequiredDescription
codestringYStatus code, SUCCESS for success, FAIL or others for failure
msgstringNFailure reason when receiving fails

Request Rules and Signature Calculation

Request Headers

HeaderRequiredDescription
X-Tap-SignYInterface signature, see Signature Calculation
X-Tap-TsYCurrent time from the requester in unix timestamp
X-Tap-NonceYRandom number, must be between 6 and 60 bytes, regenerated for each request

Request

Reserved Parameters

All HTTP METHODs must include this as part of the query parameters.

KeyDescription
client_idApplication ID on the developer platform
https://{{domain}}/order/v1/info?client_id={{client_id}}&order_id={{order_id}}

When the Request Method is POST

The HTTP body must use JSON encoding to transmit parameters, meaning the request headers should carry Content-Type: application/json; charset=utf-8.

curl -X POST \
-H 'X-Tap-Sign: {{signature}}' \
-H 'X-Tap-Ts: {{unix timestamp}}' \
-H 'X-Tap-Nonce: {{random nonce}}' \
-H 'Content-Type: application/json; charset=utf-8'
-d '{"order_id":{{order_id}},"purchase_token":"{{purchase_token}}"}'
https://{{domain}}/order/v1/verify?client_id={{client_id}}

Response

Successful Response

{
"data": {},
"now": 1640966400,
"success": true
}

Error Response

{
"data": {
"code": 100004,
"msg": "NotFound: Unknown Error",
"error_description": "order not found"
},
"now": 1640966400,
"success": false
}

Field Description

FieldTypeRequiredDescription
dataobjectYBusiness data or error description
nowintYServer time (unix timestamp)
successboolYResponse status, true for success

Error response data field description

FieldTypeRequiredDescription
codeintYError Code
msgstringYGeneral error description
error_descriptionstringYDetailed error description to aid understanding and resolution

Signature Calculation

The signature calculation uses the HMAC-SHA256 algorithm.

Obtaining the Secret Key

  • You can view it at TapTap Developer Center > Your Game > Game Services > TapPayment > Products and Orders > API Keys.

Signature Calculation Explanation

sign = HMAC.New(Sha256, "{{Server Secret}}").Hash(message)

Below is the composition of the message:

{method}\n
{url_path_and_query}\n
{headers}\n
{body}\n

method: HTTP request method, such as GET, POST.

url_path_and_query: Full request path and parameters, such as /service/v1/method?client_id={{client_id}}&foo={{foo}}&bar={{bar}}&foo_bar={{foo_bar}}

headers: Combination of all headers prefixed with X-Tap-, sorted by ASCII code order, and joined with a newline \n as a separator. For example, {key1}:{value1}\n{key2}:{value2}\n{key3}:{value3}. To avoid inconsistent sorting results across different network frameworks, convert keys to lowercase during signature calculation: key = tolower(key)

body: Request body. If the request body is empty, the last line is just a \n.

Below is the composition of the message when the request body is empty:

{method}\n
{url_path_and_query}\n
{headers}\n
\n
tip

For the request body, there is no need to handle the order of request parameters. It is recommended to use a String to receive the RequestBody for Webhook requests, validate the request signature, and then complete data deserialization.

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
import java.util.*;
import java.util.stream.Collectors;
public class SignatureExample {

public static String signRequest(String method, URI uri, String body, Map<String, List<String>> headers, String secret) throws Exception {
String urlPathAndQueryPart = uri.getRawPath() + (uri.getRawQuery() != null ? "?" + uri.getRawQuery() : "");
String headersPart = getHeadersPart(headers);

// Signature string part containing the request body
String signParts = method.toUpperCase() + "\n" + urlPathAndQueryPart + "\n" + headersPart + "\n" + body + "\n";

System.out.println("Sign Parts:\n" + signParts);

Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secretKey);

byte[] hash = sha256_HMAC.doFinal(signParts.getBytes());
return Base64.getEncoder().encodeToString(hash);
}

private static String getHeadersPart(Map<String, List<String>> headers) throws Exception {
TreeMap<String, String> sortedHeaders = new TreeMap<>();
headers.forEach((key, value) -> {
String lowerKey = key.toLowerCase();
if (lowerKey.equals("x-tap-sign")) {
return;
}
if (lowerKey.startsWith("x-tap-")) {
if (value.size() > 1) {
throw new RuntimeException("Invalid header, " + lowerKey + " has multiple values");
}
sortedHeaders.put(lowerKey, value.get(0));
}
});

return sortedHeaders.entrySet().stream()
.map(entry -> entry.getKey() + ":" + entry.getValue())
.collect(Collectors.joining("\n"));
}

public static void main(String[] args) {
try {
String secret = "VRy8aS2xbwImQUwtxc6vs4v51DaJWdlO";
String body = "{\"event_type\":\"charge.succeeded\",\"order\":{\"order_id\":\"1790288650833465345\",\"purchase_token\":\"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=\",\"client_id\":\"o6nD4iNavjQj75zPQk\",\"open_id\":\"4+Axcl2RFgXbt6MZwdh++w==\",\"user_region\":\"US\",\"goods_open_id\":\"com.goods.open_id\",\"goods_name\":\"TestGoodsName\",\"status\":\"charge.succeeded\",\"amount\":\"19000000000\",\"currency\":\"USD\",\"create_time\":\"1716168000\",\"pay_time\":\"1716168000\",\"extra\":\"1111111111111111111\"}}";
URI uri = new URI("https://example.com/my-service/v1/my-method");

HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(uri)
.header("Content-Type", "application/json; charset=utf-8")
.header("X-Tap-Ts", "1716168000")
.header("X-Tap-Nonce", "V7v7zJ");

// Considering body to be added for a POST request
HttpRequest request = requestBuilder
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();

String method = "POST"; // Since we are using the POST method in this example
String signature = signRequest(method, uri, body, request.headers().map(), secret);
System.out.println("Signature: " + signature);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Example signature calculation result:

# Part of the signature calculation
POST\n
/my-service/v1/my-method\n
x-tap-nonce:V7v7zJ\n
x-tap-ts:1716168000\n
{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345","purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk","open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id","goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD","create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}\n

# Signature result (X-Tap-Sign)
PyKQzlI65e0I9noVxcQc7FPU3nEyEFHKfRde65F6vhI=

General Object Structure Description

Order Information

ParameterTypeRequiredDescription
order_idstringYUnique order ID
purchase_tokenstringYToken used for order verification
client_idstringYClient ID of the application
open_idstringYOpen platform ID of the user
user_regionstringYUser Region
goods_open_idstringYUnique ID of the goods
goods_namestringYName of the goods
statusstringYOrder Status
amountstringYAmount (local currency amount x 1,000,000)
currencystringYCurrency
create_timestringYCreation time
pay_timestringYPayment time
extrastringYCustom data from the merchant, such as role information, no more than 255 UTF-8 characters

Order Status

Order StatusDescription
charge.pendingPending payment
charge.succeededPayment succeeded
charge.confirmedConfirmed
charge.overduePayment timed out
refund.pendingRefund pending
refund.succeededRefund succeeded
refund.failedRefund failed
refund.rejectedRefund rejected

Webhook Event Enumeration

event_typeDescription
charge.succeededRecharge succeeded
refund.succeededRefund succeeded
refund.failedRefund failed

Error Codes

codeDescription
-1Illegal request
100000Payment service exception
100004Order not found
100018Order verification error