Broadleaf REST API checkout/payment 如何创建 OrderPaymentWrapper
结论
仅凭 customerId、addressId 和 orderId,无法正确创建 OrderPaymentWrapper。
在 Broadleaf REST checkout 流程中,通常由 Spring/Jackson 将请求体反序列化为 OrderPaymentWrapper。服务端随后需要:
- 根据
orderId查询订单; - 验证订单是否属于当前登录客户,不能直接信任
customerId; - 根据
addressId查询账单地址,并校验其归属; - 设置支付类型、金额、账单地址和支付网关要求的字段;
- 将 wrapper 转换为
OrderPayment,再交给 Broadleaf 的支付服务保存或处理。
具体字段和方法名取决于 Broadleaf 版本及支付网关扩展,不能假定框架提供了一个接收这三个 ID 的通用构造函数。
推荐的请求方式
REST 客户端应通过请求体提交支付信息,例如:
POST /api/orders/123/payments
Content-Type: application/json
Authorization: Bearer <token>
请求体可以写成:
{
"type": "CREDIT_CARD",
"amount": "99.00",
"billingAddressId": 456,
"paymentToken": "gateway-generated-token"
}
这些只是常见字段名,实际应以项目中 OrderPaymentWrapper 的属性、Controller 参数和 Jackson 映射为准。
不要把原始银行卡号、CVV 等敏感数据直接传给 Broadleaf。通常由支付网关的前端 SDK 生成一次性 paymentToken,服务端只接收 token。
Endpoint 的处理思路
如果 addPaymentToOrder 已通过 @RequestBody 接收 OrderPaymentWrapper,一般不用在方法内重新 new 一个 wrapper:
@PostMapping("/orders/{orderId}/payments")
public OrderPaymentWrapper addPaymentToOrder(
@PathVariable Long orderId,
@RequestBody OrderPaymentWrapper paymentWrapper,
HttpServletRequest request,
HttpServletResponse response) {
// 1. 根据 orderId 查询订单
// 2. 验证订单属于当前登录客户
// 3. 校验 billingAddressId、amount、type 等字段
// 4. 将 wrapper 转换为 OrderPayment
// 5. 保存支付记录或调用支付网关
return paymentWrapper;
}
实际项目中,应优先复用当前 Broadleaf 版本已有的 endpoint、wrapper converter 和 payment service,避免在 Controller 里重复实现整套转换逻辑。
必须在服务端完成的校验
1. 校验订单归属
不能根据请求中的 customerId 判断订单归属。服务端应从登录上下文获取当前客户,再检查订单:
Customer customer = customerState.getCustomer();
Order order = orderService.findOrderById(orderId);
if (order == null || customer == null ||
!customer.getId().equals(order.getCustomer().getId())) {
throw new IllegalArgumentException("Order does not belong to the current customer");
}
这里的异常类型应换成项目统一使用的 REST 业务异常,例如能够返回 404 或 403 的异常。
2. 校验地址
addressId 只用于查询地址,不能直接赋给支付实体。查到地址后,还要确认当前客户有权使用它:
Address billingAddress = addressService.readAddressById(addressId);
if (billingAddress == null) {
throw new IllegalArgumentException("Billing address was not found");
}
不同 Broadleaf 版本的地址查询方法和客户地址关联方式可能不同。归属校验需要结合项目中的 CustomerAddress、AddressService 或自定义地址服务完成。
3. 设置支付信息
如果确实要在服务层手工创建 wrapper,可以使用下面的结构:
OrderPaymentWrapper wrapper = new OrderPaymentWrapper();
// 以下 setter 名称取决于当前 Broadleaf 版本
wrapper.setOrderId(order.getId());
wrapper.setAmount(amount);
wrapper.setType(paymentType);
wrapper.setPaymentToken(paymentToken);
// 某些版本接收地址 ID,某些版本接收嵌套的 AddressWrapper
wrapper.setBillingAddressId(billingAddress.getId());
这段代码只说明创建流程,不代表其中所有 setter 都存在于你的 Broadleaf 版本中。如果 OrderPaymentWrapper 使用嵌套地址对象,应先通过项目提供的 wrapper 或 converter,将 Address 转换成 AddressWrapper:
AddressWrapper addressWrapper = addressConverter.wrap(billingAddress);
wrapper.setBillingAddress(addressWrapper);
转换器名称也取决于具体版本或项目实现,应以现有源码和 Spring Bean 配置为准。
customerId 应该放在哪里
通常不需要把 customerId 放入 OrderPaymentWrapper。客户、订单和支付之间的关系应由服务端实体关系建立:
Current Customer
└── Order(orderId)
└── OrderPayment
└── Billing Address
各个 ID 和 wrapper 的职责分别是:
customerId用于鉴权或查询当前客户;orderId用于定位订单;addressId用于定位账单地址;- wrapper 用于承载支付类型、金额、token、账单地址等支付数据。
这样可以防止攻击者篡改 customerId,将支付信息添加到其他客户的订单中。
还需要注意
- 服务端应根据订单余额核对支付金额,不能完全相信客户端提交的值。
- 同一订单可能允许添加多个
OrderPayment,因此要检查累计支付金额是否超过应付金额。 - 创建
OrderPayment和真正扣款通常分属两个阶段,添加支付记录不代表支付成功。 - 支付网关可能要求 nonce、token、交易类型、网关名称等额外字段。这些字段一般放在 wrapper 的扩展字段或网关专用请求对象中。
- 如果当前版本提供了
OrderPaymentWrapper的unwrap、converter 或 endpoint adapter,应优先使用,避免手工构造OrderPayment。 - 字段定义应以项目实际依赖版本中的
OrderPaymentWrapper类和现有 checkout endpoint 为准,因为不同 Broadleaf 版本的 REST wrapper API 并不完全一致。
备注:内容仅供参考。
