﻿# 前置组件 - GooglePay

## 1. 接口介绍

### 1.1 接口列表

| 关联交互时序                       | 调用方向             | 接口类型   | 接口PATH                                                                                                                                                                      |
| ---------------------------------- | -------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2.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)          |
| 2.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) |
| 2.4.1 支付结果异步通知             | `PayerMax` -> `商户` | `后端接口` | [/collectResultNotifyUrl](https://docs.payermax.com/api.html?docName=New%20Version&docVer=v1.0&docLang=cn#/paths/collectResultNotifyUrl/post.html)                            |
| 2.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)                          |

### 1.2 环境信息

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

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

### 1.3 请求header

``` json
"headers": {
        "Accept": "application/json",
        "sign": "请参考签名规则：https://docs-v2.payermax.com/202606-version/developer/config-settings.html",//必须
        "Content-Type": "application/json"
}
```
## 2.开始集成
### 2.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
{
    "requestTime": "2024-11-20T01:56:36.753-02:00",
    "keyVersion": "1",
    "data": {
            "country":"US",
            "currency": "USD",
            "totalAmount": "19.99",
            "componentList": ["CARD","GOOGLEPAY"],
            "userId": "1447410849000200"
    },
    "appId": "381ded7c863c439a9e29b4519867965a",
    "version": "1.5",
    "merchantNo": ""
}
```
+ [/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": "",
    "code": "APPLY_SUCCESS",
    "data": {
            "sessionKey": "964bffa7c9eb4951bd5ba11a2691e5ed",
            "notSupportedComponent": [],
            "clientKey": "8eef820ecbd443b7a608c2e0863750eb"
    }
}
```

### 2.2 渲染前置组件

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

``` html

```

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

``` html
  
    

```

3. 初始化 PayerMax Frames，将GooglePay进行挂载。

``` js
const googlePay = PMdropin.create('googlepay', {
    clientKey: "8eef820ecbd443b7a608c2e0863750eb",
    sessionKey: "0fdde7eda5fe4cc28ca8fdc759e28dc1",
    sandbox: true,
    payButtonConfig: {   
        buttonRadius: "12",
        buttonColor: "white",
        buttonType: "order",
        buttonLocale: "en",
        width: "240px",
        height: "40px"
    }
});
//按钮挂载
googlePay.mount('.frame-googlepay');
```

4. 监听按钮状态，用户走完GoolePay认证流程，前端拿到`paymentToken`，服务端发起支付并返回`redirectUrl`到前端引导用户完成3DS认证

``` SQL
googlePay.on('ready', () => {
        console.log('组件初始化完成');
    })
googlePay.on('load', (res = {}) => {
    const { code, msg } = res || {};
    if (code === "SUCCESS") {
        console.log('[merchant][load]success:', res)
    } else {
        console.log('[merchant][load]fail:', res)
    }
})
googlePay.on('payButtonClick', (res) => {
    googlePay.emit('setDisabled', true);
    googlePay.emit('canMakePayment')
        .then(paymentRes => {
            console.log('canMakePayment')
            if (paymentRes.code === 'APPLY_SUCCESS') {
                const paymentToken = paymentRes?.data?.paymentToken;
                console.log('paymentToken:', paymentToken)
                // ⚠️ 发起支付接口
                // 商户自己请求后端接口进行下单, 
                // 商户自己用params构造请求参数，需要带上paymentToken
                const params = {  // 添加params定义
                    token: paymentToken,
                };
                _postapi('orderAndPay', params)
                    .then(apiRes => {
                        const code = (apiRes || {}).code;
                        if (code === 'APPLY_SUCCESS') { 
                            if(apiRes.threeDSUrl){
                            handle3DS(threeDSUrl)
                            //使用PayerMax组件内弹窗完成3ds；也可以将threeDSUrl新开浏览器tab页打开完成3ds
                            }    
                            googlePay.emit('paySuccess');//支付成功，googlePay控件消失
                        } else {
                            googlePay.emit('payFail');//支付失败，googlePay控件消失
                        }
                        googlePay.emit('setDisabled', false);// ⚠️ 支付接口完成后解冻表单
                    })
                    .catch(err => {
                        console.error('API请求失败:', err);
                        googlePay.emit('setDisabled', false);// 发生异常后解冻表单
                    });
            }
        })
        .catch(err => {
            console.error('支付能力检测失败:', err);
            googlePay.emit('setDisabled', false);
        });
}); 

// orderAndPay后若获取到返回的url（即为threeDSUrl）就使用handle3DS方法唤起弹窗
function handle3DS(threeDSUrl) {
  googlePay.create3DSPopup({
    url: threeDSUrl,
    // width/height 不传则按设备与视口自动计算；也可显式传入如 '80%'、'400px' 等
  })
  .then(res => {
    if (res.code === '3DS_PROCESSED') {
      // 3DS 流程完成，继续支付流程。
      console.log('3DS 流程完成', res.data);
      // 此处商户需要维持现有逻辑，商户侧应该主动查询支付结果 继续商户侧后续的交互
      proceedPayment(res.data);
    }
  })
  .catch(err => {
    if (err.code === 'USER_CANCEL') {
      // 商户也可不做处理，但是应允许用户继续点击支付按钮
      showMessage('您已关闭认证窗口');
    } else {
      showMessage('认证出现异常，请重试');
    }
  });
}
```
5. 拿到`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"
    }
}
```

### 2.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": "2024-11-20T01:56:45.802-02:00",
        "keyVersion": "1.5", 
        "data": {
        "currency": "USD",
        "country":"US",
        "totalAmount": 19.99,      
        "expireTime": "3600",
        "paymentDetail": {
+               "paymentToken": "dbe78b7dd4fa4b668bacfdcc7153821d",//用户输入卡信息后点击支付会回调前端paymentDetail
                "buyerInfo": {
                        "clientIp": "146.75.136.237",
                        "userAgent": "Chrome"
                },
+               "sessionKey": "abf62fdcf9c4408cb73117db6e740713"
        },
        "frontCallbackUrl": "https://",
        "subject": "xxx Game HK Limited",
        "outTradeNo": "ov1_5b89ced71d764ed9994e6882d88082f7",
        "notifyUrl": "https://",
        "userId": "1447410849000200",
        "integrate": "Direct_Payment",
        "terminalType": "WEB"
        },
        "appId": "381ded7c863c439a9e29b4519867965a",
        "version": "1.5",
        "merchantNo": "P04010116880289"
}
```

+ [/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"
    }
}
``` 

### 2.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`并非支付终态，因此，商户不应直接使用其更新支付结果。

#### 2.4.1 支付结果通知

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

#### 2.4.2 支付结果查询

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

## 3. API

使用方法 `PMdropin.API`。 

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

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

**ComponentName详解**

| **ComponentName**            | **字段类型**          | **描述**          |
|---------------------|---------------------|---------------------|
| googlepay | string |  GooglePay组件      |

**Options详解**

| **Options**            | **是否必填**          | **字段类型**          | **描述**          | **默认值**          |
|---------------------|---------------------|---------------------|---------------------|---------------------|
| clientKey | Y |  String      |  客户端公钥      |  -      |
| sessionKey | Y |  String      |  安全访问令牌      |  -      |
| sandbox | N |  Boolean      |  沙盒环境      |  `false`      |
| payButtonConfig | N |  Object      |  按钮样式	      |  '{buttonRadius: "12",buttonColor: "default",buttonType: "plain",buttonLocale: "en",width: "240px",height: "40px"}'    |

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

**Tag详解**

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

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

**Event详解**

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

### 3.4 create3DSPopup
用于在组件内弹窗打开3DS认证页面

**语言设置说明:** SDK会会自动从实例配置中获取语言，无需在`create3DSPopup`中单独传入

入参说明：
| **参数** | **类型** | **必填** | **默认值** | **说明** |
| :--- | :--- | :--- | :--- | :--- |
| url | string | 是 | - | 3DS 认证页面 URL（从 `orderandpay` 接口获取） |
| showMask | boolean | 否 | true | 是否显示背景遮罩 |
| width | string | 否 | 响应式计算 | 弹框宽度，不传则按设备/视口自动计算 |
| height | string | 否 | 响应式计算 | 弹框高度，不传则按设备/视口自动计算 |

返回：
| **CODE** | **说明** | **处理建议** |
| :--- | :--- | :--- |
| `3DS_PROCESSED` | 3DS 流程已处理 | 继续后续支付流程，通过接口查询最终认证结果 |
| `USER_CANCEL` | 用户关闭弹框 | 提示用户已关闭，可重试 |
| `INVALID_PARAMS` | 调用入参不合法 | 根据 `err.msg` 检查入参（如 url 必填且非空、类型正确等），修正后重试 |

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

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

#### 3.5.1 emit.canMakePayment

1. 检查当前组件状态是否具备发起支付条件，校验通过后返回`paymentToken`。
```js
PMdropin.emit('canMakePayment', params?)
```
`params`为可选参数。若商户服务端在调用`/applyDropinSession`接口时已传入完整支付信息，可直接调用`PMdropin.emit('canMakePayment')`而无需传参。

2. 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 | 传入金额格式有误 | 

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

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

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

#### 3.5.3 emit.setpayButtonConfig
+ 设置按钮样式
+ 类型： `Object`
+ 默认：
```Go
{
  buttonRadius: "12", // 设置googlepay按钮边框弧度 String
  buttonColor: 'default', // 设置按钮颜色  "default"/"white"/ "black"
  buttonType: 'plain',
  buttonLocale: 'en',
  width:"240px",
  height:"40px",
}
```

## 4. 内部字段说明

| 字段         | 默认值    | 类型   | 枚举值                                                             | 功能说明                                                                                                                          |
| ------------ | --------- | ------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| width        | "240px"   | String | /                                                                  | 设置googlepay按钮宽                                                                                                               |
| height       | "40px"    | String | /                                                                  | 设置googlepay按钮高                                                                                                               |
| buttonRadius | "12"      | String | /                                                                  | 设置googlepay按钮边框弧度 String                                                                                                  |
| buttonColor  | 'default' | String | 'default'/'white'/ 'black'                                         | 设置按钮颜色                                                                                                                      |
| buttonType   | 'plain'   | String | "plain","buy","book","checkout","donate","order","pay","subscribe" | 设置按钮类型 同GooglePay官方。自定义按钮[预览demo](https://developers.google.com/pay/api/web/guides/resources/customize?hl=zh-cn) |
| buttonLocale | 'en'      | String | 'en'/'ja'/'zh'/... 国际语种编码                                    | 设置按钮上文案的多语言，同GooglePay官方，需指定类型才支持，如"checkout"、"donate"等                                               |

## 5. 颜色展示

| 入参    | 效果预览                                                                                      |
| ------- | --------------------------------------------------------------------------------------------- |
| default | ![](https://img-cdn-sg.payermax.com/public/20250305-73376124-4e97-46de-88d1-898893b40e57.png) |
| white   | ![](https://img-cdn-sg.payermax.com/public/20250305-e3b076cd-8074-4d84-b24f-2845f80956de.png) |
| black   | ![](https://img-cdn-sg.payermax.com/public/20250305-8be017d4-807d-48a3-9659-705e2e6de1cb.png) |

## 6. buttonType展示

| 入参        | 效果预览                                                                                      |
| ----------- | --------------------------------------------------------------------------------------------- |
| "plain"     | ![](https://img-cdn-sg.payermax.com/public/20250306-df875c3c-83e8-40e8-9858-843a88025f45.png) |
| "buy"       | ![](https://img-cdn-sg.payermax.com/public/20250306-cf849d09-3d49-46ea-8b50-e5b82804708e.png) |
| "book"      | ![](https://img-cdn-sg.payermax.com/public/20250306-a4a2794c-1913-4a46-9f1c-bbee6b9f9cc5.png) |
| "checkout"  | ![](https://img-cdn-sg.payermax.com/public/20250306-e3579e48-c4e4-46ac-adce-bc6f5d9761b4.png) |
| "donate"    | ![](https://img-cdn-sg.payermax.com/public/20250306-0cca324d-be97-4e20-97ed-77caa0ea7930.png) |
| "order"     | ![](https://img-cdn-sg.payermax.com/public/20250306-abe3aa72-c82b-45c9-8631-f76d86137049.png) |
| "pay"       | ![](https://img-cdn-sg.payermax.com/public/20250306-802acd43-41b4-4025-ae8c-2be392ea16e7.png) |
| "subscribe" | ![](https://img-cdn-sg.payermax.com/public/20250306-bf0b5d4e-c94c-4aec-82ae-54103ee3c313.png) |

## 7. buttonLocale展示

| 入参 | 效果预览                                                                                                                                                                                            | 说明      |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| "ja" | ![](https://img-cdn-sg.payermax.com/public/20250306-b8ec4b61-0414-441c-87ac-a558a4d6792b.png)         ![](https://img-cdn-sg.payermax.com/public/20250306-6ea1767d-e71d-4e9a-902b-a7a572cd4f04.png) | Japanese  |
| "bg" | ![](https://img-cdn-sg.payermax.com/public/20250306-200a1309-839d-4d0c-88fa-be9e13532ee3.png)         ![](https://img-cdn-sg.payermax.com/public/20250306-0d463fe2-9765-43b0-a55b-7366b27fa3c1.png) | Bulgarian |
