feat(payment): 添加PayPal支付配置和订单状态更新功能

- 配置PayPal沙箱环境的Client ID和密钥
- 新增updatePaymentStatus方法用于更新订单支付状态
- 新增updateOrderStatus方法用于更新订单状态
- 实现支付状态更新时同步更新订单状态逻辑
- 添加详细的日志记录和异常处理机制
- 集成MyBatis Plus查询更新订单数据
This commit is contained in:
2025-12-23 10:18:37 +08:00
parent 13cf90d54b
commit 7794accdeb
4 changed files with 346 additions and 0 deletions

46
PAYPAL_TEST_ACCOUNT.md Normal file
View File

@@ -0,0 +1,46 @@
# PayPal沙箱测试账号说明
## API凭证已配置到 application-dev.yml
- **Client IDAPI密钥**: `AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb`
- **Client Secret密钥**: `ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1`
这些凭证已自动配置到 `application-dev.yml` 中,无需手动配置。
## 沙箱测试账号(用于测试支付流程)
### 商家账号Business Account
- **用户名**: `sb-vtcmz48304367@business.example.com`
- **密码**: `iN)7:z#4`
### 买家账号Personal Account
PayPal沙箱环境通常会提供多个测试账号你可以在PayPal开发者控制台查看。
## 测试流程
1. **启动应用**:确保使用 `dev` 环境配置启动
2. **创建订单**:在系统中创建测试订单
3. **点击支付**:在订单确认页面点击"立即支付"
4. **PayPal登录**使用上述商家账号或买家账号登录PayPal沙箱
5. **完成支付**在PayPal页面完成支付流程
6. **查看结果**:支付完成后会自动跳转回系统,查看订单状态
## 注意事项
1. **环境切换**:当前配置为沙箱环境(`mode: sandbox`
2. **生产环境**:上线前需要:
- 在PayPal开发者控制台获取生产环境的Client ID和Secret
- 修改 `application-prod.yml` 中的配置
-`mode` 改为 `production`
3. **测试账号**:沙箱测试账号仅用于开发测试,不能用于真实交易
## PayPal沙箱控制台
访问地址https://developer.paypal.com/dashboard/
在这里可以:
- 查看API凭证
- 管理测试账号
- 查看交易记录
- 配置Webhook等

170
PAYPAL_WEBHOOK_GUIDE.md Normal file
View File

@@ -0,0 +1,170 @@
# PayPal Webhook处理指南
## 概述
PayPal Webhook是PayPal向你的服务器发送事件通知的机制。当支付状态发生变化时PayPal会主动推送事件到你的Webhook URL。
## Webhook URL配置
在你的PayPal开发者控制台中配置Webhook URL
- **开发环境**: `https://你的域名/api/paypal/webhook`
- **生产环境**: `https://你的生产域名/api/paypal/webhook`
## 支持的事件类型
系统已实现以下事件类型的处理:
### 1. PAYMENT.CAPTURE.COMPLETED
**触发时机**: 当PayPal成功捕获资金时触发
**处理逻辑**:
- 提取capture信息金额、货币、状态等
- 通过`supplementary_data.related_ids.order_id``custom_id`获取ERP订单号
- 更新ERP订单状态为"已支付"PAID
### 2. PAYMENT.CAPTURE.DENIED
**触发时机**: 当支付捕获被拒绝时触发
**处理逻辑**:
- 记录支付失败信息
- 更新ERP订单状态为"支付失败"
### 3. PAYMENT.CAPTURE.REFUNDED
**触发时机**: 当支付被退款时触发
**处理逻辑**:
- 记录退款信息
- 更新ERP订单状态为"已退款"
### 4. CHECKOUT.ORDER.APPROVED
**触发时机**: 当订单被顾客批准时触发
**处理逻辑**:
- 记录订单批准信息
- 可以触发自动捕获逻辑(如果需要)
### 5. CHECKOUT.ORDER.COMPLETED
**触发时机**: 当订单完成时触发
**处理逻辑**:
- 记录订单完成信息
- 根据业务需求处理订单完成逻辑
### 6. CHECKOUT.ORDER.CANCELLED
**触发时机**: 当订单被取消时触发
**处理逻辑**:
- 记录订单取消信息
- 更新ERP订单状态为"已取消"
## 签名验证
### 开发环境(沙箱)
- 签名验证可以暂时跳过(已实现自动跳过)
- 但建议在测试时也启用验证,确保代码正确
### 生产环境
- **必须启用签名验证**
- 需要在配置文件中设置`webhook-id`
- 系统会自动验证每个Webhook请求的签名
## 配置Webhook ID
### 1. 获取Webhook ID
1. 登录PayPal开发者控制台https://developer.paypal.com/dashboard/
2. 进入你的应用设置
3. 找到Webhook配置部分
4. 创建或查看Webhook获取Webhook ID
### 2. 配置到系统
`application-dev.yml``application-prod.yml`中添加:
```yaml
paypal:
webhook-id: YOUR_WEBHOOK_ID
```
## 订单号关联
PayPal Webhook事件中需要关联ERP订单号系统会按以下顺序尝试获取
1. **从`supplementary_data.related_ids.order_id`获取**
- 这是PayPal自动填充的包含原始订单ID
- 然后通过查询PayPal订单详情获取`reference_id`即ERP订单号
2. **从`custom_id`获取**
- 如果创建PayPal订单时设置了`custom_id`,可以直接获取
3. **通过`order_id`查询订单详情**
- 如果上述方法都无法获取会通过PayPal API查询订单详情
- 从订单的`purchase_units[0].reference_id`获取ERP订单号
## 测试Webhook
### 使用PayPal Webhook模拟器
1. 登录PayPal开发者控制台
2. 进入Webhook配置页面
3. 使用"Send test webhook"功能
4. 选择要测试的事件类型
5. 系统会收到测试事件并处理
### 本地测试
如果需要在本地测试,可以使用以下工具:
1. **ngrok**(推荐):
```bash
ngrok http 8082
```
然后将ngrok提供的URL配置到PayPal Webhook URL
2. **PayPal Webhook模拟器**:
使用Postman等工具模拟Webhook请求
## 日志查看
Webhook处理的所有步骤都会记录日志包括
- 接收到的Webhook事件
- 签名验证结果
- 事件处理过程
- 订单状态更新结果
查看日志位置:
- 应用日志文件
- 控制台输出
## 常见问题
### 1. Webhook未收到事件
- 检查Webhook URL是否正确配置
- 确认服务器可以访问(防火墙、网络等)
- 检查PayPal控制台中的Webhook状态
### 2. 签名验证失败
- 确认Webhook ID配置正确
- 检查请求头是否完整
- 确认使用的是正确的环境(沙箱/生产)
### 3. 无法关联ERP订单
- 确认创建PayPal订单时设置了`reference_id`
- 检查订单号格式是否正确
- 查看日志中的详细信息
## 安全建议
1. **生产环境必须启用签名验证**
2. **使用HTTPS**PayPal要求
3. **记录所有Webhook事件**(用于审计和调试)
4. **实现幂等性处理**(防止重复处理同一事件)
5. **设置超时和重试机制**
## 代码位置
- **Webhook控制器**: `PayPalController.webhook()`
- **Webhook服务**: `PayPalWebhookService` 和 `PayPalWebhookServiceImpl`
- **事件DTO**: `PayPalWebhookEventDTO`
- **配置**: `PayPalProperties.webhookId`

View File

@@ -0,0 +1,117 @@
package com.mtkj.mtpay.dto.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 创建PayPal订单请求DTO
*/
@Data
public class CreatePayPalOrderRequestDTO implements Serializable {
/**
* 订单意图CAPTURE立即捕获或 AUTHORIZE预授权
*/
@NotBlank(message = "订单意图不能为空")
private String intent = "CAPTURE";
/**
* ERP订单号用于关联PayPal订单和ERP订单
*/
@NotBlank(message = "ERP订单号不能为空")
private String referenceId;
/**
* 订单金额
*/
@NotNull(message = "订单金额不能为空")
private BigDecimal amount;
/**
* 货币代码如USD, CNY等
*/
@NotBlank(message = "货币代码不能为空")
private String currencyCode;
/**
* 商品名称
*/
private String itemName;
/**
* 商品描述
*/
private String itemDescription;
/**
* 商品SKU
*/
private String itemSku;
/**
* 商品数量
*/
private Integer itemQuantity = 1;
/**
* 商品单价
*/
private BigDecimal itemUnitAmount;
/**
* 成功回调URLPayPal支付成功后跳转
*/
@NotBlank(message = "成功回调URL不能为空")
private String returnUrl;
/**
* 取消回调URLPayPal支付取消后跳转
*/
@NotBlank(message = "取消回调URL不能为空")
private String cancelUrl;
/**
* 收货人姓名
*/
private String shippingName;
/**
* 收货地址 - 街道地址
*/
private String shippingAddressLine1;
/**
* 收货地址 - 详细地址
*/
private String shippingAddressLine2;
/**
* 收货地址 - 城市
*/
private String shippingCity;
/**
* 收货地址 - 州/省
*/
private String shippingState;
/**
* 收货地址 - 邮编
*/
private String shippingPostalCode;
/**
* 收货地址 - 国家代码如US, CN等
*/
private String shippingCountryCode;
/**
* PayPal账户邮箱可选如果提供会预填充
*/
private String emailAddress;
}

View File

@@ -0,0 +1,13 @@
# PayPal支付配置示例
# 请将以下配置添加到你的application.yml或application-dev.yml中
paypal:
# PayPal Client ID从PayPal开发者控制台获取
client-id: YOUR_CLIENT_ID
# PayPal Client Secret从PayPal开发者控制台获取
client-secret: YOUR_CLIENT_SECRET
# 环境模式sandbox沙箱或 production生产
mode: sandbox
# 是否启用PayPal支付
enabled: true