# 支付功能使用指南

## 📋 概述

本项目已完整接入**微信支付**和**支付宝支付**，支持内容购买、账户充值等支付场景。

---

## 一、配置信息

### 1.1 微信支付配置

位置：`src/main/resources/application-dev.yml` 和 `src/main/resources/application-prod.yml`

```yaml
payment:
  wechat:
    appId: wx7d13d99de5be3bfa          # 微信应用 ID
    mchId: 1673321732                    # 商户号
    mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B  # 商户密钥
    mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D  # 商户证书序列号
    privateKeyPath: apiclient_key.pem    # 商户私钥文件路径
    domain: https://api.mch.weixin.qq.com  # 微信服务器地址
    notifyUrl: http://127.0.0.1:19001/api/pay/wx/notify  # 支付回调地址
    returnUrl: http://127.0.0.1:19001/api/pay/success    # 支付成功跳转地址
```

### 1.2 支付宝支付配置

```yaml
payment:
  alipay:
    appId: 2021004138642603              # 支付宝应用 ID
    publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB"  # 支付宝公钥
    privateKey: ""                       # 商户私钥（需配置）
    notifyUrl: http://127.0.0.1:19001/api/pay/alipay/trade/notify  # 支付回调地址
    returnUrl: https://shuziren.xueai.art/alipay-success  # 支付成功跳转地址
    signType: RSA2                       # 签名类型
    charset: UTF-8                       # 字符编码
    gatewayUrl: https://openapi.alipay.com/gateway.do  # 支付宝网关
```

---

## 二、API 接口说明

### 2.1 创建微信支付订单

**接口地址：** `POST /api/pay/wx/create`

**请求参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderNo | String | 否 | 订单号（不传则自动生成） |
| userId | Long | 是 | 用户 ID |
| userName | String | 是 | 用户名 |
| amount | BigDecimal | 是 | 支付金额（单位：元） |
| productName | String | 是 | 商品名称 |
| productDesc | String | 否 | 商品描述 |
| businessId | Long | 是 | 关联业务 ID |
| businessType | String | 是 | 业务类型：recharge(充值)/purchase_content(购买内容) |

**请求示例：**

```bash
curl -X POST http://localhost:19001/api/pay/wx/create \
  -H "Content-Type: application/json" \
  -d '{
    "userId": 123,
    "userName": "张三",
    "amount": 0.01,
    "productName": "测试商品",
    "productDesc": "商品描述",
    "businessId": 456,
    "businessType": "purchase_content"
  }'
```

**响应示例：**

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "code_url": "weixin://wxpay/bizpayurl?pr=xxx",
    "order_no": "ORDER_20260331_001"
  }
}
```

**字段说明：**
- `code_url`: 微信支付二维码链接，前端可使用此链接生成二维码
- `order_no`: 支付订单号，用于后续查询订单状态

---

### 2.2 创建支付宝支付订单

**接口地址：** `POST /api/pay/alipay/create`

**请求参数：** 与微信支付相同

**请求示例：**

```bash
curl -X POST http://localhost:19001/api/pay/alipay/create \
  -H "Content-Type: application/json" \
  -d '{
    "userId": 123,
    "userName": "张三",
    "amount": 0.01,
    "productName": "测试商品",
    "businessId": 456,
    "businessType": "purchase_content"
  }'
```

**响应示例：**

```json
{
  "code": 200,
  "message": "success",
  "data": "<form name=\"alipay\" method=\"post\" action=\"https://openapi.alipay.com/gateway.do\">...</form>"
}
```

**使用说明：**
- 返回的是 HTML 表单字符串
- 前端将此外壳写入页面后会自动提交跳转到支付宝支付页面

---

### 2.3 支付回调接口（系统自动处理）

#### 微信支付回调

**接口地址：** `POST /api/pay/wx/notify`

**说明：** 
- 由微信支付服务器自动调用
- 处理支付结果并更新订单状态
- 返回 XML 格式响应给微信服务器

#### 支付宝支付回调

**异步回调：** `POST /api/pay/alipay/trade/notify`

**同步回调：** `GET /api/pay/alipay/trade/return`

**说明：**
- 异步回调由支付宝服务器自动调用
- 同步回调用于用户支付完成后跳转回指定页面

---

## 三、使用场景示例

### 3.1 场景 1：购买付费内容

**业务流程：**

1. 用户点击购买按钮
2. 创建购买记录（待支付状态）
3. 创建支付订单
4. 用户扫码或跳转支付
5. 支付成功后更新订单和购买记录状态

**前端调用示例：**

```javascript
// 步骤 1：创建购买记录
const purchaseResponse = await fetch('/api/content/purchase', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userId: 123,
    contentId: 456,
    payType: 3  // 3=微信支付，4=支付宝支付
  })
});

const purchaseResult = await purchaseResponse.json();

// 步骤 2：创建支付订单
const payResponse = await fetch('/api/pay/wx/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userId: 123,
    amount: 9.99,
    productName: 'AI 技能教程',
    productDesc: '高级 AI 技能培训内容',
    businessId: 456,
    businessType: 'purchase_content'
  })
});

const payResult = await payResponse.json();

if (payResult.code === 200) {
  // 步骤 3：显示支付二维码
  const codeUrl = payResult.data.code_url;
  // 使用 QRCode 库生成二维码
  new QRCode(document.getElementById('qrcode'), {
    text: codeUrl,
    width: 200,
    height: 200
  });
  
  // 步骤 4：轮询查询订单状态
  const checkOrderStatus = setInterval(async () => {
    const statusResponse = await fetch(`/api/pay/order/query?orderNo=${payResult.data.order_no}`);
    const statusResult = await statusResponse.json();
    
    if (statusResult.data.status === 2) {  // 2=已支付
      clearInterval(checkOrderStatus);
      alert('支付成功！');
      window.location.reload();
    }
  }, 3000);  // 每 3 秒查询一次
  
  // 设置超时（15 分钟后停止查询）
  setTimeout(() => {
    clearInterval(checkOrderStatus);
    alert('支付超时，请重新下单');
  }, 900000);
}
```

---

### 3.2 场景 2：账户充值

**业务流程：**

```javascript
// 创建充值订单
const rechargeResponse = await fetch('/api/pay/wx/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userId: 123,
    amount: 100.00,  // 充值 100 元
    productName: '账户充值',
    businessId: 123,
    businessType: 'recharge'  // 充值业务类型
  })
});

const result = await rechargeResponse.json();
if (result.code === 200) {
  // 显示支付二维码
  showQRCode(result.data.code_url);
}
```

**说明：**
- 支付成功后，系统会自动通过回调将充值金额添加到用户账户余额

---

### 3.3 场景 3：支付宝网页支付

```javascript
// 创建支付宝订单
const aliPayResponse = await fetch('/api/pay/alipay/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userId: 123,
    amount: 0.01,
    productName: '测试商品',
    businessId: 456,
    businessType: 'purchase_content'
  })
});

const result = await aliPayResponse.json();
if (result.code === 200) {
  // 将返回的 HTML 表单写入页面
  document.body.innerHTML = result.data;
  // 表单会自动提交，跳转到支付宝支付页面
}
```

---

## 四、前端集成完整示例

### 4.1 微信支付页面

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>微信支付</title>
  <script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.0/build/qrcode.min.js"></script>
  <style>
    .payment-container {
      max-width: 600px;
      margin: 50px auto;
      padding: 20px;
      text-align: center;
    }
    .qrcode-box {
      border: 1px solid #ddd;
      padding: 20px;
      display: inline-block;
      margin: 20px 0;
    }
    .status-tip {
      color: #666;
      font-size: 14px;
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <div class="payment-container">
    <h2>微信支付</h2>
    <div class="qrcode-box">
      <div id="qrcode"></div>
    </div>
    <p class="status-tip">请使用微信扫码支付</p>
    <p class="status-tip" id="statusTip">等待支付...</p>
  </div>

  <script>
    // 从 URL 参数获取商品信息
    const urlParams = new URLSearchParams(window.location.search);
    const productId = urlParams.get('productId');
    const amount = urlParams.get('amount');

    // 创建支付订单
    async function createPayment() {
      try {
        const response = await fetch('/api/pay/wx/create', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            userId: 123,  // 实际应从登录信息中获取
            amount: parseFloat(amount),
            productName: '商品-' + productId,
            businessId: productId,
            businessType: 'purchase_content'
          })
        });

        const result = await response.json();
        if (result.code === 200) {
          // 生成二维码
          QRCode.toCanvas(document.getElementById('qrcode'), result.data.code_url, {
            width: 200,
            height: 200
          });
          
          // 开始轮询订单状态
          checkOrderStatus(result.data.order_no);
        } else {
          alert('创建订单失败：' + result.message);
        }
      } catch (error) {
        console.error('创建订单失败:', error);
        alert('网络错误，请稍后重试');
      }
    }

    // 轮询订单状态
    async function checkOrderStatus(orderNo) {
      const maxAttempts = 300;  // 最多查询 300 次（15 分钟）
      let attempts = 0;

      const timer = setInterval(async () => {
        try {
          const response = await fetch(`/api/pay/order/query?orderNo=${orderNo}`);
          const result = await response.json();
          
          if (result.code === 200 && result.data) {
            const status = result.data.status;
            const statusText = document.getElementById('statusTip');
            
            if (status === 2) {  // 已支付
              clearInterval(timer);
              statusText.textContent = '✅ 支付成功！';
              statusText.style.color = 'green';
              
              setTimeout(() => {
                window.location.href = '/pay-success.html?orderNo=' + orderNo;
              }, 1000);
              return;
            } else if (status === 3 || status === 4) {  // 支付失败或已取消
              clearInterval(timer);
              statusText.textContent = '❌ 支付失败';
              statusText.style.color = 'red';
              return;
            }
            
            attempts++;
            if (attempts >= maxAttempts) {
              clearInterval(timer);
              statusText.textContent = '⏰ 支付超时，请重新下单';
              statusText.style.color = 'orange';
            }
          }
        } catch (error) {
          console.error('查询订单状态失败:', error);
        }
      }, 3000);  // 每 3 秒查询一次
    }

    // 页面加载时创建支付订单
    createPayment();
  </script>
</body>
</html>
```

### 4.2 支付宝支付页面

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>支付宝支付</title>
</head>
<body>
  <div style="text-align: center; padding: 50px;">
    <h2>正在跳转到支付宝支付页面...</h2>
    <p>请稍候</p>
  </div>

  <script>
    // 从 URL 获取商品信息
    const urlParams = new URLSearchParams(window.location.search);
    const productId = urlParams.get('productId');
    const amount = urlParams.get('amount');

    // 创建支付订单
    async function createAlipay() {
      try {
        const response = await fetch('/api/pay/alipay/create', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            userId: 123,
            amount: parseFloat(amount),
            productName: '商品-' + productId,
            businessId: productId,
            businessType: 'purchase_content'
          })
        });

        const result = await response.json();
        if (result.code === 200) {
          // 将返回的 HTML 表单写入页面并提交
          document.body.innerHTML = result.data;
          document.forms[0].submit();
        } else {
          alert('创建订单失败：' + result.message);
          setTimeout(() => {
            window.location.href = '/index.html';
          }, 2000);
        }
      } catch (error) {
        console.error('创建订单失败:', error);
        alert('网络错误，请稍后重试');
      }
    }

    createAlipay();
  </script>
</body>
</html>
```

---

## 五、核心代码文件

| 文件路径 | 说明 |
|---------|------|
| `src/main/java/com/kexue/skills/controller/PayController.java` | 支付接口控制器 |
| `src/main/java/com/kexue/skills/service/PayService.java` | 支付服务接口 |
| `src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java` | 支付服务实现类 |
| `src/main/java/com/kexue/skills/config/PaymentConfig.java` | 支付配置类 |
| `src/main/java/com/kexue/skills/entity/PaymentOrder.java` | 支付订单实体类 |
| `src/main/java/com/kexue/skills/service/PaymentOrderService.java` | 支付订单服务接口 |
| `src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java` | 支付订单服务实现类 |

---

## 六、注意事项

### 6.1 开发环境测试

⚠️ **重要提示：** 本地开发环境下，微信和支付宝无法直接访问到你的 localhost 回调地址。

**解决方案：**

1. **使用内网穿透工具**（推荐）
   ```bash
   # 使用 ngrok
   ngrok http 19001
   
   # 将生成的域名配置到回调地址
   # 例如：https://abc123.ngrok.io/api/pay/wx/notify
   ```

2. **部署到测试服务器**
   - 直接部署到具有公网 IP 的服务器进行测试

3. **修改配置文件**
   ```yaml
   payment:
     wechat:
       notifyUrl: https://your-domain.ngrok.io/api/pay/wx/notify
     alipay:
       notifyUrl: https://your-domain.ngrok.io/api/pay/alipay/trade/notify
   ```

### 6.2 金额单位

- 前端传入金额单位：**元**
- 微信支付内部转换：**分**（代码自动处理）
- 建议最小金额：0.01 元

### 6.3 业务类型说明

| 业务类型 | 说明 | 支付成功后操作 |
|---------|------|--------------|
| `recharge` | 账户充值 | 增加用户账户余额 |
| `purchase_content` | 购买内容 | 更新内容购买记录状态 |

### 6.4 支付状态码

| 状态码 | 说明 |
|-------|------|
| 1 | 待支付 |
| 2 | 已支付 |
| 3 | 支付失败 |
| 4 | 已取消 |
| 5 | 已退款 |

### 6.5 安全建议

1. **权限验证**：所有支付接口都应添加权限验证（已部分实现）
2. **签名验证**：确保支付回调的签名验证正确
3. **幂等性处理**：支付回调应支持重复通知（已实现）
4. **日志记录**：完整的支付日志便于问题排查

---

## 七、常见问题

### Q1: 如何查询订单状态？

```java
// 通过订单号查询
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);

// 或通过主键查询
PaymentOrder order = paymentOrderService.queryById(orderId);
```

### Q2: 如何处理支付回调失败？

系统已实现幂等性处理，同一订单多次回调不会重复处理。如果回调失败，微信/支付宝会按一定频率重试。

### Q3: 如何申请退款？

当前版本暂未实现退款功能，如需退款，需要：
1. 调用微信/支付宝退款 API
2. 更新支付订单状态为已退款（5）
3. 恢复用户余额或购买权限

### Q4: 测试时使用真实资金吗？

是的，对接的是正式环境。如需测试环境，需要：
- 微信：申请沙箱环境
- 支付宝：使用测试账号

---

## 八、技术支持

如遇问题，请检查以下内容：

1. ✅ 配置文件中的商户号、密钥是否正确
2. ✅ 回调地址是否可被外网访问
3. ✅ 商户私钥文件是否存在且路径正确
4. ✅ 查看日志文件中的详细错误信息
5. ✅ 确认微信/支付宝商户号状态正常

---

**文档版本：** v1.0  
**更新时间：** 2026-03-31  
**维护人员：** 系统开发团队
