﻿# 前置组件 - ApplePay

## 1. 接入前步骤
::: tip  
ApplePay证书统一由PayerMax维护，商户仅需下载ApplePay认证文件保存在指定位置即可。
:::
1. 联系PayerMax技术支持，提供您的正式环境和测试环境的域名，以完成Apple的域名认证要求。特别注意，在测试环境中，`127.0.0.1`、`局域网IP`、`localhost`都无法拉起ApplePay，需要放在带有SSL证书的 https 域名下。

2. 下载ApplePay认证文件，并放置在指定路径`https://[待注册域名]/.well-known/apple-developer-merchantid-domain-association`。

>  - 测试环境认证文件：[apple-developer-merchantid-domain-association](https://img-cdn-sg.payermax.com/public/20250703-93d02b05-fc2a-4569-b8a0-65887e5453a6apple-developer-merchantid-domain-association)；
>  - 生产环境认证文件：[apple-developer-merchantid-domain-association](https://img-cdn-sg.payermax.com/public/20250730-0a4ca772-d0fa-49c3-82d1-ced08252b290apple-developer-merchantid-domain-association).

3. 商户配置好证书后，通知PayerMax验证域名证书，PayerMax将在Apple开发者后台操作域名证书验证。如果验证失败，可能的原因如下：

>  - 证书路径配置错误；请确保在公网上可以直接访问到`https://[待注册域名]/.well-known/apple-developer-merchantid-domain-association`

>  - 您的防火墙拦截了苹果检查服务，请配置[链接页面](https://developer.apple.com/documentation/applepayontheweb/setting-up-your-server)域名至服务器白名单；

>  - 可能已经配置过其他MerchantID的证书，此时，可使用新域名，或在原开发者账号上删除对应域名证书。

## 2. 接口介绍

### 2.1 接口列表

| 关联交互时序                       | 调用方向             | 接口类型   | 接口PATH                                                                                                                                                                      |
| ---------------------------------- | -------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 3.1 获取前置组件初始化信息         | `商户` -> `PayerMax` | `后端接口` | [/applyDropinSession](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-applyDropinSession/post.html)          |
| 3.3 创建支付，调用前置组件下单接口 | `商户` -> `PayerMax` | `后端接口` | [/orderAndPay](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderAndPay(for-drop-dont-copy-me)/post.html) |
| 3.4.1 支付结果异步通知             | `PayerMax` -> `商户` | `后端接口` | [/collectResultNotifyUrl](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/collectResultNotifyUrl/post.html)                            |
| 3.4.2 查询支付交易                 | `商户` -> `PayerMax` | `后端接口` | [/orderQuery](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderQuery/post.html)                          |

### 2.2 环境信息

- **测试环境**：https:// `pay-gate-uat.payermax.com`/aggregate-pay/api/gateway/ `<接口PATH>`

- **集成环境**：https:// `pay-gate.payermax.com`/aggregate-pay/api/gateway/ `<接口PATH>`

### 2.3 请求header

``` js
"headers": {
        "Accept": "application/json",
        "sign": "请参考签名规则：https://docs-v2.payermax.com/202606-version/developer/config-settings.html",//必须
        "Content-Type": "application/json"
}
```
## 3.开始集成
### 3.1 获取前置组件初始化信息

商户服务端通过[/applyDropinSession API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-applyDropinSession/post.html) 接口，发起HTTP POST请求，获取前置组件初始化所需的客户端令牌`clientKey`和会话令牌`sessionKey`。

+ [/applyDropinSession API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-applyDropinSession/post.html) 接口请求示例：

``` js
{
        "version": "1.5",
        "keyVersion": "1",
        "requestTime": "2025-05-14T16:30:27.174+08:00",
        "appId": "test516e8ab74578be8eecd8c4803fbe",
        "merchantNo": "TEST010117960578",
        "data": {
            "country": "MY", # 收单国家
            "currency": "MYR", # 订单币种
            "totalAmount":"50", # 订单金额
            "userId": "20220622_00086", # 用户ID，须保持唯一
            "componentList":["APPLEPAY","CARD"] # 指定本次订单支付可用的支付方式
        }
}
```
可以通过请求参数`componentList`指定本次订单实际可用的支付方式类型，当前仅支持`CARD`、`APPLEPAY`、`GOOGLEPAY`。
+ [/applyDropinSession API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-applyDropinSession/post.html) 接口响应示例：

``` json
{
  "msg": "success",
  "code": "APPLY_SUCCESS", 
  "data": {
    "sessionKey": "bf2c47b085e24c299e45dd56fd751a70",
    "clientKey": "bbd8d2639a7c4dfd8df7d005294390df" 
    }
}
```

### 3.2 渲染前置组件

1. 在相关 HTML 页面上引入 CDN 包。

``` html

```

2. 通过过`div`标签，在商户页面嵌入一个`ApplePay`按钮待展示区域。

``` html

```

3. 初始化 PayerMax Frames。
> 服务端orderAndPay接口响应成功或者失败，一定要告诉前端，前端调用applepay.emit('paySuccess'/'payFail')，ApplePay的弹窗才会消失。

```diff js
var applepay = PMdropin.create('applepay', {
      // 填入你在服务端获取到的 clientKey
      clientKey: clientKey,
      // 填入你在服务端获取到的 sessionKey
      sessionKey: sessionKey,
      theme: 'light',
      // 开启沙盒模式联调
      sandbox: true, //true
    });
    
// 将组件挂载到 dom 节点
applepay.mount('#applepay');
applepay.on('ready', (res) => console.log('[merchant][ready]:', res))
    
applepay.on('payButtonClick', (res) => {
   // 将组件设置为不可编辑状态
  applepay.emit('setDisabled', true);
  // 发起组件校验，并返回卡标识
  applepay.emit('canMakePayment')
    .then(function(response) {
      // 解除不可编辑状态
      applepay.emit('setDisabled', false);
    
      // 成功获取到 response
      if (response.code === 'APPLY_SUCCESS') {
        // 获取卡标识 paymentToken 
        var paymentToken = response.data.paymentToken;
        console.log("paymentToken:"+paymentToken);
        // 进行后续支付操作,商户请求自己服务端，自己用params构造请求参数
        //商户服务端调用支付接口orderAndPay，发起支付
        if(paymentToken){
              _postapi('orderAndPay',params).then(res =>{
                const code = (res || {}).code
                if (code == 'APPLY_SUCCESS') {    
+                 //服务端接收到支付成功回调后 ，需要将结果返回给前端，前端调用
+                 //applepay.emit('paySuccess')，ApplePay弹窗会消失，并提示支付成功
                  applepay.emit('paySuccess')
                } else {
+                 applepay.emit('payFail') // 弹窗会消失
                }  
        })}
      }
    })
    .catch(function(error) {
      // 解除不可编辑状态
      applepay.emit('setDisabled', false);
    })
})
```

4. 拿到`paymentToken`
当用户在前端确认支付后，前端 canMakePayment 接口会拿到以下信息，用作后端发起支付。

``` json
{
    "msg": "",
    "code": "APPLY_SUCCESS",
    "data": {
        "paymentToken": "CPT771e98494eff41f1a03a715ebab69cc9",
        "cardOrg": "VISA",
        "maskCardNumber": "444433****1111",
        "cardExpirationMonth": "12",
        "cardType": "CREDIT",
        "cardExpirationYear": "26",
        "cardHolderFullName": "Jemy Cheung",
        "agreementAccepted": true,
        "cardBinNo": "444433",
        "cardIssuingCountry": "US"
    }
}
```

### 3.3 创建支付

1. 商户服务端：调用[/orderAndPay API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderAndPay(for-drop-dont-copy-me)/post.html) 接口发起HTTP POST请求，创建支付。

+ [/orderAndPay API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderAndPay(for-drop-dont-copy-me)/post.html) 接口请求示例：

```diff js
{
    "requestTime": "2025-05-28T03:52:42.591-02:00",
    "keyVersion": "1",
    "appId": "tested7c863c439a9e29b4519867965a",
    "version": "1.5",
    "merchantNo": "TEST10116880289",
    "data": {
        "integrate": "Direct_Payment", # 前置组件模式下，指定Direct_Payment
        "totalAmount": 39.99,
        "country": "SA",
        "expireTime": "3600",
        "paymentDetail": {
            # 支付时，通过JS SDK的emit接口的canMakePayment事件响应获取，非空
+            "paymentToken": "TEST12637c2c2d942239d9a2661c4ad14f9", 
            "buyerInfo": {
                "clientIp": "176.16.34.144",
                "userAgent": "Chrome"
            },
            # 支付时，通过JS SDK的create接口的响应获取，非空
+            "sessionKey": "test29632c3643768e3b65ef6a31c9ce" # 前置组件模式下非空
        },
        "frontCallbackUrl": "https://front.your.com/pay/index.html",
        "subject": "xx Game",
        "outTradeNo": "ov1_da78b1f3c2f9443b966347fc89305fc9",
        "notifyUrl": "https://notify.your.com/pay/paymentWebHookPayerMaxServlet",
        "currency": "SAR",
        "userId": "1822613953000446",
        "terminalType": "WEB"
    }
}
```

+ [/orderAndPay API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderAndPay(for-drop-dont-copy-me)/post.htlm) 接口响应示例：

``` json
{
    "msg": "Success.",
    "code": "APPLY_SUCCESS",
    "data": {
        "outTradeNo": "test_da78b1f3c2f9443b966347fc89305fc9",
        "tradeToken": "T2024052805951921811176",
        "status": "SUCCESS"
    }
}
``` 

### 3.4 获取支付结果

[创建支付/orderAndPay API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderAndPay/post.html) 接口响应的`data.status`并非支付终态，因此，商户不应直接使用其更新支付结果。

#### 3.4.1 支付结果通知

请查看[支付结果-支付结果通知](https://docs.payermax.com/doc-center/acquiring/start-integration/related-capabilities-integration/payment-result.html#_3-1-支付结果通知)。

#### 3.4.2 支付结果查询

请查看[支付结果-支付结果查询](https://docs.payermax.com/doc-center/acquiring/start-integration/related-capabilities-integration/payment-result.html#_3-2-支付结果查询)。

## 4. 前端API接口

使用方法 `PMdropin.API`。 

| **API**            | **描述**          | **详情**          |
|---------------------|---------------------|---------------------|
| create | 实例化一个内置组件 |  参阅**4.1 create**      |
|  mount      | 将实例化组件挂载到`div`标签      |  参阅**4.2 mount**     |
| on           | 监听事件            |  参阅**4.3 on**    |
| emit            | 触发事件            |  参阅**4.4 emit**      |

### 4.1 create
用于初始化组件，使用方法 `PMdropin.create(ComponentName, Options)`。

**ComponentName详解**

| **ComponentName**            | **字段类型**          | **描述**          |
|---------------------|---------------------|---------------------|
| applepay | string |  ApplePay组件      |

**Options详解**

| **Options**            | **是否必填**          | **字段类型**          | **描述**          | **默认值**          |
|---------------------|---------------------|---------------------|---------------------|---------------------|
| clientKey | Y |  String      |  客户端公钥      |  -      |
| sessionKey | Y |  String      |  安全访问令牌      |  -      |
| sandbox | N |  Boolean      |  沙盒环境      |  `false`      |
| theme | N |  String      |  主题      |  `light`      |
| payButtonStyle | N |  String      |  按钮样式	      |  -      |

### 4.2 mount
用于挂载初始化组件实例，使用方法`PMdropin.mount(Tag)`。

**Tag详解**

| **Tag**            | **描述**          |
|---------------------|---------------------|
| id | 需要挂载的id元素值，如 `PMdropin.mount('#applepay-frame')`|
| class | 需要挂载的class元素值，如 `PMdropin.mount('.applepay-frame')`|

### 4.3 on
用于监听组件内置响应事件，使用方法`PMdropin.on(Event, CallbackFunction)`。

**Event详解**

| **Tag**            | **描述**          | **返回值**          |
|---------------------|---------------------|---------------------|
| ready | 组件加载完成时触发 |`null` |
| payButtonClick | ApplePay按钮被点击时触发| `null`|

示例：

```js [payButtonClick]
PMdropin.on('payButtonClick', function(event) {
  // achieve paymentToken and orderAndPay
  applePay.emit('setDisabled', true)
  applePay.emit('canMakePayment')
    .then(res => {
      const paymentToken = res?.data?.paymentToken 
      if(paymentToken){
       _postapi('orderAndPay',params).then(res =>{
                    const code = (res || {}).code
                    if (code == 'APPLY_SUCCESS') {    
                      //服务端接收到支付成功回调后 ，需要将结果返回给前端，前端调用
                      //applepay.emit('paySuccess')，ApplePay弹窗会消失，并提示支付成功
                      applepay.emit('paySuccess')
                    } else {
                      applepay.emit('payFail')
                    }
      })}else{ 
        applePay.emit('setDisabled', false)
      }
    })
    .catch(err => {
      applePay.emit('payFail')
      applePay.emit('setDisabled', false) 
    })
    
});
```

### 4.4 emit
用于调用组件内置方法，使用方法`PMdropin.emit(Event, Params)`。

| **Event**            | **Params**          | **描述**          |
|---------------------|---------------------|---------------------|
| canMakePayment | Object |  获取本次支付token      |
| switchTheme | string |  切换主题      |
| setDisabled | Boolean |  设置组件可用状态      |
| setpayButtonStyle | string |  设置按钮样式      |

#### 4.4.1 emit.canMakePayment
检查当前组件状态是否具备发起支付条件，校验通过后返回`paymentToken`。

```js
PMdropin.emit('canMakePayment', params?)
```
`params` 为可选参数。若商户服务端在调用 `/applyDropinSession` 接口时已传入完整支付信息，可直接调用`PMdropin.emit('canMakePayment')`而无需传参。

Options配置：

| **Options** | **是否必填** | **字段类型** | **描述** |
| :--- | :---: | :---: | :--- |
| totalAmount | N | String | 支付金额，格式为纯数字字符串（如 '1.00'）。仅推荐在商户服务端通过 `/applyDropinSession` 接口获取前置组件初始化信息时**未传入金额**的场景下使用，该金额仅用于展示在 Apple Pay 支付弹窗中。 |

**示例：**
```js
PMdropin.emit('canMakePayment', {
  totalAmount: '1.00'
})
```
说明：
> + `totalAmount`仅接受合法的数字字符串（如`'0.01'`、`'99.99'`）。传入非法值时，接口将返回错误码`AMOUNT_INVALID`。
> + 若商户服务端在调用`/applyDropinSession`时已传入金额，则最终提交订单时的金额必须与其保持一致，否则可能导致支付失败或风控拦截。
> + 请确保`canMakePayment`中传入的`totalAmount`与最终提交订单时的金额严格一致，否则可能导致支付失败或风控拦截。

canMakePayment Response：

| **Code码**            | **描述**          | 
|---------------------|---------------------|
| APPLY_SUCCESS | 成功获取 `paymentToken` |  
| UNKNOWN_ISSUE | 异常信息 | 
| AMOUNT_INVALID | 传入金额格式有误 | 
| APPLEPAY_INTERNAL_ERROR | Apple Pay 内部异常 | 

#### 4.4.2 emit.switchTheme
+ 设置按钮主题
+ 类型： `Boolean`
+ 默认：`light`

| **主题名称**            | **主题编码**          | **效果预览**          |
|---------------------|---------------------|---------------------|
| 白色 | light `默认` |  ![](https://img-cdn-sg.payermax.com/public/20240801-ac3e16a0-6aca-4333-8f2b-431df925b8ee.png)      |
| 黑色 | dark |  ![](https://img-cdn-sg.payermax.com/public/20240801-91bfb336-b33d-484d-a142-2ec5a3d8007d.png)      |

#### 4.4.3 emit.setDisabled
+ 设置组件可用状态
+ 类型 `Boolean`
+ 默认：`false`

```js
// 按钮不可用状态
PMdropin.emit('setDisabled', true)

// 按钮可用状态
PMdropin.emit('setDisabled', false)
```

#### 4.4.4 emit.setpayButtonStyle
+ 设置按钮样式
+ 类型： `Boolean`
+ 默认：`false`

```js
// 设置ApplePay按钮宽高、内边距、边框弧度
PMdropin.emit('payButtonStyle', `width:20rem;height:4rem;border-radius: 4rem;padding:1rem 4rem;`)
```
