端到端卡订阅支付

订阅支付是一种周期性自动扣款解决方案,可以帮助您完成周期性自动收款。仅需一次授权即可绑定支付账户享受持续的订阅服务,且支持动态调整订阅配置(如修改周期/金额、取消续订或终止服务等)。整个流程既便捷高效,又安全可靠。

本文为您介绍如何通过服务端-服务端模式集成银行卡支付方式,适合对支付流程定制程度有高要求的商户。服务端到服务端集成模式要求您符合 PCI 资格,请根据您的业务需求提供相关材料完成验证:

有关 PCI DSS 合规要求的更多信息,请参阅 PCI DSS 标准

注意:本方案由您自行采集买家的卡信息,需要您具备 PCI 资质,请确保您已按照要求完成 PCI 验证。

订阅支付体验

以下为首次授权和后续扣款的用户体验:

注意:订阅支付需要绑定卡资产时,首笔交易需要买家发起并通过 3DS 验证流程以完成身份核验。

PC 端:

首次支付用户体验-pc.png

Mobile 端:

首次支付用户体验-mobile.png

订阅生命周期

以下图片分别展示了整个订阅周期、订阅创建以及周期扣款的流程:

下图展示了整个订阅生命周期,包括创建订阅、签约绑定支付方式、完成首次扣款,以及在必要时发起退款等环节,旨在保障订阅的正常生效与费用处理的安全透明

1.png 2.png

集成准备

在您开始集成前,请阅读集成指南接口概述文档,了解服务端接口的集成步骤及调用接口的注意事项,并确保已完成以下预配置:

  • 已获得 client ID。
  • 已完成密钥配置。
  • 已完成异步通知接收地址的配置。
  • 集成服务端 SDK 资源包,并完成接口库安装及请求示例初始化。具体操作请参阅服务端 SDK

集成步骤

开始集成,请按照以下步骤操作:

  1. 添加支付方式列表
  2. 创建订阅支付请求
  3. 跳转至 3D 验证页面(normalUrl
  4. 授权结果通知
  5. 请款并获取请款通知
  6. 获取订阅通知

步骤 1:添加支付方式列表 客户端

在买家下单页面的支付方式列表中,展示本次需要集成的卡品牌标识和名称,供买家根据自身需求和偏好选择。

注意:支付方式列表页面需要由您自行实现。

步骤 2:创建订阅支付请求 服务端

您需要自行收集买家的支付信息(如卡号、有效期等)、订阅信息(如扣款金额,周期)、订单详情,设备及支付金额等信息,并调用 pay(单笔支付)接口来创建授权订阅支付请求:

创建授权订阅支付请求包含以下关键参数:

类型

参数

是否必传

描述

基础字段

paymentRequestId

授权支付请求单号,每次都需要唯一。

paymentAmount.currency

支付币种。

paymentAmount.value

支付金额。按币种的最小单位设置,除 JPY、KRW 为元,其他均为分。

settlementStrategy.settlementCurrency

结算币种。

paymentMethod.paymentMethodType

支付方式枚举值,卡支付场景下的值为 CARD

paymentFactor.isAuthorization

表示支付场景是否为授权场景。卡支付场景下固定传 true,指定为授权请款模式。

paymentFactor.captureMode

请款模式。默认由 Antom 替商户完成请款。如对首笔创建订阅交易需要设置为由您自行发起请款,可将该字段设置为 MANUAL,并在授权成功后调用 capture(单笔支付)接口

productCode

产品码,在此场景中,此字段固定传 CASHIER_PAYMENT

paymentRedirectUrl

商户端支付结果页。需传入 HTTPS 地址,您根据支付结果进行动态渲染。

paymentNotifyUrl

授权支付结果通知地址,需传入 HTTPS 地址。此字段可通过接口传递,也可通过 Antom Dashboard 设置为固定值。若两者都设置,则接口传递的值优先。

设备字段

env.terminalType

指定买家发起交易的端类型:

  • WEB:客户端终端类型为网站,通过 PC 浏览器打开。
  • WAP:客户端终端类型为 H5 页面,通过移动浏览器打开。
  • APP:客户端终端类型为移动应用。

env.clientIp

买家的 IP 地址。

订单字段

order.orderAmount

订单金额

order.referenceOrderId

商户端订单号

order.orderDescription

商户端订单描述

order.buyer

商户端买家信息。至少需要提供以下三者其中一个信息:

  • order.buyer.referenceBuyerId
  • order.buyer.buyerPhoneNo
  • order.buyer.buyerEmail

订阅字段

subscriptionInfo.subscriptionDescription

订阅描述。

subscriptionInfo.subscriptionStartTime

订阅关系生效时间,格式类似 2019-11-27T12:01:01+08:00,+08:00 代表东八区

subscriptionInfo.subscriptionNotifyUrl

订阅关系通知接收地址,需传入 HTTPS 地址。

subscriptionInfo.trials

订阅的试用信息列表。若您提供的订阅服务包含试用或折扣优惠,可通过此参数指定。

subscriptionInfo.periodRule

订阅周期,支持 YEARMONTHWEEKDAY 四类周期。

卡支付信息字段

paymentMethod.paymentMethodMetaData.cardNo

明文卡号。卡支付场景下,您自行采集卡信息需传入此参数。

paymentMethod.paymentMethodMetaData.expiryYear

银行卡的过期年份。卡支付场景下,自行采集卡信息需传入此参数。

paymentMethod.paymentMethodMetaData.expiryMonth

银行卡的过期月份。卡支付场景下,自行采集卡信息需传入此参数。

paymentMethod.paymentMethodMetaData.cvv

卡验证码(CVV)。卡支付场景下,自行采集卡信息建议传入此参数,提升支付成功率。

paymentMethod.paymentMethodMetaData.cardholderName

持卡人姓名。卡支付场景下,自行采集卡信息建议传入此参数以提升支付成功率。该参数仅支持英文字符。

paymentMethod.paymentMethodMetaData.

is3DSAuthentication

您需要根据当前交易的风险和拒付情况,决定交易是否进行 3DS 认证。有效值为:

  • true:指交易发起 3DS 认证。
  • false:指交易不进行 3DS 认证。如果此参数为空或未传递,默认为此值。

注意:卡支付场景下需设置为 true 做 3D 交易,保障首笔银行卡支付对买家进行身份验证。

以上参数是创建订阅支付请求的基本参数,完整参数和特定支付方式的额外要求请参考 pay(单笔支付)

以下示例代码展示了如何调用 pay(单笔支付)接口:

copy
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();
            // 处理错误信息
        }
    }

以下为请求报文示例:

copy
{
    "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 验证页面链接。
copy
{
    "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

信息

后续操作

S

授权支付成功。

后续可发起请款,同时存储 paymentId 用于后续的请款和退款。

F

授权支付失败。

关闭当前交易或重新更换 paymentRequestId 再次下单。

U

授权支付进行中。

  • 若返回了 normalUrl:商户前端会跳转到 normalUrl,您需要同时存储 paymentId 用于后续的请款和退款。
  • 若没有返回 normalUrl等待 notifyPayment(订阅)接口发送异步通知或您主动调用 inquiryPayment 接口发起查询同时您需要存储 paymentId 用于后续的请款和退款。

注意:如果您未收到响应报文,可能是网络超时所致,您可以等待 notifyPayment(订阅)接口发送异步通知或您主动调用 inquiryPayment 接口发起查询。

常见问题

问:首笔交易是否一定要强制 3D 验证

答:首笔交易是买家参与的交易,需要进行身份验证,用于保障后续买家不在场的周期性扣费的安全。因此对于首笔交易的身份验证有如下要求:

  • 通过 Antom 完成 3D 验证:买家选择卡支付,设置 paymentMethodTypeCARD,均需要完成 3DS 交易,以完成买家身份验证。可以在 pay(单笔支付)接口请求中设置参数 is3DSAuthentication 的值为 true通过 Antom 3DS 实现。
  • 通过第三方机构完成 3D 验证:服务端到服务端模式下,您自行采集卡信息且通过第三方机构完成 3DS 认证后,需传入 mpiData.ecimpiData.cavv 参数。

步骤 3:跳转至 3D 验证页面(normalUrl 客户端

商户服务端拿到 Antom 返回的 3D 验证页面链接(normalUrl )后,将该地址传递给前端,由商户前端跳转至 3D 验证页面。

以下为商户前端加载 normalUrl 的示例代码:

copy
if (serverResponse.normalUrl != null) {
    window.open(serverResponse.normalUrl, '_blank');
}

以下图片展示了 Web 和 App 端重定向至 3DS 认证页面的用户体验:

3.png

常见问题

问:如何处理不同的支付体验?

答:通过 API 集成接入卡支付返回的 normalUrl 均为 H5 链接,对于 WEB 和 WAP 直接跳转即可,对于 APP 建议您通过 WebView 形式加载 normalUrl,提升用户体验。

问:paymentRedirectUrl 在传参上有些什么注意点?

答:默认设置为 HTTPS 地址,同时 URL 中的特殊字符能进行编码处理,否则会支付异常。

问:支付结果页内容如何展示?

答:您需要通过在 pay(单笔支付)接口中指定 paymentRedirectUrl 字段来提供一个 HTTPS 地址。该地址用于在商户端显示支付结果。在支付成功和支付失败的情况下,可能都有入口可以从支付方式端回跳到商户页面。因此,请勿将 paymentRedirectUrl 固定写成“订阅创建成功页面”,而是以服务端返回的结果为准,避免引起买家误解。

问:回跳商户结果页是否代表订阅创建成功?

答:不能仅凭回跳商户页面来判断订阅创建是否成功,具体有以下三种情况

  • 买家支付成功后,可能因网络等原因导致未能回跳至商户页。
  • 即使买家未完成支付,也可能通过支付方式端的入口回跳至商户页面。
  • 即使买家完成支付,也可能因为请款失败而导致订阅关系没有生效。

步骤 4:授权结果通知 服务端

无论授权成功或失败,Antom 都会使用 notifyPayment(订阅)接口将支付结果发送给您。请按以下步骤获取授权支付结果通知:

  1. 设置接收通知的 webhook URL: pay(单笔支付)接口请求的 paymentNotifyUrl 参数设置和 Antom Dashboard 配置。

以下为异步通知请求体的代码示例:

copy
{
  "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"
}
  1. 异步通知验签。

当您收到 Antom 的异步通知,您需要按照返回收到确认信息的格式返回响应,但无需做加签处理。

您需要按照以下方法对 Antom 发送的支付通知进行验签:

copy
 /**
     * 接收支付通知
     *
     * @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();
    }
  1. 响应通知结果。无论订单是否支付成功,每个通知请求均需按以下固定格式响应。否则,Antom 会重新发送异步通知。
copy
{
    "result": {
        "resultCode": "SUCCESS",
        "resultStatus": "S",
        "resultMessage": "success"
    }
}

步骤 5:请款并获取请款通知 服务端

注意:只有授权支付成功才会触发请款。

授权支付成功后,Antom 默认自动为您请款,也支持您手动发起请款。同时,Antom 会使用 notifyCapture(单笔支付)接口将请款结果通知发送给您,您也可以通过主动查询来获取请款结果。您需要根据请款结果来决定是否发货,具体操作请参阅请款

步骤 6:获取订阅通知 服务端

订阅关系生效后,Antom 会为您发送以下通知:

首期订阅通知

Antom 将通过 HTTPS 向您在接口或 Antom Dashboard 中配置的 Webhook 推送以下事件通知:

通知类型

接口

请求报文信息

订阅状态通知

订阅关系生效后,Antom 会使用 notifySubscription口将订阅结果通知发送给您。

  • 请求报文 subscriptionNotificationType 的值为 CREATE 时,subscriptionStatus 判断订阅关系生效 (ACTIVE) 或失效 (TERMINATED)。
  • 请求报文 subscriptionNotificationType 的值为 TERMINATE 时,订阅关系失效。

当期扣款结果通知

订阅支付成功开通或续期成功后,Antom 会使用 notifyPayment(订阅)接口将本期订阅扣款结果发送给您。

请求报文 notifyType 的值为 PAYMENT_RESULTresult.resultStatus 判断本期扣款成功 (S) 还是失败 (F)。

请按以下步骤获取订阅状态通知:

  1. 设置接收通知的 webhook URL:通过 pay(单笔支付)接口的 subscriptionInfo.subscriptionNotifyUrl 参数设置。以下是两种订阅状态的通知示例:
  • 当 subscriptionNotificationType 的值为 CREATE 时,请根据 subscriptionStatus 的值判断订阅关系:
  • ACTIVE:表示订阅关系生效。
  • TERMINATED:表示订阅关系失效。
copy
{
  "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 时,订阅关系失效。
copy
{
  "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"
}
  1. 异步通知验签。

当您收到 Antom 的异步通知,您需要按照返回收到确认信息的格式返回响应,但无需做加签处理。

您需要按照以下方法对 Antom 发送的通知进行验签:

copy
/**
     * 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();
    }
  1. 响应通知结果。无论订单是否支付成功,每个通知请求均需按以下固定格式响应。否则,Antom 会重新发送异步通知。
copy
{
    "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 天,需要传入以下参数:

  • periodRule.periodCount = 3
  • periodRule.periodType =DAY

2024-09-27T20:20:04+08:00

2024-09-28T20:10:17+08:00

2024-09-25T20:10:17+08:00

设置订阅周期为 1 周,需要传入以下参数:

  • periodRule.periodCount = 1
  • periodRule.periodType =WEEK

2024-10-01T20:10:17+08:00

2024-10-02T20:10:17+08:00

2024-09-25T20:10:17+08:00

设置订阅周期为 1 个月,需要传入以下参数:

  • periodRule.periodCount = 1
  • periodRule.periodType=MONTH

2024-10-24T20:10:17+08:00

2024-10-25T20:10:17+08:00

2024-09-25T20:10:17+08:00

设置订阅周期为 1 年,需要传入以下参数:

  • periodRule.periodCount = 1
  • periodRule.periodType =YEAR

2025-09-24T20:10:17+08:00

2025-09-25T20:10:17+08:00

异步通知通常分为以下场景:

常见场景

首次绑定支付

后续周期扣款

授权支付结果通知

有通知,告知授权结果和 3DS 认证结果。

有通知,告知授权结果和 3DS 认证结果。

请款通知

授权成功则有通知,请款结果为发货依据。

授权成功则有通知,请款结果为发货依据。

订阅状态通知

有通知,告知订阅创建结果。

无通知。

当期扣费结果通知

有通知,告知本期扣费状态、金额和有效期。

有通知,告知每期扣费状态、金额和有效期

以下为各场景异步通知的示例代码:

copy
{
  "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。
问:后续的周期扣款如何跟首期合约关联?

答:针对周期扣款通知,可以根据通知请求中的 subscriptionRequestIdsubscriptionId 与首期合约关联。针对卡支付的周期扣款, Antom 还会额外发送授权结果通知和请款结果通知,这两个通知可以根据 paymentId 关联到周期扣款通知中的 paymentId 并最终关联上首期合约的 subscriptionId

问:针对卡支付,如果首次创建订阅关系时选择由商户自行请款,即字段 paymentFactor.captureMode 的值为 MANUAL,那后续的自动续费会如何处理?

答:后续自动续费均由 Antom 发起,无需商户侧参与发起请款,即均默认 paymentFactor.captureMode 的值为 AUTOMATIC

订阅后操作

订阅试用

Antom 提供订阅试用功能,帮助买家在正式购买订阅计划前,以免费或优惠价格在限定时间内体验产品或服务。详情请参阅订阅试用文档。

终止订阅

终止订阅功能允许买家在不需要继续使用相关服务时,随时取消当前订阅。详情请参阅终止订阅文档。

取消交易 服务端

对于支付成功后的订单,如买家在当天内申请取消订单或退款,您可以通过 Antom 提供的取消交易能力将订单状态取消或解冻。此外,对于尚未完成支付的订单,您也可以直接进行取消,具体的集成方案见取消交易文档。

退款 服务端

对于已支付成功的订单,如您需向买家发起退款,Antom 提供以下两种方式。

  • 由您的运营人员直接在 Antom Dashboard 平台上进行人工退款。
  • 通过 API 方式接入 refund 接口发起退款。

Antom 的退款能力如下:

  • 支持全额退款。
  • 支持部分多次退款,多次退款的总金额需小于等于请款金额。

具体的集成方案见退款文档。

更多内容

测试卡

使用测试银行卡确保您的集成已准备好投入生产。想要了解更多关于下载测试钱包的信息,请参阅测试资源

最佳实践

为了提高集成效率,Antom 为您提供以下最佳实践方案: