端到端卡订阅支付
订阅支付是一种周期性自动扣款解决方案,可以帮助您完成周期性自动收款。仅需一次授权即可绑定支付账户享受持续的订阅服务,且支持动态调整订阅配置(如修改周期/金额、取消续订或终止服务等)。整个流程既便捷高效,又安全可靠。
本文为您介绍如何通过服务端-服务端模式集成银行卡支付方式,适合对支付流程定制程度有高要求的商户。服务端到服务端集成模式要求您符合 PCI 资格,请根据您的业务需求提供相关材料完成验证:
- 如果您预计年银行卡交易量超过 600 万,请完成并提交 PCI 合规确认书(AoC)文件进行验证。
- 如果您预计年银行卡交易量低于 600 万,请完成并提交 PCI DSS 自我评估问卷(SAQs)文件进行验证。
有关 PCI DSS 合规要求的更多信息,请参阅 PCI DSS 标准。
注意:本方案由您自行采集买家的卡信息,需要您具备 PCI 资质,请确保您已按照要求完成 PCI 验证。
订阅支付体验
以下为首次授权和后续扣款的用户体验:
注意:订阅支付需要绑定卡资产时,首笔交易需要买家发起并通过 3DS 验证流程以完成身份核验。
PC 端:
Mobile 端:
订阅生命周期
以下图片分别展示了整个订阅周期、订阅创建以及周期扣款的流程:
下图展示了整个订阅生命周期,包括创建订阅、签约绑定支付方式、完成首次扣款,以及在必要时发起退款等环节,旨在保障订阅的正常生效与费用处理的安全透明:
集成准备
在您开始集成前,请阅读集成指南及接口概述文档,了解服务端接口的集成步骤及调用接口的注意事项,并确保已完成以下预配置:
- 已获得 client ID。
- 已完成密钥配置。
- 已完成异步通知接收地址的配置。
- 集成服务端 SDK 资源包,并完成接口库安装及请求示例初始化。具体操作请参阅服务端 SDK。
集成步骤
开始集成,请按照以下步骤操作:
- 添加支付方式列表
- 创建订阅支付请求
- 跳转至 3D 验证页面(normalUrl)
- 授权结果通知
- 请款并获取请款通知
- 获取订阅通知
步骤 1:添加支付方式列表
在买家下单页面的支付方式列表中,展示本次需要集成的卡品牌标识和名称,供买家根据自身需求和偏好选择。
注意:支付方式列表页面需要由您自行实现。
步骤 2:创建订阅支付请求
您需要自行收集买家的支付信息(如卡号、有效期等)、订阅信息(如扣款金额,周期)、订单详情,设备及支付金额等信息,并调用 pay(单笔支付)接口来创建授权订阅支付请求:
创建授权订阅支付请求包含以下关键参数:
类型 | 参数 | 是否必传 | 描述 |
基础字段 | paymentRequestId | 是 | 授权支付请求单号,每次都需要唯一。 |
paymentAmount.currency | 是 | 支付币种。 | |
paymentAmount.value | 是 | 支付金额。按币种的最小单位设置,除 JPY、KRW 为元,其他均为分。 | |
settlementStrategy.settlementCurrency | 是 | 结算币种。 | |
paymentMethod.paymentMethodType | 是 | 支付方式枚举值,卡支付场景下的值为 | |
paymentFactor.isAuthorization | 是 | 表示支付场景是否为授权场景。卡支付场景下固定传 | |
paymentFactor.captureMode | 否 | 请款模式。默认由 Antom 替商户完成请款。如对首笔创建订阅交易需要设置为由您自行发起请款,可将该字段设置为 | |
productCode | 是 | 产品码,在此场景中,此字段固定传 | |
paymentRedirectUrl | 是 | 商户端支付结果页。需传入 HTTPS 地址,您根据支付结果进行动态渲染。 | |
paymentNotifyUrl | 否 | 授权支付结果通知地址,需传入 HTTPS 地址。此字段可通过接口传递,也可通过 Antom Dashboard 设置为固定值。若两者都设置,则接口传递的值优先。 | |
设备字段 | env.terminalType | 是 | 指定买家发起交易的端类型:
|
env.clientIp | 是 | 买家的 IP 地址。 | |
订单字段 | order.orderAmount | 是 | 订单金额 |
order.referenceOrderId | 是 | 商户端订单号 | |
order.orderDescription | 是 | 商户端订单描述 | |
order.buyer | 是 | 商户端买家信息。至少需要提供以下三者其中一个信息:
| |
订阅字段 | subscriptionInfo.subscriptionDescription | 是 | 订阅描述。 |
subscriptionInfo.subscriptionStartTime | 是 | 订阅关系生效时间,格式类似 2019-11-27T12:01:01+08:00,+08:00 代表东八区 | |
subscriptionInfo.subscriptionNotifyUrl | 是 | 订阅关系通知接收地址,需传入 HTTPS 地址。 | |
subscriptionInfo.trials | 否 | 订阅的试用信息列表。若您提供的订阅服务包含试用或折扣优惠,可通过此参数指定。 | |
subscriptionInfo.periodRule | 是 | 订阅周期,支持 | |
卡支付信息字段 | paymentMethod.paymentMethodMetaData.cardNo | 是 | 明文卡号。卡支付场景下,您自行采集卡信息需传入此参数。 |
paymentMethod.paymentMethodMetaData.expiryYear | 是 | 银行卡的过期年份。卡支付场景下,您自行采集卡信息需传入此参数。 | |
paymentMethod.paymentMethodMetaData.expiryMonth | 是 | 银行卡的过期月份。卡支付场景下,您自行采集卡信息需传入此参数。 | |
paymentMethod.paymentMethodMetaData.cvv | 是 | 卡验证码(CVV)。卡支付场景下,您自行采集卡信息建议传入此参数,提升支付成功率。 | |
paymentMethod.paymentMethodMetaData.cardholderName | 是 | 持卡人姓名。卡支付场景下,您自行采集卡信息建议传入此参数以提升支付成功率。该参数仅支持英文字符。 | |
paymentMethod.paymentMethodMetaData. is3DSAuthentication | 是 | 您需要根据当前交易的风险和拒付情况,决定交易是否进行 3DS 认证。有效值为:
|
以上参数是创建订阅支付请求的基本参数,完整参数和特定支付方式的额外要求请参考 pay(单笔支付)。
以下示例代码展示了如何调用 pay(单笔支付)接口:
public static void payByCardServer2Server() {
AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
alipayPayRequest.setProductCode(ProductCodeType.CASHIER_PAYMENT);
// 替换为您的 paymentRequestId
String paymentRequestId = UUID.randomUUID().toString();
alipayPayRequest.setPaymentRequestId(paymentRequestId);
// 设置订阅信息
SubscriptionInfo subscriptionInfo = new SubscriptionInfo();
subscriptionInfo.setSubscriptionDescription("Subscription Description");
LocalDateTime localNow = LocalDateTime.now();
OffsetDateTime offsetDateTime = localNow.atOffset(ZoneOffset.of("+08:00"));
String startTime = offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
subscriptionInfo.setSubscriptionStartTime(startTime);
subscriptionInfo.setSubscriptionNotifyUrl("https://www.yourDomain.com/subscription/notify");
PeriodRule periodRule = new PeriodRule();
periodRule.setPeriodType("MONTH");
periodRule.setPeriodCount(1);
subscriptionInfo.setPeriodRule(periodRule);
alipayPayRequest.setSubscriptionInfo(subscriptionInfo);
// 设置金额
Amount amount = Amount.builder().currency("SGD").value("4200").build();
alipayPayRequest.setPaymentAmount(amount);
// 设置支付方式
PaymentMethod paymentMethod = PaymentMethod.builder().paymentMethodType("CARD").build();
alipayPayRequest.setPaymentMethod(paymentMethod);
// 卡信息
Map<String, Object> paymentMethodMetaData = new HashMap<>();
paymentMethodMetaData.put("cardNo", "4054695723100768");
paymentMethodMetaData.put("cvv", "123");
paymentMethodMetaData.put("expiryMonth", "12");
paymentMethodMetaData.put("expiryYear", "99");
paymentMethodMetaData.put("tokenizeMode", false);
paymentMethodMetaData.put("is3DSAuthentication", true);
JSONObject cardholderName = new JSONObject();
cardholderName.put("firstName", "John");
cardholderName.put("lastName", "Doe");
paymentMethodMetaData.put("cardholderName", cardholderName);
paymentMethod.setPaymentMethodMetaData(paymentMethodMetaData);
// 卡支付信息
SettlementStrategy settlementStrategy = SettlementStrategy.builder().settlementCurrency("SGD").build();
alipayPayRequest.setSettlementStrategy(settlementStrategy);
// 替换为您的 orderId
String orderId = UUID.randomUUID().toString();
// 设置买家信息
Buyer buyer = Buyer.builder().referenceBuyerId("yourBuyerId").build();
// 设置订单信息
Order order = Order.builder().referenceOrderId(orderId)
.orderDescription("antom testing order").orderAmount(amount).buyer(buyer).build();
alipayPayRequest.setOrder(order);
// 设置环境信息
Env env = Env.builder().terminalType(TerminalType.WEB).clientIp("1.2.3.4").build();
alipayPayRequest.setEnv(env);
// 设置授权请款模式
PaymentFactor paymentFactor = PaymentFactor.builder().isAuthorization(true).build();
alipayPayRequest.setPaymentFactor(paymentFactor);
// 替换为您的通知地址
alipayPayRequest.setPaymentNotifyUrl("https://www.yourNotifyUrl.com");
// 替换为您的跳转 url
alipayPayRequest.setPaymentRedirectUrl("https://www.yourMerchantWeb.com");
// 支付
AlipayPayResponse alipayPayResponse = null;
try {
alipayPayResponse = CLIENT.execute(alipayPayRequest);
} catch (AlipayApiException e) {
String errorMsg = e.getMessage();
// 处理错误信息
}
}以下为请求报文示例:
{
"subscriptionInfo": {
"periodRule": {
"periodCount": 1,
"periodType": "MONTH"
},
"subscriptionDescription": "Subscription Description",
"subscriptionNotifyUrl": "https://www.yourDomain.com/subscription/notify",
"subscriptionStartTime": "2026-03-11T17:08:32.543+08:00"
},
"order": {
"buyer": {
"referenceBuyerId": "yourBuyerId"
},
"orderAmount": {
"currency": "SGD",
"value": "4200"
},
"orderDescription": "antom testing order",
"referenceOrderId": "281c86fa-a780-4804-8d43-77375998****"
},
"env": {
"clientIp": "1.2.3.4",
"terminalType": "WEB"
},
"paymentAmount": {
"currency": "SGD",
"value": "4200"
},
"paymentFactor": {
"isAuthorization": true
},
"paymentMethod": {
"paymentMethodMetaData": {
"cvv": "123",
"cardholderName": {
"firstName": "John",
"lastName": "Doe"
},
"expiryMonth": "12",
"expiryYear": "99",
"cardNo": "4054695723100768",
"is3DSAuthentication": true,
"tokenizeMode": false
},
"paymentMethodType": "CARD"
},
"settlementStrategy": {
"settlementCurrency": "SGD"
},
"paymentNotifyUrl": "https://www.yourNotifyUrl.com",
"paymentRedirectUrl": "https://www.yourMerchantWeb.com",
"paymentRequestId": "275624ab-5d33-43a5-9a4d-6e660bd0****",
"productCode": "CASHIER_PAYMENT"
}以下代码展示了一个响应的示例,其中包含以下关键参数:
- result.resultStatus:判断授权支付状态。
- normalUrl:为 3DS 验证页面链接。
{
"normalUrl": "https://iexpfront-sea.alipay.com/authorise3d?merchantId=1887SjzZ56b1lvObJWQTaqG4PhACjUGncnl0Sihzg8en5I%3D&ddcId=m47qH0MLK7A7yJNVdjLYJOlH5V9T9z53bmrgN8gJXC0%3D&pass=lt%5E1&needDDC=Y&from=iexpcore",
"paymentActionForm": "{\"method\":\"POST\",\"parameters\":\"{\\\"JWT\\\":\\\"eyJraWQiOiJDQVJESU5BTF9KV1RfYWxpcGF5IiwiY3R5IjoiQ0FSRElOQUxfSldUX2FsaXBheSIsImFsZyI6IkhTMjU2In0.eyJPcmdVbml0SWQiOiI1ZDVmMTA2MjRiNzkyYTFhZTBkN2Y3MTEiLCJSZWZlcmVuY2VJZCI6IjFmOTVkMTQ0LTcwZTAtNGFkYS1hZTY2LWViNjM2OWQ2YmMzMiIsIlJldHVyblVybCI6Imh0dHBzOi8vb3Blbi1uYS5hbGlwYXkuY29tL2FwaS9vcGVuL3VybF9jYWxsYmFja19nZXQvY2FyZGluYWwvY2FyZGluYWwwMDcuaHRtP3NpZ25EYXRhPVF0ZlolMkZuU0Y2dDVURTFJVnRRNkVycHQ3NSUyQjBxOE1zMlYxbW9iUm9WVnBlMUdpYlVTNFJBWEJldDNNJTJCNHRFWDU5OEx0JTJCVzhUem1XRXRpelc0czdRQVhLRGZwajJDSG5MUHRjQSUyRjNIT3Z6TXp1JTJCSzM1NSUyQnBLS1h3eXNSVkU4cTJVaUZWTzhMRTVWTE5JZ0paRjdVQUZMWVE2RElmSmNKenFVNFBUMm1oaTRDT3ZEZVJ3NFVNSzRWWjA4U1M0SnJ2Yk9teXhHaXgwV2tJUEdJSk5RcVpUVyUyQnhyZFAlMkJSMXhNMCUyRjVlejVqc2JkTHowMmFJNGp0SzFLanU4d3pvNnBxVWxrNXU4dm1jb1d5MHI0dlJwRUslMkJHbmJNM0o4eUxuWmNDNER5TiUyRjIlMkJIVGFiYnBpRFdWSGZRbjF5QyUyQlZ3ZE4yN25xcHl5RTBVc0ZDaFhveEFaTDQ5NkElM0QlM0QmaW5TZXJpYWxObz0yMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0Jm91dE9yZGVyTm89MjAyNjAzMTM4OTAzMTM0MTAwMDdENjI0MjgyOTA2NCIsImV4cCI6MTc3MzM2ODMxOSwiaWF0IjoxNzczMzY3NDE5LCJpc3MiOiI1ZDVmZGRlOGNjZWY3NjFhZDhiODJiNjgiLCJqdGkiOiIyMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0In0.D1yRpHR9TwvCkBlcqtGSjpj9fjphqV_FGDnls8AnuoI\\\",\\\"Bin\\\":\\\"405469\\\",\\\"DDCFlag\\\":\\\"Y\\\"}\",\"paymentActionFormType\":\"RedirectActionForm\",\"redirectUrl\":\"https://iexpfront-sea.alipay.com/authorise3d?merchantId=1887SjzZ56b1lvObJWQTaqG4PhACjUGncnl0Sihzg8en5I%3D&ddcId=m47qH0MLK7A7yJNVdjLYJOlH5V9T9z53bmrgN8gJXC0%3D&pass=lt%5E1&needDDC=Y&from=iexpcore\"}",
"paymentAmount": {
"currency": "SGD",
"value": "4200"
},
"paymentCreateTime": "2026-03-12T19:03:38-07:00",
"paymentId": "2026031319401080****1887D0280499441",
"paymentRequestId": "d6af2336-3cd6-4a17-94a2-10d44386****",
"paymentResultInfo": {
"cardBrand": "VISA",
"cardNo": "************0768",
"credentialTypeUsed": "PAN",
"threeDSResult": {
}
},
"paymentTime": "2026-03-12T19:03:39-07:00",
"redirectActionForm": {
"method": "POST",
"parameters": "{\"JWT\":\"eyJraWQiOiJDQVJESU5BTF9KV1RfYWxpcGF5IiwiY3R5IjoiQ0FSRElOQUxfSldUX2FsaXBheSIsImFsZyI6IkhTMjU2In0.eyJPcmdVbml0SWQiOiI1ZDVmMTA2MjRiNzkyYTFhZTBkN2Y3MTEiLCJSZWZlcmVuY2VJZCI6IjFmOTVkMTQ0LTcwZTAtNGFkYS1hZTY2LWViNjM2OWQ2YmMzMiIsIlJldHVyblVybCI6Imh0dHBzOi8vb3Blbi1uYS5hbGlwYXkuY29tL2FwaS9vcGVuL3VybF9jYWxsYmFja19nZXQvY2FyZGluYWwvY2FyZGluYWwwMDcuaHRtP3NpZ25EYXRhPVF0ZlolMkZuU0Y2dDVURTFJVnRRNkVycHQ3NSUyQjBxOE1zMlYxbW9iUm9WVnBlMUdpYlVTNFJBWEJldDNNJTJCNHRFWDU5OEx0JTJCVzhUem1XRXRpelc0czdRQVhLRGZwajJDSG5MUHRjQSUyRjNIT3Z6TXp1JTJCSzM1NSUyQnBLS1h3eXNSVkU4cTJVaUZWTzhMRTVWTE5JZ0paRjdVQUZMWVE2RElmSmNKenFVNFBUMm1oaTRDT3ZEZVJ3NFVNSzRWWjA4U1M0SnJ2Yk9teXhHaXgwV2tJUEdJSk5RcVpUVyUyQnhyZFAlMkJSMXhNMCUyRjVlejVqc2JkTHowMmFJNGp0SzFLanU4d3pvNnBxVWxrNXU4dm1jb1d5MHI0dlJwRUslMkJHbmJNM0o4eUxuWmNDNER5TiUyRjIlMkJIVGFiYnBpRFdWSGZRbjF5QyUyQlZ3ZE4yN25xcHl5RTBVc0ZDaFhveEFaTDQ5NkElM0QlM0QmaW5TZXJpYWxObz0yMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0Jm91dE9yZGVyTm89MjAyNjAzMTM4OTAzMTM0MTAwMDdENjI0MjgyOTA2NCIsImV4cCI6MTc3MzM2ODMxOSwiaWF0IjoxNzczMzY3NDE5LCJpc3MiOiI1ZDVmZGRlOGNjZWY3NjFhZDhiODJiNjgiLCJqdGkiOiIyMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0In0.D1yRpHR9TwvCkBlcqtGSjpj9fjphqV_FGDnls8AnuoI\",\"Bin\":\"405469\",\"DDCFlag\":\"Y\"}",
"redirectUrl": "https://iexpfront-sea.alipay.com/authorise3d?merchantId=1887SjzZ56b1lvObJWQTaqG4PhACjUGncnl0Sihzg8en5I%3D&ddcId=m47qH0MLK7A7yJNVdjLYJOlH5V9T9z53bmrgN8gJXC0%3D&pass=lt%5E1&needDDC=Y&from=iexpcore"
},
"result": {
"resultCode": "PAYMENT_IN_PROCESS",
"resultMessage": "payment in process",
"resultStatus": "U"
}
}下表展示了响应代码中 result.resultStatus 字段可能返回的值,请您根据指引进行处理:
result.resultStatus | 信息 | 后续操作 |
| 授权支付成功。 | 后续可发起请款,同时存储 paymentId 用于后续的请款和退款。 |
| 授权支付失败。 | 关闭当前交易或重新更换 paymentRequestId 再次下单。 |
| 授权支付进行中。 |
|
注意:如果您未收到响应报文,可能是网络超时所致,您可以等待 notifyPayment(订阅)接口发送异步通知或您主动调用 inquiryPayment 接口发起查询。
常见问题
问:首笔交易是否一定要强制 3D 验证?
答:首笔交易是买家参与的交易,需要进行身份验证,用于保障后续买家不在场的周期性扣费的安全。因此对于首笔交易的身份验证有如下要求:
- 通过 Antom 完成 3D 验证:买家选择卡支付,设置 paymentMethodType 为
CARD,均需要完成 3DS 交易,以完成买家身份验证。可以在 pay(单笔支付)接口请求中设置参数 is3DSAuthentication 的值为true,通过 Antom 3DS 实现。- 通过第三方机构完成 3D 验证:服务端到服务端模式下,您自行采集卡信息且通过第三方机构完成 3DS 认证后,需传入 mpiData.eci 和 mpiData.cavv 参数。
步骤 3:跳转至 3D 验证页面(normalUrl)
商户服务端拿到 Antom 返回的 3D 验证页面链接(normalUrl )后,将该地址传递给前端,由商户前端跳转至 3D 验证页面。
以下为商户前端加载 normalUrl 的示例代码:
if (serverResponse.normalUrl != null) {
window.open(serverResponse.normalUrl, '_blank');
}以下图片展示了 Web 和 App 端重定向至 3DS 认证页面的用户体验:
常见问题
问:如何处理不同的支付体验?
答:通过 API 集成接入卡支付返回的 normalUrl 均为 H5 链接,对于 WEB 和 WAP 直接跳转即可,对于 APP 建议您通过 WebView 形式加载 normalUrl,提升用户体验。
问:paymentRedirectUrl 在传参上有些什么注意点?
答:默认设置为 HTTPS 地址,同时 URL 中的特殊字符能进行编码处理,否则会支付异常。
问:支付结果页内容如何展示?
答:您需要通过在 pay(单笔支付)接口中指定 paymentRedirectUrl 字段来提供一个 HTTPS 地址。该地址用于在商户端显示支付结果。在支付成功和支付失败的情况下,可能都有入口可以从支付方式端回跳到商户页面。因此,请勿将 paymentRedirectUrl 固定写成“订阅创建成功页面”,而是以服务端返回的结果为准,避免引起买家误解。
问:回跳商户结果页是否代表订阅创建成功?
答:不能仅凭回跳商户页面来判断订阅创建是否成功,具体有以下三种情况:
- 买家支付成功后,可能因网络等原因导致未能回跳至商户页。
- 即使买家未完成支付,也可能通过支付方式端的入口回跳至商户页面。
- 即使买家完成支付,也可能因为请款失败而导致订阅关系没有生效。
步骤 4:授权结果通知
无论授权成功或失败,Antom 都会使用 notifyPayment(订阅)接口将支付结果发送给您。请按以下步骤获取授权支付结果通知:
- 设置接收通知的 webhook URL:在 pay(单笔支付)接口请求的 paymentNotifyUrl 参数设置和 Antom Dashboard 配置。
以下为异步通知请求体的代码示例:
{
"notifyType": "PAYMENT_RESULT",
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "success"
},
"paymentRequestId": "2020010123456789XXXX",
"paymentId": "2020010123456789XXXX",
"paymentAmount": {
"value": "8000",
"currency": "EUR"
},
"paymentCreateTime": "2020-01-01T12:01:00+08:30",
"paymentTime": "2020-01-01T12:01:01+08:30"
}- 异步通知验签。
当您收到 Antom 的异步通知,您需要按照返回收到确认信息的格式返回响应,但无需做加签处理。
您需要按照以下方法对 Antom 发送的支付通知进行验签:
/**
* 接收支付通知
*
* @param request request
* @param notifyBody notify body
* @return Result
*/
@PostMapping("/receivePaymentNotify")
@ResponseBody
public Result receivePaymentNotify(HttpServletRequest request, @RequestBody String notifyBody) {
// 从 HTTP 请求中获取所需参数
String requestUri = request.getRequestURI();
String requestMethod = request.getMethod();
// 从请求头中获取必要参数
String requestTime = request.getHeader("request-time");
String clientId = request.getHeader("client-id");
String signature = request.getHeader("signature");
try {
// 验签
boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
if (!verifyResult) {
throw new RuntimeException("Invalid notify signature");
}
// 反序列化通知报文体
AlipaySubscriptionPayNotify paymentNotify = JSON.parseObject(notifyBody, AlipaySubscriptionPayNotify.class);
if (paymentNotify != null && "SUCCESS".equals(paymentNotify.getResult().getResultCode())) {
// 处理你的业务逻辑
// 例如:通过 subscriptionRequestId 与用户 ID 的关系保存用户的支付信息。
System.out.println("receive payment notify: " + JSON.toJSONString(paymentNotify));
return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
}
} catch (Exception e) {
return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
}
return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
}- 响应通知结果。无论订单是否支付成功,每个通知请求均需按以下固定格式响应。否则,Antom 会重新发送异步通知。
{
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "success"
}
}步骤 5:请款并获取请款通知
注意:只有授权支付成功才会触发请款。
授权支付成功后,Antom 默认自动为您请款,也支持您手动发起请款。同时,Antom 会使用 notifyCapture(单笔支付)接口将请款结果通知发送给您,您也可以通过主动查询来获取请款结果。您需要根据请款结果来决定是否发货,具体操作请参阅请款。
步骤 6:获取订阅通知
订阅关系生效后,Antom 会为您发送以下通知:
首期订阅通知
Antom 将通过 HTTPS 向您在接口或 Antom Dashboard 中配置的 Webhook 推送以下事件通知:
通知类型 | 接口 | 请求报文信息 |
订阅状态通知 | 订阅关系生效后,Antom 会使用 notifySubscription 接口将订阅结果通知发送给您。 |
|
当期扣款结果通知 | 在订阅支付成功开通或续期成功后,Antom 会使用 notifyPayment(订阅)接口将本期订阅扣款结果发送给您。 | 请求报文 notifyType 的值为 |
请按以下步骤获取订阅状态通知:
- 设置接收通知的 webhook URL:通过 pay(单笔支付)接口的 subscriptionInfo.subscriptionNotifyUrl 参数设置。以下是两种订阅状态的通知示例:
- 当 subscriptionNotificationType 的值为
CREATE时,请根据 subscriptionStatus 的值判断订阅关系:
ACTIVE:表示订阅关系生效。TERMINATED:表示订阅关系失效。
{
"periodRule": {
"periodCount": 1,
"periodType": "MONTH"
},
"subscriptionEndTime": "2074-02-20T01:16:17-08:00",
"subscriptionId": "************0226",
"subscriptionNotificationType": "CREATE",
"subscriptionRequestId": "SUBSCRIPTION_202444****oo009851_AUTO",
"subscriptionStartTime": "2024-09-13T19:30:17-07:00",
"subscriptionStatus": "ACTIVE"
}- 当 subscriptionNotificationType 的值为
TERMINATE时,订阅关系失效。
{
"periodRule": {
"periodCount": 1,
"periodType": "WEEK"
},
"subscriptionId": "2025102619******00000160000671943",
"subscriptionLastUpdateTime": "2025-10-26T09:51:13-07:00",
"subscriptionNotificationType": "TERMINATE",
"subscriptionRequestId": "PR_en_****176",
"subscriptionStartTime": "2025-10-26T10:01:13-07:00",
"subscriptionStatus": "TERMINATED"
}- 异步通知验签。
当您收到 Antom 的异步通知,您需要按照返回收到确认信息的格式返回响应,但无需做加签处理。
您需要按照以下方法对 Antom 发送的通知进行验签:
/**
* receive subscription notify
*
* @param request request
* @param notifyBody notify body
* @return Result
*/
@PostMapping("/receiveSubscriptionNotify")
@ResponseBody
public Result receiveSubscriptionNotify(HttpServletRequest request, @RequestBody String notifyBody) {
// retrieve the required parameters from http request
String requestUri = request.getRequestURI();
String requestMethod = request.getMethod();
// retrieve the required parameters from request header
String requestTime = request.getHeader("request-time");
String clientId = request.getHeader("client-id");
String signature = request.getHeader("signature");
try {
// verify the signature of notification
boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
if (!verifyResult) {
throw new RuntimeException("Invalid notify signature");
}
// deserialize the notification body
AlipaySubscriptionNotify subscriptionNotify = JSON.parseObject(notifyBody, AlipaySubscriptionNotify.class);
if (subscriptionNotify != null && SubscriptionNotificationType.CREATE.equals(subscriptionNotify.getSubscriptionNotificationType())) {
// handle your own business logic.
// e.g. The subscription information of the user is saved through the relationship between the subscriptionRequestId and the user ID.
System.out.println("receive subscription notify: " + JSON.toJSONString(subscriptionNotify));
return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
}
} catch (Exception e) {
return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
}
return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
}- 响应通知结果。无论订单是否支付成功,每个通知请求均需按以下固定格式响应。否则,Antom 会重新发送异步通知。
{
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "success"
}
}常见问题
问:Antom 会重新发送异步通知吗?
答:会。对于以下情况,异步通知将在 24 小时内自动重新发送:
- 由于网络原因未收到异步通知。
- 如果收到来自 Antom 的异步通知,但您没有按照返回收到确认信息的格式进行响应。
通知可以重发最多 8 次,或者直到收到正确的响应以终止传递。发送间隔如下:0 分钟,2 分钟,10 分钟,10 分钟,1 小时,2 小时,6 小时和 15 小时。
问:收到支付结果通知是否需要验签?
答:需要。通过验签 Antom 会发送保障回调请求给您,验签时请注意拼装待验签报文时需按标准处理:
<http-method> <http-uri> <client-id>.<request-time>.<request-body>,特别是针对<request-body>需直接取值而非解析 JSON 后拼装。
问:若首次支付失败,订阅关系会生效吗?
答:当订阅开通的首次扣款失败时,订阅关系将不会生效,Antom 系统会通过 Webhook 推送subscriptionStatus 为
TERMINATED的状态通知给您,表明订阅开通失败。
问:订阅超时时间是多久?
答:针对卡支付类支付方式,默认为 7 天超时时间,您也可以通过 subscriptionExpiryTime 字段来指定超时时间。
问:订阅当期扣款结果通知和授权支付通知、请款通知有什么区别?
答:授权支付阶段通知中包含 3DS 等相关的信息;请款通知则体现最终的支付结果,建议以此通知作为发货依据;订阅当期支付结果通知中包含了订阅周期信息,包含当期的开始,结束时间及期数等。
问:订阅的首次绑定支付支持商户自行发起请款吗?
答:支持,需要在 pay(单笔支付)接口中将字段 paymentFactor.captureMode 的值设置为
MANUAL,然后在收到授权支付成功的异步通知后,自行发起 capture(单笔支付)调用。在此场景下,需保障请款成功才算首次绑定支付成功,Antom 才会生成后续的周期扣款计划。
订阅续期通知
当订阅创建成功并建立有效关系后,Antom 系统将会根据您配置的订阅规则,自动发起续订扣款,并通过 Webhook 推送相应的支付结果通知,实现周期性扣费。触发续订扣款的时间和规则如下:
- 触发时间:续订扣款将在下一个订阅周期起始日的前 24 小时自动触发。您可以依据上一周期支付结果通知中的 periodEndTime 字段,向前推 24 小时以判断下一周期扣款的发起时间。
- 周期规则:续费周期及扣款频率将按照订阅时设定的 periodRule 执行,例如按日、按月、按季度或按年扣款。
首期扣款时间 首期 (paymentTime = periodStartTime) | 订阅周期 | 下期扣款时间 (paymentTime) | 下期生效时间 (periodStartTime) |
2024-09-25T20:10:17+08:00 | 设置订阅周期为 3 天,需要传入以下参数:
| 2024-09-27T20:20:04+08:00 | 2024-09-28T20:10:17+08:00 |
2024-09-25T20:10:17+08:00 | 设置订阅周期为 1 周,需要传入以下参数:
| 2024-10-01T20:10:17+08:00 | 2024-10-02T20:10:17+08:00 |
2024-09-25T20:10:17+08:00 | 设置订阅周期为 1 个月,需要传入以下参数:
| 2024-10-24T20:10:17+08:00 | 2024-10-25T20:10:17+08:00 |
2024-09-25T20:10:17+08:00 | 设置订阅周期为 1 年,需要传入以下参数:
| 2025-09-24T20:10:17+08:00 | 2025-09-25T20:10:17+08:00 |
异步通知通常分为以下场景:
常见场景 | 首次绑定支付 | 后续周期扣款 |
授权支付结果通知 | 有通知,告知授权结果和 3DS 认证结果。 | 有通知,告知授权结果和 3DS 认证结果。 |
请款通知 | 授权成功则有通知,请款结果为发货依据。 | 授权成功则有通知,请款结果为发货依据。 |
订阅状态通知 | 有通知,告知订阅创建结果。 | 无通知。 |
当期扣费结果通知 | 有通知,告知本期扣费状态、金额和有效期。 | 有通知,告知每期扣费状态、金额和有效期 |
以下为各场景异步通知的示例代码:
{
"notifyType": "PAYMENT_RESULT",
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "success"
},
"paymentRequestId": "2020010123456789XXXX",
"paymentId": "2020010123456789XXXX",
"paymentAmount": {
"value": "8000",
"currency": "EUR"
},
"paymentCreateTime": "2020-01-01T12:01:00+08:30",
"paymentTime": "2020-01-01T12:01:01+08:30"
}常见问题
问:若扣款失败会导致订阅关系失效吗?
答:创建订阅的首期扣款如果失败,订阅关系将不会生效;若订阅关系已生效但后续的周期扣款失败(如余额不足),订阅状态仍保持有效;若未主动取消订阅,Antom 将会在下一期继续周期扣款。
问:扣款失败会发送当期扣款通知吗,会重试吗?
答:扣款失败会发送扣款失败通知;卡支付的订阅支付场景下,Antom 不会发起重试。商户侧如需自行发起重试,可联系技术支持确定方案.
问:假如首期扣款是 2 月 28 日、3 月 31 日或 4 月 30 日这些月末时间点,那下一期的扣费时间怎么定义?
答:订阅的周期逻辑为按选定的日期来发起下一次扣款,如果下一个周期没有这个日期,则往前推到最后一天,例如:
问:后续的周期扣款如何跟首期合约关联?
- 首期 1.28,二期 2.28,三期 3.28,四期 4.28。
- 首期 1.31,二期 2.28,三期 3.31,四期 4.30。
- 首期 1.30,二期 2.28,三期 3.30,四期 4.30。
答:针对周期扣款通知,可以根据通知请求中的 subscriptionRequestId 或 subscriptionId 与首期合约关联。针对卡支付的周期扣款, Antom 还会额外发送授权结果通知和请款结果通知,这两个通知可以根据 paymentId 关联到周期扣款通知中的 paymentId 并最终关联上首期合约的 subscriptionId。
问:针对卡支付,如果首次创建订阅关系时选择由商户自行请款,即字段 paymentFactor.captureMode 的值为
MANUAL,那后续的自动续费会如何处理?答:后续自动续费均由 Antom 发起,无需商户侧参与发起请款,即均默认 paymentFactor.captureMode 的值为
AUTOMATIC。
订阅后操作
订阅试用
Antom 提供订阅试用功能,帮助买家在正式购买订阅计划前,以免费或优惠价格在限定时间内体验产品或服务。详情请参阅订阅试用文档。
终止订阅
终止订阅功能允许买家在不需要继续使用相关服务时,随时取消当前订阅。详情请参阅终止订阅文档。
取消交易
对于支付成功后的订单,如买家在当天内申请取消订单或退款,您可以通过 Antom 提供的取消交易能力将订单状态取消或解冻。此外,对于尚未完成支付的订单,您也可以直接进行取消,具体的集成方案见取消交易文档。
退款
对于已支付成功的订单,如您需向买家发起退款,Antom 提供以下两种方式。
- 由您的运营人员直接在 Antom Dashboard 平台上进行人工退款。
- 通过 API 方式接入 refund 接口发起退款。
Antom 的退款能力如下:
- 支持全额退款。
- 支持部分多次退款,多次退款的总金额需小于等于请款金额。
具体的集成方案见退款文档。
更多内容
测试卡
使用测试银行卡确保您的集成已准备好投入生产。想要了解更多关于下载测试钱包的信息,请参阅测试资源。
最佳实践
为了提高集成效率,Antom 为您提供以下最佳实践方案: