# 个人支付平台 · 网站对接文档

面向需要接入本支付平台的网站（下称“商户”）。接入后，你的网站用户选择微信/支付宝支付时，
会跳转到本平台收银台扫码付款；平台通过你手机上的 APP 监听到账后，以**异步通知**告诉你的网站支付结果。

---

## 一、接入流程总览

```
你的网站                 支付平台                 用户手机
   |  1.服务端签名下单       |                        |
   | ---------------------> |                        |
   |  2.返回 pay_url         |                        |
   | <--------------------- |                        |
   |  3.浏览器跳转收银台      |                        |
   | ---------------------> |  4.展示收款二维码        |
   |                        | <--------------------- |
   |                        |  5.APP 轮询待支付订单     |
   |                        | <====================> |
   |                        |  6.用户扫码转账          |
   |                        | <--------------------- |
   |                        |  7.APP 监听到账并回调      |
   |                        | <--------------------- |
   |  8.异步通知(POST+签名)   |                        |
   | <--------------------- |                        |
   |  9.验签/发货/回 success  |                        |
   | ---------------------> |                        |
   |  10.用户浏览器带签名跳回  |                        |
   | <--------------------- |                        |
```

---

## 二、接入准备

联系平台管理员，在后台 `admin.php` →「对接网站」中添加你的网站，获取：

| 参数 | 说明 | 示例 |
|---|---|---|
| `app_id` | 商户 ID | `S0911A8F3K2` |
| `app_secret` | 通信密钥（仅服务端使用，**严禁下发到前端**） | `3a9f...（32位）` |

同时登记（或在下单时每次传）：

| 地址 | 用途 |
|---|---|
| `notify_url` | 异步通知地址，支付成功后平台**服务器**回调，用于发货 |
| `return_url` | 同步返回地址，用户浏览器支付完成后跳转，仅用于展示 |

---

## 三、签名规则（重要）

所有商户接口的请求和回调均用同一套 MD5 签名：

1. 收集除 `sign` 外的所有参数；
2. **剔除值为空**的参数；
3. 按参数名 ASCII 字典序（A-Z,a-z）**升序排序**；
4. 拼接成 `key1=value1&key2=value2`；
5. 末尾追加 `&app_secret=你的密钥`；
6. 做 **MD5**，取 **32 位小写十六进制**。

PHP 示例（可直接复制）：

```php
function pay_sign(array $params, string $secret): string
{
    unset($params['sign']);
    ksort($params);
    $pairs = [];
    foreach ($params as $k => $v) {
        if ($v === null || $v === '') continue;
        $pairs[] = $k . '=' . $v;
    }
    return md5(implode('&', $pairs) . '&app_secret=' . $secret);
}
```

> 金额参数一律保留两位小数，如 `1.37`，不要传 `1.3700` 或 `1.3`。

---

## 四、统一下单

**服务器端接口，请勿在前端页面暴露 app_secret。**

- 请求地址：`http(s)://平台地址/mapi.php?act=submit`
- 请求方式：POST（GET 也可，建议 POST）
- 数据格式：`application/x-www-form-urlencoded`

### 请求参数

| 参数 | 必填 | 说明 |
|---|---|---|
| act | 是 | 固定 `submit` |
| app_id | 是 | 商户 ID |
| out_trade_no | 是 | 你的网站订单号，最长 64 位，字母/数字/下划线/横线，**同一商户下唯一** |
| amount | 是 | 金额（元），0.01 ~ 50000，两位小数 |
| method | 是 | `wechat` 微信 / `alipay` 支付宝 |
| subject | 否 | 商品名称，显示在收银台 |
| notify_url | 否 | 异步通知地址，不传则用后台登记的默认地址 |
| return_url | 否 | 同步返回地址，不传则用后台默认地址 |
| sign | 是 | 按第三节计算的签名 |

### 成功返回（JSON）

```json
{
  "code": 0,
  "msg": "success",
  "exists": false,
  "order_no": "202609111030451234",
  "out_trade_no": "D20260911103045001",
  "amount": 1.37,
  "orig_amount": 1.37,
  "adjusted": false,
  "method": "wechat",
  "expire_time": 1789056645,
  "pay_url": "http://平台地址/cashier.php?order_no=202609111030451234"
}
```

拿到 `pay_url` 后，让用户浏览器 **302 跳转或直接跳转**即可进入收银台。

> ⚠️ **同金额自动调整**：因所有网站的钱进入同一个收款账户，当 5 分钟内已有相同金额的
> 待支付订单时，平台会把本单应付金额自动 **+0.01 递增**（返回 `adjusted=true`，
> `amount` 为实际应付金额），收银台展示和异步通知均以 `amount` 为准。建议商品定价带随机分，
> 如 1.37、9.93。
>
> **下单成功后请把返回的 `amount` 作为"应付金额"保存到本地订单**（可能与你的商品原价
> 相差 0.01 的整数倍），异步通知核账时以此金额为准，不要用原价硬比对。

> 同一 `out_trade_no` 重复下单视为幂等请求，直接返回原订单（`exists=true`），不会重复创建。

---

## 五、异步通知（支付结果通知）

用户支付成功、手机 APP 监听到账后，平台会向 `notify_url` 发起 **POST 表单**请求。

### 通知参数

| 参数 | 说明 |
|---|---|
| app_id | 商户 ID |
| out_trade_no | 你的订单号 |
| order_no | 平台订单号 |
| amount | 实际到账金额（两位小数，可能与下单金额相差 0.01 的整数倍） |
| method | wechat / alipay |
| status | 固定 `1`（支付成功） |
| pay_time | 支付时间戳 |
| sign | 签名，**必须验签** |

### 商户处理要求

1. 用相同规则**验证 sign**；
2. 按 `out_trade_no` 找到本地订单，核对 `amount`；
3. 执行业务（发货/充值/开通会员），务必保证**幂等**，同一订单通知多次只处理一次；
4. 处理完成后，响应体必须**原样输出** `success`（小写，无空格、无 HTML）。

平台未收到 `success` 会判定通知失败并自动补发（共最多 5 次，由 `cron.php` 每分钟补发）。
因此**不要把异步通知地址放在需要登录的页面**。

参考实现：`demo/notify.php`

---

## 六、同步返回

收银台显示支付成功后，会引导用户浏览器（约 3 秒）跳转到 `return_url`，以 **GET query**
携带与异步通知相同的参数（含 `sign`）。

- 同步返回仅用于**给用户看结果页面**，可能被用户手动关闭页面而不触发；
- **发货只能以验签通过的异步通知为准**，不要仅凭同步返回发货。

参考实现：`demo/return.php`

---

## 七、订单查询（服务器端）

用于主动核对订单状态（建议在异步通知不可靠时定时查询）。

- 请求地址：`http(s)://平台地址/mapi.php?act=query`
- 方式：POST / GET

### 请求参数

| 参数 | 必填 | 说明 |
|---|---|---|
| act | 是 | `query` |
| app_id | 是 | 商户 ID |
| out_trade_no | 二选一 | 你的订单号 |
| order_no | 二选一 | 平台订单号 |
| sign | 是 | 签名 |

### 返回

```json
{
  "code": 0,
  "msg": "success",
  "order": {
    "app_id": "S0911A8F3K2",
    "out_trade_no": "D20260911103045001",
    "order_no": "202609111030451234",
    "amount": 1.37,
    "method": "wechat",
    "status": 1,
    "status_text": "已支付",
    "create_time": 1789056345,
    "expire_time": 1789056645,
    "pay_time": 1789056420
  }
}
```

`status`：**0 待支付 / 1 已支付 / 2 已超时**。

---

## 八、错误码

| code | 含义 | 处理建议 |
|---|---|---|
| 0 | 成功 | — |
| 1 | 参数错误（金额/订单号/URL 格式等） | 看 msg 修正请求 |
| 2 | 签名错误 | 检查签名拼接、密钥、金额两位小数 |
| 3 | 商户不存在或已停用 | 联系平台管理员 |
| 4 | 查询时订单不存在 | 检查订单号 |
| 403 | APP 通信 token 错误（非商户接口） | 与商户对接无关 |
| 404 | act 错误 | 仅支持 submit / query |
| 500 | 平台内部错误 | 稍后重试或联系管理员 |

---

## 九、PHP 完整下单示例（复制即用）

```php
$secret = '你的app_secret';
$params = [
    'app_id'       => '你的app_id',
    'out_trade_no' => 'D' . date('YmdHis') . mt_rand(100, 999),
    'amount'       => number_format(1.37, 2, '.', ''),
    'method'       => 'wechat',
    'subject'      => 'VIP会员月卡',
    'notify_url'   => 'https://你的网站/notify.php',
    'return_url'   => 'https://你的网站/return.php',
];
$params['sign'] = pay_sign($params, $secret);

$ch = curl_init('http(s)://平台地址/mapi.php?act=submit');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($params),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
]);
$json = json_decode(curl_exec($ch), true);
curl_close($ch);

if (($json['code'] ?? -1) === 0) {
    header('Location: ' . $json['pay_url']); // 跳转收银台
    exit;
}
die('下单失败：' . ($json['msg'] ?? '网络错误'));
```

完整可运行示例见平台 `demo/` 目录（`config.php` / `index.php` / `notify.php` / `return.php`）。

其他语言（Python/Java/Node）按同一签名规则实现即可。

---

## 十、注意事项

1. **订单有效期 5 分钟**：超时未支付订单自动关闭，不再接受到账回调，收银台会提示超时，请引导用户重新下单。
2. **金额去重调整**：同时多笔同金额订单时实际金额会 +0.01 递增，商户通知处理以平台 `amount` 为准；建议金额带随机分。
3. **异步通知幂等**：通知可能重复送达，务必用订单号做唯一处理。
4. **密钥保密**：`app_secret` 只在你的服务器使用，不要写进 H5/APP/小程序前端。
5. **验签必做**：异步通知和同步返回都必须校验 sign，防止伪造支付成功。
6. **建议使用 HTTPS** 部署平台与商户站。
7. 平台需配置每分钟执行一次 `cron.php`（CLI 计划任务或 URL 监控访问），用于补发失败通知。
