Alipay, China's leading third-party online payment solutionAlipay, China's leading third-party online payment solution

代扣(App)

本文介绍了支持应用自动扣款的集成方案。集成后,您可以使用电子钱包、银行卡和银行转账等多种支付方式。

用户体验

登录授权 WAP 页的用户体验

买家从商户应用跳转到 WAP 需要账户和密码来完成授权的钱包页面。

登录授权APP1.webp

注意

  • 如果买家已安装钱包应用,会通过以下方式之一打开钱包应用
    • 通过通用链接或应用链接
      • 通用链接:允许买家在 iOS 系统中智能地跟随链接跳转到您的应用或网站内,请参阅 iOS 通用链接了解更多信息
      • 应用链接:点击后买家会直接跳转到特定应用内的深层链接,请参阅 Android 应用链接 了解更多信息
    • 通过中间页
    • 通过动态链接(仅适用于 GCash)
  • 买家跳转到的钱包终端类型取决于目标钱包的功能以及商户的集成方式 授权咨询 响应中的 authUrl 字段的值会返回三种类型的链接,每种链接会导致不同的钱包跳转体验:
    • 如果 authUrl 是普通的 WAP 地址,买家会跳转到登录授权的 WAP 页面。
    • 如果 authUrl 是 URL scheme(WAP-scheme),买家会先跳转至浏览器页面,然后打开钱包应用。
    • 如果 authUrl 通用链接或应用链接,买家将直接跳转打开钱包应用。

以下表格展示了当商户终端类型为 APP 时,各种钱包提供的体验类型:

支付方式

登录授权 WAP 页面

钱包应用

已安装

未安装

先打开中间页,再打开钱包

通过通用链接或应用链接打开钱包

打开 WAP 页面

GCash

✔️

✔️ 登录授权 WAP 页面

DANA

✔️

AlipayHK

✔️

✔️ 登录授权 WAP 页面

Kakao Pay

✔️

✔️ 钱包应用下载 WAP 页面

Touch'n Go eWallet

✔️

✔️ 登录授权 WAP 页面

TrueMoney

✔️

✔️ 钱包应用下载 WAP 页面

Alipay

✔️

✔️ 钱包应用下载 WAP 页面

NAVER Pay

✔️

Boost

✔️✔️

✔️ 登录授权 WAP 页面

LINE Pay

✔️

✔️ 钱包应用下载 WAP 页面

Maya

✔️

支付流程

在获得买家授权后,您可以直接发起代扣服务,无需为每次支付再次进行授权流程。

在进行自动扣款之前,您需要首先获得买家的授权。一旦买家同意授权,使用授权码获取支付令牌,用于后续的代扣操作。

1.png

集成步骤

在进行代扣之前,需要获得买家的授权。授权成功后,获取授权码为后续代扣请求支付令牌。通过以下集成步骤,可以获取买家授权并进行代扣操作:

  1. 获取并跳转到授权链接
  2. 获取授权结果
  3. 申请支付令牌
  4. 发起代扣
  5. 获取支付结果

步骤 1:获取并跳转到授权链接 服务端

首先您需要获取授权链接,然后将买家跳转到授权链接以完成授权。

获取授权链接

Antom 提供了多种语言的服务器端接口库。以下代码以 Java 为例,您需要安装 Java 6 及更高版本。

安装接口库

GitHub 上查找最新版本。

copy
<dependency>
    <groupId>com.alipay.global.sdk</groupId>
    <artifactId>global-open-sdk-java</artifactId>
    <version>2.0.44</version>
</dependency>

初始化请求实例

copy
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

在此场景中,该字段设置为 AGREEMENT_PAY

terminalType

表示商户端所在的终端类型。

以下为调用 授权咨询 接口以获取授权链接的代码示例

copy
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.APP);
    alipayAuthConsultRequest.setOsType(OsType.ANDROID);

    // 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
    }
}

以下为请求报文的代码示例:

copy
{
    "authRedirectUrl": "http://www.yourRedirectUrl.com",
    "authState": "21d6d7f5-5f04-4dbc-8741-3e8a02290d73",
    "customerBelongsTo": "GCASH",
    "osType": "ANDROID",
    "scopes": [
        "AGREEMENT_PAY"
    ],
    "terminalType": "APP"
}

接收咨询响应

响应中返回以下参数:

  • normalUrl
  • schemeUrl
  • applinkUrl
  • appIdentifier

以下为响应的代码示例:

copy
{
    "appIdentifier": "com.iap.linker_portal",
    "applinkUrl": "https://g.alipayplus.com/page/aplus-linker/acwallet/authorization.html?acqSiteId=1022188***",
    "normalUrl": "https://g.alipayplus.com/page/aplus-linker/acwallet/authorization.html?acqSiteId=1022***",
    "result": {
        "resultCode": "SUCCESS",
        "resultMessage": "success.",
        "resultStatus": "S"
    },
    "schemeUrl": "alipayconnect://platformapi/signcontract/?source=AlipayConnect&needCallback=false***"
}

常见问题

问:如何设置 terminalType

答:terminalType 的有效值为:

  • 如果买家在 PC 端发起交易,需要将 terminalType 指定为 WEB
  • 如果买家在移动浏览器上发起交易,需要将 terminalType 指定为 WAP。添加 osType 参数,并根据买家的手机填写相应的系统参数 ANDROIDIOS
  • 如果买家在应用内发起交易,需要将 terminalType 指定为 APP

问:请求参数的值可以使用中文字符吗?

答:为了避免某些支付方式的不兼容情况,请求中的字段请勿使用中文字符。

问:如何设置接收支付通知的链接?

答:Antom 通过 支付通知 接口发送支付结果,您可以在 Antom Dashboard 中提供接收通知的地址。

跳转到授权链接

当您的服务器获取到 normalUrl 后,将该链接传给客户端。您的前端页面会执行将买家跳转到 normalUrl 指定的地址的操作。授权过程可能导致授权成功或失败,每种情况下的跳转地址都不同:

  • 授权成功授权成功后,买家跳转到由 authRedirectUrlauthCode authState 的值重构链接指向的页面。
  • 授权失败
    • 如果买家离开授权页面,某些支付方式允许买家跳转到商户页面,商户页面的地址由 authRedirectUrl 的值指定。
    • 如果买家因授权超时或失败,未能从支付方式应用跳转到商户页面,建议引导买家重新启动授权流程。通常,如果买家在 15 分钟后仍未能从支付方式应用跳转到商户页面,授权将失败。

注意授权链接只能使用一次,如果买家授权失败,您需要重新调用 授权咨询 接口并提供一个新的 authState 值。

步骤 2:获取授权结果 服务端

买家完成授权后,您可以通过以下方式之一获取 authCode

  • 从支付方式返回的重构链接中获取授权码。
  • Antom 发送的异步授权通知中获取授权码。

从重构链接获取 authCode

在完成授权流程后,买家会跳转到支付方式返回的重构链接。此链接由以下三个部分组成:

  • 您在 授权咨询 接口中指定的 authRedirectUrl 参数的值。
  • 该支付方式返回的 authCode
  • 您在 授权咨询 接口中指定的 authState 参数的值。

以下是重构链接的示例:

copy
https://www.alipay.com/?authCode=d2f60253-ecdc-e9bc-27d1-566970191040&authState=663A8FA9-D836-48EE-8AA1-1FF682989DC7

您可以通过重构链接获取 authCode 值。但在使用 authCode 之前,需要检查重构链接中 authState 的值是否与 授权咨询 接口中指定的 authState 参数值一致。如果不一致,重构链接中的 authCode 不可用,因为可能存在攻击等恶意事件导致跳转过程中的链接不安全。

从授权通知中获取 authCode

由于网络问题或其他原因,您可能无法获取重构链接。此时,您可以按照以下步骤从Antom 发送的授权通知中获取 authCode

  1. 配置接收异步授权通知的地址,请参阅通知了解更多详情
  2. 当买家同意授权后,您将收到 Antom 发送的授权成功通知
  3. 收到异步通知后,按照要求所示的格式向 Antom 发送响应。否则,Antom 会重新发送异步通知。

注意您可以通过重构链接或授权通知获取 authCode。如果您收到了多个 authCodes,请使用最先收到的 authCode在申请支付令牌时不要重复使用相同的 authCode 值。

以下是通知请求的代码示例:

copy
{
    "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

传入固定值 AUTHORIZATION_CODE

customerBelongsTo

指定您请求授权的目标支付方式。

authCode

传入您获取到的 authcode 值。

以下是申请支付令牌的代码示例:

copy
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
    }
}

以下是申请支付令牌的请求报文示例

copy
{
  "authCode": "663A8FA9D83648EE8AA11FF68298XXXX",
  "customerBelongsTo": "GCASH",
  "grantType": "AUTHORIZATION_CODE"
}

在响应中,您将收到以下关键参数:

  • accessToken:支付令牌。

注意

  • 由于历史原因,以上关键参数适用于新商户,同时请参阅下表了解更多令牌有效期信息。若之前已经使用了accessTokenExpiryTimerefreshToken 和 refreshTokenExpiryTime 字段,您可以继续维持原有逻辑。
  • 如果调用接口后未收到响应,建议使用相同的参数和值再次发起请求。
  • 如果调用接口后收到了响应,但响应中未返回支付令牌(accessToken),请采取以下措施:
    • 如果 result.resultStatus 的值为 U ,请使用相同的参数和值再次发起请求。
    • 如果 result.resultStatus 的值为 F,根据 result.resultCode 提供的提示解决相关问题。如果需要再次调用 申请支付令牌 接口获取支付令牌,由于 authCode 只能使用一次,需要从步骤 1 重新开始,获取授权链接,并跳转到授权链接。获取新的 authCode 后,再次调用 申请支付令牌 接口。
  • 如果需要向买家显示授权账户,使用在 申请支付令牌 接口中返回的 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

在此场景中,此字段设置为 CASHIER_PAYMENT

paymentRequestId

由商户生成的专属 ID,每次发起支付时都会创建新 ID。

paymentAmount

支付金额需设置为单一币种的最小单位,如 CNY 为分,KRW 为韩元。

paymentMethod

支付方式枚举值。

paymentMethod.paymentMethodId

调用 申请支付令牌 接口获取的 accessToken 的值

paymentRedirectUrl

商户端的支付结果页面需要根据服务器端的结果来显示,并非固定是成功页面。

paymentNotifyUrl

支付结果通知地址可以通过接口传输,或者在平台中设置固定值。

settlementStrategy

此支付的结算货币。如果业务已签署多个结算货币,需要在接口中指定。

order

包括商户订单金额、订单号、订单描述等信息的订单详情

env

买家发起交易的终端类型。例如,交易从商户 PC 网站发起,值为 env.terminalType = WEB

上述参数并不全面,请参阅 支付(代扣) 接口以获取完整参数列表以及特定支付方式的额外要求

以下为发起代扣的代码示例

copy
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.APP).osType(OsType.ANDROID).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
    }
}

以下是请求报文的代码示例:

copy
{
    "env": {
        "clientIp": "114.121.121.01",
        "osType": "ANDROID",
        "terminalType": "APP"
    },
    "order": {
        "buyer": {
            "referenceBuyerId": "yourBuyerId"
        },
        "orderAmount": {
            "currency": "SGD",
            "value": "550000"
        },
        "orderDescription": "antom api testing order",
        "referenceOrderId": "334b8883-5a7f-4ee4-a624-5a8b8e01cc00"
    },
    "paymentAmount": {
        "currency": "SGD",
        "value": "550000"
    },
    "paymentMethod": {
        "paymentMethodId": "2828XXX77801726307481000Iba1Pm20IU171000179",
        "paymentMethodType": "GCASH"
    },
    "paymentNotifyUrl": "http://www.yourNotifyUrl.com",
    "paymentRequestId": "8573febe-f65a-4e82-95f3-261e5ebc20bd",
    "productCode": "AGREEMENT_PAYMENT"
}

常见问题

问:如何设置 terminalType

答:terminalType 的有效值为:

  • 如果买家在 PC 端发起交易,需要将 terminalType 指定为 WEB
  • 如果买家在移动浏览器上发起交易,需要将 terminalType 指定为 WAP。添加 osType 参数,并根据买家的手机操作系统填写相应的参数 ANDROIDIOS
  • 如果买家在应用内发起交易,需要将 terminalType 指定为 APP

问:请求参数的值可以使用中文字符吗?

答:为了避免某些支付方式的不兼容情况,请勿在请求字段中使用中文字符。

问:如何设置接收支付通知的链接?

答: 支付会话创建(收银台)口中指定 paymentNotifyUrl 以接收支付结果(支付通知的异步通知,或者在 Antom Dashboard 中配置接收链接。如果请求和 Antom Dashboard 都指定了链接,则请求中指定的值优先。

步骤 5:获取支付结果

买家完成支付或支付超时后,您可以通过以下方式之一获取相应的支付结果:

  • 接收来自Antom 的异步通知
  • 主动查询支付结果

接收异步通知

完成支付或支付失败时,Antom 会通过 支付(代扣)接口中的 paymentNotifyUrl 参数指定的地址发送异步通知(支付通知),您也可以在 Antom Dashboard 中配置该地址。

以下是通知的代码示例

copy
{
    "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"
    }
}

关于如何验证通知的签名并作出响应,请参阅签名与验签

以下是通知响应的代码示例

copy
{
  "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。
  • paymentIdAntom 生成的支付订单 ID,用于退款和对账。
  • paymentAmount:表示支付金额。

查询支付结果

发起 支付结果查询 请求,并传入以下参数:

字段名称

是否为必填字段?

描述

paymentRequestId

商户生成的支付请求 ID。

以下为调用接口的代码示例:

copy
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
    }
}

以下为接口响应的代码示例:

copy
{
    "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 来判断订单状态:
    • SUCCESSFAIL 表示最终结果。
    • PROCESSING 表示处理中。
  • paymentAmount表示支付的金额。

最佳实践

遵循以下最佳实践以提高集成效率。

配置授权链接

在 App 场景中,授权咨询 接口的响应中不同的支付方式可能会返回以下三个链接中的一个或全部:

  • normalUrl
  • applinkUrl
  • schemeUrl

注意:对于移动应用支付,除了上述提到的重定向链接参数外,授权咨询 接口的响应还包含 appIdentifier 参数。这个参数是对应支付方式应用的包名,用于判断是否已安装应用支付。它也可用于避免在 Android 上出现消歧对话框。

选择链接

如果 授权咨询 接口响应只包含三个链接中的一个,直接将收到的链接用作跳转链接。如果收到多个链接,我们建议您根据以下说明选择链接:

  • 对于 iOS 系统:优先使用 applinkUrl,然后是 schemeUrl,最后是 normalUrl
  • 对于 Android 系统:优先使用 schemeUrl,然后是 applinkUrl,最后是 normalUrl

打开链接

对于 Android 和 iOS,您可以使用以下方法打开授权链接:

iOS

Android

  • 应用外跳转:调用 iOS 方法打开授权链接 。
  • 应用内跳转:
    • 使用 WKWebView
    • 使用 WKWebView 和 JSBridge
    • 使用 SafariViewController
  • 根据支付方式应用是否已安装,选择在应用外或应用内打开授权链接。
  • 应用外跳转:调用 Android 方法打开授权链接。
  • 应用内跳转:
    • 使用 WKWebView
    • 使用 WKWebView 和 JavascriptInterface
    • 使用自定义标签插件
  • 根据支付方式应用是否已安装,选择在应用外或应用内打开授权链接。

每种跳转方法的优缺点

每种跳转方法都有其优点和缺点,建议您根据操作系统和集成场景选择最适合的跳转方法。

跳转方法

描述

应用外跳转:调用 Android/iOS 方法打开授权链接。

Antom 推荐使用此方法。

  • 优点:集成成本低。一次性集成适用于所有支付方式。
  • 缺点:支付流程不完全在您的客户端内进行。

应用内跳转

  • WKWebView
  • WebView

WKWebView/WebView 是内置控件,可以在应用中嵌入一个网络浏览器,您可以使用它来加载网站链接。

  • 优点:可以为买家定制更好的支付体验。在应用内跳转可以实现无感支付体验,这意味着买家不会离开您的应用,在应用内完成后续支付流程。
  • 缺点:需要更多的开发和测试工作。
  • WKWebView+JSBridge
  • WebView+JavascriptInterface

基于 WKWebView/WebView 的升级解决方案。使用 JSBridge/JavascriptInterface 后,可以直接在 WebView 中调用 Android/iOS 方法来打开授权链接。

  • 优点: 相比 WKWebView/WebView,此方法的性能、可扩展性和安全性有所提升,可以为买家提供定制化的支付体验。
  • 缺点:需要更多的开发和测试工作。
  • SafariViewController
  • 自定义标签

由 iOS/Android 提供,在应用内打开授权链接。 相比 WebView,此方法有更全面的功能。

  • 优点:可以与系统浏览器集成,与系统浏览器共享如 cookie 这样的能力,集成相对简单。
  • 缺点:不支持自定义支付体验。

根据支付方式应用是否安装,选择在应用外或应用内打开授权链接。

根据支付方式应用是否安装,提供不同的跳转方法。

  • 优点:为买家提供友好的支付体验。
  • 缺点:需要更多的开发和测试工作。

代码示例(iOS)

方法1:调用 iOS 方法打开授权链接

以下代码示例展示了如何使用 iOS 系统中的 iOS 方法将买家从您的应用跳转到支付方式应用。

copy
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url] options:@{} completionHandler:nil];
}else{ 
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url]];
}
方法2:在应用内打开授权链接

使用以下链接类型在您的应用中打开授权链接

  • normalUrl
  • applinkUrl

注意:某些支付方式不支持在移动网站上进行授权。打开 normalUrl 会触发通过跳转调用相应的支付方式应用,而 WKWebView 会拦截跳转。您需要使用 WKWebView 的 decidePolicyForNavigationAction 方法,并调用 iOS 方法来打开授权链接,以实现从您的 WKWebView 到支付方式应用的跳转

使用 WKWebView

以下代码示例展示了如何在 iOS 应用中使用 WKWebView 加载支付方式页面:

copy
// Initialize webview configuration
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
// Initialize webView 
WKWebView *webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:configuration];
webView.navigationDelegate = self;
//Load the payment method URL
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:Url]]];
[self.view addSubview:self.webView];

以下代码示例展示了如何使用 WKWebView 的 decidePolicyForNavigationAction 方法实现从 WKWebView 到支付方式应用的跳转:

copy
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // WKWebView intercepts the redirection by default. Enable the redirection in the following ways.
    // Operations such as opening Safari
    NSURL *url = navigationAction.request.URL;
    NSString *absoluteString = url.absoluteString;
    NSString *scheme = url.baseURL.scheme;

    DLog(@"navigationAction.request.URL.absoluteString=====> %@", absoluteString);
    if ([absoluteString isEqualToString:@"about:blank"]) {
        decisionHandler(WKNavigationActionPolicyCancel);
    }
    if (!([absoluteString containsString:@"https://"] || [absoluteString containsString:@"http://"])) {
        if ([absoluteString containsString:@"://"]) {
            NSString *urlHeader =  [absoluteString componentsSeparatedByString:@"://"][0];
            [MBProgressHUD showAutoMessage:[NSString stringWithFormat:@"Scheme\n%@://", urlHeader] toView:self.view];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self openAppWithUrlStr:navigationAction.request.URL];
                decisionHandler(WKNavigationActionPolicyAllow);
            });
        }
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}
使用 WKWebView 和 JSBridge

使用 WKWebView 和 JSBridge 可以优化买家的支付体验,但需要较强的开发能力。将使用 JSBridge 与通过 WKWebView 打开链接相结合,有利于项目的维护和扩展性,同时提升性能和安全性。JSBridge 使得网页与本地应用之间可以交互,允许 WAP 页面直接调用应用的原生方法。

以下为在 WKWebView 中使用 JSBridge 的代码示例

copy
[self.wkWebView evaluateJavaScript:bridge completionHandler:nil];
使用 SFSafariViewController

SFSafariViewController 支持打开和渲染 normalUrlapplinkUrlschemeUrl 。相比于 WebView,它具有更全面的功能和更简单的集成。关于如何使用 SFSafariViewController 打开授权链接,请参阅 SFSafariViewController 用户指南。

以下为使用 SFSafariViewController 的代码示例

copy
SFSafariViewController *safariVC = [[SFSafariViewController alloc] initWithURL:Url entersReaderIfAvailable:NO];
safariVC.delegate = self;
[self.navigationController presentViewController:safariVC animated:YES completion:nil];
方法3:根据支付方式应用是否已安装处理跳转

首先需要判断买家是否安装了支付方式应用,然后根据情况处理授权链接的打开方式。请参考以下步骤:

  1. LSApplicationQueriesSchemes 添加到 info.plist 的配置中。请注意,info.plist 中的方案数量有限制。
  2. 使用 canOpenURL 方法判断是否已安装支付方式应用。请参考以下代码:
copy
NSString *walletSchemeUrl = @"gcash://";

if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:walletSchemeUrl]]){
    // The payment method app is installed. Open the app directly.
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url] options:@{} completionHandler:nil];
    
    return YES;
} else {
    // The payment method app is not installed. We recommend that you load the mobile website in WebView.
    SFSafariViewController *safariVC = [[SFSafariViewController alloc] initWithURL:Url entersReaderIfAvailable:NO];
    safariVC.delegate = self;
    [self.navigationController presentViewController:safariVC animated:YES completion:nil];
    
    return NO;
}

代码示例(Android)

方法1:调用 Android 方法打开授权链接

以下代码示例展示了如何使用 Android 方法打开授权链接,实现从您的应用到支付方式应用的跳转。

copy
try {
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Url));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
    // use the startActivity function to redirect to the wallet app 
    startActivity(intent);
} catch (Exception e) {
    e.printStackTrace();
}

注意:使用此方法可能会出现消歧的对话框。

什么是消歧对话框

Android App Link 在安装应用时会向谷歌服务器请求验证。如果验证失败,Android App Link 将无效,买家尝试通过此类链接打开应用时会出现一个消歧对话框。此时,买家需要手动选择如何打开链接。

此图显示了买家点击 Android App Link 时可能出现的消歧对话框示例。买家需要手动选择如何打开链接:使用谷歌地图还是谷歌浏览器。

image.png

避免出现消歧对话框

当出现消歧对话框时,买家需要手动选择打开应用的方式,这可能影响支付成功率。因此,您需要采取以下措施来防止消歧对话框的出现:

  • 如果买家已安装支付方式应用,指定支付方式的安装包名称并打开支付方式的链接。
  • 如果买家未安装支付方式应用,使用以下代码示例打开链接。
copy
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(applinkUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
//Specify the package name of the payment method
intent.setPackage(walletPackageName);
PackageManager packageManager = MainActivity.this.getPackageManager();
//Check whether there is an app that supports opening this link.
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
if (activities.size() <= 0) {
   //The payment method app is not installed. Initialize the Intent object. This link will be opened by the default Android method.
   intent = new Intent(Intent.ACTION_VIEW, Uri.parse(applinkUrl));
   intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     startActivity(intent);
} else {
  //The payment method app is installed. Specify the package name of the payment method and open the payment method URL. The disambiguation dialog box will not appear.
     startActivity(intent);
}

注意:一些支付方式的 packageName 值如下所示:

  • AlipayHK: hk.alipay.wallet
  • GCash: com.globe.gcash.android
  • TrueMoney: th.co.truemoney.wallet
  • Kakao Pay: com.kakao.talk
  • Touch'n Go eWallet: my.com.tngdigital.ewallet
  • DANA: id.dana
方法2:在应用内打开授权链接

在您的应用程序中打开授权链接,可以使用 WebView 加载 normalUrlapplinkUrl 作为移动网站:

  • normalUrl
  • applinkUrl

注意:某些支付方式不支持在移动网站上进行授权。打开 normalUrl 会通过跳转调用相应的支付方式应用,而 WKWebView 会拦截跳转。您需要使用 WKWebView 的 shouldOverrideUrlLoading 方法并调用 Android 方法来打开授权链接,以实现从您的 WKWebView 到支付方式应用的跳转

使用 WebView

以下代码示例展示了如何在 WebView 中加载支付方式页面。

copy
WebView webView = findViewById(R.id.webview);
//Set webview
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        view.loadUrl(request.getUrl().toString());
        return super.shouldOverrideUrlLoading(view, request);
    }});

WebSettings webSettings = webView.getSettings();
//Enable javascript
webSettings.setJavaScriptEnabled(true);
//Enable scaling
webSettings.setSupportZoom(true);
//Enable scaling controls (buttons)
webSettings.setBuiltInZoomControls(true);
//Webview has two cache modes. Cache is not used here.
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
//Allow JavaScript to open a new tab (false by default)
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//Allow JavaScript to load local storage
webSettings.setDomStorageEnabled(true);
//WAP cache size (settings are not needed)
//webSettings.setAppCacheMaxSize(1024 * 1024 * 8);
//WAP cache path
String absolutePath = getApplicationContext().getCacheDir().getAbsolutePath();
//WAP cache size
webSettings.setAppCachePath(absolutePath);
//Set whether to allow WebView to access files (true by default)
webSettings.setAllowFileAccess(true);
//Allow WAP cache to be saved
webSettings.setAppCacheEnabled(true);
//In preview mode, if the page width exceeds the WebView display, scale down the page to fit WebView (false by default)
webSettings.setLoadWithOverviewMode(true);
//Support the viewport HTML meta tag
webSettings.setUseWideViewPort(true);
//Load the payment method URL
webView.loadUrl(Url);

为 WebView 设置 WebViewClient,使用 WebViewClient.shouldOverrideUrlLoading 方法拦截链接,并调用相应的 Android 方法。请参考以下代码示例:

copy
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    if (request.getUrl().toString().startsWith("http")) {
        view.loadUrl(request.getUrl().toString());
        return super.shouldOverrideUrlLoading(view, request);
    } else {
        view.onPause();
        view.stopLoading();
        try {
          Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
          intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
          view.getContext().startActivity(intent);
        } catch (URISyntaxException e) {
          //Alert that this scheme is not supported
        }
        return false;
    }
}

注意:如果您的应用有可跳转的应用白名单,请正确配置白名单,并顾及与旧版本的兼容性问题,减少应用的发布次数。

使用 WebView 和 JavascriptInterface

这种方法将优化买家的支付体验,但需要较强的研发能力。我们建议在 WevView 中使用 JavascriptInterface。使用JavascriptInterface 可以促进 Android 应用和 WebView 之间的交互,允许移动网页直接调用应用的原生方法,使您的应用更加灵活和强大,有利于后续项目的维护和升级。以下代码示例展示了如何在 WebView 中使用JavascriptInterface。

  1. 在 WebView 中声明 JavascriptInterface
copy
@JavascriptInterface
    public void openActivity(String url) {
        //Add the code if needed
        try {
        Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    } catch (Exception e) {
        Toast.makeText(context, "App not installed", Toast.LENGTH_SHORT).show();
    }
}
  1. 在 WebView 中添加 JavaScriptInterface
copy
mWebView.addJavascriptInterface(this, "bridge");
  1. 使用以下代码打开授权链接
copy
window.bridge.openActivity("Url");
使用自定义标签

自定义标签支持打开并渲染 normalUrlapplinkUrlschemeUrl 。相比于 WebView,它具有更全面的功能和更简单的集成。以下代码示例展示了如何使用自定义标签。

  1. build.gradle 文件中添加自定义标签。
copy
dependencies {
    ...
    implementation "androidx.browser:browser:1.4.0"
}
  1. 在您的应用中使用自定义标签打开 Chrome 标签页。
copy
// Use CustomTabsIntent.Builder to configure CustomTabsIntent.
// Use CustomTabsIntent.Builder.build() to create CustomTabsIntent
// Use CustomTabsIntent.launchUrl() to launch the URL

CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(this, Uri.parse(url));

关于如何使用 CustomTabs 打开链接的更多信息,请参阅自定义标签用户指南

方法3:根据支付方式应用是否已安装处理跳转

您需要首先判断买家是否安装了支付方式应用,然后根据情况处理授权链接的打开方式。请按照以下步骤操作:

  1. 为了遵循 Android 系统的隐私和安全策略,需要在 AndroidManifest.xml 文件中添加 <queries> 标签,以确保授权链接能够调起相应的支付方式应用。需要在 <queries> 标签中配置支付方式应用的安装包名称。您可以通过 Antom 返回的 支付(收银台)接口相应中的 appIdentifier 参数获取相应值,或联系 Antom 技术支持获取。
copy
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.llw.scandemo">
  
  ...
  <queries>
    <package android:name="th.co.truemoney.wallet" />
    <package android:name="com.eg.android.AlipayGphone" />
    <package android:name="my.com.tngdigital.ewallet" />
    ...
  </queries>
  ...
  
</manifest>
  1. 判断是否已安装支付方式应用,并调起授权页面:
copy
String Url = "applinkUrl/schemeUrl";
try {
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Url));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setPackage("my.com.tngdigital.ewallet");
    // Determine whether the payment method app is installed
    PackageManager packageManager = MainActivity.this.getPackageManager();
    List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
    
    if (activities.size() <= 0) {
        // The payment method app is not installed. We recommend that you open the mobile website URL in WebView.

        intent = new Intent(MainActivity.this, WebviewActivity.class);
        intent.setData(Uri.parse(Url));
        startActivity(intent);
    } else {
        
        // The payment method app is installed. Open the payment method app directly.
        startActivity(intent);
    }
} catch (Exception e) {
    // Handle exceptions
    e.printStackTrace();
}