Java — API Checkout
請替換示例憑證
運行前請替換所有佔位符:
"your-api-secret"→ 商戶後台的實際 API Secret"MCH_20240101_ABC123"→ 您的實際商戶號
API Checkout 不使用 X-Api-Key,只需 X-Timestamp 和 X-Signature。
需要開通權限
API Checkout 為獨立產品,需單獨申請開通。請聯繫技術支持啟用。
依賴(Maven)
xml
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>簽名工具類
API Checkout 使用與託管支付相同的簽名算法,但不包含 X-Api-Key。
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
public class CheckoutSignatureHelper {
private final String apiSecret;
public CheckoutSignatureHelper(String apiSecret) {
this.apiSecret = apiSecret;
}
/** 為 POST 請求生成簽名。 */
public String signPost(String timestamp, String path, String body) throws Exception {
byte[] bodyHash = MessageDigest.getInstance("SHA-256")
.digest(body.getBytes(StandardCharsets.UTF_8));
String bodyHashB64 = Base64.getEncoder().encodeToString(bodyHash);
return hmacBase64(timestamp + "\n" + "POST" + "\n" + path + "\n" + bodyHashB64);
}
/** 為無查詢字符串的 GET 請求生成簽名。 */
public String signGet(String timestamp, String path) throws Exception {
return hmacBase64(timestamp + "\n" + "GET" + "\n" + path);
}
private String hmacBase64(String data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
/** 驗證 Webhook 簽名。 */
public boolean verifyWebhook(String timestamp, String signature, String rawBody) throws Exception {
String expected = hmacBase64(timestamp + "\n" + rawBody);
return expected.equals(signature);
}
}第一步:獲取可用支付方式
渲染支付界面前必須在運行時調用此接口,禁止硬編碼幣種/網絡組合。
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetPaymentMethodsExample {
private static final String BASE_URL = "https://api.paystablecoin.global";
private static final String MERCHANT_ID = "MCH_20240101_ABC123";
private static final String API_SECRET = "your-api-secret";
public static void main(String[] args) throws Exception {
String timestamp = String.valueOf(System.currentTimeMillis());
String path = "/api/v1/merchants/" + MERCHANT_ID + "/checkout/payment-methods";
CheckoutSignatureHelper signer = new CheckoutSignatureHelper(API_SECRET);
String signature = signer.signGet(timestamp, path);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path))
.header("X-Timestamp", timestamp)
.header("X-Signature", signature)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// 響應包含:cryptoCurrency, network, networkDisplayName,
// minAmount, maxAmount, estimatedConfirmationTimeSec, displayOrder
// 按 displayOrder 升序排列後展示給用戶。
}
}第二步:創建 Checkout 訂單
客戶選擇幣種和網絡後,立即創建訂單。
java
public class CreateCheckoutOrderExample {
private static final String BASE_URL = "https://api.paystablecoin.global";
private static final String MERCHANT_ID = "MCH_20240101_ABC123";
private static final String API_SECRET = "your-api-secret";
public static void main(String[] args) throws Exception {
String timestamp = String.valueOf(System.currentTimeMillis());
String path = "/api/v1/merchants/" + MERCHANT_ID + "/checkout/orders";
// orderAmount.currency 必須為穩定幣:USDC、USDT 或 USD1
// network 必須與客戶選擇的支付方式匹配
String body = String.format(
"{\"merchantOrderId\":\"ORDER_%s\",\"orderAmount\":{\"value\":\"100.50\",\"currency\":\"USDC\"}," +
"\"paymentMethodType\":\"ON_CHAIN_TRANSFER\",\"network\":\"tron\"," +
"\"expiresAt\":\"2026-12-31T23:59:59Z\"," +
"\"callbackUrl\":\"https://yoursite.com/webhook/checkout\"}",
timestamp
);
CheckoutSignatureHelper signer = new CheckoutSignatureHelper(API_SECRET);
String signature = signer.signPost(timestamp, path, body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path))
.header("X-Timestamp", timestamp)
.header("X-Signature", signature)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// 響應包含:
// data.depositAddress — 以文字和二維碼展示
// data.cryptoPaymentAmount.value — 客戶需支付的精確金額
// data.networkDisplayName — 醒目展示(轉入錯誤網絡=資產丟失)
// data.expiresAt — 倒計時依據
//
// 重要:展示 cryptoPaymentAmount,而非 orderAmount
}
}第三步:查詢 Checkout 訂單狀態
當 Webhook 未收到時作為備選方案。
java
public class QueryCheckoutOrderExample {
private static final String BASE_URL = "https://api.paystablecoin.global";
private static final String MERCHANT_ID = "MCH_20240101_ABC123";
private static final String API_SECRET = "your-api-secret";
public static void main(String[] args) throws Exception {
String merchantOrderId = "ORDER_1738112345000"; // 替換為實際訂單號
String timestamp = String.valueOf(System.currentTimeMillis());
String path = "/api/v1/merchants/" + MERCHANT_ID + "/checkout/orders/" + merchantOrderId;
CheckoutSignatureHelper signer = new CheckoutSignatureHelper(API_SECRET);
String signature = signer.signGet(timestamp, path);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path))
.header("X-Timestamp", timestamp)
.header("X-Signature", signature)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// 每 5-10 秒輪詢一次,直到狀態達到終態:
// SUCCEEDED、FAILED 或 CLOSED
}
}第四步:驗證 Webhook 簽名
API Checkout Webhook 要求嚴格響應:5 秒內返回 HTTP 200 + {"code":"00000"}。
java
import com.sun.net.httpserver.HttpExchange;
public class CheckoutWebhookHandler {
private static final String API_SECRET = "your-api-secret";
public void handle(HttpExchange exchange) throws Exception {
String timestamp = exchange.getRequestHeaders().getFirst("X-Timestamp");
String signature = exchange.getRequestHeaders().getFirst("X-Signature");
String rawBody = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
// 第一步:驗證簽名
CheckoutSignatureHelper signer = new CheckoutSignatureHelper(API_SECRET);
if (!signer.verifyWebhook(timestamp, signature, rawBody)) {
exchange.sendResponseHeaders(401, 0);
exchange.close();
return;
}
// 第二步:驗證時間戳在 5 分鐘內
long webhookTime = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() - webhookTime) > 300_000) {
exchange.sendResponseHeaders(401, 0);
exchange.close();
return;
}
// 第三步:5 秒內返回 HTTP 200 + {"code":"00000"}(必須)
byte[] responseBody = "{\"code\":\"00000\"}".getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json");
exchange.sendResponseHeaders(200, responseBody.length);
exchange.getResponseBody().write(responseBody);
exchange.close();
// 第四步:異步處理業務邏輯
// 使用 acquiringOrderId 或 merchantOrderId 進行去重
// 同一事件可能多次推送。
}
}相關文檔
- API Checkout 整合指南 — 分步整合說明
- API Checkout 參考文檔 — 完整接口與字段說明
- 簽名算法 — 簽名計算詳解