# 处理支付

> Shoplazza 支付应用处理支付的流程：平台发来的请求、跳转 / iframe / 直连卡三种模式、重试策略与结果回调。

当 Shoplazza 向支付应用发送 HTTP 请求时，支付处理流程开始。根据支付模型不同，应用会返回跳转 URL 或支付结果。支付完成后，应用必须通过支付回调 API 将交易结果通知 Shoplazza。

## 支付应用流程如何工作

1. 顾客完成结账并开始支付。
2. Shoplazza 向支付应用发送后端请求，包含金额、币种、订单、顾客和回调信息。
3. 支付应用创建或处理支付。
4. 对于跳转支付或 iframe 支付，支付应用返回支付页面 URL。
5. Shoplazza 将顾客跳转到支付页面，或在 iframe 中加载支付页面。
6. 顾客在支付服务商侧完成支付。
7. 必要时，支付应用调用 Shoplazza 完成支付流程。
8. 支付应用发送最终支付结果通知。
9. Shoplazza 更新订单支付状态。

上下游请求都必须具备幂等性。请使用支付 `id` 作为幂等键。

## 异步通信

支付处理依赖 Shoplazza 与支付应用之间的异步 HTTP 通信。如果 Shoplazza 没有以 HTTP `200 OK` 确认回调，支付应用必须按照重试策略进行重试。

## 初始化支付流程

支付流程从 Shoplazza 向支付应用配置的 Payment Session URL 发起请求开始。请求体以 `x-www-form-urlencoded` 形式发送，包含顾客、订单、账单地址、收货地址和回调信息。

```json
{
  "id": "7eb3fefb-6b43-4400-b40a-a2a0531364ae",
  "app_id": "db5fc9a6-2a64-11ec-8d3d-0242ac130003",
  "account_id": "TIOnETZE",
  "shoplazza_order_id": "2711-WFT50903",
  "amount": "254.20",
  "currency": "CAD",
  "products": "[{\"name\":\"name\",\"quantity\":\"2\",\"unit_price\":\"20.00\",\"sku\":\"2023\",\"url\":\"http://a.bc/def\",\"desc\":\"desc\"}]",
  "cancel_url": "https://developer.myshoplaza.com/checkout/3444-222GOA08029",
  "complete_url": "https://developer.myshoplaza.com/openapi/2021-07/payments_apps/complete_callbacks?version=1.0",
  "callback_url": "https://developer.myshoplaza.com/openapi/2021-07/payments_apps/notify_callbacks?version=1.0",
  "customer_email": "lvjiaxing@shoplazza.com",
  "customer_phone_number": "18202787518",
  "customer_billing_address1": "Address detail 1",
  "customer_billing_address2": "Address detail 2",
  "customer_billing_last_name": "lv",
  "customer_billing_first_name": "jianxing",
  "customer_billing_province": "Liaoning",
  "customer_billing_city": "Heishan County",
  "customer_billing_postal_code": "123123",
  "customer_billing_country_code": "CN",
  "customer_billing_company": "shoplazza",
  "customer_billing_state": "LN",
  "customer_billing_phone": "13922235670",
  "customer_shipping_address1": "Shipping address 1",
  "customer_shipping_address2": "Shipping address 2",
  "customer_shipping_last_name": "jiaxing",
  "customer_shipping_first_name": "lv",
  "customer_shipping_province": "Liaoning",
  "customer_shipping_city": "Heishan County",
  "customer_shipping_postal_code": "123123",
  "customer_shipping_country_code": "CN",
  "customer_shipping_company": "Shoplazza",
  "customer_shipping_state": "LN",
  "customer_shipping_phone": "13922235670",
  "test": false,
  "type": "sale",
  "timestamp": "1697704968"
}
```

## 请求头

| Header | 说明 |
| --- | --- |
| `Shoplazza-Shop-Domain` | 商家店铺的永久域名。 |
| `Custom-Domain` | 当前订单使用的顾客访问域名。 |
| `Custom-Ip` | 顾客 IP 地址。 |
| `User-Agent` | 顾客浏览器 User Agent。 |
| `Shoplazza-Shop-Id` | Shoplazza 店铺 ID。 |
| `Shoplazza-Request-Id` | 用于排查问题的唯一请求 ID。 |
| `Shoplazza-Api-Version` | 支付应用配置中选择的 API 版本。 |
| `Shoplazza-Hmac-Sha256` | 用于请求校验的 HMAC 签名。 |
| `Customer-Cpf` | 适用场景下的税号。 |

## 请求参数

| Key | 是否必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| `id` | 是 | string | 支付 ID。用作幂等键。 |
| `app_id` | 是 | string | 支付应用 ID。 |
| `account_id` | 是 | string | 支付服务商系统中的商家账户 ID。 |
| `shoplazza_order_id` | 是 | string | Shoplazza 订单 ID。同一个订单只能支付一次。 |
| `amount` | 是 | string | 订单金额，保留两位小数。 |
| `currency` | 是 | string | 三位 ISO 4217 币种代码。 |
| `products` | 是 | string | 商品支付信息的 JSON 字符串。 |
| `cancel_url` | 是 | string | 顾客取消支付时跳转的 URL。 |
| `complete_url` | 是 | string | 同步完成支付回调 URL。 |
| `callback_url` | 是 | string | 异步支付结果通知回调 URL。 |
| `customer_email` | 否 | string | 顾客邮箱。 |
| `customer_phone_number` | 否 | string | 顾客手机号。 |
| `customer_billing_*` | 视字段而定 | string | 账单地址字段。 |
| `customer_shipping_*` | 视字段而定 | string | 收货地址字段。 |
| `test` | 否 | boolean | 请求是否为测试模式。 |
| `type` | 是 | string | 支付类型，例如 `sale` 或 `card`。 |
| `cards` | 否 | string | 信用卡直接支付使用的序列化信用卡数据。 |
| `timestamp` | 是 | string | 请求时间戳。 |

## 响应

Shoplazza 必须收到 HTTP `2xx` 响应，支付 Session 创建才算成功。如果请求失败，Shoplazza 可能会重试请求。响应内容取决于支付模型。

对于跳转支付或 iframe 支付，返回跳转 URL：

```json
{
  "redirect_url": "https://payment-gateway.example/pay"
}
```

对于无需 3DS 的信用卡直接支付，直接返回支付结果：

```json
{
  "app_id": "12345",
  "payment_id": "7eb3fefb-6b43-4400-b40a-a2a0531364ae",
  "amount": "254.20",
  "currency": "CAD",
  "status": "paid",
  "transaction_no": "123456789",
  "type": "sale",
  "test": false,
  "timestamp": "2021-09-01T18:32:20Z"
}
```

失败时返回错误码和错误信息：

```json
{
  "code": "ERR-1234",
  "message": "Invalid amount"
}
```

## 重试策略

如果 Shoplazza 没有以 HTTP `200 OK` 确认回调请求，请使用递增策略重试。重试策略允许在 24 小时内最多重试 18 次。

| 参数 | 说明 | 值 |
| --- | --- | --- |
| 最大重试次数 | 未收到 HTTP `200` 时允许的总重试次数。 | 18 次 |
| 初始重试延迟 | 第一次重试前的等待时间。 | 5 秒 |
| 指数退避因子 | 后续重试按递增间隔执行，直到收到确认或超时。 | 见示例 |

重试间隔示例：

```text
[0 seconds, 5 seconds, 10 seconds, 30 seconds, 45 seconds, 1 minute, 2 minutes, 5 minutes, 12 minutes, 38 minutes, 1 hour, 2 hours] + [4 hours] * 5
```

## 跳转支付（Sale）

在 sale 模式下，顾客从 Shoplazza 结账页跳转到第三方支付页面完成交易。支付 Session 请求中 `type` 设为 `sale`。

![跳转支付时序图](https://cnres.appracle.com/178dd1f68bbaa6a65ac907e631816449.png)

1. 顾客选择支付方式并点击完成支付。
2. 浏览器向 Shoplazza 服务器发送支付请求。
3. Shoplazza 服务器将支付请求转发给支付应用服务器。
4. 支付应用服务器向 Shoplazza 服务器返回跳转 URL。
5. Shoplazza 服务器将跳转 URL 返回给浏览器。
6. 浏览器将顾客重定向到支付网关页面。
7. 顾客在支付网关页面提交支付信息。
8. 支付应用服务器调用完成支付 API。
9. Shoplazza 向支付应用服务器返回跳转 URL。
10. 支付失败时，顾客被重定向到请求中的 `cancel_url`。
11. 支付成功时，顾客被重定向到 Shoplazza 返回的 `complete_url`。
12. 顾客进入订单支付结果页。
13. 支付应用服务器向回调 URL 发送最终支付结果通知。
14. Shoplazza 以 HTTP `200` 确认收到通知。

## Iframe 支付

iframe 模式在 Shoplazza 结账页内嵌第三方支付窗口。支付 Session 请求结构与 sale 模式相同，Shoplazza 在 iframe 或弹窗容器中加载返回的 `redirect_url`。

![Iframe 支付时序图](https://cnres.appracle.com/178dd1f68bbaa6a65ac907e631816449.png)

iframe 尺寸要求：

* 宽高以像素为单位，不带单位。
* 最小宽度 `300px`。
* 最小高度 `200px`。
* 最大宽高不超过屏幕尺寸。
* 超过屏幕尺寸时默认 `80vw x 80vh`。

## 信用卡直接支付

card 模式让顾客直接在 Shoplazza 结账页输入信用卡信息，需要 PCI-DSS 合规，并在需要时支持 3DS 验证。在 `cards` 字段中传入序列化的信用卡信息。

![信用卡支付时序图](https://cnres.appracle.com/1dc2471b0b51a6426cfb5ae0c7b3cdfb.png)

1. 顾客输入信用卡信息并点击完成支付。
2. 浏览器将支付请求发送到 Shoplazza 服务器。
3. Shoplazza 服务器将支付请求转发给支付应用服务器。
4. 支付应用服务器向 Shoplazza 服务器返回支付状态。
5. Shoplazza 服务器将支付结果返回给浏览器。
6. 如果需要 3DS 验证，浏览器会将顾客跳转到 3DS 验证页面。否则流程继续进入结果页。
7. 顾客在 3DS 页面提交验证信息。
8. 支付应用服务器调用 Complete Payment API。
9. Shoplazza 向支付应用服务器返回跳转 URL。
10. 如果支付失败，顾客会被跳转到支付请求中的 `cancel_url`。
11. 如果支付成功，顾客会被跳转到 Shoplazza 返回的 `complete_url`。
12. 顾客进入订单支付结果页。
13. 支付应用服务器向 callback URL 发送最终支付结果通知。
14. Shoplazza 以 HTTP `200` 确认收到通知。

| 字段 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| `cards` | 否 | string | 序列化的信用卡信息 JSON 对象。 |
| `cards.first_name` | 否 | string | 持卡人名。 |
| `cards.last_name` | 否 | string | 持卡人姓。 |
| `cards.card_number` | 否 | string | 信用卡号。 |
| `cards.expire_year` | 否 | string | 两位过期年份。 |
| `cards.expire_month` | 否 | string | 两位过期月份。 |
| `cards.card_cvv` | 否 | string | CVV，3 或 4 位。 |

需要 3DS 验证时，返回验证跳转 URL：

```json
{
  "redirect_url": "https://3ds-verification.example"
}
```
