主机到主机集成
Quick Reference
What服务器到服务器收费 API(应用程序编程接口)
Why对重试、幂等与错误处理的最大控制
Reading Time20 分钟
Difficulty高级
Prerequisites身份验证 → 环境配置
主机到主机(H2H)是直连 API 集成:您的后端携带卡与订单数据调用 A55。您完全掌控重试、幂等与错误处理。
为何选择 H2H(对比)
| 标准 | H2H | SDK | Checkout Page |
|---|---|---|---|
| 卡数据安全 | 您负责传输安全 | A55 通过 SDK 处理 | A55 全面管理 |
| 控制程度 | 完全 | 部分 | 最低 |
| 卡数据处理 | 您的服务器 | 浏览器 SDK | A55 托管 |
| 3DS(3D Secure 验证)管理 | 由您处理重定向 | 由 SDK 处理 | 由 A55 处理 |
| 最适合 | 需要完全服务器端控制 | 中端市场 | 低代码 / 无代码 |
何时使用
- 您需要对卡数据传输拥有完全服务器端控制。
- 您需要对收费全生命周期的最大服务端控制。
- 当 API 返回
pending与url_3ds时,您将实现 3DS 重定向。
卡字段(请求体)
| 字段 | 说明 |
|---|---|
card_name | 卡面印刷的持卡人姓名 |
card_number | 主账号(PAN) |
card_expiry_month | 到期月份(1–12) |
card_expiry_year | 到期年份(四位) |
card_cvv | 卡验证值 |
技术流程
分步集成
1
获取访问令牌
- cURL
- Python
- Node.js
- Go
- Java
- PHP
curl -s -X POST "https://a55-auth.auth.us-east-1.amazoncognito.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET"
import os, requests
resp = requests.post(
"https://a55-auth.auth.us-east-1.amazoncognito.com/oauth2/token",
data={"grant_type": "client_credentials",
"client_id": os.environ["CLIENT_ID"],
"client_secret": os.environ["CLIENT_SECRET"]},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
access_token = resp.json()["access_token"]
const resp = await fetch(
"https://a55-auth.auth.us-east-1.amazoncognito.com/oauth2/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "grant_type=client_credentials"
+ "&client_id=" + process.env.CLIENT_ID
+ "&client_secret=" + process.env.CLIENT_SECRET,
}
);
const { access_token } = await resp.json();
data := url.Values{
"grant_type": {"client_credentials"},
"client_id": {os.Getenv("CLIENT_ID")},
"client_secret": {os.Getenv("CLIENT_SECRET")},
}
resp, _ := http.PostForm(
"https://a55-auth.auth.us-east-1.amazoncognito.com/oauth2/token", data)
defer resp.Body.Close()
var tok struct{ AccessToken string `json:"access_token"` }
json.NewDecoder(resp.Body).Decode(&tok)
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://a55-auth.auth.us-east-1.amazoncognito.com/oauth2/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(
"grant_type=client_credentials&client_id=" + CLIENT_ID
+ "&client_secret=" + CLIENT_SECRET))
.build();
HttpResponse<String> resp = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
String accessToken = new JSONObject(resp.body()).getString("access_token");
$ch = curl_init("https://a55-auth.auth.us-east-1.amazoncognito.com/oauth2/token");
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/x-www-form-urlencoded"],
CURLOPT_POSTFIELDS => http_build_query([
"grant_type" => "client_credentials",
"client_id" => getenv("CLIENT_ID"),
"client_secret" => getenv("CLIENT_SECRET"),
]),
]);
$token = json_decode(curl_exec($ch))->access_token;
2
构建并发送收费请求
POST https://core-manager.a55.tech/api/v1/bank/wallet/charge/
- cURL
- Python
- Node.js
- Go
- Java
- PHP
curl -sS -X POST 'https://core-manager.a55.tech/api/v1/bank/wallet/charge/' \
-H 'Authorization: Bearer $ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: ord_001' \
-d '{
"wallet_uuid": "00000000-0000-4000-8000-000000000001",
"merchant_id": "merchant_123",
"payer_name": "张伟",
"payer_email": "zhangwei@example.com",
"payer_cell_phone": "+5511999999999",
"payer_tax_id": "12345678901",
"items": [{"name": "订单", "quantity": 1, "value": 10000}],
"payer_address": {"street": "Av. Paulista 1000", "city": "São Paulo", "state": "SP", "zip_code": "01310100", "country": "BR"},
"currency": "BRL",
"installment_value": 10000,
"installment_count": 1,
"due_date": "2026-12-31",
"description": "H2H 测试收费",
"type_charge": "credit_card",
"card_name": "张伟",
"card_number": "4111111111111111",
"card_expiry_month": 12,
"card_expiry_year": 2030,
"card_cvv": "123",
"reference_external_id": "ord_001",
"threeds_authentication": false,
"webhook_url": "https://merchant.example/webhooks/a55",
"redirect_url": "https://merchant.example/return"
}'
import os, requests
r = requests.post(
"https://core-manager.a55.tech/api/v1/bank/wallet/charge/",
headers={
"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}",
"Content-Type": "application/json",
},
json={
"wallet_uuid": "00000000-0000-4000-8000-000000000001",
"merchant_id": "merchant_123",
"payer_name": "张伟", "payer_email": "zhangwei@example.com",
"payer_tax_id": "12345678901",
"currency": "BRL", "installment_value": 10000, "installment_count": 1,
"type_charge": "credit_card",
"card_name": "张伟", "card_number": "4111111111111111",
"card_expiry_month": 12, "card_expiry_year": 2030, "card_cvv": "123",
"reference_external_id": "ord_001",
"threeds_authentication": False,
"webhook_url": "https://merchant.example/webhooks/a55",
},
timeout=60,
)
print(r.status_code, r.json())
const res = await fetch(
"https://core-manager.a55.tech/api/v1/bank/wallet/charge/",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
wallet_uuid: "00000000-0000-4000-8000-000000000001",
merchant_id: "merchant_123",
payer_name: "张伟", payer_email: "zhangwei@example.com",
payer_tax_id: "12345678901",
currency: "BRL", installment_value: 10000, installment_count: 1,
type_charge: "credit_card",
card_name: "张伟", card_number: "4111111111111111",
card_expiry_month: 12, card_expiry_year: 2030, card_cvv: "123",
reference_external_id: "ord_001",
threeds_authentication: false,
webhook_url: "https://merchant.example/webhooks/a55",
}),
}
);
console.log(await res.json());
payload := map[string]interface{}{
"wallet_uuid": "00000000-0000-4000-8000-000000000001",
"type_charge": "credit_card",
"card_name": "张伟", "card_number": "4111111111111111",
"card_expiry_month": 12, "card_expiry_year": 2030, "card_cvv": "123",
"currency": "BRL", "installment_value": 10000, "installment_count": 1,
"reference_external_id": "ord_001",
"threeds_authentication": false,
"webhook_url": "https://merchant.example/webhooks/a55",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST",
"https://core-manager.a55.tech/api/v1/bank/wallet/charge/", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("ACCESS_TOKEN"))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String json = """
{"wallet_uuid":"00000000-0000-4000-8000-000000000001",
"type_charge":"credit_card","card_name":"张伟",
"card_number":"4111111111111111","card_expiry_month":12,
"card_expiry_year":2030,"card_cvv":"123",
"currency":"BRL","installment_value":10000,"installment_count":1,
"reference_external_id":"ord_001","threeds_authentication":false,
"webhook_url":"https://merchant.example/webhooks/a55"}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://core-manager.a55.tech/api/v1/bank/wallet/charge/"))
.header("Authorization", "Bearer " + System.getenv("ACCESS_TOKEN"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json)).build();
HttpResponse<String> resp = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
$payload = [
"wallet_uuid" => "00000000-0000-4000-8000-000000000001",
"type_charge" => "credit_card",
"card_name" => "张伟", "card_number" => "4111111111111111",
"card_expiry_month" => 12, "card_expiry_year" => 2030, "card_cvv" => "123",
"currency" => "BRL", "installment_value" => 10000, "installment_count" => 1,
"reference_external_id" => "ord_001", "threeds_authentication" => false,
"webhook_url" => "https://merchant.example/webhooks/a55",
];
$ch = curl_init("https://core-manager.a55.tech/api/v1/bank/wallet/charge/");
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ACCESS_TOKEN"),
"Content-Type: application/json",
"Idempotency-Key: ord_001"],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
echo curl_exec($ch);
3
处理响应
- 已通过
- 3DS 待处理
- 错误
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "confirmed",
"reference_external_id": "ord_001"
}
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"url_3ds": "https://confirmacion.a55.tech/charge/a1b2c3d4...",
"reference_external_id": "ord_001"
}
将买家重定向到 url_3ds。挑战处理请参阅 3D Secure。
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "error",
"message": [{"message": "Declined or validation failed"}],
"code": "CARD_DECLINED"
}
4
处理 3DS 重定向(若状态为 pending)
若响应包含 url_3ds,请重定向买家,然后监听完成事件:
window.addEventListener('message', function (event) {
if (event.data?.event === '3ds-auth-complete') {
const chargeUuid = event.data.chargeUuid;
// 查询收费状态并更新 UI
}
});
5
通过 Webhook(网络钩子)确认
配置 webhook_url,以便在买家关闭浏览器后仍能收到异步状态更新。
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "paid",
"transaction_reference": "txn_ref_001"
}
使用 Sandbox(沙箱环境)测试卡
| Card Number | Brand | Scenario | Expected Status |
|---|---|---|---|
4111 1111 1111 1111 | Visa | 支付成功 | confirmed |
5500 0000 0000 0004 | Mastercard | 支付成功 | confirmed |
4000 0000 0000 0002 | Visa | 卡被拒 | declined |
4000 0000 0000 0101 | Visa | 需要 3DS 挑战 | confirmed |
4000 0000 0000 0069 | Visa | 处理错误 | error |
5105 1051 0510 5100 | Mastercard | 余额不足 | declined |
希望提高通过率?
Visa Data Only(仅数据共享) 通过 3DS 通道向发卡行发送增强交易数据——无需任何挑战重定向。Square 在超过 600 万笔交易的试点中,通过率最高提升 646 个基点(1 bp = 0.01%)。
若您已为 3DS 发送 device_info,数据已齐备。只需将 threeds_authentication: true 改为 data_only: true。
权衡:无责任转移(商户承担欺诈风险)。对拉美地区高交易量、低风险的订单,通过率提升通常超过欺诈成本。