代扣(Web)
本文介绍了支持桌面浏览器自动扣款的集成方案。集成后,您可以访问各种支付方式,如电子钱包、银行卡和银行转账。
用户体验
二维码扫描的用户体验
在扫码授权模式下,买家跳转到支付方式 Web 页面,通过扫描页面二维码进行授权。如下图所示:
支付流程
在获得买家授权后,您可以直接发起代扣服务,无需为每次支付再次进行授权流程。
在进行自动扣款之前,您需要首先获得买家的授权。一旦买家同意授权,使用授权码获取支付令牌,用于后续的代扣操作。
集成步骤
要获得买家授权并进行代扣,请完成以下集成步骤:
- 获取并跳转到授权链接
- 获取授权结果
- 申请支付令牌
- 发起代扣
- 获取支付结果
步骤 1:获取并跳转到授权链接
您需要先获取授权链接,然后将买家跳转到授权链接以完成授权。对于 Web 集成,授权链接可以从 授权咨询 接口响应中返回的 normalUrl 中获取。
获取授权链接
Antom 提供了多种语言的服务器端接口库。以下代码以 Java 为例,您需要安装 Java 6 及更高版本。
安装接口库
在 GitHub 上查找最新版本。
<dependency>
<groupId>com.alipay.global.sdk</groupId>
<artifactId>global-open-sdk-java</artifactId>
<version>2.0.44</version>
</dependency>
初始化请求实例
import com.alipay.global.api.AlipayClient;
import com.alipay.global.api.DefaultAlipayClient;
import com.alipay.global.api.model.constants.EndPointConstants;
public class Sample {
public static final String CLIENT_ID = "";
public static final String ANTOM_PUBLIC_KEY = "";
public static final String MERCHANT_PRIVATE_KEY = "";
private final static AlipayClient CLIENT = new DefaultAlipayClient(
EndPointConstants.SG, MERCHANT_PRIVATE_KEY, ANTOM_PUBLIC_KEY, CLIENT_ID);
}
调用 咨询 接口
在 授权咨询 请求中传入以下参数:
字段名称 | 是否为必填字段? | 描述 |
authRedirectUrl | 是 | 授权完成后,用于将买家跳转到商户页面的链接。 |
authState | 是 | 识别授权请求而传入的字符串。 |
customerBelongsTo | 是 | 传入请求授权的目标支付方式。 |
scopes | 是 | 在此场景中,该字段设置为 |
terminalType | 是 | 指商户端所在的终端类型。此场景下,字段值为 |
以下代码示例用于调用 授权咨询 接口以获取授权链接:
public static void authorizationConsult() {
AlipayAuthConsultRequest alipayAuthConsultRequest = new AlipayAuthConsultRequest();
// replace with your authState
String authState = UUID.randomUUID().toString();
alipayAuthConsultRequest.setAuthState(authState);
// set the target payment method for which you are requesting authorization
alipayAuthConsultRequest.setCustomerBelongsTo(CustomerBelongsTo.GCASH);
// set the authorization scope
alipayAuthConsultRequest.setScopes(new ScopeType[]{ScopeType.AGREEMENT_PAY});
// set terminalType
alipayAuthConsultRequest.setTerminalType(TerminalType.WEB);
// replace with your authRedirectUrl
alipayAuthConsultRequest.setAuthRedirectUrl("http://www.yourRedirectUrl.com");
// do authorization consult
AlipayAuthConsultResponse alipayAuthConsultResponse;
try {
alipayAuthConsultResponse = CLIENT.execute(alipayAuthConsultRequest);
} catch (AlipayApiException e) {
String errorMsg = e.getMessage();
// handle error condition
}
}
以下为请求报文的代码示例:
{
"authRedirectUrl": "http://www.yourRedirectUrl.com",
"authState": "556c1e32-3723-4b02-88ed-8c46087540ca",
"customerBelongsTo": "GCASH",
"scopes": [
"AGREEMENT_PAY"
],
"terminalType": "WEB"
}
接收咨询响应
授权咨询 接口的响应包含买家需要跳转的授权链接(normalUrl):
{
"authCodeForm": {
"codeDetails": [
{
"codeValue": "https://iexpfront-sea.alipay.com/showQrImage.htm?logoImg=a***",
"displayType": "IMAGE"
},
{
"codeValue": "https://open-sea-global.alipayplus.com/aps/long/vbxsTndnmS",
"displayType": "TEXT"
}
]
},
"normalUrl": "https://g.alipayplus.com/page/aplus-linker/acwallet/authorization.html?acqSiteId=1***",
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
常见问题
问:如何设置 terminalType?
答:terminalType 的有效值如下:
- 如果买家在 PC 端发起交易,需要将 terminalType 指定为
WEB
。- 如果买家在手机浏览器上发起交易,需要将 terminalType 指定为
WAP
。添加 osType 参数,并根据买家的手机填写相应的系统参数ANDROID
或IOS
。- 如果买家在应用内发起交易,需要将 terminalType 指定为
APP
。问:请求字段值可以使用中文字符吗?
答:为了避免特定支付方式的不兼容情况,请求中的字段请勿使用中文字符。
问:如何设置接收支付通知的链接?
答:Antom 通过 支付通知 接口发送支付结果,您可以 Antom Dashboard 中提供接收通知的地址。
问:收到返回的 normalUrl 后应该怎么做?
答:对于在桌面浏览器中进行的交易, Antom 在 授权咨询 响应中返回 normalUrl 参数。 您的服务器需要传入此链接给客户端进行跳转。建议不要缓存返回的 normalUrl,当重新发起授权时,需要获取新的 normalUrl 用于跳转。
跳转到授权链接
当您的服务器获取到 normalUrl 后,将该链接传给客户端。您的前端页面会执行将买家跳转到 normalUrl 指定的地址的操作。授权过程可能导致授权成功或失败,每种情况下的跳转地址都不同:
- 授权成功:授权成功后,买家跳转到由 authRedirectUrl、authCode 和 authState 的值重构的链接指向的一个页面。
- 授权失败:
- 如果买家在未完成授权流程的情况下离开授权页面,某些支付方式允许跳转到 authRedirectUrl 指向的页面。
- 如果授权超时或失败,且买家无法跳转到 authRedirectUrl 页面,建议引导买家重新开始授权流程。如果买家在支付方式应用内未能在 15 分钟内跳转到 authRedirectUrl 页面,授权将失败。
注意:授权链接只能使用一次,如果买家授权失败,您需要重新调用 授权咨询 接口并提供一个新的 authState 值。
步骤 2:获取授权结果
买家完成授权后,您可以通过以下方式之一获取授权码(authCode):
- 从支付方式返回的重构链接中获取 authCode。
- 从 Antom 发送的异步授权通知中获取 authCode。
从重构链接获取 authCode
在完成授权流程后,买家会跳转到支付方式返回的重构链接。此链接由以下三个部分组成:
以下是重构链接的示例:
https://www.alipay.com/?authCode=d2f60253-ecdc-e9bc-27d1-566970191040&authState=663A8FA9-D836-48EE-8AA1-1FF682989DC7
您可以通过重构链接获取 authCode 值。但在使用 authCode 之前,需要检查重构链接中 authState 的值是否与 授权咨询 接口中指定的 authState 参数值一致。如果不一致,重构链接中的 authCode 不可用,因为可能存在攻击等恶意事件导致跳转过程中的链接不安全。
从授权通知获取 authCode
由于网络问题或其他原因,您可能无法获取重构链接。此时,您可以按照以下步骤从Antom 发送的授权通知中获取 authCode:
- 配置接收异步授权通知的地址,请参阅通知了解更多详情 。
- 当买家同意授权后,您将收到 Antom 发送的授权成功通知。
- 收到异步通知后,按照要求所示的格式向 Antom 发送响应。否则,Antom 会重新发送异步通知。
注意:您可以通过重构链接或授权通知获取 authCode。如果您收到了多个 authCodes,请使用最先收到的 authCode,在申请支付令牌时不要重复使用相同的 authCode 值。
以下是通知请求的代码示例:
{
"authorizationNotifyType": "AUTHCODE_CREATED",
"authState": "556c1e32-3723-4b02-88ed-8c46087540ca",
"authCode": "28100113_1631148338197000019ba74",
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success",
"resultStatus": "S"
}
}
关于如何验证通知的签名并做出响应,请参阅签名与验签。
常见问题
问:什么时候会发送通知?
答:买家授权完成后,Antom 会向您发送异步通知。
问:Antom 会重新发送异步通知吗?
答:会。对于以下情况,异步通知将在 24 小时内自动重新发送:
- 因网络原因未收到异步通知。
- 如果您收到来自Antom的异步通知,但您未按照处理通知的示例代码格式做出响应。
通知最多可重新发送 8 次,或直到收到正确的响应以终止发送。发送间隔为:0 分钟,2 分钟,10 分钟,10 分钟,1 小时,2 小时,6 小时和 15 小时。
问:在响应异步通知时,需要添加签名吗?
答:如果收到 Antom 发送的异步通知,您需要按照处理通知的示例代码格式返回响应,但不需要对响应进行签名。
问:在通知中需要使用哪些关键参数?
答:请注意以下关键参数:
- authorizationNotifyType:通知类型。 在此情况下值为
AUTHCODE_CREATED
。- authState:调用 授权咨询 接口时提供的请求 ID。
- authCode:用户完成授权后生成的授权码,使用此授权码来请求支付令牌。
步骤 3:申请支付令牌
在收到授权码(authCode)后一分钟内,调用 申请支付令牌 接口来申请支付令牌(accessToken)。否则,authCode 将过期并失效。只有获得 accessToken,才能实现对买家账户的自动扣款。
调用 申请支付令牌 接口时,请在请求中正确传入以下参数:
字段名称 | 是否为必填字段? | 描述 |
grantType | 是 | 传入固定值为 |
customerBelongsTo | 是 | 传入您请求授权的目标支付方式。 |
authCode | 是 | 传入您收到的 authcode 值。 |
以下是申请支付令牌的代码示例:
public static void applyToken() {
String authCode = "yourAuthCode";
AlipayAuthApplyTokenRequest alipayPayRequest = new AlipayAuthApplyTokenRequest();
// set grant type
alipayPayRequest.setGrantType(GrantType.AUTHORIZATION_CODE);
// set the target payment method for which you are requesting authorization
alipayPayRequest.setCustomerBelongsTo(CustomerBelongsTo.GCASH);
// set auth code
alipayPayRequest.setAuthCode(authCode);
// do apply token
AlipayAuthApplyTokenResponse authApplyTokenResponse;
try {
authApplyTokenResponse = CLIENT.execute(alipayPayRequest);
} catch (AlipayApiException e) {
String errorMsg = e.getMessage();
// handle error condition
}
}
以下是申请支付令牌的请求报文示例:
{
"authCode": "663A8FA9D83648EE8AA11FF68298XXXX",
"customerBelongsTo": "GCASH",
"grantType": "AUTHORIZATION_CODE"
}
在响应中,您将收到以下关键参数:
- accessToken:支付令牌。
注意:
- 由于历史原因,以上关键参数适用于新商户,同时请参阅下表了解更多令牌有效期信息。若之前已经使用了accessTokenExpiryTime、refreshToken 和 refreshTokenExpiryTime 字段,您可以继续维持原有逻辑。
- 如果调用接口后未收到响应,建议使用相同的参数和值再次发起请求。
- 如果调用接口后收到了响应,但响应中未返回支付令牌(accessToken),请采取以下措施:
- 如果需要向买家显示授权账户,使用在 申请支付令牌 接口中返回的 userLoginId 字段的值。该字段返回的值已经经过隐私保护处理,可以直接显示。
关于代扣支持的支付方式,支付令牌有效期如下表所示:
支付方式 | 支付令牌有效期 |
Alipay | 92年 |
Kakao Pay | 100年 |
AlipayHK | 100年 |
GCash | 100年 |
DANA | 100年 |
Touch'n Go eWallet | 100年 |
TrueMoney | 100年 |
PayPay | 100年 |
NAVER Pay | 2年 |
Boost | 100年 |
KrungThai Bank | 76年 |
Siam Commercial Bank | 76年 |
Grabpay SG | 10年 |
Grabpay MY/PH | 10年 |
K PLUS | 100年 |
Maya | 100年 |
LINE Pay | 100年 |
Toss Pay | 14年 |
ZaloPay | 10年 |
步骤 4:发起代扣
在获得买家授权后,您可以直接为买家提供代扣服务,无需他们在每次支付时都进行授权流程。
发起代扣时,请指定以下参数:
字段名称 | 是否为必填字段? | 描述 |
productCode | 是 | 在此场景中,此字段设置为 |
paymentRequestId | 是 | 由商户生成的专属 ID,每次发起支付时都会创建新 ID。 |
paymentAmount | 是 | 支付金额需设置为单一币种的最小单位,如 |
paymentMethod | 是 | 支付方式枚举值。 |
paymentMethod.paymentMethodId | 是 | 调用 申请支付令牌 接口获取的支付令牌(accessToken)的值 |
paymentNotifyUrl | 否 | 支付结果通知地址可以通过接口传输,或者在门户中设置固定值。 |
settlementStrategy | 否 | 此支付订单的结算币种。如果业务已签署多个结算币种,需要在接口中指定。 |
order | 是 | 包括商户订单金额、订单号、订单描述等信息的订单详情。 |
env | 是 | 买家发起交易的终端类型。例如,交易从商户 PC 网站发起,值为 env.terminalType = |
上述参数并不全面,请参阅 支付(代扣)接口以获取完整参数列表以及特定支付方式的额外要求。
以下为发起代扣的示例代码:
public static void pay() {
AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
alipayPayRequest.setProductCode(ProductCodeType.AGREEMENT_PAYMENT);
// replace with your paymentRequestId
String paymentRequestId = UUID.randomUUID().toString();
alipayPayRequest.setPaymentRequestId(paymentRequestId);
// set amount
// you should convert amount unit(in practice, amount should be calculated on your serverside)
Amount amount = Amount.builder().currency("SGD").value("550000").build();
alipayPayRequest.setPaymentAmount(amount);
// set paymentMethod
PaymentMethod paymentMethod = PaymentMethod.builder().paymentMethodType("GCASH").
paymentMethodId("2828XXX77801726307481000Iba1Pm20IU171000179").build();
alipayPayRequest.setPaymentMethod(paymentMethod);
// set buyer info
Buyer buyer = Buyer.builder().referenceBuyerId("yourBuyerId").build();
// replace with your orderId
String orderId = UUID.randomUUID().toString();
// set order Info
Order order = Order.builder().referenceOrderId(orderId).
orderDescription("antom api testing order").orderAmount(amount).buyer(buyer).build();
alipayPayRequest.setOrder(order);
// set env info
Env env = Env.builder().terminalType(TerminalType.WEB).clientIp("114.121.121.01").build();
alipayPayRequest.setEnv(env);
// replace with your notify url
alipayPayRequest.setPaymentNotifyUrl("http://www.yourNotifyUrl.com");
AlipayPayResponse alipayPayResponse;
try {
alipayPayResponse = CLIENT.execute(alipayPayRequest);
} catch (AlipayApiException e) {
String errorMsg = e.getMessage();
// handle error condition
}
}
以下是请求报文的代码示例:
{
"env": {
"clientIp": "114.121.121.01",
"terminalType": "WEB"
},
"order": {
"buyer": {
"referenceBuyerId": "yourBuyerId"
},
"orderAmount": {
"currency": "SGD",
"value": "550000"
},
"orderDescription": "antom api testing order",
"referenceOrderId": "f69cb774-8d47-4da9-bf91-08c656581cdf"
},
"paymentAmount": {
"currency": "SGD",
"value": "550000"
},
"paymentMethod": {
"paymentMethodId": "2828XXX77801726307481000Iba1Pm20IU171000179",
"paymentMethodType": "GCASH"
},
"paymentNotifyUrl": "http://www.yourNotifyUrl.com",
"paymentRequestId": "214c60e7-672d-4ad8-9163-905ad4abe71f",
"productCode": "AGREEMENT_PAYMENT"
}
常见问题
问:如何设置 terminalType?
答:terminalType 的有效值为:
- 如果买家在 PC 端发起交易,需要将 terminalType 指定为
WEB
。- 如果买家在移动浏览器上发起交易,需要将 terminalType 指定为
WAP
。添加 osType 参数,并根据买家的手机操作系统填写相应的参数ANDROID
或IOS
。- 如果买家在应用内发起交易,需要将 terminalType 指定为
APP
。问:请求参数的值可以使用中文字符吗?
答:为了避免特定支付方式的不兼容情况,请勿在请求字段中使用中文字符。
问:如何设置接收支付通知的链接?
答:在 支付会话创建(收银台)接口中指定 paymentNotifyUrl 以接收支付结果(支付通知)的异步通知,或者在 Antom Dashboard 中配置接收链接。如果请求和 Antom Dashboard 都指定了链接,则请求中指定的值优先。
步骤 5:获取支付结果
买家完成支付或支付超时后,可以通过以下方式之一获取相应的支付结果:
- 接收来自Antom的异步通知
- 主动查询支付结果
接收异步通知
完成支付或支付失败时,Antom 会通过 支付(代扣)接口中的 paymentNotifyUrl 参数指定的地址发送异步通知(支付通知),您也可以在 Antom Dashboard 中配置该地址。
以下是通知的代码示例:
{
"actualPaymentAmount": {
"currency": "SGD",
"value": "550000"
},
"notifyType": "PAYMENT_RESULT",
"paymentAmount": {
"currency": "SGD",
"value": "550000"
},
"paymentCreateTime": "2024-09-14T02:59:28-07:00",
"paymentId": "202409141940108001001888H0209958443",
"paymentRequestId": "214c60e7-672d-4ad8-9163-905ad4abe71f",
"paymentResultInfo": {},
"paymentTime": "2024-09-14T02:59:30-07:00",
"pspCustomerInfo": {
"pspCustomerId": "use***@alipay.com",
"pspName": "GCASH"
},
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
关于如何验证通知的签名并作出响应,请参阅签名与验签。
以下是通知响应的代码示例:
{
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "Success"
}
}
常见问题
问:什么时候会发送通知?
答:这取决于支付是否完成:
- 如果支付成功完成,Antom 通常会在 3 到 5 秒内发送异步通知。对于像 OTC 这种类型的支付方式,通知可能会稍有延迟。
- 如果支付未完成,Antom 需要先关闭订单,然后发送异步通知。不同支付方式关闭订单所需的时间会有所不同,通常默认为 14 分钟。
问:Antom 会重新发送异步通知吗?
答:会。对于以下情况,异步通知将在 24 小时内自动重新发送:
- 由于网络原因未收到异步通知。
- 如果收到来自 Antom 的异步通知,但您没有以处理通知的示例代码格式进行响应。
通知可以重发最多 8 次,或者直到收到正确的响应以终止传递。发送间隔如下:0 分钟,2 分钟,10 分钟,10 分钟,1 小时,2 小时,6 小时和15 小时。
问:在响应异步通知时,需要添加签名吗?
答:如果您收到来自 Antom 的异步通知,您需要以处理通知的示例代码格式返回响应,但不需要对响应进行签名。
问:在通知中需要使用哪些关键参数?
答: 请注意以下关键参数:
- result:表示订单的支付结果。
- paymentRequestId:用于咨询、取消和对账的支付请求 ID。
- paymentId:Antom 生成的支付订单 ID,用于退款和对账。
- paymentAmount:表示支付金额。
查询支付结果
发起 支付结果查询 请求,并传入以下参数:
字段名称 | 是否为必填字段? | 描述 |
paymentRequestId | 否 | 由商户生成的支付请求 ID。 |
以下为调用 支付结果查询 接口的代码示例:
public static void inquiryPayment() {
AlipayPayQueryRequest alipayPayQueryRequest = new AlipayPayQueryRequest();
// replace with your paymentRequestId
alipayPayQueryRequest.setPaymentRequestId("yourPaymentRequestId");
AlipayPayQueryResponse alipayPayQueryResponse = null;
try {
alipayPayQueryResponse = CLIENT.execute(alipayPayQueryRequest);
} catch (AlipayApiException e) {
String errorMsg = e.getMessage();
// handle error condition
}
}
以下为 支付结果查询 响应的代码示例:
{
"actualPaymentAmount": {
"currency": "SGD",
"value": "550000"
},
"paymentAmount": {
"currency": "SGD",
"value": "550000"
},
"paymentId": "202409141940108001001888H0209958443",
"paymentRequestId": "214c60e7-672d-4ad8-9163-905ad4abe71f",
"paymentResultCode": "SUCCESS",
"paymentResultMessage": "success",
"paymentStatus": "SUCCESS",
"paymentTime": "2024-09-14T02:59:30-07:00",
"pspCustomerInfo": {
"pspName": "GCASH"
},
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
常见问题
问:应该多久调用一次支付结果查询接口?
答:以 2 秒的间隔持续调用 支付结果查询 接口,直到获取最终的支付结果或收到异步支付结果通知。
问:在通知中需要使用哪些关键参数?
答:请注意以下关键参数:
- result:表示此 支付结果查询 接口调用的结果,需要根据 paymentStatus 来判断订单状态:
SUCCESS
和FAIL
表示最终结果。PROCESSING
表示处理中。
- paymentAmount: 表示支付的金额。
最佳实践
遵循以下最佳实践以提高集成效率。
配置授权链接
在调用 授权咨询 接口后,根据授权过程使用的支付方式,接口响应中可能会返回不同类型的授权链接。关于每种支付方式返回的授权链接的详细信息,请参阅 部分支付方式返回的链接。
对于终端类型为 WEB
的情况,normalUrl 将在 授权咨询 接口的响应中返回。
打开 normalUrl
获取到 normalUrl 值后,您需要使用该 normalUrl 指定的授权链接将买家跳转到授权页面。建议在新标签页中打开链接,引导买家完成授权。打开新页面的代码如下:
if (serverResponse.normalUrl != null) {
window.open(serverResponse.normalUrl, '_blank');
}
使用 normalUrl 渲染的授权页面可能是二维码扫描页或登录页。以下图表展示了这两种情况下的授权流程: