﻿# 纯API - ApplePay

::: tip  
该文档介绍纯API模式下，使用ApplePay的集成步骤。
:::

纯API集成模式下，商户需要自行构建相关的支付页面，如：收银页、支付结果页等；此外，还需要商户进行复杂的证书配置以及加解密处理。因此，该模式需要商户投入更多的研发成本。

## 1. 交互流程

```mermaid
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#e6f0ff',
    'primaryTextColor': '#333',
    'primaryBorderColor': '#5b9bd5',
    'lineColor': '#888',
    'actorMargin': 40,
    'noteBkgColor': '#0056b3',
    'noteTextColor': '#ffffff',
    'noteBorderColor': '#004a99'
  }
}}%%
sequenceDiagram
    participant Client as 商户客户端
    participant MServer as 商户服务端
    participant PMServer as PayerMax服务端
    participant Google as Apple

    %% 第一阶段：下单与调起选卡页
    Client->>MServer: 1.1 用户下单并选择Apple Pay
    MServer->>Google: 1.2 商户用自己的开发者账号信息请求Apple Pay并拉起选卡页

    %% 第二阶段：选卡与加密
    Client->>Google: 2.1 用户在选卡页选择要使用的卡或添加新卡，并确认
    Google->>Google: 2.2 使用商户公钥加密卡信息
    Google-->>Client: 2.3 返回加密Token

    %% 第三阶段：支付处理
    Client->>MServer: 3.1 支付
    MServer->>MServer: 3.2 使用私钥解密
    MServer->>PMServer: 3.3 提交支付请求
    PMServer->>PMServer: 3.4 完成支付
    PMServer-->>MServer: 3.5 支付结果
```

## 2. 集成准备

- 根据[配置与签名](https://docs.payermax.com/202606-version/developer/config-settings.md)引导，获取PayerMax侧的商户自助平台账号、获取商户appId和密钥、配置异步通知地址、配置公钥和私钥。

- 进行ApplePay的相关证书配置流程，主要包括：

  - 创建商户IDs

  - 注册并验证商户域名

  - 创建 Payment Processing Certificate

  - 创建 Merchant Identity Certificate

::: warning 注意：
若商户已完成ApplePay的相关证书配置流程，可直接按照步骤3.4进行对接即可。
:::

### 2.1 创建商户IDs

登录 Apple Developer，[添加 Merchant ID](https://developer.apple.com/account/resources/identifiers/list/merchant)。进入对应模块 **Certificates, Identifiers & Profiles** -> **Identifiers** -> **Merchant IDs**。

![](https://img-cdn-sg.payermax.com/public/20250811-d57f34d8-66e3-44b6-89b7-e96721aa1953.png)

### 2.2 注册并验证商户域名

使用ApplePay的页面必须使用HTTPS访问，该页面的域名必须有SSL证书。ApplePay会在处理过程中验证该域名证书的有效性，证书过期则无法提供服务。

1. 域名不能位于代理服务器或者重定向后，并且允许Apple服务器访问，详情请参考[Allow Apple IP Addresses for Domain Verification](https://developer.apple.com/documentation/applepayontheweb/setting-up-your-server#3179116)。

2. 一个Merchant ID下可以添加多个域名。

![](https://img-cdn-sg.payermax.com/public/20250811-dbcc1f64-e7de-4a57-969f-a5b79b088709.png)

3. 添加域名后下载域名验证文件，将下载到的域名验证文件上传到自己服务上。确保可以通过 `https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association.txt` 访问到该文件。Apple通过访问该文件来验证配置域名的合法性。

- 使用自己的真实域名替换`yourdomain.com`；

- 在你的Web服务器根目录下创建一个文件夹`.well-known`并将下载到的文件放在该文件夹中。

![](https://img-cdn-sg.payermax.com/public/20250811-5f596f0d-338a-43a6-a1a3-62b0449cb354.png)

4. 配置好域名验证文件后在Apple Developer后台进行域名有效性验证。

![](https://img-cdn-sg.payermax.com/public/20250811-9f43bd63-a939-45ac-957c-ad3f4d692ffc.png)

验证通过后操作界面会展示验证有效期，该有效期等同于域名SSL证书的有效期。示例中的域名证书有效期如下：

![](https://img-cdn-sg.payermax.com/public/20250811-d9b0b0fa-e755-4736-b364-a6bd0acea50c.png)

::: warning 注意：
示例图片中时间差异是因为两者采用了不同的时区展示。
:::

5. 证书过期更新：

::: danger 特别提醒：
Apple 会在域名SSL证书过期前的`30天`、`15天`、`7 天`分别去检查你的域名证书是否更新。
:::

- 如果你在证书过期前完成了更新，Apple检测到了更新后的证书并且域名验证没问题，那么你不需要做任何额外的事情；

- 如果没有在证书过期前完成更新，则要重新下载域名认证文件并再次完成验证（参考第2、3步）。

最佳实践是在域名证书过期前的`7天`之前完成证书更新，这样在`7天`的检查中可以拿到更新后的证书信息。

### 2.3 创建 Payment Processing Certificate

该证书用于Server端和ApplePay的交互，属于正常HTTPS请求的客户端证书。操作步骤请参考[Apple官方文档](https://developer.apple.com/help/account/capabilities/configure-apple-pay-on-the-web#create-a-merchant-identity-certificate)。

#### 2.3.1 生成 Certificate Signing Request (CSR)

两种方式，一种是参考[Apple官方文档](https://developer.apple.com/help/account/certificates/create-a-certificate-signing-request)，使用`Keychain Access`生成，还有一种是使用命令行生成。接下来主要介绍第二种方式。

1. 需要先[安装OpenSSL](https://openssl-library.org/source/)；

2. 生成`私钥`，需使用ECC算法，`256`长度：

```
openssl ecparam -genkey -name prime256v1 -out applepay-ppc-ecc-256-private.key
```

会得到`applepay-ppc-ecc-256-private.key`文件，这个就是`私钥`，需要保存到系统中，后续解密Token会用到。

3. 生成`csr`文件：

```
openssl req -new -key applepay-ppc-ecc-256-private.key -out applepay-pcc-ecc-256.csr
```

需要填写公司相关的信息，例如：

![](https://img-cdn-sg.payermax.com/public/20250811-2b429e43-4be3-4958-ae3b-c6a392bf340b.png)

会得到`applepay-pcc-ecc-256.csr`文件。

#### 2.3.2 上传证书

- 在 **Merchant Id** 详情页中的 **Apple Pay Payment Processing Certificate** 部分，点击 **Create Certificate** 创建证书：

![](https://img-cdn-sg.payermax.com/public/20250811-2e75f5e6-66ab-4f3e-be89-e6cb9a4c3d16.png)

- 选择上一步创建的`CSR文件`：

![](https://img-cdn-sg.payermax.com/public/20250811-757139e2-589d-48a5-b24b-ca51d794be97.png)

- 上传完成后点击 **Continue**。成功后Apple会生成证书，点击 **Download** 下载证书。

![](https://img-cdn-sg.payermax.com/public/20250811-8edeee3e-54b4-4354-89b8-dbfeb8fb3230.png)

### 2.4 创建 Merchant Identity Certificate

调用Apple Pay的`create sesion`接口是SSL双向认证，需要使用客户端证书，就是`Merchant Identity Certificate`。创建这个证书也需要`生成私钥`、`CSR`等步骤。

#### 2.4.1 生成私钥

这里是要生成`RSA 2048`的私钥：

```
openssl genrsa -out applepay-mic-rsa-2048-private.key 2048
```

得到`applepay-mic-rsa-2048-private.key`私钥文件，这个私钥需要保存好，后续在调用接口时会用到。

#### 2.4.2 生成 CSR

```
openssl req -new -key applepay-mic-rsa-2048-private.key -out applepay-mic-rsa-2048.csr
```

这里一样需要填写证书所有者的信息。填写完成后会得到`applepay-mic-rsa-2048.csr`文件。

#### 2.4.3 上传 CSR

打开 **Merchant Id** 的详情页，找到 **Apple Pay Merchant Identity Certificate** 章节：

![](https://img-cdn-sg.payermax.com/public/20250811-351c883b-7321-40a6-82e7-0502bbaed159.png)

点击 **Create Certificate** 按钮，选择上一步生成的`CSR文件`后 **Continue**Continue，然后就能得到证书。

![](https://img-cdn-sg.payermax.com/public/20250811-a307447c-5c9a-45dc-92d6-79e4efeb5ef2.png)

保存好证书，后续支付处理过程中会使用到。

## 3. 集成步骤

商户需要自己获取`ApplePay Token`，`解密Token`得到`卡信息`，然后将解密后的`卡信息`传递给PayerMax。

::: warning 注意：
若商户已在自己收银台集成过Apple Pay，可直接按步骤3.4对接即可。
:::

### 3.1 初始化页面时获取 Apple Pay Session

参考链接：

- [Creating an Apple Pay Session | Apple Developer](https://developer.apple.com/documentation/applepayontheweb/creating-an-apple-pay-session)

- [Requesting an Apple Pay payment session | Apple Developer](https://developer.apple.com/documentation/applepayontheweb/requesting-an-apple-pay-payment-session)

::: warning 注意：
在调用Apple的接口获取`session`时，需要使用到SSL的客户端证书，即前面创建的`Merchant Identity Certificate`。
:::

### 3.2 用户支付获取 ApplePay Token

在`session`的`onpaymentauthorized`回调方法入参中可获取加密Token，详情请参考：[onpaymentauthorized | Apple Developer](https://developer.apple.com/documentation/applepayontheweb/applepaysession/onpaymentauthorized)

```js
...
this.session = new window.ApplePaySession(APPLE_PAY_VERSION, this.payRequest);

...

this.session.onpaymentauthorized = (event) => {
  // event.payment.token 即为加密 token
};
...
```

拿到的 token 示例如下：

![](https://img-cdn-sg.payermax.com/public/20250811-58e6d674-4299-4e53-b2c4-dea4d1a1573b.png)

```json
{
    "paymentData": {
        "data": "",
        "signature": "",
        "header": {
            "publicKeyHash": "",
            "ephemeralPublicKey": "",
            "transactionId": ""
        },
        "version": "EC_v1"
    },
    "paymentMethod": {
        "displayName": "Visa 8007",
        "network": "Visa",
        "type": "debit"
    },
    "transactionIdentifier": ""
}
```

### 3.3 解密 Apple Pay Token

- `Apple Pay Token`的结构，详情请参考：[Payment token format reference | Apple Developer](https://developer.apple.com/documentation/passkit/payment-token-format-reference)；

- 解密Token需要使用到前面创建的`Payment Processing Certificate`；

- 解密后的`data`示例如下：

```json
{
    "applicationExpirationDate": "280228",
    "applicationPrimaryAccountNumber": "42710600003562",
    "currencyCode": "124",
    "deviceManufacturerIdentifier": "040010030273",
    "paymentData": {
        "eciIndicator": "5",
        "onlinePaymentCryptogram": "/wAAADcAv7mhHpQAAAAAgPdgE4A="
    },
    "paymentDataType": "3DSecure",
    "transactionAmount": "5564"
}
```

### 3.4 调用PayerMax进行支付

[创建支付/orderAndPay API](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/aggregate-pay-api-gateway-orderAndPay-delSuffixStart1/post) 接口请求，其中关键字段：

- `paymentDetail.paymentMethodType`：`APPLEPAY`

- `paymentDetail.applePayPaymentData`：为解密后的支付信息

接口请求示例：

将`Apple Pay Token`解密后的卡信息，传递给`data.paymentDetail.applePayPaymentData`字段中。

```json
{
  "version": "1.5",
  "keyVersion": "1",
  "requestTime": "2022-02-25T09:23:06.473+00:00",
  "appId": "6666c8b036a24579974497c2f9800001",
  "merchantNo": "020213834421234",
  "data": {
    "outTradeNo": "Test1645780876511",
    "subject": "this is subject",
    "totalAmount": 1,
    "currency": "AED",
    "country": "AE",
    "userId": "userId001",
    "integrate": "Direct_Payment",
    "expireTime": "1800",
    "paymentDetail": {
      "paymentMethodType": "APPLEPAY",
      "buyerInfo": {
        "firstName": "James",
        "lastName": "Smith",
        "phoneNo": "903124360628",
        "email": "james@google.com",
        "clientIp": "124.156.108.193",
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
      },
      "applePayPaymentData": {
          "applicationExpirationDate": "2312",
          "applicationPrimaryAccountNumber": "4111111111111111",
          "currencyCode": "USD",
          "deviceManufacturerIdentifier": "A1B2C3D4",
          "paymentDataType": "3DSecure",
          "transactionAmount": "100.00",
          "paymentData": {
            "onlinePaymentCryptogram": "Aa0KZXFURkhF...",
            "eciIndicator": "07"
          },
          "network": "VISA",
          "type": "credit",
          "displayName": "Visa 0492"
     },
    "goodsDetails": [
      {
        "goodsId": "D002",
        "goodsName": "Key buckle",
        "quantity": "2",
        "price": "0.5",
        "goodsCurrency": "AED",
        "showUrl": "http://ttt.com",
        "goodsCategory": "电脑"
      }
    ],
    "shippingInfo": {
      "firstName": "James",
      "lastName": "Smith",
      "phoneNo": "903124360628",
      "email": "xxx@google.com",
      "address1": "address1",
      "city": "GAZIOSMANPASA/ANKAR",
      "country": "TR",
      "zipCode": "06700"
    },
    "billingInfo": {
      "firstName": "James",
      "lastName": "Smith",
      "phoneNo": "903124360628",
      "email": "xxx@google.com",
      "address1": "address1",
      "city": "GAZIOSMANPASA/ANKAR",
      "country": "TR",
      "zipCode": "06700"
    },
    "riskParams": {
      "registerName": "lily",
      "regTime": "2023-07-01 12:08:34",
      "liveCountry": "VN",
      "payerAccount": "987654XXX",
      "payerName": "lily",
      "taxId": "1234567890"
    },
    "language": "en",
    "reference": "020213827524152",
    "terminalType": "WAP",
    "frontCallbackUrl": "https://xxx.com",
    "notifyUrl": "https://yyy.com"
  }
}
```

接口响应示例：

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