diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/ApprovalApiController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/ApprovalApiController.java index 2cf8374..95ce59c 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/ApprovalApiController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/ApprovalApiController.java @@ -5,6 +5,7 @@ import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.domain.shop.approval.ReturnApprovalApplicationService; import com.agileboot.domain.shop.approval.command.AddReturnApprovalCommand; +import com.agileboot.domain.shop.approval.db.ReturnApprovalEntity; import com.agileboot.domain.shop.approval.model.ReturnApprovalModel; import com.agileboot.domain.shop.order.OrderApplicationService; import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity; @@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; +import java.util.Date; /** * 审批请求控制器 @@ -51,37 +53,25 @@ public class ApprovalApiController { */ @PostMapping("/submit") @ApiOperation(value = "提交退货审批") - public ResponseDTO submitApproval(@Valid @RequestBody AddReturnApprovalCommand command) { - try { - if (null == command.getOrderId()) { - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "订单ID不能为空")); - } - if (null == command.getGoodsId()) { - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "商品ID不能为空")); - } - if (null == command.getReturnQuantity()) { - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "归还数量不能为空")); - } - if (StringUtils.isBlank(command.getReturnImages())) { - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "归还图片不能为空")); - } - - // 查询订单商品信息 - ShopOrderGoodsEntity orderGoods = orderApplicationService.getOrderGoodsByOrderIdAndGoodsId(command.getOrderId(), command.getGoodsId()); - if (null == orderGoods) { - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "订单商品不存在")); - } - - // 设置商品价格并初始化审批状态 - command.setGoodsPrice(orderGoods.getPrice()); - command.setStatus(1); - - // 执行业务逻辑 - ReturnApprovalModel returnApprovalModel = approvalApplicationService.addApproval(command); - return ResponseDTO.ok(returnApprovalModel); - } catch (Exception e) { - log.error("提交审批失败", e); - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "提交审批失败")); + public ResponseDTO submitApproval(@Valid @RequestBody AddReturnApprovalCommand command) { + if (null == command.getOrderGoodsId()) { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "订单商品ID不能为空")); } + if (null == command.getReturnQuantity()) { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "归还数量不能为空")); + } + if (StringUtils.isBlank(command.getReturnImages())) { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "归还图片不能为空")); + } + + // 查询订单商品信息 + ShopOrderGoodsEntity orderGoods = orderApplicationService.getOrderGoodsById(command.getOrderGoodsId()); + if (null == orderGoods) { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "订单商品不存在")); + } + + // 执行业务逻辑 + ReturnApprovalEntity returnApproval = approvalApplicationService.submitApproval(command, orderGoods); + return ResponseDTO.ok(returnApproval); } } diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java index f909623..bc35a1f 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java @@ -1,31 +1,37 @@ package com.agileboot.api.controller; +import com.agileboot.common.constant.PayApiConstants; import com.agileboot.common.core.base.BaseController; import com.agileboot.domain.shop.order.dto.CreateOrderResult; import com.agileboot.domain.shop.order.dto.GetOrdersByOpenIdDTO; +import com.agileboot.domain.shop.order.model.OrderGoodsModelFactory; +import com.agileboot.domain.shop.order.model.OrderModel; +import com.agileboot.domain.shop.payment.PaymentApplicationService; +import com.agileboot.domain.shop.payment.dto.RefundVO; +import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.domain.shop.order.OrderApplicationService; import com.agileboot.domain.shop.order.command.SubmitOrderCommand; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; + +import java.math.BigDecimal; +import java.math.RoundingMode; /** * 调度日志操作处理 * * @author valarchie */ +@Slf4j @RequiredArgsConstructor @RestController @RequestMapping("/api/order") public class OrderController extends BaseController { private final OrderApplicationService orderApplicationService; + private final PaymentApplicationService paymentApplicationService; // 新增提交订单接口 @PostMapping("/submit") @@ -45,4 +51,30 @@ public class OrderController extends BaseController { GetOrdersByOpenIdDTO result = orderApplicationService.getOrdersByOpenId(openid); return ResponseDTO.ok(result); } + + @PostMapping("/refund/{orderId}") + public ResponseDTO refundOrder(@PathVariable Long orderId, @RequestParam int money) { + OrderModel orderModel = orderApplicationService.loadById(orderId); + try { + // 退款金额对比, 退款金额不能大于订单金额 + // 金额转换(元转分)并四舍五入 + BigDecimal amountInFen = orderModel.getTotalAmount() + .multiply(new BigDecimal("100")) + .setScale(0, RoundingMode.HALF_UP); + + if (money < 0) { + throw new IllegalArgumentException("退款金额不能为负数"); + } + BigDecimal moneyDecimal = new BigDecimal(money); + if (moneyDecimal.compareTo(amountInFen) > 0) { + throw new IllegalArgumentException("退款金额不能超过订单总额"); + } + + RefundVO refundVO = paymentApplicationService.refund(PayApiConstants.biz_id, PayApiConstants.appkey, orderModel.getBizOrderId(), orderModel.getUcid(), "退还", money); + log.info("退款结果:{}", refundVO); + return ResponseDTO.ok(refundVO); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java index fad09fe..54243ac 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java @@ -22,6 +22,7 @@ import com.agileboot.domain.qywx.user.QyUserApplicationService; import com.agileboot.domain.qywx.user.db.QyUserEntity; import com.agileboot.domain.qywx.userQySys.SysUserQyUserApplicationService; import com.agileboot.domain.shop.order.OrderApplicationService; +import com.agileboot.domain.shop.payment.PaymentApplicationService; import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest; import java.nio.charset.StandardCharsets; import java.util.*; @@ -56,12 +57,12 @@ import org.springframework.web.client.RestTemplate; @RequiredArgsConstructor @RequestMapping("/api/payment") public class PaymentController { - private final OrderApplicationService orderApplicationService; private final AccessTokenApplicationService accessTokenApplicationService; private final QyUserApplicationService qyUserApplicationService; private final AuthCorpInfoApplicationService authCorpInfoApplicationService; private final SysUserQyUserApplicationService sysUserQyUserApplicationService; private final MenuApplicationService menuApplicationService; + private final PaymentApplicationService paymentApplicationService; // 新增回调接口 /** @@ -78,31 +79,7 @@ public class PaymentController { public String paymentCallback(HttpServletRequest request, @RequestBody String requestBody) { log.info("支付回调requestBody:{}", requestBody); try { - // 1. 参数解析 - PaymentCallbackRequest callbackReq = parseCallbackRequest(requestBody); - PaymentCallbackRequest.BizContent bizContent = parseBizContent(callbackReq.getBiz_content()); - - // 2. 签名验证(需要根据biz_id获取对应的appKey) - String appKey = getAppKeyByBizId(callbackReq.getBiz_id()); // 需要实现根据biz_id获取appKey的逻辑 - boolean signValid = OpenSignUtil.checkOpenSign(appKey, callbackReq.getSign(), requestBody); - - if (!signValid) { - log.error("支付回调签名验证失败:{}", requestBody); - return "fail"; - } - - if (bizContent.getTrade_status().equals("SUCCESS")) { - // 3. 业务处理(需要实现幂等性校验) - handlePaymentSuccess( - bizContent.getBiz_order_id(), - bizContent.getTotal_amount(), - bizContent.getTrade_id(), - bizContent.getTrade_pay_time() - ); - } else { - log.error("支付订单失败requestBody:{}", requestBody); - } - + paymentApplicationService.paymentCallback(requestBody); return "success"; } catch (Exception e) { log.error("支付回调处理失败", e); @@ -110,6 +87,35 @@ public class PaymentController { } } + /** + * 微信支付退款回调接口 + * @param request HTTP请求对象,用于获取请求头信息 + * @param requestBody 回调请求体(URL编码格式的XML数据) + * @return 处理结果:"success"表示成功处理并停止通知,"fail"表示需要微信重新发起通知 + * @throws ApiException 当出现以下情况时抛出: + *
    + *
  • 签名验证失败
  • + *
  • 数据解密失败
  • + *
  • 退款状态异常
  • + *
+ * @apiNote 该接口需要处理以下流程: + * 1. 签名验证(使用微信支付API密钥) + * 2. 解密退款结果(如需加密传输) + * 3. 退款状态判断(成功/失败) + * 4. 更新订单退款状态(需保证幂等性) + * 5. 按微信接口规范返回正确处理结果 + */ + @PostMapping("/refund/callback") + public String refundCallback(HttpServletRequest request, @RequestBody String requestBody) { + log.info("退款回调requestBody:{}", requestBody); + try { + return paymentApplicationService.refundCallback(requestBody); + } catch (Exception e) { + log.error("支付回调处理失败", e); + return "fail"; + } + } + /** * 获取微信用户OpenID * @param code 微信授权码 @@ -255,37 +261,7 @@ public class PaymentController { return ResponseDTO.ok(response); } - private PaymentCallbackRequest parseCallbackRequest(String requestBody) { - // 实现将URL参数解析为PaymentCallbackRequest - // 示例实现(需要根据实际参数格式调整): - Map paramMap = new HashMap<>(); - for (String param : requestBody.split("&")) { - String[] pair = param.split("="); - if (pair.length == 2) { - paramMap.put(pair[0], URLDecoder.decode(pair[1], StandardCharsets.UTF_8)); - } - } - return BeanUtil.toBean(paramMap, PaymentCallbackRequest.class); - } - private PaymentCallbackRequest.BizContent parseBizContent(String bizContent) { - // 实现biz_content的JSON解析 - return JSONUtil.toBean(bizContent, PaymentCallbackRequest.BizContent.class); - } - // 需要实现的方法(根据业务需求补充) - private String getAppKeyByBizId(String bizId) { - // 根据biz_id从数据库或配置获取对应的appKey - return "wxshop202503081132"; - } - - private void handlePaymentSuccess(String bizOrderId, Integer amount, String tradeId, String tradePayTime) { - // 实现订单状态更新和幂等性校验 - if (StringUtils.isNotBlank(bizOrderId) && bizOrderId.startsWith("wxshop-")) { - // 订单号格式为 wxshop-1-time,提取中间的订单号 - String orderId = bizOrderId.split("-")[1]; - orderApplicationService.handlePaymentSuccess(Long.valueOf(orderId), amount, tradeId, tradePayTime); - } - } } diff --git a/agileboot-common/src/main/java/com/agileboot/common/constant/PayApiConstants.java b/agileboot-common/src/main/java/com/agileboot/common/constant/PayApiConstants.java new file mode 100644 index 0000000..c63320a --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/constant/PayApiConstants.java @@ -0,0 +1,7 @@ +package com.agileboot.common.constant; + +public class PayApiConstants { + public static final String biz_id = "wxshop"; + public static final String appkey = "wxshop202503081132"; + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/OpenSignUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/OpenSignUtil.java index baa5067..58e3b55 100644 --- a/agileboot-common/src/main/java/com/agileboot/common/utils/OpenSignUtil.java +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/OpenSignUtil.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; + +import com.agileboot.common.constant.PayApiConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.Md5Crypt; import org.apache.commons.collections4.MapUtils; @@ -82,7 +84,7 @@ public class OpenSignUtil { + "2448492%22%2C%22trade_id%22%3A%221063669415%22%2C%22total_amount%22%3A1%2C%22extra%22%3A%22%22%2C%22trade_pay_time%22%3A%222025-03-20+17%3A20%3A55%22%2C%22trade_status%22%3A%22SUCCESS%22%2C%22pay_type%22%3A%22116%22%2C%22callback_content%22%3A%22%3Cxml%3E%3Cappid%3E%3C%21%5BCDATA%5Bwx9922dfbb0d4cd7bb%5D%5D%3E%3C%2Fappid%3E%5Cn%3Cattach%3E%3C%21%5BCDATA%5B%257B%2522biz_id%2522%253A%2522wxshop%2522%252C%2522trade_id%2522%253A%25221063669415%2522%257D%5D%5D%3E%3C%2Fattach%3E%5Cn%3Cbank_type%3E%3C%21%5BCDATA%5BOTHERS%5D%5D%3E%3C%2Fbank_type%3E%5Cn%3Ccash_fee%3E%3C%21%5BCDATA%5B1%5D%5D%3E%3C%2Fcash_fee%3E%5Cn%3Cfee_type%3E%3C%21%5BCDATA%5BCNY%5D%5D%3E%3C%2Ffee_type%3E%5Cn%3Cis_subscribe%3E%3C%21%5BCDATA%5BN%5D%5D%3E%3C%2Fis_subscribe%3E%5Cn%3Cmch_id%3E%3C%21%5BCDATA%5B1625101806%5D%5D%3E%3C%2Fmch_id%3E%5Cn%3Cnonce_str%3E%3C%21%5BCDATA%5B24NffiTHxNYm0ppw3QE9WezmzJQDnJQV%5D%5D%3E%3C%2Fnonce_str%3E%5Cn%3Copenid%3E%3C%21%5BCDATA%5BoMRxw6Eum0DB1IjI_pEX_yrawBHw%5D%5D%3E%3C%2Fopenid%3E%5Cn%3Cout_trade_no%3E%3C%21%5BCDATA" + "%5Bwxshop-10-1742462448212%5D%5D%3E%3C%2Fout_trade_no%3E%5Cn%3Cresult_code%3E%3C%21%5BCDATA%5BSUCCESS%5D%5D%3E%3C%2Fresult_code%3E%5Cn%3Creturn_code%3E%3C%21%5BCDATA%5BSUCCESS%5D%5D%3E%3C%2Freturn_code%3E%5Cn%3Csign%3E%3C%21%5BCDATA%5B9CE8A123437E591166DDAF92A750C122%5D%5D%3E%3C%2Fsign%3E%5Cn%3Ctime_end%3E%3C%21%5BCDATA%5B20250320172055%5D%5D%3E%3C%2Ftime_end%3E%5Cn%3Ctotal_fee%3E1%3C%2Ftotal_fee%3E%5Cn%3Ctrade_type%3E%3C%21%5BCDATA%5BJSAPI%5D%5D%3E%3C%2Ftrade_type%3E%5Cn%3Ctransaction_id%3E%3C%21%5BCDATA%5B4200002695202503209706830758%5D%5D%3E%3C%2Ftransaction_id%3E%5Cn%3C%2Fxml%3E%22%2C%22title%22%3A%22%E5%95%86%E5%93%81%E8%AE%A2%E5%8D%95%E6%94%AF%E4%BB%98%22%2C%22biz_order_id%22%3A%22wxshop-10-1742462448212%22%7D&nonce_str=6d80bd6b542a499994adf4ee4c1e89c4&sign=f1ffc9cfc61cf60d8a1acbac0399cd0b&biz_id=wxshop&sign_type=MD5&version=1.0×tamp=1742462460390"; - Boolean res = checkOpenSign("wxshop202503081132", "f1ffc9cfc61cf60d8a1acbac0399cd0b", body); + Boolean res = checkOpenSign(PayApiConstants.appkey, "f1ffc9cfc61cf60d8a1acbac0399cd0b", body); log.info("res:{}", res); } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java index 8c2b9dd..43a0c90 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java @@ -10,7 +10,12 @@ import com.agileboot.domain.shop.approval.dto.ReturnApprovalDTO; import com.agileboot.domain.shop.approval.model.ReturnApprovalModel; import com.agileboot.domain.shop.approval.model.ReturnApprovalModelFactory; import com.agileboot.domain.shop.approval.query.SearchReturnApprovalQuery; +import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity; +import com.agileboot.domain.shop.order.model.OrderGoodsModel; +import com.agileboot.domain.shop.order.model.OrderGoodsModelFactory; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import java.util.Date; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -24,6 +29,7 @@ public class ReturnApprovalApplicationService { private final ReturnApprovalService approvalService; private final ReturnApprovalModelFactory modelFactory; + private final OrderGoodsModelFactory orderGoodsModelFactory; public PageDTO getApprovalList(SearchReturnApprovalQuery query) { Page page = approvalService.getApprovalList(query); @@ -52,4 +58,29 @@ public class ReturnApprovalApplicationService { model.deleteById(); } } + + public ReturnApprovalEntity submitApproval(AddReturnApprovalCommand command, ShopOrderGoodsEntity orderGoods) { + // 设置商品价格并初始化审批状态 + command.setGoodsId(orderGoods.getGoodsId()); + command.setOrderId(orderGoods.getOrderId()); + command.setGoodsPrice(orderGoods.getPrice()); + command.setReturnImages(command.getReturnImages()); + command.setReturnRemark(command.getReturnRemark()); + command.setStatus(1); + command.setCreatorId(0L); + command.setCreateTime(new Date()); + command.setUpdaterId(0L); + command.setUpdateTime(new Date()); + command.setDeleted(false); + + // 执行业务逻辑 + ReturnApprovalModel returnApprovalModel = addApproval(command); + + // 更新订单商品状态 + OrderGoodsModel orderGoodsModel = orderGoodsModelFactory.create(orderGoods); + orderGoodsModel.setStatus(5); + orderGoodsModel.updateById(); + + return returnApprovalModel.selectById(); + } } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalEntity.java index 3bc4751..34c128d 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalEntity.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalEntity.java @@ -40,6 +40,10 @@ public class ReturnApprovalEntity extends BaseEntity { @TableField("goods_id") private Long goodsId; + @ApiModelProperty("关联订单商品ID") + @TableField("order_goods_id") + private Long orderGoodsId; + @ApiModelProperty("归还数量") @TableField("return_quantity") private Integer returnQuantity; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalMapper.java index 13efa6c..c318e3b 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalMapper.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/db/ReturnApprovalMapper.java @@ -17,7 +17,7 @@ import org.apache.ibatis.annotations.Select; * @since 2025-04-03 */ public interface ReturnApprovalMapper extends BaseMapper { - @Select("SELECT approval_id, order_id, goods_id, return_quantity, goods_price, return_amount, return_images, audit_images, return_remark, audit_remark, status " + + @Select("SELECT * " + "FROM return_approval " + "${ew.customSqlSegment}") Page getApprovalList( diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/dto/ReturnApprovalDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/dto/ReturnApprovalDTO.java index 2cfc844..f3218b0 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/dto/ReturnApprovalDTO.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/dto/ReturnApprovalDTO.java @@ -41,6 +41,9 @@ public class ReturnApprovalDTO { @ExcelColumn(name = "关联商品ID") private Long goodsId; + @ExcelColumn(name = "关联订单商品ID") + private Long orderGoodsId; + @ExcelColumn(name = "归还数量") private Integer returnQuantity; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java index 551c7cf..c650743 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java @@ -1,6 +1,7 @@ package com.agileboot.domain.shop.order; import cn.hutool.core.date.DateUtil; +import com.agileboot.common.constant.PayApiConstants; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity; @@ -160,7 +161,7 @@ public class OrderApplicationService { WxJsApiPreCreateRequest request = new WxJsApiPreCreateRequest(); request.setIp("222.218.10.217"); request.setOpenid(orderModel.getOpenid()); // - request.setBiz_order_id("wxshop-" + orderModel.getOrderId() + "-" + new Date().getTime()); // 使用订单唯一编号 + request.setBiz_order_id(orderModel.getBizOrderId()); // 使用订单唯一编号 // 金额转换(元转分)并四舍五入 BigDecimal amountInFen = orderModel.getTotalAmount() .multiply(new BigDecimal("100")) @@ -169,7 +170,7 @@ public class OrderApplicationService { request.setTitle("商品订单支付"); request.setNotify_url("http://wxshop.ab98.cn/shop-api/api/payment/callback"); - request.setBiz_id("wxshop"); + request.setBiz_id(PayApiConstants.biz_id); request.setUcid(orderModel.getUcid()); request.setExtra(""); return request; @@ -203,6 +204,7 @@ public class OrderApplicationService { } // 更新订单总金额 + orderModel.setBizOrderId("wxshop-" + orderModel.getOrderId() + "-" + new Date().getTime()); orderModel.setTotalAmount(totalAmount); orderModel.updateById(); } @@ -252,31 +254,7 @@ public class OrderApplicationService { public void handlePaymentSuccess(Long orderId, Integer amount, String tradeId, String tradePayTime) { OrderModel orderModel = orderModelFactory.loadById(orderId); - // 状态校验 - orderModel.validateStatusTransition(2); // 2代表已支付 - // 更新订单状态 - orderModel.setStatus(2); - orderModel.setPayStatus(2); - orderModel.setTradeId(tradeId); - try { -// if (tradePayTime.contains("+")) { -// tradePayTime = tradePayTime.replace("+", " "); -// } - orderModel.setPayTime(DateUtil.parse(tradePayTime)); - } catch (Exception e) { - log.error("支付时间转换失败", e); - } - orderModel.updateById(); - - - // 发送指令 - // 客户端手动开柜 -// QueryWrapper orderGoodsQueryWrapper = new QueryWrapper<>(); -// orderGoodsQueryWrapper.eq("order_id", orderId); -// List orderGoods = orderGoodsService.list(orderGoodsQueryWrapper); -// orderGoods.forEach(g -> { -// openOrderGoodsCabinet(orderId, g.getOrderGoodsId()); -// }); + orderModel.handlePaymentSuccess(amount, tradeId, tradePayTime); } public static void main(String[] args) { @@ -307,4 +285,12 @@ public class OrderApplicationService { public ShopOrderGoodsEntity getOrderGoodsByOrderIdAndGoodsId(Long orderId, Long goodsId) { return orderGoodsService.getByOrderIdAndGoodsId(orderId, goodsId); } + + public ShopOrderGoodsEntity getOrderGoodsById(Long orderGoodsId) { + return orderGoodsService.getById(orderGoodsId); + } + + public OrderModel loadById(Long orderId) { + return orderModelFactory.loadById(orderId); + } } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java index 049d69e..9cbb826 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java @@ -45,6 +45,10 @@ public class ShopOrderEntity extends BaseEntity { @TableField("trade_id") private String tradeId; + @ApiModelProperty("业务系统订单ID(对接外部系统)") + @TableField("biz_order_id") + private String bizOrderId; + @ApiModelProperty("订单总金额") @TableField("total_amount") private BigDecimal totalAmount; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderGoodsEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderGoodsEntity.java index 96dc3c2..6a8d2c8 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderGoodsEntity.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderGoodsEntity.java @@ -52,7 +52,7 @@ public class ShopOrderGoodsEntity extends BaseEntity { @TableField("total_amount") private BigDecimal totalAmount; - @ApiModelProperty("商品状态(1正常 2已退货 3已换货 4已完成)") + @ApiModelProperty("商品状态(1正常 2已退货 3已换货 4已完成 5审核中 6退货未通过)") @TableField("`status`") private Integer status; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderService.java index c4363f5..c87b01e 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderService.java @@ -11,5 +11,4 @@ import com.baomidou.mybatisplus.extension.service.IService; * @since 2025-03-10 */ public interface ShopOrderService extends IService { - } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderServiceImpl.java index fc724c4..159bae0 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderServiceImpl.java @@ -1,5 +1,7 @@ package com.agileboot.domain.shop.order.db; +import cn.hutool.core.date.DateUtil; +import com.agileboot.domain.shop.order.model.OrderModel; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @@ -13,5 +15,4 @@ import org.springframework.stereotype.Service; */ @Service public class ShopOrderServiceImpl extends ServiceImpl implements ShopOrderService { - } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java index bbfbf0f..ccb92c8 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/model/OrderModel.java @@ -1,6 +1,7 @@ package com.agileboot.domain.shop.order.model; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; import com.agileboot.common.config.AgileBootConfig; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; @@ -11,7 +12,9 @@ import java.math.BigDecimal; import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +@Slf4j @EqualsAndHashCode(callSuper = true) @Data public class OrderModel extends ShopOrderEntity { @@ -68,4 +71,25 @@ public class OrderModel extends ShopOrderEntity { String orderNo = String.valueOf(System.currentTimeMillis() + (int)(Math.random()*1000)); this.setUcid(orderNo); } + + public void handlePaymentSuccess(Integer amount, String tradeId, String tradePayTime) { + // 状态校验 + this.validateStatusTransition(2); // 2代表已支付 + // 更新订单状态 + this.setStatus(2); + this.setPayStatus(2); + this.setTradeId(tradeId); + try { + this.setPayTime(DateUtil.parse(tradePayTime)); + } catch (Exception e) { + log.error("支付时间转换失败", e); + } + this.updateById(); + } + + public void handleRefundSuccess() { + // 更新订单状态 + this.setPayStatus(4); + this.updateById(); + } } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/PaymentApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/PaymentApplicationService.java index d5f156c..3d31876 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/PaymentApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/PaymentApplicationService.java @@ -1,22 +1,38 @@ package com.agileboot.domain.shop.payment; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.TypeReference; +import cn.hutool.core.net.URLDecoder; +import cn.hutool.core.util.URLUtil; import cn.hutool.http.ContentType; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; -import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateRequest; -import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse; +import com.agileboot.common.constant.PayApiConstants; +import com.agileboot.common.utils.OpenSignUtil; +import com.agileboot.domain.shop.order.model.OrderModel; +import com.agileboot.domain.shop.order.model.OrderModelFactory; +import com.agileboot.domain.shop.payment.dto.*; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + @Slf4j @Service @RequiredArgsConstructor public class PaymentApplicationService { + private final OrderModelFactory orderModelFactory; + private static final Object LOCKER = new Object(); + public WxJsApiPreCreateResponse callJsApiPreCreate(WxJsApiPreCreateRequest request) { String gatewayUrl = "http://222.218.10.217:7890/open/trade/wx/jsapi/precreate"; @@ -61,13 +77,157 @@ public class PaymentApplicationService { } } - // 添加错误响应处理类 - @Data - private static class PaymentGatewayError { - private String timestamp; - private Integer status; - private String error; - private String message; - private String path; + public void paymentCallback(String requestBody) { + + // 1. 参数解析 + PaymentCallbackRequest callbackReq = parseCallbackRequest(requestBody); + PaymentCallbackRequest.BizContent bizContent = parseBizContent(callbackReq.getBiz_content()); + + // 2. 签名验证(需要根据biz_id获取对应的appKey) + String appKey = getAppKeyByBizId(callbackReq.getBiz_id()); // 需要实现根据biz_id获取appKey的逻辑 + boolean signValid = OpenSignUtil.checkOpenSign(appKey, callbackReq.getSign(), requestBody); + + if (!signValid) { + log.error("支付回调签名验证失败:{}", requestBody); + throw new RuntimeException("支付回调签名验证失败"); + } + + if (bizContent.getTrade_status().equals("SUCCESS")) { + // 3. 业务处理(需要实现幂等性校验) + handlePaymentSuccess( + bizContent.getBiz_order_id(), + bizContent.getTotal_amount(), + bizContent.getTrade_id(), + bizContent.getTrade_pay_time() + ); + } else { + log.error("支付订单失败requestBody:{}", requestBody); + } + } + + /** + * 退款 + * @param bizId 支付网关分配的业务应用ID + * @param appKey 支付网关分配的业务应用秘钥 + * @param orderId 支付时的订单ID + * @param uid 用户ID + * @param reason 退款理由 + * @param money 退款金额,单位:分 + */ + public RefundVO refund(String bizId, String appKey, String orderId, String uid, String reason, int money) throws Exception { + String url = "http://222.218.10.217:7890/open/trade/refund"; + JSONObject bizContent = new JSONObject(); + bizContent.set("userId", uid); + bizContent.set("bizId", bizId); + bizContent.set("orderId", orderId); + bizContent.set("refundFee", money); + bizContent.set("refundReason", reason); + Map params = new HashMap<>(); + params.put("client_id", UUID.randomUUID().toString()); + params.put("biz_id", bizId); + params.put("sign_type", "md5"); + params.put("timestamp", String.valueOf(System.currentTimeMillis())); + params.put("version", "1.0"); + params.put("nonce_str", UUID.randomUUID().toString()); + params.put("biz_content", URLUtil.encode(JSONUtil.toJsonStr(bizContent))); + params.put("sign", SignUtils.openSign(appKey, params)); + StringBuilder sb = new StringBuilder(); + for (String key : params.keySet()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(key).append("=").append(params.get(key)); + } + String result = HttpUtil.post(url, sb.toString()); + CommonResponse res = JSONUtil.toBean(result, new TypeReference>(){}, true); + if (res.getCode() != CommonErrorCode.OK.getErrorCode()) { + throw new Exception(res.getMsg()); + } + return res.getData(); + } + + private PaymentCallbackRequest parseCallbackRequest(String requestBody) { + // 实现将URL参数解析为PaymentCallbackRequest + // 示例实现(需要根据实际参数格式调整): + Map paramMap = new HashMap<>(); + for (String param : requestBody.split("&")) { + String[] pair = param.split("="); + if (pair.length == 2) { + paramMap.put(pair[0], URLDecoder.decode(pair[1], StandardCharsets.UTF_8)); + } + } + + return BeanUtil.toBean(paramMap, PaymentCallbackRequest.class); + } + + private PaymentCallbackRequest.BizContent parseBizContent(String bizContent) { + // 实现biz_content的JSON解析 + return JSONUtil.toBean(bizContent, PaymentCallbackRequest.BizContent.class); + } + + // 需要实现的方法(根据业务需求补充) + private String getAppKeyByBizId(String bizId) { + // 根据biz_id从数据库或配置获取对应的appKey + return PayApiConstants.appkey; + } + + private void handlePaymentSuccess(String bizOrderId, Integer amount, String tradeId, String tradePayTime) { + // 实现订单状态更新和幂等性校验 + if (StringUtils.isNotBlank(bizOrderId) && bizOrderId.startsWith("wxshop-")) { + // 订单号格式为 wxshop-1-time,提取中间的订单号 + String orderId = bizOrderId.split("-")[1]; + OrderModel orderModel = orderModelFactory.loadById(Long.valueOf(orderId)); + orderModel.handlePaymentSuccess(amount, tradeId, tradePayTime); + } + } + + public String refundCallback(String reqBody) { + JSONObject res = new JSONObject(); + System.out.println("退款回调:" + reqBody); + CommonRequest notifyRequest = CommonRequest.build(reqBody, RefundVO.class); + if (notifyRequest == null || notifyRequest.getBizContent() == null) { + res.set("callback_code", 1); + res.set("callback_msg", "请求body或bizcontent为空"); + System.out.println("退款回调处理失败, 原因:请求body或bizcontent为空"); + return JSONUtil.toJsonStr(res); + } + MyError error = checkSign(notifyRequest.getBizId(), notifyRequest.getSign(), reqBody); + if (error != null) { + res.set("callback_code", 1); + res.set("callback_msg", error.getCode()); + System.out.printf("退款回调处理失败, 原因:%s%n", error.getMsg()); + return JSONUtil.toJsonStr(res); + } + RefundVO bizContent = notifyRequest.getBizContent(); + + String orderNO = bizContent.getBizOrderId(); + if (StringUtils.isBlank(orderNO)) { + res.put("callback_code", 1); + res.put("callback_msg", "缺少退款订单ID"); + return JSONUtil.toJsonStr(res); + } + synchronized (LOCKER) { + //根据退款订单号 orderNo 做你的业务处理。退款回调可能会 + //有多次,业务应用要做好幂等处理 + if (orderNO.startsWith("wxshop-")) { + // 订单号格式为 wxshop-1-time,提取中间的订单号 + String orderId = orderNO.split("-")[1]; + OrderModel orderModel = orderModelFactory.loadById(Long.valueOf(orderId)); + orderModel.handleRefundSuccess(); + } + } + res.set("callback_code", 0); + res.set("callback_msg", "ok"); + return JSONUtil.toJsonStr(res); + } + + private MyError checkSign(String bizId, String sign, String reqBody) { + if (StringUtils.isBlank(bizId)) { + return new MyError(CommonErrorCode.BAD_PARAMETER, "bizId is blank"); + } + if (!SignUtils.checkOpenSign(getAppKeyByBizId(bizId), sign, reqBody)) { + return new MyError(CommonErrorCode.BAD_PARAMETER, String.format("Invalid sign: %s", sign)); + } + return null; } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/SignUtils.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/SignUtils.java new file mode 100644 index 0000000..39e414b --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/SignUtils.java @@ -0,0 +1,122 @@ +package com.agileboot.domain.shop.payment; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.util.*; + +@Slf4j +public final class SignUtils { + + /** + * 珊瑚签名计算 + */ + public static String coralSign(String secret, Map params) { + return commonSign(secret, params); + } + + /** + * 业务方签名计算 + */ + public static String bizSign(String secret, Map params) { + return commonSign(secret, params); + } + + /** + * 默认签到计算:params根据key进行字典排序,然后将对应值直接拼成字符串(null跳过),再将业务预生成的"secret"拼在最后,用MD5算法生成签名 + * @param secret appKey/appSecret + * @param params 需要签名的参数 + * @return 签名 + */ + public static String commonSign(String secret, Map params) { + Map sortedParams = new TreeMap<>(params); + StringBuilder plainText = new StringBuilder(); + sortedParams.values().stream().filter(Objects::nonNull).forEach(plainText::append); + plainText.append(secret); + try { + return md5(plainText.toString().getBytes("utf-8")); + } catch (Exception e) { + return null; + } + } + + /** + * 服务端接口加签校验,按照Key升序排列,拼接key1=value1&key2=value2(value为null跳过, value需要url encode),在后面拼接上&app_key=xxx,md5生成sign值 + * @param appKey + * @param params + * @return + */ + public static String openSign(String appKey, Map params) { + if(MapUtils.isEmpty(params)) { + params = new HashMap(1); + } + Map sortedParams = new TreeMap<>(params); + List kvPairList = getKVList(sortedParams); + String sourceText = StringUtils.join(kvPairList, "&"); + sourceText = sourceText + "&app_key=" + appKey; + log.info("sourceText:" + sourceText); + try { + return md5(sourceText.getBytes("utf-8")); + } catch (Exception e) { + return null; + } + } + + public static boolean checkOpenSign(String appKey, String sign, String reqBody) { + String[] fields = reqBody.split("&"); + Map fieldMap = new HashMap<>(); + for (String field : fields) { + String[] pair = field.split("="); + if (pair.length != 2) { + continue; + } + // sign字段不参与校验,空字段不校验 + if (!"sign".equals(pair[0]) && StringUtils.isNotBlank(pair[1])) { + fieldMap.put(pair[0], pair[1]); + } + } + String generateSign = openSign(appKey, fieldMap); + return Objects.equals(generateSign, sign); + } + + public static List getKVList(Map params) { + if(MapUtils.isEmpty(params)) { + return Collections.emptyList(); + } + List kvPairs = new ArrayList<>(); + for(String key : params.keySet()) { + String value = params.get(key); + if(value == null) { + continue; + } + String kv = key + "=" + value; + kvPairs.add(kv); + } + return kvPairs; + } + private SignUtils() {} + + private static String md5(byte[] textBytes) { + try { + if (textBytes == null) { + return null; + } else { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(textBytes); + BigInteger number = new BigInteger(1, messageDigest); + + String hashtext; + for(hashtext = number.toString(16); hashtext.length() < 32; hashtext = "0" + hashtext) { + ; + } + + return hashtext; + } + } catch (Exception var5) { + return null; + } + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonErrorCode.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonErrorCode.java new file mode 100644 index 0000000..aebb4bf --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonErrorCode.java @@ -0,0 +1,39 @@ +package com.agileboot.domain.shop.payment.dto; + +public class CommonErrorCode { + // 成功 + public static final CommonErrorCode OK = new CommonErrorCode(2000000, "OK"); + // 400 + public static final CommonErrorCode BAD_PARAMETER = new CommonErrorCode(4000001, "bad parameter: {0}"); + public static final CommonErrorCode INVALID_REQUEST_BODY = new CommonErrorCode(4000002, "bad reqeust body"); + // 403 + public static final CommonErrorCode USER_NOT_LOGIN = new CommonErrorCode(4030001, "user not login"); + public static final CommonErrorCode INVALID_REQUEST = new CommonErrorCode(4030003, "invalid request"); + // 404 + public static final CommonErrorCode REQUEST_NOT_FOUND = new CommonErrorCode(4040001, "request not found, {0}"); + // 500 + public static final CommonErrorCode INTERNAL_SERVER_ERROR = new CommonErrorCode(5000000, "internal server error, {0}"); + public static final CommonErrorCode TAIR_SERVER_ERROR = new CommonErrorCode(5000003, "internal server error"); + + private int errorCode; + private String messagePattern; + + protected CommonErrorCode() {} + + protected CommonErrorCode(int errorCode, String messagePattern) { + this.errorCode = errorCode; + this.messagePattern = messagePattern; + } + + public int getErrorCode() { + return errorCode; + } + + public String getMessagePattern() { + return messagePattern; + } + + public void throwMyError(Object... msgArgs) { + throw new MyError(this, msgArgs); + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonRequest.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonRequest.java new file mode 100644 index 0000000..f846bac --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonRequest.java @@ -0,0 +1,155 @@ +package com.agileboot.domain.shop.payment.dto; + +import cn.hutool.core.util.URLUtil; +import cn.hutool.json.JSONUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class CommonRequest { + private String clientId; + + private String bizId; + + private String sign; + + private String signType; + + private Long timestamp; + + private String version; + + private String nonceStr; + + private T bizContent; + + public CommonRequest() { + super(); + } + + public CommonRequest(String clientId, String bizId, String nonceStr, T bizContent) { + super(); + this.clientId = clientId; + this.bizId = bizId; + this.signType = "MD5"; + this.timestamp = Instant.now().toEpochMilli(); + this.version = "1.0"; + this.nonceStr = nonceStr; + this.bizContent = bizContent; + } + + public static Map getSSMapFromKvPairs(String reqBody) { + String[] fields = reqBody.split("&"); + Map fieldMap = new HashMap<>(); + for (String field : fields) { + String[] pair = field.split("="); + if (pair.length != 2) { + continue; + } + + fieldMap.put(pair[0], pair[1]); + } + return fieldMap; + } + + public static CommonRequest build(String reqBody, Class clazz) { + if (StringUtils.isBlank(reqBody)) { + return null; + } + + // TODO: 防止恶意传入& + Map fieldMap = getSSMapFromKvPairs(reqBody); + + CommonRequest req = new CommonRequest(); + Optional.ofNullable(fieldMap.get("client_id")).ifPresent(e -> req.setClientId(e)); + Optional.ofNullable(fieldMap.get("biz_id")).ifPresent(e -> req.setBizId(e)); + Optional.ofNullable(fieldMap.get("sign")).ifPresent(e -> req.setSign(e)); + Optional.ofNullable(fieldMap.get("sign_type")).ifPresent(e -> req.setSignType(e)); + Optional.ofNullable(fieldMap.get("timestamp")).ifPresent(e -> { + if (NumberUtils.isNumber(e)) { + req.setTimestamp(Long.valueOf(e)); + } + }); + Optional.ofNullable(fieldMap.get("version")).ifPresent(e -> req.setVersion(e)); + Optional.ofNullable(fieldMap.get("nonce_str")).ifPresent(e -> { + req.setNonceStr(URLUtil.decode(e)); + }); + Optional.ofNullable(fieldMap.get("biz_content")).ifPresent(e -> { + req.setBizContent(JSONUtil.toBean(URLUtil.decode(e), clazz)); + }); + return req; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getBizId() { + return bizId; + } + + public void setBizId(String bizId) { + this.bizId = bizId; + } + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getSignType() { + return signType; + } + + public void setSignType(String signType) { + this.signType = signType; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getNonceStr() { + return nonceStr; + } + + public void setNonceStr(String nonceStr) { + this.nonceStr = nonceStr; + } + + public T getBizContent() { + return bizContent; + } + + public void setBizContent(T bizContent) { + this.bizContent = bizContent; + } + + @Override + public String toString() { + return JSONUtil.toJsonStr(this); + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonResponse.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonResponse.java new file mode 100644 index 0000000..315f57f --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/CommonResponse.java @@ -0,0 +1,58 @@ +package com.agileboot.domain.shop.payment.dto; + +public class CommonResponse { + private Integer code; + + private String msg; + + private T data; + + public CommonResponse() { + super(); + } + + public CommonResponse(Integer code, String msg, T data) { + this.code = code; + this.msg = msg; + this.data = data; + } + + public CommonResponse(Integer code, String msg) { + this.code = code; + this.msg = msg; + } + + public static CommonResponse resultOk(T data) { + CommonResponse response = new CommonResponse(CommonErrorCode.OK.getErrorCode(), CommonErrorCode.OK.getMessagePattern(), data); + return response; + } + + public static CommonResponse resultError(Integer errorCode,String errorMsg) { + CommonResponse response = new CommonResponse(errorCode, errorMsg); + return response; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/MyError.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/MyError.java new file mode 100644 index 0000000..bb670e0 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/MyError.java @@ -0,0 +1,70 @@ +package com.agileboot.domain.shop.payment.dto; + +import cn.hutool.json.JSONUtil; + +import java.text.MessageFormat; + +public class MyError extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private String msg; + + private int code; + + public MyError() { + super(); + } + + public MyError(int code, String msg) { + super(msg, null, true, true); + this.code = code; + this.msg = msg; + } + + public MyError(CommonErrorCode errorCode, Object... msgArgs) { + this(errorCode.getErrorCode(), MessageFormat.format(errorCode.getMessagePattern(), msgArgs)); + } + + public static void invalidUrl() { + throw new MyError(CommonErrorCode.INVALID_REQUEST); + } + + public static void badParameter(String msg) { + throw new MyError(CommonErrorCode.BAD_PARAMETER, msg); + } + + public static void internalError(String msg) { + throw new MyError(CommonErrorCode.INTERNAL_SERVER_ERROR, msg); + } + + public static void userNotLogin() { + throw new MyError(CommonErrorCode.USER_NOT_LOGIN); + } + + public static void throwError(CommonErrorCode errorCode, Object ...args) { + throw new MyError(errorCode, args); + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + @Override + public String toString() { + return JSONUtil.toJsonStr(this); + } + +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/PaymentGatewayError.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/PaymentGatewayError.java new file mode 100644 index 0000000..c9528c1 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/PaymentGatewayError.java @@ -0,0 +1,12 @@ +package com.agileboot.domain.shop.payment.dto; + +import lombok.Data; + +@Data +public class PaymentGatewayError { + private String timestamp; + private Integer status; + private String error; + private String message; + private String path; +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/RefundVO.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/RefundVO.java new file mode 100644 index 0000000..4fee488 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/payment/dto/RefundVO.java @@ -0,0 +1,49 @@ +package com.agileboot.domain.shop.payment.dto; + +public class RefundVO { + private Boolean success; + private String msg; + private String refundId; + private String bizOrderId; + private String wxCallbackJson; + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getRefundId() { + return refundId; + } + + public void setRefundId(String refundId) { + this.refundId = refundId; + } + + public String getBizOrderId() { + return bizOrderId; + } + + public void setBizOrderId(String bizOrderId) { + this.bizOrderId = bizOrderId; + } + + public String getWxCallbackJson() { + return wxCallbackJson; + } + + public void setWxCallbackJson(String wxCallbackJson) { + this.wxCallbackJson = wxCallbackJson; + } +} \ No newline at end of file diff --git a/sql/20250328_return_approval.sql b/sql/20250328_return_approval.sql index 3942fde..3241163 100644 --- a/sql/20250328_return_approval.sql +++ b/sql/20250328_return_approval.sql @@ -40,4 +40,13 @@ CREATE TABLE `wx_user_info` ( `deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志(0存在 1删除)', PRIMARY KEY (`wx_user_id`), UNIQUE KEY `uk_openid` (`openid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='微信用户基本信息表'; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='微信用户基本信息表'; + +-- 在已有表结构下方添加 +ALTER TABLE `return_approval` + ADD COLUMN `order_goods_id` BIGINT NOT NULL COMMENT '关联订单商品ID' AFTER `goods_id`, + ADD KEY `idx_order_goods` (`order_goods_id`), + ADD CONSTRAINT `fk_return_order_goods` FOREIGN KEY (`order_goods_id`) REFERENCES `shop_order_goods` (`order_goods_id`); + +ALTER TABLE `shop_order` +ADD COLUMN `biz_order_id` VARCHAR(32) NULL COMMENT '业务系统订单ID(对接外部系统)' AFTER `trade_id`; \ No newline at end of file