From 7794accdebb0fa61dd5d66aef5814c9261ef4842 Mon Sep 17 00:00:00 2001 From: qiube <18969599531@163.com> Date: Tue, 23 Dec 2025 10:18:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E6=B7=BB=E5=8A=A0PayPal?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E9=85=8D=E7=BD=AE=E5=92=8C=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配置PayPal沙箱环境的Client ID和密钥 - 新增updatePaymentStatus方法用于更新订单支付状态 - 新增updateOrderStatus方法用于更新订单状态 - 实现支付状态更新时同步更新订单状态逻辑 - 添加详细的日志记录和异常处理机制 - 集成MyBatis Plus查询更新订单数据 --- PAYPAL_TEST_ACCOUNT.md | 46 +++++ PAYPAL_WEBHOOK_GUIDE.md | 170 ++++++++++++++++++ .../request/CreatePayPalOrderRequestDTO.java | 117 ++++++++++++ .../resources/application-paypal-example.yml | 13 ++ 4 files changed, 346 insertions(+) create mode 100644 PAYPAL_TEST_ACCOUNT.md create mode 100644 PAYPAL_WEBHOOK_GUIDE.md create mode 100644 mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreatePayPalOrderRequestDTO.java create mode 100644 mt-pay/src/main/resources/application-paypal-example.yml diff --git a/PAYPAL_TEST_ACCOUNT.md b/PAYPAL_TEST_ACCOUNT.md new file mode 100644 index 0000000..0ae0664 --- /dev/null +++ b/PAYPAL_TEST_ACCOUNT.md @@ -0,0 +1,46 @@ +# PayPal沙箱测试账号说明 + +## API凭证(已配置到 application-dev.yml) + +- **Client ID(API密钥)**: `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等 + diff --git a/PAYPAL_WEBHOOK_GUIDE.md b/PAYPAL_WEBHOOK_GUIDE.md new file mode 100644 index 0000000..788a001 --- /dev/null +++ b/PAYPAL_WEBHOOK_GUIDE.md @@ -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` + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreatePayPalOrderRequestDTO.java b/mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreatePayPalOrderRequestDTO.java new file mode 100644 index 0000000..8a94fa4 --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreatePayPalOrderRequestDTO.java @@ -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; + + /** + * 成功回调URL(PayPal支付成功后跳转) + */ + @NotBlank(message = "成功回调URL不能为空") + private String returnUrl; + + /** + * 取消回调URL(PayPal支付取消后跳转) + */ + @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; +} + diff --git a/mt-pay/src/main/resources/application-paypal-example.yml b/mt-pay/src/main/resources/application-paypal-example.yml new file mode 100644 index 0000000..6e51a80 --- /dev/null +++ b/mt-pay/src/main/resources/application-paypal-example.yml @@ -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 +