diff --git a/cash-api/account-server/src/main/java/com/czg/controller/admin/AuthorizationController.java b/cash-api/account-server/src/main/java/com/czg/controller/admin/AuthorizationController.java index ce2784489..ae6277e34 100644 --- a/cash-api/account-server/src/main/java/com/czg/controller/admin/AuthorizationController.java +++ b/cash-api/account-server/src/main/java/com/czg/controller/admin/AuthorizationController.java @@ -1,28 +1,18 @@ package com.czg.controller.admin; -import cn.hutool.http.server.HttpServerRequest; -import cn.hutool.http.server.HttpServerResponse; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; import com.czg.account.dto.SysLoginDTO; import com.czg.account.entity.ShopInfo; import com.czg.account.entity.SysUser; -import com.czg.account.service.*; +import com.czg.account.service.AuthorizationService; +import com.czg.account.service.ShopInfoService; +import com.czg.account.service.SysUserService; import com.czg.account.vo.LoginVO; -import com.czg.annotation.SaAdminCheckPermission; -import com.czg.annotation.SaStaffCheckPermission; -import com.czg.config.RabbitPublisher; import com.czg.resp.CzgResult; import com.czg.sa.StpKit; import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.io.IOException; -import java.util.HashMap; import java.util.Map; /** @@ -76,27 +66,6 @@ public class AuthorizationController { return CzgResult.success(StpKit.USER.getPermissionList()); } - @Resource - RabbitPublisher rabbitPublisher; - @Resource - ShopTableService shopTableService; - - @GetMapping("test") - public CzgResult login(HttpServletRequest request, HttpServletResponse response) throws IOException { -// shopTableService.createQrCode(1L, 1, response, request); - - -// rabbitPublisher.sendOrderPrintMsg("552"); -// printMqListener.orderPrint("1"); -// return CzgResult.success(Map.of("token", StpKit.USER.getShopId())); - return CzgResult.success(StpKit.USER.getLoginId()); - } - - @GetMapping("test1") - public CzgResult login1() throws IOException { - authorizationService.switchTo(86L); - return CzgResult.success(StpKit.USER.getLoginId()); - } /** * 核销获取 信息使用 diff --git a/cash-api/account-server/src/main/java/com/czg/controller/admin/MenuController.java b/cash-api/account-server/src/main/java/com/czg/controller/admin/MenuController.java index 15617009d..45f547efd 100644 --- a/cash-api/account-server/src/main/java/com/czg/controller/admin/MenuController.java +++ b/cash-api/account-server/src/main/java/com/czg/controller/admin/MenuController.java @@ -4,14 +4,18 @@ import com.czg.account.dto.menu.MenuAddDTO; import com.czg.account.dto.menu.MenuDelDTO; import com.czg.account.dto.menu.MenuEditDTO; import com.czg.account.entity.CashMenu; +import com.czg.account.entity.QuickMenu; import com.czg.account.entity.SysMenu; +import com.czg.account.service.QuickMenuService; import com.czg.account.service.SysMenuService; import com.czg.account.vo.MenuVO; import com.czg.annotation.SaAdminCheckPermission; import com.czg.annotation.SaAdminCheckRole; import com.czg.resp.CzgResult; import com.czg.sa.StpKit; +import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -19,6 +23,7 @@ import java.util.List; /** * 菜单管理 + * * @author zs */ @RestController @@ -27,9 +32,12 @@ public class MenuController { @Resource private SysMenuService menuService; + @Resource + private QuickMenuService quickMenuService; /** * 获取当前用户菜单列表 + * * @return 菜单结构 */ @GetMapping @@ -40,6 +48,7 @@ public class MenuController { /** * 收银机菜单 + * * @return 所有菜单 */ @GetMapping("/list/cash") @@ -49,6 +58,7 @@ public class MenuController { /** * 获取所有菜单 + * * @return 菜单结构 */ @SaAdminCheckPermission(parentName = "菜单管理", value = "menu:list", name = "菜单列表") @@ -62,6 +72,7 @@ public class MenuController { /** * 菜单详情 + * * @return 菜单结构 */ @SaAdminCheckRole("管理员") @@ -73,6 +84,7 @@ public class MenuController { /** * 菜单添加 + * * @return 是否成功 */ @SaAdminCheckRole("管理员") @@ -84,6 +96,7 @@ public class MenuController { /** * 菜单修改 + * * @return 是否成功 */ @SaAdminCheckRole("管理员") @@ -95,12 +108,15 @@ public class MenuController { /** * 菜单删除 + * * @return 是否成功 */ @SaAdminCheckRole("管理员") @SaAdminCheckPermission(parentName = "菜单管理", value = "menu:del", name = "菜单删除") @DeleteMapping() + @Transactional public CzgResult edit(@RequestBody @Validated MenuDelDTO menuDelDTO) { + quickMenuService.remove(QueryWrapper.create().eq(QuickMenu::getMenuId, menuDelDTO.getId())); return CzgResult.success(menuService.removeById(menuDelDTO.getId())); } diff --git a/cash-api/account-server/src/main/java/com/czg/controller/admin/QuickMenuController.java b/cash-api/account-server/src/main/java/com/czg/controller/admin/QuickMenuController.java new file mode 100644 index 000000000..44c786733 --- /dev/null +++ b/cash-api/account-server/src/main/java/com/czg/controller/admin/QuickMenuController.java @@ -0,0 +1,70 @@ +package com.czg.controller.admin; + +import cn.hutool.core.collection.CollUtil; +import com.czg.account.entity.QuickMenu; +import com.czg.account.service.QuickMenuService; +import com.czg.annotation.SaAdminCheckPermission; +import com.czg.resp.CzgResult; +import com.czg.sa.StpKit; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Set; + +/** + * 悬浮窗/快捷菜单 + * + * @author ww + * @description + */ +@RestController +@RequestMapping("/admin/quick") +@Slf4j +public class QuickMenuController { + + @Resource + private QuickMenuService quickMenuService; + + @SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-列表") + @GetMapping + public CzgResult> getQuickList(@RequestParam(required = false) Integer status, + @RequestParam(required = false, defaultValue = "0") Integer isEdit) { + List list = quickMenuService.list(QueryWrapper.create() + .eq(QuickMenu::getShopId, StpKit.USER.getShopId()) + .eq(QuickMenu::getStatus, status) + .orderBy(QuickMenu::getSort, true)); + if (isEdit.equals(0)) { + if (CollUtil.isEmpty(list)) { + list = quickMenuService.list(QueryWrapper.create() + .eq(QuickMenu::getShopId, 1) + .eq(QuickMenu::getStatus, status) + .orderBy(QuickMenu::getSort, true)); + } + } + return CzgResult.success(list); + } + + @SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-新增") + @PostMapping + public CzgResult saveQuick(@RequestBody @Validated QuickMenu quickMenu) { + quickMenu.setShopId(StpKit.USER.getShopId()); + return CzgResult.success(quickMenuService.save(quickMenu)); + } + + @SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-修改") + @PutMapping + public CzgResult updateQuick(@RequestBody @Validated QuickMenu quickMenu) { + return CzgResult.success(quickMenuService.update(quickMenu, QueryWrapper.create() + .eq(QuickMenu::getId, quickMenu.getId()).eq(QuickMenu::getShopId, StpKit.USER.getShopId()))); + } + + @SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-删除") + @DeleteMapping + public CzgResult deleteQuick(@RequestBody Set ids) { + return CzgResult.success(quickMenuService.remove(QueryWrapper.create().in(QuickMenu::getId, ids).eq(QuickMenu::getShopId, StpKit.USER.getShopId()))); + } +} diff --git a/cash-api/market-server/src/main/java/com/czg/task/DistributionTask.java b/cash-api/market-server/src/main/java/com/czg/task/DistributionTask.java index 5e59af7ec..cdc53aad2 100644 --- a/cash-api/market-server/src/main/java/com/czg/task/DistributionTask.java +++ b/cash-api/market-server/src/main/java/com/czg/task/DistributionTask.java @@ -1,11 +1,8 @@ package com.czg.task; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateUtil; import com.czg.account.entity.ShopInfo; -import com.czg.account.entity.ShopUser; import com.czg.account.service.ShopInfoService; -import com.czg.account.service.ShopUserService; import com.czg.constant.TableValueConstant; import com.czg.constants.SystemConstants; import com.czg.exception.CzgException; @@ -15,13 +12,11 @@ import com.czg.market.service.MkDistributionUserService; import com.czg.market.service.OrderInfoService; import com.czg.order.entity.OrderInfo; import com.czg.service.market.enums.OrderStatusEnums; -import com.czg.utils.FunUtils; import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; diff --git a/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java b/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java index af97cc9c7..07f4c90ce 100644 --- a/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java @@ -3,9 +3,14 @@ package com.czg.controller; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.IoUtil; import com.alibaba.fastjson2.JSONObject; -import com.czg.CzgPayUtils; +import com.czg.PayCst; +import com.czg.PolyPayUtils; +import com.czg.constant.PayChannelCst; import com.czg.constants.PayTypeConstants; -import com.czg.entity.CzgBaseRespParams; +import com.czg.dto.req.WechatNotifyReqDto; +import com.czg.dto.req.WechatPayNotifyDataDto; +import com.czg.entity.PolyBaseResp; +import com.czg.exception.CzgException; import com.czg.market.entity.MkShopConsumeDiscountRecord; import com.czg.market.service.MkDistributionUserService; import com.czg.market.service.MkShopConsumeDiscountRecordService; @@ -14,16 +19,15 @@ import com.czg.order.entity.OrderInfo; import com.czg.order.entity.OrderPayment; import com.czg.order.service.OrderInfoCustomService; import com.czg.order.service.OrderPaymentService; +import com.czg.pay.PayNotifyRespDTO; import com.czg.service.market.service.impl.AppWxServiceImpl; +import com.czg.third.wechat.WechatReqUtils; import com.czg.utils.AssertUtil; import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -59,13 +63,57 @@ public class NotifyController { return "success"; } + /** + * 原生支付回调 + */ + @RequestMapping("/native/pay/{platform}") + public String pay(@PathVariable String platform, @RequestBody JSONObject json) { + if (PayCst.Platform.WECHAT.equalsIgnoreCase(platform)) { + // 微信 + WechatNotifyReqDto reqDto = JSONObject.parseObject(json.toJSONString(), WechatNotifyReqDto.class); + log.info("【微信支付回调】收到微信支付回调 data: {}", JSONObject.toJSONString(reqDto)); + + String decrypted = WechatReqUtils.decryptRespParam(null, reqDto); + log.info("【微信支付回调】解密数据 {}", decrypted); + + WechatPayNotifyDataDto dataDto = JSONObject.parseObject(decrypted, WechatPayNotifyDataDto.class); + PayNotifyRespDTO respDTO = dataDto.convertToPayNotifyRespDTO(); + orderInfoCustomService.payCallBackOrder(respDTO.getMchOrderNo(), respDTO, PayChannelCst.NATIVE, 0); + return "success"; + } else if (PayCst.Platform.ALIPAY.equalsIgnoreCase(platform)) { + // 支付宝 + return "success"; + } + throw new CzgException("不支持的支付平台"); + } + + /** + * 原生退款回调 + */ + @RequestMapping("/native/refund/{platform}") + public String refund(@PathVariable String platform, @RequestBody JSONObject json) { + if (PayCst.Platform.WECHAT.equalsIgnoreCase(platform)) { + // 微信 + WechatNotifyReqDto reqDto = JSONObject.parseObject(json.toJSONString(), WechatNotifyReqDto.class); + log.info("【微信退款回调】收到微信退款回调 data: {}", JSONObject.toJSONString(reqDto)); + String decrypted = WechatReqUtils.decryptRespParam(null, reqDto); + log.info("【微信退款回调】解密数据 {}", decrypted); + + return "success"; + } else if (PayCst.Platform.ALIPAY.equalsIgnoreCase(platform)) { + // 支付宝 + return "success"; + } + throw new CzgException("不支持的支付平台"); + } + @RequestMapping("/payCallBack") - public String notifyCallBack(@RequestBody CzgBaseRespParams respParams) { - JSONObject czg = CzgPayUtils.getCzg(respParams); - AssertUtil.isNull(czg, "支付回调数据为空"); - log.info("支付回调数据为:{}", czg); - orderInfoCustomService.payCallBackOrder(czg.getString("mchOrderNo"), czg, 0); + public String notifyCallBack(@RequestBody PolyBaseResp respParams) { + PayNotifyRespDTO respDTO = PolyPayUtils.getNotifyResp(respParams); + AssertUtil.isNull(respDTO, "支付回调数据为空"); + log.info("支付回调数据为:{}", respDTO); + orderInfoCustomService.payCallBackOrder(respDTO.getMchOrderNo(), respDTO, PayChannelCst.POLY, 0); return SUCCESS; } @@ -87,12 +135,15 @@ public class NotifyController { } + /** + * 微信原生支付回调 + */ @RequestMapping("/native/wx/pay/distributionRecharge") public String nativeNotify(HttpServletRequest request) throws IOException { - String timestamp = request.getHeader("Wechatpay-Timestamp"); - String nonce = request.getHeader("Wechatpay-Nonce"); - String serialNo = request.getHeader("Wechatpay-Serial"); - String signature = request.getHeader("Wechatpay-Signature"); +// String timestamp = request.getHeader("Wechatpay-Timestamp"); +// String nonce = request.getHeader("Wechatpay-Nonce"); +// String serialNo = request.getHeader("Wechatpay-Serial"); +// String signature = request.getHeader("Wechatpay-Signature"); String result = IoUtil.readUtf8(request.getInputStream()); JSONObject jsonObject = JSONObject.parseObject(result); JSONObject resource = jsonObject.getJSONObject("resource"); @@ -127,8 +178,8 @@ public class NotifyController { @RequestMapping("/refundCallBack") - public String refundCallBack(@RequestBody CzgBaseRespParams respParams) { - JSONObject czg = CzgPayUtils.getCzg(respParams); + public String refundCallBack(@RequestBody PolyBaseResp respParams) { + JSONObject czg = PolyPayUtils.getCzg(respParams); AssertUtil.isNull(czg, "退款回调数据为空"); log.info("退款回调数据为:{}", czg); orderInfoCustomService.refundCallBackOrder(czg.getString("mchOrderNo"), czg); diff --git a/cash-api/order-server/src/main/java/com/czg/controller/admin/AdminOrderController.java b/cash-api/order-server/src/main/java/com/czg/controller/admin/AdminOrderController.java index 4447e4578..47d5f6db8 100644 --- a/cash-api/order-server/src/main/java/com/czg/controller/admin/AdminOrderController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/admin/AdminOrderController.java @@ -9,7 +9,7 @@ import com.czg.order.vo.HistoryOrderVo; import com.czg.order.vo.OrderInfoVo; import com.czg.resp.CzgResult; import com.czg.sa.StpKit; -import com.czg.service.order.service.PayService; +import com.czg.service.order.service.OrderPayService; import com.czg.utils.AssertUtil; import com.czg.utils.ServletUtil; import com.mybatisflex.core.paginate.Page; @@ -31,7 +31,7 @@ public class AdminOrderController { @Resource private OrderInfoCustomService orderInfoService; @Resource - private PayService payService; + private OrderPayService orderPayService; /** * 订单列表 @@ -94,7 +94,7 @@ public class AdminOrderController { @PostMapping("/refundOrder") @Debounce(value = "#refundDTO.orderId") public CzgResult refundOrder(@Validated @RequestBody OrderInfoRefundDTO refundDTO) { - return payService.refundOrderBefore(refundDTO); + return orderPayService.refundOrderBefore(refundDTO); } /** diff --git a/cash-api/order-server/src/main/java/com/czg/controller/admin/EntryManagerController.java b/cash-api/order-server/src/main/java/com/czg/controller/admin/EntryManagerController.java new file mode 100644 index 000000000..ec099ad80 --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/controller/admin/EntryManagerController.java @@ -0,0 +1,105 @@ +package com.czg.controller.admin; + +import com.alibaba.fastjson2.JSONObject; +import com.czg.EntryManager; +import com.czg.annotation.Debounce; +import com.czg.dto.req.AggregateMerchantDto; +import com.czg.dto.resp.WechatBankBranchRespDto; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.resp.CzgResult; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.czg.service.order.dto.MerchantQueryDTO; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.czg.task.EntryManagerTask; +import com.czg.utils.AssertUtil; +import com.mybatisflex.core.paginate.Page; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * 进件管理 + * + * @author ww + */ +@AllArgsConstructor +@RestController +@RequestMapping("/admin/data/entryManager") +public class EntryManagerController { + + @Resource + private ShopDirectMerchantService shopDirectMerchantService; + @Resource + private EntryManagerTask entryManagerTask; + + /** + * ocr识别填充 + * 阿里 ocr识别图片 + * 本接口支持:PNG、JPG、JPEG、BMP、GIF、TIFF、WebP、PDF。 + * 图片长宽需要大于 15 像素,小于 8192 像素。 + * 长宽比需要小于 50。长宽均大于 500px。 + * 图片二进制文件不能超过 10MB。 + * 图片过大会影响接口响应速度,建议使用小于 1.5M 图片进行识别, + * + * @param url 图片地址 + * @param type IdCard 身份证 + * BankCard 银行卡 + * BusinessLicense 营业执照 + */ + @GetMapping("getInfoByImg") + public CzgResult getInfoByImg(String url, String type) throws Exception { + return CzgResult.success(shopDirectMerchantService.getInfoByImg(url, type)); + } + + /** + * 查询银行支行列表 + * + * @param bankAliceCode 银行别名code bankAliasCode 从 /system/admin/common/bankInfo 获取 + * @param cityCode 市编码 wxProvinceCode 从 /system/admin/common/region 获取 + */ + @GetMapping("bankBranchList") + public CzgResult queryBankBranchList(String bankAliceCode, String cityCode) { + AssertUtil.isBlank(bankAliceCode, "请选择银行别名"); + AssertUtil.isBlank(cityCode, "请选择城市"); + return CzgResult.success(EntryManager.queryBankBranchList(bankAliceCode, cityCode)); + } + + /** + * 获取进件列表 + */ + @GetMapping("list") + public CzgResult> getEntryList(MerchantQueryDTO queryParam) { + return CzgResult.success(shopDirectMerchantService.getEntryList(queryParam)); + } + + + /** + * 获取进件信息 + */ + @GetMapping + public CzgResult getEntry(Long shopId) { + return CzgResult.success(shopDirectMerchantService.getEntry(shopId)); + } + + /** + * 主动查询进件信息状态 + * 进件状态是INIT 待处理 AUDIT 审核中 SIGN 待签约 + * 3分钟内只能查一次 + */ + @GetMapping("queryEntry") + @Debounce(value = "#shopId", interval = 1000 * 60 * 3) + public CzgResult queryEntry(Long shopId) { + entryManagerTask.entryManager(shopId); + return CzgResult.success(); + } + + /** + * 申请进件 + */ + @Debounce(value = "#reqDto.shopId") + @PostMapping + public CzgResult entryManager(@RequestBody AggregateMerchantDto reqDto) { + return CzgResult.success(shopDirectMerchantService.entryManager(reqDto)); + } + +} diff --git a/cash-api/account-server/src/main/java/com/czg/controller/admin/ShopMerchantController.java b/cash-api/order-server/src/main/java/com/czg/controller/admin/ShopMerchantController.java similarity index 63% rename from cash-api/account-server/src/main/java/com/czg/controller/admin/ShopMerchantController.java rename to cash-api/order-server/src/main/java/com/czg/controller/admin/ShopMerchantController.java index 24e348bfc..630e9cdc4 100644 --- a/cash-api/account-server/src/main/java/com/czg/controller/admin/ShopMerchantController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/admin/ShopMerchantController.java @@ -1,18 +1,17 @@ package com.czg.controller.admin; -import com.czg.account.dto.merchant.ShopMerchantEditDTO; -import com.czg.account.entity.ShopMerchant; -import com.czg.account.service.ShopMerchantService; import com.czg.annotation.SaAdminCheckPermission; import com.czg.annotation.SaAdminCheckRole; +import com.czg.order.dto.ShopMerchantDTO; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.order.service.ShopMerchantService; import com.czg.resp.CzgResult; -import com.czg.sa.StpKit; import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** * 商户信息管理 + * * @author Administrator */ @RestController @@ -24,25 +23,36 @@ public class ShopMerchantController { /** * 商户支付信息获取 * 权限标识: shopMerchant:detail + * * @param shopId 店铺id * @return 支付信息 */ @SaAdminCheckRole("管理员") @SaAdminCheckPermission(parentName = "支付参数信息", value = "shopMerchant:detail", name = "商户支付信息获取") @GetMapping - public CzgResult detail(@RequestParam Integer shopId) { + public CzgResult detail(@RequestParam Long shopId) { return CzgResult.success(shopMerchantService.detail(shopId)); } /** * 商户支付信息修改 * 权限标识: shopMerchant:edit + * * @return 是否成功 */ @SaAdminCheckRole("管理员") @SaAdminCheckPermission(parentName = "支付参数信息", value = "shopMerchant:edit", name = "商户支付信息修改") @PutMapping - public CzgResult edit(@RequestBody @Validated ShopMerchantEditDTO shopMerchantEditDTO) { - return CzgResult.success(shopMerchantService.edit(shopMerchantEditDTO)); + public CzgResult edit(@RequestBody ShopMerchantDTO shopMerchant) { + return CzgResult.success(shopMerchantService.editEntry(shopMerchant, true)); + } + + /** + * 获取当前店铺的主店进件信息 + */ + @SaAdminCheckRole("管理员") + @GetMapping("getMainMerchant") + public CzgResult getMainMerchant(Long shopId) { + return CzgResult.success(shopMerchantService.getMainMerchant(shopId)); } } diff --git a/cash-api/order-server/src/main/java/com/czg/controller/pay/DistributionPayController.java b/cash-api/order-server/src/main/java/com/czg/controller/pay/DistributionPayController.java new file mode 100644 index 000000000..14efd7a33 --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/controller/pay/DistributionPayController.java @@ -0,0 +1,53 @@ +package com.czg.controller.pay; + +import com.czg.annotation.Debounce; +import com.czg.order.dto.MkDistributionPayDTO; +import com.czg.resp.CzgResult; +import com.czg.service.order.service.DistributionPayService; +import com.czg.utils.AssertUtil; +import com.czg.utils.ServletUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 分销员开通 + * + * @author ww + * @description + */ +@RestController +@RequestMapping("/pay/distribution") +public class DistributionPayController { + @Resource + private DistributionPayService payService; + + /** + * 小程序支付 + * payType 必填 支付方式,aliPay 支付宝,wechatPay 微信 + * openId 必填 + */ + @PostMapping("/ltPayOrder") + @Debounce(value = "#payParam.userId") + public CzgResult> ltPayOrder(HttpServletRequest request, @Validated @RequestBody MkDistributionPayDTO payParam) { + return payService.ltPayOrder(ServletUtil.getClientIP(request), payParam); + } + + + /** + * 运营端小程序余额充值 + * payType 必填 支付方式,aliPay 支付宝,wechatPay 微信 + */ + @PostMapping("/mchRecharge") + @Debounce(value = "#payParam.userId") + public CzgResult> mchRecharge(@Validated @RequestBody MkDistributionPayDTO payParam) { + AssertUtil.isBlank(payParam.getCode(), "微信code不为空"); + return CzgResult.success(payService.mchRecharge(payParam)); + } +} diff --git a/cash-api/order-server/src/main/java/com/czg/controller/OrderPayController.java b/cash-api/order-server/src/main/java/com/czg/controller/pay/OrderPayController.java similarity index 89% rename from cash-api/order-server/src/main/java/com/czg/controller/OrderPayController.java rename to cash-api/order-server/src/main/java/com/czg/controller/pay/OrderPayController.java index b6fd2622e..c597608e8 100644 --- a/cash-api/order-server/src/main/java/com/czg/controller/OrderPayController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/pay/OrderPayController.java @@ -1,4 +1,4 @@ -package com.czg.controller; +package com.czg.controller.pay; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -10,7 +10,7 @@ import com.czg.order.entity.OrderInfo; import com.czg.order.service.OrderInfoCustomService; import com.czg.resp.CzgResult; import com.czg.service.order.dto.OrderPayParamDTO; -import com.czg.service.order.service.PayService; +import com.czg.service.order.service.OrderPayService; import com.czg.system.service.SysParamsService; import com.czg.utils.AssertUtil; import com.czg.utils.ServletUtil; @@ -35,7 +35,7 @@ import java.util.Map; @RequestMapping("/pay") public class OrderPayController { @Resource - private PayService payService; + private OrderPayService orderPayService; @Resource private OrderInfoCustomService orderInfoCustomService; @Resource @@ -47,14 +47,14 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult creditPayOrder(@RequestHeader Long shopId, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.creditPayOrder(payParam); + return orderPayService.creditPayOrder(payParam); } @PostMapping("/cashPay") @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult cashPayOrder(@RequestHeader Long shopId, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.cashPayOrder(payParam); + return orderPayService.cashPayOrder(payParam); } /** @@ -66,7 +66,7 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> rechargePayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.rechargePayOrder(ServletUtil.getClientIP(request), payParam); + return orderPayService.rechargePayOrder(ServletUtil.getClientIP(request), payParam); } /** @@ -83,7 +83,7 @@ public class OrderPayController { public CzgResult vipPayOrder(@RequestHeader Long shopId, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); AssertUtil.isBlank(payParam.getPayType(), "支付类型不可为空"); - return payService.vipPayOrder(payParam); + return orderPayService.vipPayOrder(payParam); } /** @@ -93,7 +93,7 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> h5PayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.h5PayOrder(ServletUtil.getClientIP(request, ""), payParam); + return orderPayService.h5PayOrder(ServletUtil.getClientIP(request, ""), payParam); } /** @@ -106,7 +106,7 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> jsPayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.jsPayOrder(ServletUtil.getClientIP(request), payParam); + return orderPayService.jsPayOrder(ServletUtil.getClientIP(request), payParam); } /** @@ -118,7 +118,7 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> ltPayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.ltPayOrder(ServletUtil.getClientIP(request), payParam); + return orderPayService.ltPayOrder(ServletUtil.getClientIP(request), payParam); } /** @@ -128,7 +128,7 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> scanPayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.scanPayOrder(ServletUtil.getClientIP(request), payParam); + return orderPayService.scanPayOrder(ServletUtil.getClientIP(request), payParam); } /** @@ -139,7 +139,7 @@ public class OrderPayController { @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> microPayOrder(@RequestHeader Long shopId, @Validated @RequestBody OrderPayParamDTO payParam) { payParam.setShopId(shopId); - return payService.microPayOrder(payParam); + return orderPayService.microPayOrder(payParam); } /** @@ -174,7 +174,7 @@ public class OrderPayController { @PostMapping("/shopPayApi/js2Pay") @Debounce(value = "#payParam.checkOrderPay.orderId") public CzgResult> js2PayOrder(HttpServletRequest request, @RequestBody OrderPayParamDTO payParam) { - return payService.js2PayOrder(ServletUtil.getClientIP(request), payParam); + return orderPayService.js2PayOrder(ServletUtil.getClientIP(request), payParam); } /** diff --git a/cash-api/order-server/src/main/java/com/czg/controller/VipPayController.java b/cash-api/order-server/src/main/java/com/czg/controller/pay/VipPayController.java similarity index 81% rename from cash-api/order-server/src/main/java/com/czg/controller/VipPayController.java rename to cash-api/order-server/src/main/java/com/czg/controller/pay/VipPayController.java index 801de4c18..7774747eb 100644 --- a/cash-api/order-server/src/main/java/com/czg/controller/VipPayController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/pay/VipPayController.java @@ -1,14 +1,18 @@ -package com.czg.controller; +package com.czg.controller.pay; import com.czg.annotation.Debounce; -import com.czg.entity.resp.CzgBaseResp; +import com.czg.order.entity.OrderPayment; +import com.czg.order.service.OrderPaymentService; +import com.czg.pay.QueryOrderRespDTO; import com.czg.resp.CzgResult; import com.czg.service.order.dto.VipMemberPayParamDTO; import com.czg.service.order.dto.VipPayParamDTO; import com.czg.service.order.dto.VipRefundDTO; import com.czg.service.order.service.PayService; +import com.czg.service.order.service.ShopUserPayService; import com.czg.utils.AssertUtil; import com.czg.utils.ServletUtil; +import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import org.springframework.validation.annotation.Validated; @@ -28,6 +32,11 @@ import java.util.Map; public class VipPayController { @Resource private PayService payService; + @Resource + private ShopUserPayService shopUserPayService; + + @Resource + private OrderPaymentService paymentService; /** * 现金充值 @@ -39,7 +48,7 @@ public class VipPayController { public CzgResult cashPayVip(@Validated @RequestBody VipPayParamDTO payParam) { AssertUtil.isNull(payParam.getShopUserId(), "充值失败 未指定店铺用户Id"); payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType")); - return payService.cashPayVip(payParam); + return shopUserPayService.cashPayVip(payParam); } /** @@ -52,7 +61,7 @@ public class VipPayController { public CzgResult> jsPayVip(HttpServletRequest request, @Validated @RequestBody VipPayParamDTO payParam) { AssertUtil.isNull(payParam.getShopUserId(), "充值失败 未指定店铺用户Id"); payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType")); - return payService.jsPayVip(ServletUtil.getClientIP(request), payParam); + return shopUserPayService.jsPayVip(ServletUtil.getClientIP(request), payParam); } /** @@ -65,14 +74,11 @@ public class VipPayController { public CzgResult> ltPayVip(HttpServletRequest request, @Validated @RequestBody VipPayParamDTO payParam) { AssertUtil.isNull(payParam.getShopUserId(), "充值失败 未指定店铺用户Id"); payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType")); - return payService.ltPayVip(ServletUtil.getClientIP(request), payParam); + return shopUserPayService.ltPayVip(ServletUtil.getClientIP(request), payParam); } /** * 智慧充值 - * @param request - * @param rechargeDTO - * @return */ @PostMapping("/recharge") @Debounce(value = "#rechargeDTO.shopUserId") @@ -81,21 +87,18 @@ public class VipPayController { return CzgResult.failure("充值失败 未指定充值金额"); } rechargeDTO.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType")); - return payService.recharge(ServletUtil.getClientIP(request), rechargeDTO, rechargeDTO.getShopUserId()); + return shopUserPayService.recharge(ServletUtil.getClientIP(request), rechargeDTO, rechargeDTO.getShopUserId()); } /** * 会员购买支付 - * @param request - * @param payParam - * @return */ @PostMapping("/ltPayMember") @Debounce(value = "#payParam.shopUserId") public CzgResult> ltPayMember(HttpServletRequest request, @Validated @RequestBody VipMemberPayParamDTO payParam) { AssertUtil.isNull(payParam.getShopUserId(), "购买失败 未指定店铺用户Id"); payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType")); - return payService.ltPayMember(ServletUtil.getClientIP(request), payParam); + return shopUserPayService.ltPayMember(ServletUtil.getClientIP(request), payParam); } @@ -108,7 +111,7 @@ public class VipPayController { public CzgResult> scanPayVip(HttpServletRequest request, @Validated @RequestBody VipPayParamDTO payParam) { AssertUtil.isNull(payParam.getShopUserId(), "充值失败 未指定店铺用户Id"); payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(request, "platformType")); - return payService.scanPayVip(ServletUtil.getClientIP(request), payParam); + return shopUserPayService.scanPayVip(ServletUtil.getClientIP(request), payParam); } /** @@ -121,7 +124,7 @@ public class VipPayController { public CzgResult> microPayVip(@Validated @RequestBody VipPayParamDTO payParam) { AssertUtil.isNull(payParam.getShopUserId(), "充值失败 未指定店铺用户Id"); payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType")); - return payService.microPayVip(payParam); + return shopUserPayService.microPayVip(payParam); } /** @@ -132,7 +135,7 @@ public class VipPayController { @PostMapping("/refundVipBefore") @Debounce(value = "#payParam.flowId") public CzgResult> refundVipBefore(@Validated @RequestBody VipRefundDTO payParam) { - return payService.refundVipBefore(payParam); + return shopUserPayService.refundVipBefore(payParam); } /** @@ -155,7 +158,7 @@ public class VipPayController { return CzgResult.failure("退款金额过大"); } payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(request, "platformType")); - return payService.refundVip(payParam); + return shopUserPayService.refundVip(payParam); } /** @@ -165,10 +168,16 @@ public class VipPayController { public CzgResult queryOrderStatus(Long shopId, String payOrderNo) { AssertUtil.isNull(shopId, "店铺id不能为空"); AssertUtil.isBlank(payOrderNo, "支付单号不能为空"); + + OrderPayment payment = paymentService.getOne(QueryWrapper.create().eq(OrderPayment::getOrderNo, payOrderNo)); + if (payment == null) { + return CzgResult.failure("支付单号不存在"); + } + CzgResult result = CzgResult.success(); - CzgResult queryPayOrder = payService.queryPayOrder(shopId, null, payOrderNo); - if (queryPayOrder.getCode() == 200 && queryPayOrder.getData() != null) { - String state = queryPayOrder.getData().getState(); + CzgResult queryPayOrder = payService.queryPayOrder(shopId, null, payOrderNo, payment.getPlatformType()); + if (queryPayOrder.isSuccess() && queryPayOrder.getData() != null) { + String state = queryPayOrder.getData().getStatus(); result.setData(state); switch (state) { case "TRADE_AWAIT" -> result.setMsg("等待用户付款"); diff --git a/cash-api/order-server/src/main/java/com/czg/mq/EntryManagerMqListener.java b/cash-api/order-server/src/main/java/com/czg/mq/EntryManagerMqListener.java new file mode 100644 index 000000000..f2db120dc --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/mq/EntryManagerMqListener.java @@ -0,0 +1,144 @@ +package com.czg.mq; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.EntryManager; +import com.czg.PayCst; +import com.czg.config.RabbitConstants; +import com.czg.config.RedisCst; +import com.czg.dto.resp.EntryRespDto; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.service.RedisService; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.mybatisflex.core.query.QueryWrapper; +import com.rabbitmq.client.Channel; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.ThreadContext; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.*; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 打印mq消息处理器 + * + * @author Administrator + */ +@Component +@Slf4j +public class EntryManagerMqListener { + @Resource + private RedisService redisService; + @Resource + private ShopDirectMerchantService shopDirectMerchantService; + + String key = RedisCst.SHOP_ENTRY; + + @RabbitListener( + bindings = @QueueBinding( + value = @Queue(value = "${spring.profiles.active}-" + RabbitConstants.Queue.SHOP_ENTRY_MANAGER, + durable = "true", exclusive = "false", autoDelete = "false"), + exchange = @Exchange(value = "${spring.profiles.active}-" + RabbitConstants.Exchange.CASH_EXCHANGE), + key = "${spring.profiles.active}-" + RabbitConstants.Queue.SHOP_ENTRY_MANAGER + ), + concurrency = "5" + ) + @RabbitHandler + public void handle(Message message, Channel channel, String msg) throws IOException { + log.info("进件1MQ对接开始 店铺标识:{}", msg); + long deliveryTag = message.getMessageProperties().getDeliveryTag(); + if (StrUtil.isBlank(msg)) { + channel.basicNack(deliveryTag, false, false); + return; + } + String[] split = msg.split(":"); + if (split.length != 2) { + log.error("进件MQ对接参数异常 店铺标识:{}", msg); + channel.basicNack(deliveryTag, false, false); + return; + } + Long shopId = Long.valueOf(split[0]); + if (shopId == null) { + channel.basicNack(deliveryTag, false, false); + return; + } + if (hasMessageId(msg)) { + return; + } + try { + // 将唯一标识添加到日志上下文 + ThreadContext.put("traceId", String.valueOf(shopId)); + log.info("进件2MQ对接开始shopId:{}", msg); + // 安全转换shopId + AggregateMerchantVO entry = shopDirectMerchantService.getEntry(shopId); + log.info("进件3MQ对接开始shopId:{}", msg); + if (entry != null) { + EntryManager.uploadParamImage(entry); + List platform = new ArrayList<>(); + if (PayCst.EntryStatus.WAIT.equals(entry.getAlipayStatus())) { + platform.add(PayCst.Platform.ALIPAY); + } + if (PayCst.EntryStatus.WAIT.equals(entry.getWechatStatus())) { + platform.add(PayCst.Platform.WECHAT); + } + EntryRespDto resp = EntryManager.entryMerchant(entry, platform.toArray(new String[0])); + ShopDirectMerchant merchant = new ShopDirectMerchant(); + merchant.setMerchantBaseInfo(JSONObject.toJSONString(entry.getMerchantBaseInfo())); + merchant.setLegalPersonInfo(JSONObject.toJSONString(entry.getLegalPersonInfo())); + merchant.setBusinessLicenceInfo(JSONObject.toJSONString(entry.getBusinessLicenceInfo())); + merchant.setStoreInfo(JSONObject.toJSONString(entry.getStoreInfo())); + merchant.setSettlementInfo(JSONObject.toJSONString(entry.getSettlementInfo())); + + + merchant.setWechatApplyId(resp.getWechatApplyId()); + merchant.setWechatStatus(resp.getWechatStatus()); + merchant.setWechatErrorMsg(resp.getWechatErrorMsg()); + merchant.setAlipayOrderId(resp.getAlipayOrderId()); + merchant.setAlipayStatus(resp.getAlipayStatus()); + merchant.setAlipayAuthInfo(resp.getAlipayAuthInfo()); + merchant.setAlipayErrorMsg(resp.getAlipayErrorMsg()); + shopDirectMerchantService.update(merchant, new QueryWrapper().eq(ShopDirectMerchant::getShopId, shopId).eq(ShopDirectMerchant::getLicenceNo, split[1])); + } + channel.basicAck(deliveryTag, false); + } catch (Exception e) { + log.error("进件MQ对接业务异常shopId:{}", msg, e); + ShopDirectMerchant merchant = new ShopDirectMerchant(); + merchant.setWechatStatus(PayCst.EntryStatus.REJECTED); + merchant.setAlipayStatus(PayCst.EntryStatus.REJECTED); + merchant.setErrorMsg("系统错误,请联系管理员后重试。"); + shopDirectMerchantService.update(merchant, new QueryWrapper().eq(ShopDirectMerchant::getShopId, shopId).eq(ShopDirectMerchant::getLicenceNo, split[1])); + channel.basicNack(deliveryTag, false, false); + } finally { + delMessageId(msg); +// 清除日志上下文信息 + ThreadContext.remove("messageId"); + } + } + + public boolean hasMessageId(String messageId) { + if (!redisService.hasKey(key)) { + if (StrUtil.isNotBlank(messageId)) { + redisService.leftPush(key, messageId); + return false; + } else { + return true; + } + } + List list = redisService.lGet(key, 0, -1); + if (!list.contains(messageId)) { + redisService.leftPush(key, messageId); + return false; + } + return true; + } + + public void delMessageId(String messageId) { + redisService.lRemove(key, 0, messageId); + } + +} diff --git a/cash-api/order-server/src/main/java/com/czg/mq/OrderMqListener.java b/cash-api/order-server/src/main/java/com/czg/mq/OrderMqListener.java index e8054e747..395f1a25e 100644 --- a/cash-api/order-server/src/main/java/com/czg/mq/OrderMqListener.java +++ b/cash-api/order-server/src/main/java/com/czg/mq/OrderMqListener.java @@ -9,7 +9,7 @@ import com.czg.order.entity.MqLog; import com.czg.order.service.MqLogService; import com.czg.order.service.OrderInfoCustomService; import com.czg.order.service.OrderInfoRpcService; -import com.czg.service.order.utils.FunUtil; +import com.czg.service.RedisService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; @@ -31,7 +31,7 @@ public class OrderMqListener { @Resource private OrderInfoCustomService orderInfoCustomService; @Resource - private FunUtil funUtil; + private RedisService redisService; /** * 订单上菜 @@ -44,13 +44,10 @@ public class OrderMqListener { info = info.replace("UP_ORDER_DETAIL:", ""); log.info("接收到修改菜品状态mq, info: {}", info); String finalInfo = info; - funUtil.debounce("UP_ORDER_DETAIL:" + info, 5, () -> { + redisService.debounce("UP_ORDER_DETAIL:" + info, 5, () -> { orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo)); }); - info = info.replace("UP_ORDER_DETAIL:", ""); - System.out.println(info); - } @RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.ORDER_STOCK_QUEUE}) diff --git a/cash-api/order-server/src/main/java/com/czg/mq/PrintMqListener.java b/cash-api/order-server/src/main/java/com/czg/mq/PrintMqListener.java index 0657298ac..9cbaf0972 100644 --- a/cash-api/order-server/src/main/java/com/czg/mq/PrintMqListener.java +++ b/cash-api/order-server/src/main/java/com/czg/mq/PrintMqListener.java @@ -6,13 +6,12 @@ import com.czg.config.RabbitConstants; import com.czg.config.RedisCst; import com.czg.order.entity.MqLog; import com.czg.order.service.MqLogService; +import com.czg.service.RedisService; import com.czg.service.order.print.PrinterHandler; -import com.czg.service.order.utils.FunUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.context.annotation.Lazy; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import java.util.function.Consumer; @@ -28,19 +27,15 @@ public class PrintMqListener { @Resource private MqLogService mqLogService; @Resource - private FunUtil funUtil; - - // 注入自定义线程池(建议单独配置,避免使用默认线程池) - @Resource - private ThreadPoolTaskExecutor asyncExecutor; + private RedisService redisService; @Lazy @Resource private PrinterHandler printerHandler; - private void invokeFun(String type, String plat, T data, Consumer consumer) { + private void invokeFun(String queue, String type, String plat, T data, Consumer consumer) { long startTime = DateUtil.date().getTime(); log.info("接收到{}打印消息:{}", type, data); - MqLog mqLog = new MqLog().setQueue(RabbitConstants.Queue.ORDER_MACHINE_PRINT_QUEUE).setMsg(data.toString()) + MqLog mqLog = new MqLog().setQueue(queue).setMsg(data.toString()) .setType(type).setPlat(plat).setCreateTime(DateUtil.date().toLocalDateTime()); try { consumer.accept(data); @@ -56,45 +51,18 @@ public class PrintMqListener { @RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.ORDER_MACHINE_PRINT_QUEUE}) public void orderPrint(String req) { // 执行核心打印逻辑 - invokeFun("orderPrint", "java.order", req, (data) -> { + invokeFun(RabbitConstants.Queue.ORDER_MACHINE_PRINT_QUEUE, "orderPrint", "java.order", req, (data) -> { JSONObject jsonObject = JSONObject.parseObject(data); String orderId = jsonObject.getString("orderId"); if (orderId == null) { throw new RuntimeException("订单打印失败,未传递orderId"); } Boolean printOrder = jsonObject.getBoolean("printOrder"); - funUtil.runFunAndCheckKey(() -> { + redisService.runFunAndCheckKey(() -> { printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER); return null; }, RedisCst.getLockKey("orderPrint", orderId)); }); -// // 使用异步线程池执行延迟任务,不阻塞当前消费者线程 -// CompletableFuture.runAsync(() -> { -// try { -// // 延迟3秒处理 -// TimeUnit.SECONDS.sleep(3); -// // 执行核心打印逻辑 -// invokeFun("orderPrint", "java.order", req, (data) -> { -// JSONObject jsonObject = JSONObject.parseObject(data); -// String orderId = jsonObject.getString("orderId"); -// if (orderId == null) { -// throw new RuntimeException("订单打印失败,未传递orderId"); -// } -// Boolean printOrder = jsonObject.getBoolean("printOrder"); -// funUtil.runFunAndCheckKey(() -> { -// printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER); -// return null; -// }, RedisCst.getLockKey("orderPrint", orderId)); -// }); -// } catch (InterruptedException e) { -// Thread.currentThread().interrupt(); -// // 记录中断日志 -// log.warn("打印任务被中断,req:{}", req, e); -// } catch (Exception e) { -// // 记录业务异常日志 -// log.error("打印任务处理失败,req:{}", req, e); -// } -// }, asyncExecutor); } /** @@ -102,14 +70,16 @@ public class PrintMqListener { */ @RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.ORDER_HANDOVER_PRINT_QUEUE}) public void handoverPrint(String id) { - invokeFun("handoverPrint", "java.order", id, (data) -> printerHandler.handler(data, PrinterHandler.PrintTypeEnum.HANDOVER)); + invokeFun(RabbitConstants.Queue.ORDER_HANDOVER_PRINT_QUEUE, "handoverPrint", "java.order", id, (data) -> + printerHandler.handler(data, PrinterHandler.PrintTypeEnum.HANDOVER)); } /** - * 交班打印 + * 叫号打印 */ - @RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE}) + @RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.CALL_TABLE_QUEUE}) public void callTablePrint(String id) { - invokeFun("handoverPrint", "java.order", id, (data) -> printerHandler.handler(data, PrinterHandler.PrintTypeEnum.CALL)); + invokeFun(RabbitConstants.Queue.CALL_TABLE_QUEUE, "callTable", "java.order", id, (data) -> + printerHandler.handler(data, PrinterHandler.PrintTypeEnum.CALL)); } } diff --git a/cash-api/order-server/src/main/java/com/czg/task/EntryManagerTask.java b/cash-api/order-server/src/main/java/com/czg/task/EntryManagerTask.java new file mode 100644 index 000000000..738c6850f --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/task/EntryManagerTask.java @@ -0,0 +1,97 @@ +package com.czg.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.EntryManager; +import com.czg.PayCst; +import com.czg.dto.resp.QueryStatusResp; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.order.service.ShopMerchantService; +import com.czg.pay.AlipayAuthInfoDto; +import com.czg.pay.NativeMerchantDTO; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 进件查询 + * + * @author ww + */ +@Component +@Slf4j +public class EntryManagerTask { + @Resource + private ShopDirectMerchantService shopDirectMerchantService; + @Resource + private ShopMerchantService shopMerchantService; + + //每10分钟查一次 + @Scheduled(cron = "0 0/10 * * * ? ") + public void run() { + log.info("进件查询,定时任务执行"); + long start = System.currentTimeMillis(); + entryManager(null); + log.info("进件查询,定时任务执行完毕,耗时:{}ms", start - System.currentTimeMillis()); + } + + /** + * 查询状态为待处理、待签约、待审核的进件 + */ + public void entryManager(Long shopId) { + List list = shopDirectMerchantService.list(QueryWrapper.create() + .eq(ShopDirectMerchant::getShopId, shopId) + .in(ShopDirectMerchant::getWechatStatus, PayCst.EntryStatus.NEED_QUERY_LIST) + .or(ShopDirectMerchant::getAlipayStatus).in(PayCst.EntryStatus.NEED_QUERY_LIST)); + if (CollUtil.isEmpty(list)) { + return; + } + for (ShopDirectMerchant shopDirectMerchant : list) { + String wechatMerchantId = ""; + String alipayMerchantId = ""; + if (PayCst.EntryStatus.NEED_QUERY_LIST.contains(shopDirectMerchant.getWechatStatus()) && StrUtil.isNotBlank(shopDirectMerchant.getWechatApplyId())) { + QueryStatusResp wechatStatus = EntryManager.queryWechatEntryStatus(shopDirectMerchant.getWechatApplyId()); + shopDirectMerchant.setWechatStatus(wechatStatus.getStatus()); + shopDirectMerchant.setWechatErrorMsg(wechatStatus.getFailReason()); + shopDirectMerchant.setWechatSignUrl(""); + shopDirectMerchant.setWechatMerchantId(wechatStatus.getThirdMerchantId()); + if (PayCst.EntryStatus.FINISH.equals(wechatStatus.getStatus())) { + wechatMerchantId = wechatStatus.getThirdMerchantId(); + } + } + if (PayCst.EntryStatus.NEED_QUERY_LIST.contains(shopDirectMerchant.getAlipayStatus()) && StrUtil.isNotBlank(shopDirectMerchant.getAlipayOrderId())) { + QueryStatusResp alipayStatus = EntryManager.queryAlipayEntryStatus(shopDirectMerchant.getAlipayOrderId()); + shopDirectMerchant.setAlipayStatus(alipayStatus.getStatus()); + shopDirectMerchant.setAlipayErrorMsg(alipayStatus.getFailReason()); + shopDirectMerchant.setAlipaySignUrl(""); + shopDirectMerchant.setAlipayMerchantId(alipayStatus.getThirdMerchantId()); + if (PayCst.EntryStatus.FINISH.equals(alipayStatus.getStatus())) { + alipayMerchantId = alipayStatus.getThirdMerchantId(); + } + } + shopDirectMerchantService.updateById(shopDirectMerchant); + if (StrUtil.isNotBlank(wechatMerchantId) || StrUtil.isNotBlank(alipayMerchantId)) { +// ShopMerchantDTO shopMerchantDTO = new ShopMerchantDTO(); +// shopMerchantDTO.setShopId(shopId); +// shopMerchantDTO.setChannel(PayChannelCst.NATIVE); +// shopMerchantDTO.setRelatedId(shopDirectMerchant.getShopId()); + NativeMerchantDTO nativeMerchantDTO = new NativeMerchantDTO(); + nativeMerchantDTO.setWechatMerchantId(wechatMerchantId); + nativeMerchantDTO.setAlipayMerchantId(alipayMerchantId); + if (StrUtil.isNotBlank(shopDirectMerchant.getAlipayAuthInfo())) { + AlipayAuthInfoDto alipayAuthInfoDto = JSONObject.parseObject(shopDirectMerchant.getAlipayAuthInfo(), AlipayAuthInfoDto.class); + nativeMerchantDTO.setAlipayAuthInfo(alipayAuthInfoDto); + } +// shopMerchantDTO.setNativeMerchantDTO(nativeMerchantDTO); +// shopMerchantService.editEntry(shopMerchantDTO, false); + shopMerchantService.upMerchant(shopDirectMerchant.getShopId(), nativeMerchantDTO); + } + } + } +} \ No newline at end of file diff --git a/cash-api/order-server/src/main/java/com/czg/task/OTimeTask.java b/cash-api/order-server/src/main/java/com/czg/task/OTimeTask.java index 4214ed379..fb57769c5 100644 --- a/cash-api/order-server/src/main/java/com/czg/task/OTimeTask.java +++ b/cash-api/order-server/src/main/java/com/czg/task/OTimeTask.java @@ -28,6 +28,7 @@ import java.util.List; /** * 订单过期处理 + * 退款失败 补偿 * * @author ww */ diff --git a/cash-api/order-server/src/main/resources/application-dev.yml b/cash-api/order-server/src/main/resources/application-dev.yml index a2d9fc027..2ac1a07d7 100644 --- a/cash-api/order-server/src/main/resources/application-dev.yml +++ b/cash-api/order-server/src/main/resources/application-dev.yml @@ -25,7 +25,23 @@ spring: port: 5672 username: chaozg password: chaozg123 - + # 关键优化:解决MissedHeartbeatException 心跳超时问题 + connection-timeout: 10000 # 连接超时时间(10秒,避免连接建立过慢) + requested-heartbeat: 30 # 心跳间隔调整为30秒(原60秒过长,降低超时概率;过短易误触发) + # 自动重连配置(Spring AMQP 自带,关键兜底) + publisher-returns: true + template: + retry: + enabled: true # 开启消息发送重试 + max-attempts: 3 # 最大重试次数 + initial-interval: 3000 # 首次重试间隔2秒 + multiplier: 1.5 # 重试间隔倍增因子 + listener: + simple: + retry: + enabled: true # 开启消费者重试 + max-attempts: 3 # 消费者最大重试次数 + acknowledge-mode: auto # 确认模式(可根据业务改为manual) dubbo: application: name: order-server diff --git a/cash-api/system-server/src/main/java/com/czg/controller/admin/SysCommonController.java b/cash-api/system-server/src/main/java/com/czg/controller/admin/SysCommonController.java new file mode 100644 index 000000000..d8eecea94 --- /dev/null +++ b/cash-api/system-server/src/main/java/com/czg/controller/admin/SysCommonController.java @@ -0,0 +1,59 @@ +package com.czg.controller.admin; + +import com.czg.BaseQueryParam; +import com.czg.resp.CzgResult; +import com.czg.system.entity.SysBankInfo; +import com.czg.system.entity.SysRegion; +import com.czg.system.service.SysBankInfoService; +import com.czg.system.service.SysCategoryInfoService; +import com.czg.system.service.SysRegionService; +import com.czg.system.vo.SysCategoryInfoVO; +import com.mybatisflex.core.paginate.Page; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 通用 + * + * @author Administrator + */ +@RestController +@RequestMapping("/admin/common") +public class SysCommonController { + @Resource + private SysRegionService sysRegionService; + @Resource + private SysBankInfoService bankInfoService; + @Resource + private SysCategoryInfoService categoryInfoService; + + /** + * 获取所有地域 + */ + @GetMapping("/region") + public CzgResult> region() { + return CzgResult.success(sysRegionService.regionList()); + } + + /** + * 获取银行信息 + */ + @GetMapping("/bankInfo") + public CzgResult> bankInfo(BaseQueryParam param, @RequestParam String bankName) { + return CzgResult.success(bankInfoService.bankInfoList(param, bankName)); + } + + + /** + * 类目信息表 + */ + @GetMapping("/category") + public CzgResult> category() { + return CzgResult.success(categoryInfoService.categoryList()); + } +} diff --git a/cash-common/cash-common-api-config/src/main/java/com/czg/config/FastJson2Config.java b/cash-common/cash-common-api-config/src/main/java/com/czg/config/FastJson2Config.java index a4c28e015..f4fe62181 100644 --- a/cash-common/cash-common-api-config/src/main/java/com/czg/config/FastJson2Config.java +++ b/cash-common/cash-common-api-config/src/main/java/com/czg/config/FastJson2Config.java @@ -50,9 +50,8 @@ public class FastJson2Config implements WebMvcConfigurer { // 设置支持的媒体类型 List supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); - supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); converter.setSupportedMediaTypes(supportedMediaTypes); // 将转换器添加到 Spring MVC 的消息转换器列表中 - converters.add(0, converter); + converters.addFirst(converter); } } diff --git a/cash-common/cash-common-api-config/src/main/java/com/czg/config/FilteredNacosRegistry.java b/cash-common/cash-common-api-config/src/main/java/com/czg/config/FilteredNacosRegistry.java index ff986780d..cacb01360 100644 --- a/cash-common/cash-common-api-config/src/main/java/com/czg/config/FilteredNacosRegistry.java +++ b/cash-common/cash-common-api-config/src/main/java/com/czg/config/FilteredNacosRegistry.java @@ -54,7 +54,7 @@ public class FilteredNacosRegistry extends NacosRegistry { public void register(URL url) { // 1. 获取原始注册的方法列表 String originalMethods = url.getParameter("methods"); - log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods); +// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods); if (originalMethods != null && !originalMethods.isEmpty()) { // 2. 过滤黑名单中的方法名 List filteredMethods = Arrays.stream(originalMethods.split(",")) @@ -67,12 +67,12 @@ public class FilteredNacosRegistry extends NacosRegistry { // 3. 处理过滤后的结果 if (filteredMethods.isEmpty()) { // 若所有方法都被过滤,直接终止注册(可选:根据业务决定是否保留服务注册) - log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface()); +// log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface()); return; } else { // 替换 URL 中的 methods 参数为过滤后的列表 url = url.addParameter("methods", String.join(",", filteredMethods)); - log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods); +// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods); } } diff --git a/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConfig.java b/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConfig.java index 5ba087dca..ab5b007cc 100644 --- a/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConfig.java +++ b/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConfig.java @@ -21,12 +21,24 @@ public class RabbitConfig { @Value("${spring.profiles.active}") private String activeProfile; + @Bean @Primary public DirectExchange directExchange() { return new DirectExchange(activeProfile + "-" + RabbitConstants.Exchange.CASH_EXCHANGE); } + //------------------------------------------------------ 商家入驻 + @Bean + public Queue entryManagerQueue() { + return new Queue(activeProfile + "-" + RabbitConstants.Queue.SHOP_ENTRY_MANAGER, true, false, false); + } + + @Bean + public Binding entryManagerExchange(Queue entryManagerQueue, DirectExchange exchange) { + return BindingBuilder.bind(entryManagerQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.SHOP_ENTRY_MANAGER); + } + //------------------------------------------------------订单打印队列 @Bean public Queue orderPrintQueue() { @@ -36,6 +48,7 @@ public class RabbitConfig { args.put("x-message-ttl", 180000); return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_PRINT_QUEUE, true, false, false, args); } + @Bean public Binding bindingOrderPrintExchange(Queue orderPrintQueue, DirectExchange exchange) { return BindingBuilder.bind(orderPrintQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_PRINT_QUEUE); @@ -51,6 +64,7 @@ public class RabbitConfig { // args.put("x-message-ttl", 180000); return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_MACHINE_PRINT_QUEUE, true, false, false); } + @Bean public Binding bindingOrderMachinePrintExchange(Queue orderMachinePrintQueue, DirectExchange exchange) { return BindingBuilder.bind(orderMachinePrintQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_MACHINE_PRINT_QUEUE); @@ -61,6 +75,7 @@ public class RabbitConfig { public Queue handoverPrintQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_HANDOVER_PRINT_QUEUE, true, false, false); } + @Bean public Binding bindingHandoverPrintExchange(Queue handoverPrintQueue, DirectExchange exchange) { return BindingBuilder.bind(handoverPrintQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_HANDOVER_PRINT_QUEUE); @@ -69,11 +84,12 @@ public class RabbitConfig { //------------------------------------------------------叫号 打票 @Bean public Queue callTablePrintQueue() { - return new Queue(activeProfile + "-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE, true, false, false); + return new Queue(activeProfile + "-" + RabbitConstants.Queue.CALL_TABLE_QUEUE, true, false, false); } + @Bean public Binding bindingCallTablePrintExchange(Queue callTablePrintQueue, DirectExchange exchange) { - return BindingBuilder.bind(callTablePrintQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE); + return BindingBuilder.bind(callTablePrintQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.CALL_TABLE_QUEUE); } //------------------------------------------------------订单取消 @@ -81,9 +97,10 @@ public class RabbitConfig { public Queue orderCancelQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_CANCEL_QUEUE, true); } + @Bean - public Binding bindingOrderCancelExchange(Queue orderPrintQueue, DirectExchange exchange) { - return BindingBuilder.bind(orderPrintQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_CANCEL_QUEUE); + public Binding bindingOrderCancelExchange(Queue orderCancelQueue, DirectExchange exchange) { + return BindingBuilder.bind(orderCancelQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_CANCEL_QUEUE); } //------------------------------------------------------ 订单库存更新 @@ -91,6 +108,7 @@ public class RabbitConfig { public Queue orderStockQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_STOCK_QUEUE, true); } + @Bean public Binding bindingOrderStockExchange(Queue orderStockQueue, DirectExchange exchange) { return BindingBuilder.bind(orderStockQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_STOCK_QUEUE); @@ -102,6 +120,7 @@ public class RabbitConfig { public Queue productInfoChangeQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.PRODUCT_INFO_CHANGE_QUEUE, true); } + @Bean public Binding bindingProductInfoChange(Queue productInfoChangeQueue, DirectExchange exchange) { return BindingBuilder.bind(productInfoChangeQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.PRODUCT_INFO_CHANGE_QUEUE); @@ -112,6 +131,7 @@ public class RabbitConfig { public Queue orderRefundQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_REFUND_QUEUE, true); } + @Bean public Binding bindingOrderRefundExchange(Queue orderRefundQueue, DirectExchange exchange) { return BindingBuilder.bind(orderRefundQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_REFUND_QUEUE); @@ -119,6 +139,7 @@ public class RabbitConfig { //------------------------------------------------------ 申请短信模板队列 + /** * 1,2,applySmsTemp 模版审核 * 1,2,sendMarkSms 发送营销短信 @@ -129,16 +150,18 @@ public class RabbitConfig { public Queue applySmsTemplateQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.APPLY_SMS_TEMPLATE_QUEUE, true); } + @Bean public Binding bindingApplySmsTemplateExchange(Queue applySmsTemplateQueue, DirectExchange exchange) { return BindingBuilder.bind(applySmsTemplateQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.APPLY_SMS_TEMPLATE_QUEUE); } - //------------------------------------------------------ 生日礼品短信队列 + //------------------------------------------------------ 生日礼品短信队列 @Bean public Queue birthdayGiftSmsQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.BIRTHDAY_GIFT_SMS_QUEUE, true); } + @Bean public Binding bindingBirthdayGiftSmsExchange(Queue birthdayGiftSmsQueue, DirectExchange exchange) { return BindingBuilder.bind(birthdayGiftSmsQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.BIRTHDAY_GIFT_SMS_QUEUE); @@ -149,6 +172,7 @@ public class RabbitConfig { public Queue orderProductStatusQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_PRODUCT_STATUS_QUEUE, true); } + @Bean public Binding bindingOrderProductStatusExchange(Queue orderProductStatusQueue, DirectExchange exchange) { return BindingBuilder.bind(orderProductStatusQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_PRODUCT_STATUS_QUEUE); @@ -160,6 +184,7 @@ public class RabbitConfig { public Queue orderDetailStatusQueue() { return new Queue(activeProfile + "-" + RabbitConstants.Queue.ORDER_DETAIL_STATUS_QUEUE, true); } + @Bean public Binding bindingOrderDetailStatusExchange(Queue orderDetailStatusQueue, DirectExchange exchange) { return BindingBuilder.bind(orderDetailStatusQueue).to(exchange).with(activeProfile + "-" + RabbitConstants.Queue.ORDER_DETAIL_STATUS_QUEUE); diff --git a/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConstants.java b/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConstants.java index c43827b41..d4a0a9b69 100644 --- a/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConstants.java +++ b/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitConstants.java @@ -10,13 +10,14 @@ public interface RabbitConstants { } class Queue { + public static final String SHOP_ENTRY_MANAGER = "shop.entry.manager"; public static final String ORDER_STOCK_QUEUE = "order.stock.queue"; public static final String ORDER_REFUND_QUEUE = "order.refund.queue"; public static final String ORDER_CANCEL_QUEUE = "order.cancel.queue"; public static final String ORDER_PRINT_QUEUE = "order.print.queue"; public static final String ORDER_MACHINE_PRINT_QUEUE = "order.machine.print.queue"; public static final String ORDER_HANDOVER_PRINT_QUEUE = "order.handover.print.queue"; - public static final String CALL_TABLE_PRINT_QUEUE = "call.table.print.queue"; + public static final String CALL_TABLE_QUEUE = "call.table.print.queue"; public static final String PRODUCT_INFO_CHANGE_QUEUE = "product.info.change.queue"; /** diff --git a/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitPublisher.java b/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitPublisher.java index a5e49a8a5..4f591498f 100644 --- a/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitPublisher.java +++ b/cash-common/cash-common-mq/src/main/java/com/czg/config/RabbitPublisher.java @@ -130,6 +130,13 @@ public class RabbitPublisher { sendMsg(RabbitConstants.Queue.ORDER_PRODUCT_STATUS_QUEUE, qrContent); } + /** + * 进件 + */ + public void sendEntryManagerMsg(String shopId) { + sendMsg(RabbitConstants.Queue.SHOP_ENTRY_MANAGER, shopId); + } + /** * 订单商品状态消息 diff --git a/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java b/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java index cbfd9dd99..e915eb0f0 100644 --- a/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java +++ b/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java @@ -33,6 +33,8 @@ public interface RedisCst { public static final String EXPIRED_WECHAT = "expired:wechat:"; } + //商家进件 + String SHOP_ENTRY = "shop:entry"; String SMS_CODE = "sms:code:"; // 店铺会员动态支付码 diff --git a/cash-common/cash-common-redis/src/main/java/com/czg/service/RedisService.java b/cash-common/cash-common-redis/src/main/java/com/czg/service/RedisService.java index e84608854..c5d078a80 100644 --- a/cash-common/cash-common-redis/src/main/java/com/czg/service/RedisService.java +++ b/cash-common/cash-common-redis/src/main/java/com/czg/service/RedisService.java @@ -6,14 +6,15 @@ import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; /** * @author GYJoker @@ -650,4 +651,106 @@ public class RedisService { } return JSON.parseArray(jsonStr, type); } + + + public static int retryCount = 5; + + /** + * 执行任务并保证锁唯一 + * + * @param supplier 业务逻辑 + * @param lockKey Redis锁的Key + * @return 业务逻辑返回值 + */ + public T runFunAndCheckKey(Supplier supplier, String lockKey) { + String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId()); + try { + // 尝试获取锁,超时时间 5 秒,防止死锁 + boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)); + int count = 0; + // 初始等待 10ms + int retryDelay = 10; + + while (!lock) { + // 最多重试 10 次,大约 10 秒 + if (count++ > 50) { + throw new RuntimeException("系统繁忙, 稍后再试"); + } + Thread.sleep(retryDelay); + // 指数退避,最大等待 200ms + retryDelay = Math.min(retryDelay * 2, 200); + lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)); + } + + // 执行任务 + return supplier.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("线程被中断", e); + } catch (Exception e) { + log.error("执行出错:{}", e.getMessage(), e); + throw e; + } finally { + // 释放锁(使用 Lua 脚本确保原子性) + unlock(lockKey, lockValue); + } + } + + /** + * 使用 Lua 脚本确保释放锁的原子性 + * + * @param lockKey 锁的 Key + * @param lockValue 当前线程的锁值 + */ + private void unlock(String lockKey, String lockValue) { + String luaScript = + "if redis.call('get', KEYS[1]) == ARGV[1] then " + + "return redis.call('del', KEYS[1]) " + + "else return 0 end"; + redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), + Collections.singletonList(lockKey), lockValue); + } + + public static R runFunAndRetry( + Supplier function, + Function check, Consumer errFun) { + R result = function.get(); + boolean flag = check.apply(result); + + while (flag && retryCount-- > 0) { + result = function.get(); + flag = check.apply(result); + } + + if (flag) { + errFun.accept(result); + } + return result; + } + + + /** + * 防抖函数:在指定秒数内相同 Key 的任务只会执行一次 + * + * @param key 防抖使用的 Redis Key + * @param seconds 防抖时间(秒) + * @param task 要执行的业务逻辑 + * @return true 执行了任务;false 在防抖期内被拦截 + */ + public boolean debounce(String key, long seconds, Runnable task) { + try { + Boolean success = redisTemplate.opsForValue().setIfAbsent( + key, "1", seconds, TimeUnit.SECONDS + ); + + if (Boolean.TRUE.equals(success)) { + task.run(); + return true; + } + return false; + } catch (Exception e) { + log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e); + return false; + } + } } diff --git a/cash-common/cash-common-service/pom.xml b/cash-common/cash-common-service/pom.xml index 212020144..ce95f89fe 100644 --- a/cash-common/cash-common-service/pom.xml +++ b/cash-common/cash-common-service/pom.xml @@ -10,7 +10,7 @@ cash-common-service jar - global-service + common-service https://maven.apache.org diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/shopinfo/ShopInfoEditDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/shopinfo/ShopInfoEditDTO.java index df25a24ff..ef7865b69 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/shopinfo/ShopInfoEditDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/shopinfo/ShopInfoEditDTO.java @@ -285,4 +285,13 @@ public class ShopInfoEditDTO { */ private Integer isCountStick; + /** + * 企业id + */ + private String weworkId; + /** + * 企业接入链接 + */ + private String weworkUrl; + } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/QuickMenu.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/QuickMenu.java new file mode 100644 index 000000000..5d72f08e5 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/QuickMenu.java @@ -0,0 +1,69 @@ +package com.czg.account.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import java.io.Serializable; +import java.time.LocalDateTime; + +import java.io.Serial; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 悬浮窗配置 实体类。 + * + * @author ww + * @since 2025-12-29 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("tb_quick_menu") +public class QuickMenu implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Auto) + private Integer id; + + /** + * 店铺Id + */ + private Long shopId; + /** + * 菜单图标 + */ + private String url; + + + /** + * 菜单Id + */ + @NotNull(message = "关联菜单不能为空") + private Long menuId; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态 1-启用 0-禁用 + */ + private Integer status; + + @Column(onInsertValue = "now()") + private LocalDateTime createTime; + + @Column(onInsertValue = "now()", onUpdateValue = "now()") + private LocalDateTime updateTime; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopConfig.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopConfig.java index d2659f44c..e1cfc1c4f 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopConfig.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopConfig.java @@ -135,5 +135,13 @@ public class ShopConfig implements Serializable { private String dingAppKey; private String dingAppSecret; + /** + * 企业id + */ + private String weworkId; + /** + * 企业接入链接 + */ + private String weworkUrl; } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopInfo.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopInfo.java index 359870ae8..8ed2ff56e 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopInfo.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopInfo.java @@ -32,7 +32,6 @@ public class ShopInfo implements Serializable { private static final long serialVersionUID = 1L; - /** * 使用系统用户 sys_user id */ @@ -363,4 +362,15 @@ public class ShopInfo implements Serializable { */ private BigDecimal amount; + /** + * 企业id + */ + @Column(ignore = true) + private String weworkId; + /** + * 企业接入链接 + */ + @Column(ignore = true) + private String weworkUrl; + } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/service/QuickMenuService.java b/cash-common/cash-common-service/src/main/java/com/czg/account/service/QuickMenuService.java new file mode 100644 index 000000000..ce1a41c9e --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/service/QuickMenuService.java @@ -0,0 +1,14 @@ +package com.czg.account.service; + +import com.mybatisflex.core.service.IService; +import com.czg.account.entity.QuickMenu; + +/** + * 悬浮窗配置 服务层。 + * + * @author ww + * @since 2025-12-29 + */ +public interface QuickMenuService extends IService { + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopInfoService.java b/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopInfoService.java index f87dfec90..4d951bba3 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopInfoService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopInfoService.java @@ -23,8 +23,9 @@ public interface ShopInfoService extends IService { /** * 检测开关 - * @param shopId 店铺id - * @param switchType ShopInfo的某列 开关 目前只支持Integer类型字段 + * + * @param shopId 店铺id + * @param switchType ShopInfo的某列 开关 目前只支持Integer类型字段 * @return true:开启 false:关闭 */ boolean checkSwitch(Long shopId, ShopSwitchTypeEnum switchType) throws ValidateException; diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopMerchantService.java b/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopMerchantService.java deleted file mode 100644 index 9422a925a..000000000 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/service/ShopMerchantService.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.czg.account.service; - -import com.czg.account.dto.merchant.ShopMerchantEditDTO; -import com.czg.account.entity.ShopMerchant; -import com.mybatisflex.core.service.IService; - -import java.io.Serializable; - -/** - * 第三方商户进件 服务层。 - * - * @author Administrator - * @since 2025-02-11 - */ -public interface ShopMerchantService extends IService { - - ShopMerchant detail(Integer shopId); - - Boolean edit(ShopMerchantEditDTO shopMerchantEditDTO); - @Override - ShopMerchant getById(Serializable id); -} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/constants/ParamCodeCst.java b/cash-common/cash-common-service/src/main/java/com/czg/constants/ParamCodeCst.java index 62829bdb5..53116dee5 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/constants/ParamCodeCst.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/constants/ParamCodeCst.java @@ -79,7 +79,27 @@ public interface ParamCodeCst { * 超掌柜支付回调地址 *

支付宝/微信支付完成后,支付平台回调我方系统的地址

*/ - public static String PAY_CZG_NOTIFY_URL = "pay_czg_notify_url"; + public static String NATIVE_PAY_NOTIFY_URL = "native_pay_notify_url"; + public static String NATIVE_REFUND_NOTIFY_URL = "native_refund_notify_url"; + + /** + * 超掌柜支付域名 + *

超掌柜支付相关接口的根域名

+ */ + public static String POLY_DOMAIN = "poly_domain"; + public static String POLY_PAY_NOTIFY_URL = "poly_pay_notify_url"; + public static String POLY_REFUND_NOTIFY_URL = "poly_refund_notify_url"; + + /** + * 微信原生回调地址 + *

微信原生支付接口的回调地址(区别于超掌柜封装的回调)

+ */ + public static String NATIVE_NOTIFY_URL = "native_notify_url"; + /** + * 店铺订单支付BaseUrl + *

店铺订单支付页面的基础域名

+ */ + public static String SHOP_ORDER_PAY_BASE_URL = "shop_order_pay_base_url"; /** * 排队到号通知 */ @@ -111,11 +131,6 @@ public interface ParamCodeCst { */ public static String SMS_FEE = "sms_fee"; - /** - * 店铺订单支付BaseUrl - *

店铺订单支付页面的基础域名

- */ - public static String SHOP_ORDER_PAY_BASE_URL = "shop_order_pay_base_url"; /** * 平台名称 @@ -123,30 +138,12 @@ public interface ParamCodeCst { */ public static String PLATE_NAME = "plate_name"; - /** - * 超掌柜退款回调地址 - *

支付平台处理退款后,回调我方系统的地址

- */ - public static String PAY_CZG_REFUND_NOTIFY_URL = "pay_czg_refund_notify_url"; - - /** - * 超掌柜支付域名 - *

超掌柜支付相关接口的根域名

- */ - public static String PAY_CZG_DOMAIN = "pay_czg_domain"; - /** * 叫号页面地址 *

餐厅叫号系统的前端页面地址

*/ public static String CALL_PAGE_URL = "call_page_url"; - /** - * 微信原生回调地址 - *

微信原生支付接口的回调地址(区别于超掌柜封装的回调)

- */ - public static String NATIVE_NOTIFY_URL = "native_notify_url"; - /** * 公众号关注位置 *

公众号关注入口的展示位置,可选值:mine-我的页面、order-订单页面、eat-就餐页面

diff --git a/cash-common/cash-common-service/src/main/java/com/czg/constants/SystemConstants.java b/cash-common/cash-common-service/src/main/java/com/czg/constants/SystemConstants.java index 054303c9c..f480ce98d 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/constants/SystemConstants.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/constants/SystemConstants.java @@ -43,4 +43,30 @@ public interface SystemConstants { */ public static final String CUSTOM = "custom"; } + + + /** + * 三方支付类型 + */ + class PayType { + /** + * 微信支付 + */ + public static final String WECHAT = "wechatPay"; + + /** + * 支付宝支付 + */ + public static final String ALIPAY = "alipay"; + + /** + * 微信小程序支付 + */ + public static final String WECHAT_APP_ID = "wxd88fffa983758a30"; + + /** + * 支付宝小程序支付 + */ + public static final String ALIPAY_APP_ID = "2021004145625815"; + } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/LtPayOtherDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/LtPayOtherDTO.java index 9a28a035d..bc0365296 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/LtPayOtherDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/LtPayOtherDTO.java @@ -3,6 +3,7 @@ package com.czg.order.dto; import com.czg.utils.AssertUtil; import jakarta.validation.constraints.NotNull; import lombok.Data; +import lombok.experimental.Accessors; import java.math.BigDecimal; @@ -13,6 +14,7 @@ import java.math.BigDecimal; * @author ww */ @Data +@Accessors(chain = true) public class LtPayOtherDTO { /** * 积分商品id/团购商品id diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java index 8fd8f06bd..05b7ece75 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java @@ -24,6 +24,10 @@ public class MkDistributionPayDTO implements Serializable { private Long shopId; private String platformType = "DIS"; private Long userId; + /** + * 支付类型 + * {@link com.czg.constants.SystemConstants.PayType} + */ private String payType; private String returnUrl; private String buyerRemark; diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoAddDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoAddDTO.java index e40f33d1f..6e6f53be1 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoAddDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoAddDTO.java @@ -28,6 +28,7 @@ public class OrderInfoAddDTO implements Serializable { * 已出菜 SENT_OUT * 已上菜 DELIVERED * 已超时 EXPIRED + * 加急 URGENT */ private String subStatus; //限时折扣部分 diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoDTO.java deleted file mode 100644 index 411cb3fb6..000000000 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/OrderInfoDTO.java +++ /dev/null @@ -1,267 +0,0 @@ - -package com.czg.order.dto; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.time.LocalDateTime; - -import com.alibaba.fastjson2.annotation.JSONField; - -import java.io.Serial; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 订单表 实体类。 - * - * @author ww - * @since 2025-02-13 - */ -@Data - -@NoArgsConstructor -@AllArgsConstructor -public class OrderInfoDTO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - private Long id; - - /** - * 订单编号 - * pc 收银机客户端 PC+雪花ID - * wechat 微信小程序 WX+雪花ID - * alipay 支付宝小程序 ALI+雪花ID - * admin-pc PC管理端 WEB+雪花ID - * admin-app APP管理端 APP+雪花ID - */ - private String orderNo; - - /** - * 店铺Id - */ - private Long shopId; - - /** - * 用户Id user_info表的id - */ - private Long userId; - - /** - * 退单金额 - */ - private BigDecimal refundAmount; - - /** - * 订单原金额 不含折扣价格 - */ - private BigDecimal originAmount; - /** - * 抹零金额 减免多少钱 - */ - private BigDecimal roundAmount; - /** - * 优惠总金额 - */ - private BigDecimal discountAllAmount; - - /** - * 订单金额 (扣除各类折扣) - */ - private BigDecimal orderAmount; - - /** - * 实际支付金额 - */ - private BigDecimal payAmount; - - /** - * 积分抵扣金额 - */ - private BigDecimal pointsDiscountAmount; - - /** - * 使用的积分数量 - */ - private Integer pointsNum; - - /** - * 商品优惠券抵扣金额 - */ - private BigDecimal productCouponDiscountAmount; - - /** - * 用户使用的卡券 - */ - private String couponInfoList; - - /** - * 满减活动抵扣金额 - */ - private BigDecimal discountActAmount; - - /** - * 折扣金额 - */ - private BigDecimal discountAmount; - -// /** -// * 折扣比例 -// */ -// private BigDecimal discountRatio; - - /** - * 打包费 - */ - private BigDecimal packFee; - - /** - * 台桌Id - */ - private String tableCode; - - /** - * 台桌名称 - */ - private String tableName; - - /** - * 订单类型- - * cash收银(除小程序以外 都属于收银) - * miniapp小程序 - */ - private String orderType; - - /** - * 平台类型 pc 收银机客户端 wechat 微信小程序 alipay 支付宝小程序 admin-pc PC管理端 admin-app APP管理端 - */ - private String platformType; - - /** - * 用餐模式 堂食 dine-in 外带 take-out 外卖 take-away - */ - private String dineMode; - - /** - * 支付模式: - * 后付费 after-pay - * 先付费 before-pay - * 无桌码 no-table - */ - private String payMode; - - /** - * 支付类型 - * 主扫 main-scan - * 被扫 back-scan - * 微信小程序 wechat-mini - * 支付宝小程序 alipay-mini - * 会员支付 vip-pay - * 现金支付 cash-pay - */ - private String payType; - - /** - * 状态: unpaid-待支付;in-production 制作中;wait_out 待取餐;;done-订单完成;refunding-申请退单;refund-退单;part_refund 部分退单;cancelled-取消订单 - */ - private String status; - - /** - * 折扣信息 json - */ - private String discountInfo; - - /** - * 限时折扣信息 json - */ - private String limitRate; - - /** - * 是否支持退款,1支持退单, 0不支持退单 - */ - private Integer refundAble; - - /** - * 支付时间 - */ - @JSONField(format = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime paidTime; - - @JSONField(format = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @JSONField(format = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; - - /** - * 支付订单号 - * tb_order_payment.id - * tb_shop_user_flow.id - */ - private Long payOrderId; - - /** - * 交易日期 - */ - private String tradeDay; - - /** - * 备注 - */ - private String remark; - - /** - * 取餐码 - */ - private String takeCode; - - /** - * 员工id - */ - private Long staffId; - - /** - * 当前订单下单次数 - */ - private Integer placeNum; - - /** - * 用餐人数 - */ - private Integer seatNum; - - /** - * 餐位费 - */ - private BigDecimal seatAmount; - - /** - * 退款备注 - */ - private String refundRemark; - - /** - * 是否使用了霸王餐 - */ - private Integer isFreeDine; - - /** - * 是否等叫 0 否 1 等叫 - */ - private Integer isWaitCall; - - /** - * 挂账人id - */ - private Long creditBuyerId; - - /** - * 是否回收站 0-否,1回收站 - */ - private Integer isDel; - private String failMsg; - -} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/ShopMerchantDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/ShopMerchantDTO.java new file mode 100644 index 000000000..959e61b5a --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/ShopMerchantDTO.java @@ -0,0 +1,40 @@ +package com.czg.order.dto; + +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.pay.NativeMerchantDTO; +import com.czg.pay.PolyMerchantDTO; +import lombok.Data; + +/** + * 支付信息 + * + * @author ww + */ +@Data +public class ShopMerchantDTO { + + private Long shopId; + /** + * poly 聚合(支付平台) native 原生(wx/ali 原生) + * {@link com.czg.constant.PayChannelCst} + */ + private String channel; + /** + * 聚合支付商户 + * native 必填 对应 tb_shop_direct_merchant 的 shopId + */ + private Long relatedId; + /** + * 原生支付参数 + */ + private NativeMerchantDTO nativeMerchantDTO; + /** + * 聚合支付参数 + */ + private PolyMerchantDTO polyMerchantDTO; + + /** + * 店铺绑定的商户信息 + */ + private ShopDirectMerchant shopDirectMerchant; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderInfo.java b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderInfo.java index 1c62bce3e..f8df987f4 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderInfo.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderInfo.java @@ -1,6 +1,8 @@ package com.czg.order.entity; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; import com.czg.order.dto.LimitRateDTO; import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Id; @@ -298,8 +300,10 @@ public class OrderInfo implements Serializable { private Integer isDel; private String failMsg; - - + /** + * 打印状态 Json格式 + */ + private String printStatus; public String getRefundRemark() { @@ -342,4 +346,41 @@ public class OrderInfo implements Serializable { // 如果需要加上抹零金额,可以取消下面这行注释 // .add(this.getRoundAmount() != null ? this.getRoundAmount() : BigDecimal.ZERO); } + + private JSONArray getPrintStatusAsArray() { + if (StrUtil.isBlank(printStatus)) { + return new JSONArray(); + } + try { + return JSONArray.parseArray(printStatus.trim()); + } catch (Exception e) { + return new JSONArray(); + } + } + + public void upPrintStatus(JSONObject printJson, boolean isPrintSuccess) { + String currentDeviceId = printJson.getString("id"); + JSONArray oldPrintStatusArray = getPrintStatusAsArray(); + // 3. 初始化新的打印状态JSON数组(用于存储处理后的结果) + JSONArray newPrintStatusArray = new JSONArray(); + // 场景1:打印成功 - 移除原有数组中与当前设备ID一致的记录,保留其余记录 + if (oldPrintStatusArray != null && !oldPrintStatusArray.isEmpty()) { + for (int i = 0; i < oldPrintStatusArray.size(); i++) { + JSONObject deviceObj = oldPrintStatusArray.getJSONObject(i); + String deviceId = deviceObj.getString("id"); + // 仅保留非当前设备ID的记录 + if (currentDeviceId != null && !currentDeviceId.equals(deviceId)) { + newPrintStatusArray.add(deviceObj); + } + } + } + if (!isPrintSuccess) { + newPrintStatusArray.add(printJson); + } + if (!newPrintStatusArray.isEmpty()) { + printStatus = newPrintStatusArray.toJSONString(); + } else { + printStatus = ""; + } + } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderPayment.java b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderPayment.java index 16a58f2b5..06aa287b5 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderPayment.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/OrderPayment.java @@ -5,6 +5,7 @@ import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Id; import com.mybatisflex.annotation.KeyType; import com.mybatisflex.annotation.Table; + import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -40,6 +41,17 @@ public class OrderPayment implements Serializable { */ private Long shopId; + /** + * 支付渠道 + * {@link com.czg.constant.PayChannelCst} + */ + private String channel; + + /** + * 平台类型 + * {@link com.czg.PayCst.Platform} + */ + private String platformType; /** * 来源Id 订单Id或充值id */ @@ -104,28 +116,67 @@ public class OrderPayment implements Serializable { public OrderPayment() { } - public OrderPayment(@NonNull Long shopId,@NonNull Long sourceId, @NotBlank String sourceType,@NotBlank String payType, @NotBlank String orderNo, - String authCode, @NonNull BigDecimal amount) { - this.shopId = shopId; - this.sourceId = sourceId; - this.sourceType = sourceType; - this.payType = payType; - this.orderNo = orderNo; - this.authCode = authCode; - this.amount = amount; - this.payStatus = PayTypeConstants.PayStatus.INIT; + /** + * 订单专用支付 + * + * @param sourceId 订单Id + * @param amount 单元 元 + * @param authCode 扫码支付时必填 扫描码 + */ + public static OrderPayment orderPay(@NonNull Long shopId, @NonNull Long sourceId, + @NotBlank String orderNo, @NonNull BigDecimal amount, String authCode) { + OrderPayment orderPayment = getInstance(shopId, sourceId, PayTypeConstants.SourceType.ORDER, orderNo, amount, authCode, null); + orderPayment.setPayType(PayTypeConstants.PayType.PAY); + orderPayment.setPayStatus(PayTypeConstants.PayStatus.INIT); + return orderPayment; } - public OrderPayment(@NonNull Long shopId,@NonNull Long sourceId, @NotBlank String sourceType,@NotBlank String payType, @NotBlank String orderNo, - String authCode, @NonNull BigDecimal amount, Long relatedId) { - this.shopId = shopId; - this.sourceId = sourceId; - this.sourceType = sourceType; - this.payType = payType; - this.orderNo = orderNo; - this.authCode = authCode; - this.amount = amount; - this.relatedId = relatedId; - this.payStatus = PayTypeConstants.PayStatus.INIT; + /** + * 初始化支付参数 + * + * @param sourceId 充值时为会员Id shopUserID + * 购买时为商品Id + * @param sourceType {@link PayTypeConstants.SourceType} 支付来源 + * @param amount 单元 元 + * @param authCode 扫码支付时必填 扫描码 + * @param relatedId 霸王餐充值为 订单id 会员充值为 活动id 充值并支付时 为 MkShopRechargeDetail + */ + public static OrderPayment pay(@NonNull Long shopId, @NonNull Long sourceId, @NotBlank String sourceType, + @NotBlank String orderNo, @NonNull BigDecimal amount, + String authCode, Long relatedId) { + OrderPayment orderPayment = getInstance(shopId, sourceId, sourceType, orderNo, amount, authCode, relatedId); + orderPayment.setPayType(PayTypeConstants.PayType.PAY); + orderPayment.setPayStatus(PayTypeConstants.PayStatus.INIT); + return orderPayment; + } + + public static OrderPayment refund(@NonNull Long shopId, @NonNull Long sourceId, @NotBlank String sourceType, + @NotBlank String orderNo, @NonNull BigDecimal amount, Long relatedId, String platformType) { + OrderPayment orderPayment = getInstance(shopId, sourceId, sourceType, orderNo, amount, null, relatedId); + orderPayment.setPayType(PayTypeConstants.PayType.REFUND); + orderPayment.setPayStatus(PayTypeConstants.PayStatus.INIT); + orderPayment.setPlatformType(platformType); + return orderPayment; + } + + public static OrderPayment refundCompensate(@NonNull Long shopId, @NonNull Long sourceId, @NotBlank String sourceType, + @NotBlank String orderNo, @NonNull BigDecimal amount, Long relatedId) { + OrderPayment orderPayment = getInstance(shopId, sourceId, sourceType, orderNo, amount, null, relatedId); + orderPayment.setPayType(PayTypeConstants.PayType.REFUND_COMPENSATE); + orderPayment.setPayStatus(PayTypeConstants.PayStatus.INIT); + return orderPayment; + } + + private static OrderPayment getInstance(Long shopId, Long sourceId, String sourceType, + String orderNo, BigDecimal amount, String authCode, Long relatedId) { + OrderPayment orderPayment = new OrderPayment(); + orderPayment.shopId = shopId; + orderPayment.sourceId = sourceId; + orderPayment.sourceType = sourceType; + orderPayment.orderNo = orderNo; + orderPayment.authCode = authCode; + orderPayment.amount = amount; + orderPayment.relatedId = relatedId; + return orderPayment; } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopDirectMerchant.java b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopDirectMerchant.java new file mode 100644 index 000000000..928e1dd31 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopDirectMerchant.java @@ -0,0 +1,149 @@ +package com.czg.order.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import java.io.Serial; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商户进件 实体类。 + * + * @author ww + * @since 2026-01-07 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("tb_shop_direct_merchant") +public class ShopDirectMerchant implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 店铺id + */ + @Id + private Long shopId; + /** + * 店铺名称 + */ + @Column(ignore = true) + private String shopName; + + /** + * 营业执照编号 + */ + private String licenceNo; + /** + * 支付宝账号 + */ + private String alipayAccount; + /** + * 商户编号(在当前系统唯一) + */ + private String merchantCode; + /** + * 【必填】 + * 商户类型 + * 0: 个体商户; + * 1: 企业商户; + * 3: 小微商户 暂不支持 + */ + private String userType; + + /** + * 【必填】 + * 商户简称--企业、个体必填 + */ + private String shortName; + /** + * 商户基础信息 + */ + private String merchantBaseInfo; + + /** + * 法人信息 + */ + private String legalPersonInfo; + + /** + * 营业执照信息 + */ + private String businessLicenceInfo; + + /** + * 门店信息 + */ + private String storeInfo; + + /** + * 结算信息 + */ + private String settlementInfo; + + @Column(onInsertValue = "now()") + private LocalDateTime createTime; + + @Column(onInsertValue = "now()", onUpdateValue = "now()") + private LocalDateTime updateTime; + + + private String errorMsg; + + private String wechatApplyId; + /** + * 微信状态 + */ + private String wechatStatus; + + /** + * 微信进件错误信息 + */ + private String wechatErrorMsg; + /** + * 微信进件签名地址 + */ + private String wechatSignUrl; + + private String alipayOrderId; + + /** + * 支付宝状态 + */ + private String alipayStatus; + + /** + * 支付宝进件错误信息 + */ + private String alipayErrorMsg; + /** + * 支付宝进件签名地址 + */ + private String alipaySignUrl; + + /** + * 支付宝授信息 + */ + private String alipayAuthInfo; + /** + * 微信商户id + */ + private String wechatMerchantId; + /** + * 支付宝商户id + */ + private String alipayMerchantId; + + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopMerchant.java b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopMerchant.java similarity index 61% rename from cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopMerchant.java rename to cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopMerchant.java index 25f37f90d..2e345d184 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/ShopMerchant.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopMerchant.java @@ -1,4 +1,4 @@ -package com.czg.account.entity; +package com.czg.order.entity; import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Id; @@ -18,57 +18,54 @@ import java.time.LocalDateTime; * @since 2025-02-11 */ @Data - @NoArgsConstructor @AllArgsConstructor -@Table("tb_shop_merchant") +@Table("tb_shop_merchant2") public class ShopMerchant implements Serializable { @Serial private static final long serialVersionUID = 1L; + @Id + private Long id; /** * 店铺id */ - @Id private Long shopId; /** - * 支付系统商户id + * poly 聚合(支付平台) native 原生(wx/ali 原生) + * {@link com.czg.constant.PayChannelCst} */ - private String storeId; - - private String merchantName; + private String channel; /** - * 商户应用id + * 聚合支付商户 + * native 必填 对应 tb_shop_direct_merchant 的 shopId */ - private String appId; + private Long relatedId; /** - * 商户token + * 微信appid */ - private String appSecret; + private String wechatAppId; + /** + * 支付宝appid + */ + private String alipayAppId; /** - * 微信小程序appid + * 聚合支付参数 */ - private String wechatSmallAppid; - + private String polyPayJson; /** - * 支付宝小程序appid + * 原生支付参数 */ - private String alipaySmallAppid; - - /** - * 支付密码 - */ - private String payPassword; + private String nativePayJson; @Column(onInsertValue = "now()") private LocalDateTime createTime; @Column(onInsertValue = "now()", onUpdateValue = "now()") private LocalDateTime updateTime; - } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/service/OrderInfoCustomService.java b/cash-common/cash-common-service/src/main/java/com/czg/order/service/OrderInfoCustomService.java index 62cdec4d5..bda46f3d6 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/service/OrderInfoCustomService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/service/OrderInfoCustomService.java @@ -12,9 +12,9 @@ import com.czg.order.enums.PayEnums; import com.czg.order.vo.HistoryOrderPrintVo; import com.czg.order.vo.HistoryOrderVo; import com.czg.order.vo.OrderInfoVo; +import com.czg.pay.PayNotifyRespDTO; import com.czg.resp.CzgResult; import com.mybatisflex.core.paginate.Page; -import com.mybatisflex.core.service.IService; import jakarta.validation.constraints.NotBlank; import org.jetbrains.annotations.NotNull; @@ -44,7 +44,7 @@ public interface OrderInfoCustomService { CzgResult mergeOrder(MergeOrderDTO param); - void payCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson, int retryCount); + void payCallBackOrder(@NotBlank String orderNo, @NotNull PayNotifyRespDTO notifyRespDTO, String channel, int retryCount); void refundCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson); diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/service/PrintMachineLogService.java b/cash-common/cash-common-service/src/main/java/com/czg/order/service/PrintMachineLogService.java index 22d8d77f8..5449bec74 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/service/PrintMachineLogService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/service/PrintMachineLogService.java @@ -11,6 +11,8 @@ import com.czg.order.entity.PrintMachineLog; * @since 2025-03-11 */ public interface PrintMachineLogService extends IService { - void save(PrintMachine config, String bizType, String printContent, Object respJson); + void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson); + + void save(PrintMachine config, String bizType, String printContent, String respJson); } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/service/ShopMerchantService.java b/cash-common/cash-common-service/src/main/java/com/czg/order/service/ShopMerchantService.java new file mode 100644 index 000000000..7a8342148 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/service/ShopMerchantService.java @@ -0,0 +1,37 @@ +package com.czg.order.service; + +import com.czg.order.dto.ShopMerchantDTO; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.order.entity.ShopMerchant; +import com.czg.pay.NativeMerchantDTO; +import com.mybatisflex.core.service.IService; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * 第三方商户进件 服务层。 + * + * @author Administrator + * @since 2025-02-11 + */ +public interface ShopMerchantService extends IService { + + ShopMerchantDTO detail(Long shopId); + + /** + * 进件结果保存/ 支付参数修改 + */ + Boolean editEntry(ShopMerchantDTO shopMerchantParam, boolean isUp); + + + /** + * 已绑定的支付 + * 更新商户支付参数 + */ + void upMerchant(@NotBlank Long relatedId, @NotNull NativeMerchantDTO nativeMerchantDTO); + + ShopMerchant getByShopId(Long shopId); + + + ShopDirectMerchant getMainMerchant(Long shopId); +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderDetailSmallVO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderDetailSmallVO.java index 044421de9..d6158b4c4 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderDetailSmallVO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderDetailSmallVO.java @@ -42,6 +42,5 @@ public class OrderDetailSmallVO implements Serializable { private LocalDateTime dishOutTime; private LocalDateTime foodServeTime; private Integer isTemporary; - } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderInfoVo.java b/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderInfoVo.java index 190d13386..9c7a7a8c5 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderInfoVo.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/vo/OrderInfoVo.java @@ -133,6 +133,11 @@ public class OrderInfoVo implements Serializable { * 备注 */ private String remark; + /** + * 打印状态 Json格式 + * [{"id":"124","name":"111","time":"2025-12-29 11:05:18"},{"id":"111","name":"标签","time":"2025-12-29 11:05:30"}] + */ + private String printStatus; /** * 是否使用了霸王餐 diff --git a/cash-common/cash-common-service/src/main/java/com/czg/pay/AlipayAuthInfoDto.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/AlipayAuthInfoDto.java new file mode 100644 index 000000000..7b4c47235 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/AlipayAuthInfoDto.java @@ -0,0 +1,63 @@ +package com.czg.pay; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 支付宝授权信息 + * @author yjjie + * @date 2026/1/9 11:31 + */ +@Data +@Accessors(chain = true) +public class AlipayAuthInfoDto { + + /** + * 授权商户的user_id + */ + @JSONField(name = "user_id") + private String userId; + + /** + * 授权商户的open_id + */ + @JSONField(name = "open_id") + private String openId; + + /** + * 授权商户的appid + */ + @JSONField(name = "auth_app_id") + private String authAppId; + + /** + * 应用授权令牌 + */ + @JSONField(name = "app_auth_token") + private String appAuthToken; + + /** + * 应用授权令牌有效期 + */ + @JSONField(name = "expires_in") + private String expiresIn; + + /** + * 刷新令牌 + */ + @JSONField(name = "app_refresh_token") + private String appRefreshToken; + + /** + * 刷新令牌的有效时间 + */ + @JSONField(name = "re_expires_in") + private String reExpiresIn; + + /** + * 签约单号 + */ + @JSONField(name = "order_no") + private String orderNo; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/pay/CzgPayBaseReq.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/CzgPayBaseReq.java new file mode 100644 index 000000000..e5f5d0475 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/CzgPayBaseReq.java @@ -0,0 +1,133 @@ +package com.czg.pay; + +import lombok.Data; +import lombok.NonNull; + +/** + * @author ww + */ +@Data +public class CzgPayBaseReq { + //必填范围 + /** + * 订单标题 + */ + private String subject; + + /** + * 订单描述 String(256) + */ + private String body; + /** + * 交易金额 分 + */ + private Long amount; + /** + * 货币类型 cny + */ + private String currency = "cny"; + /** + * 商户订单号 String(30) + * 操作方式+雪花算法 + * 收银机客户端 PC+雪花ID + * 微信小程序 WX+雪花ID + * 支付宝小程序 ALI+雪花ID + * PC管理端 WEB+雪花ID + * APP管理端 APP+雪花ID + *

+ * 退款 re+雪花ID + */ + private String mchOrderNo; + + /** + * 异步通知地址 String(128) + * 支付结果异步回调URL,只有传了该值才会发起回调 + */ + private String notifyUrl; + /** + * 扩展参数 String(512) + * 商户扩展参数,回调时会原样返回 + * * 扩展参数 + * * { + * * "pay_type": "order/vip" + * * } + */ + private String extParam; + + + /** + * 原生支付 不需要 store_id + */ + private String storeId; + + + /** + * 支付类型 + * {@link com.czg.constants.SystemConstants.PayType} + */ + private String payType; + /** + * 用户唯一标识 String(30) + * 微信支付时,传入用户的openId; + * 支付宝支付和银联支付时,传入用户的userId + */ + private String userId; + /** + * 用户IP + * 需要传付款用户客户端IP地址 + */ + private String clientIp; + /** + * 子商户 appid ,微信付款支付的时候 需要上送 + * isScreen 为 false 的情况下需要传入 + */ + private String subAppid; + + /** + * 支付条码 + */ + private String authCode; + + public CzgPayBaseReq() { + + } + + public CzgPayBaseReq(String mchOrderNo, String detail, Long amount, String payType, String userId, String clientIp) { + this.mchOrderNo = mchOrderNo; + this.subject = detail; + this.body = detail; + this.amount = amount; + this.payType = payType; + this.userId = userId; + this.clientIp = clientIp; + } + + public void polyBase(@NonNull String storeId, @NonNull String notifyUrl) { + this.storeId = storeId; + this.notifyUrl = notifyUrl; + } + + public static CzgPayBaseReq ltPayReq(@NonNull String mchOrderNo, @NonNull String detail, @NonNull Long amount + , @NonNull String payType, @NonNull String openId, @NonNull String clientIp) { + return new CzgPayBaseReq(mchOrderNo, detail, amount, payType, openId, clientIp); + } + + public static CzgPayBaseReq h5PayReq(@NonNull String mchOrderNo, @NonNull String detail, @NonNull Long amount, @NonNull String clientIp) { + return new CzgPayBaseReq(mchOrderNo, detail, amount, null, null, clientIp); + } + + public static CzgPayBaseReq jsPayReq(@NonNull String mchOrderNo, @NonNull String detail, @NonNull Long amount + , @NonNull String payType, @NonNull String openId, @NonNull String clientIp) { + return new CzgPayBaseReq(mchOrderNo, detail, amount, payType, openId, clientIp); + } + + public static CzgPayBaseReq scanPayReq(@NonNull String mchOrderNo, @NonNull String detail, @NonNull Long amount, @NonNull String clientIp) { + return new CzgPayBaseReq(mchOrderNo, detail, amount, null, null, clientIp); + } + + public static CzgPayBaseReq microPay(@NonNull String mchOrderNo, @NonNull String detail, @NonNull Long amount, @NonNull String authCode) { + CzgPayBaseReq stringReq = new CzgPayBaseReq(mchOrderNo, detail, amount, null, null, null); + stringReq.setAuthCode(authCode); + return stringReq; + } +} diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgRefundReq.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/CzgRefundReq.java similarity index 75% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgRefundReq.java rename to cash-common/cash-common-service/src/main/java/com/czg/pay/CzgRefundReq.java index 52a68dcfd..89593b7e1 100644 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgRefundReq.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/CzgRefundReq.java @@ -1,4 +1,4 @@ -package com.czg.entity.req; +package com.czg.pay; import lombok.Data; import lombok.NonNull; @@ -23,7 +23,10 @@ public class CzgRefundReq { * 单位为分 */ private Long refundAmount; - + /** + * 原订单总金额 单位 分 + */ + private Long orderTotalAmount; //非必填范围 /** @@ -47,15 +50,22 @@ public class CzgRefundReq { */ private String notifyUrl; + /** + * 支付平台 + */ + private String platform; + /** * payOrderId和mchOrderNo 二选一 必填 */ public CzgRefundReq(@NonNull String mchRefundNo, @NonNull String refundReason, @NonNull Long refundAmount, - String mchOrderNo, String extParam) { + @NonNull Long orderTotalAmount, String mchOrderNo, String extParam, String platform) { this.mchRefundNo = mchRefundNo; this.refundReason = refundReason; this.refundAmount = refundAmount; + this.orderTotalAmount = orderTotalAmount; this.mchOrderNo = mchOrderNo; this.extParam = extParam; + this.platform = platform; } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/pay/NativeMerchantDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/NativeMerchantDTO.java new file mode 100644 index 000000000..43b9f744b --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/NativeMerchantDTO.java @@ -0,0 +1,17 @@ +package com.czg.pay; + +import lombok.Data; + +/** + * 原生支付参数 + * @author ww + */ +@Data +public class NativeMerchantDTO { + private String wechatMerchantId; + private String alipayMerchantId; + /** + * 支付宝 授权信息解析 + */ + private AlipayAuthInfoDto alipayAuthInfo; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/pay/PayNotifyRespDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/PayNotifyRespDTO.java new file mode 100644 index 000000000..d02b87a4e --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/PayNotifyRespDTO.java @@ -0,0 +1,67 @@ +package com.czg.pay; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 统一支付回调响应数据 + * @author yjjie + * @date 2026/1/15 09:16 + */ +@Data +@Accessors(chain = true) +public class PayNotifyRespDTO { + + /** + * 商户订单号 + */ + private String mchOrderNo; + + /** + * 三方订单号 + */ + private String thirdOrderNo; + + /** + * 订单状态 + * INIT - 订单初始化; + * TRADE_AWAIT - 待支付; + * TRADE_SUCCESS - 支付成功; + * TRADE_FAIL -支付失败; + * TRADE_CANCEL -交易取消; + * TRADE_REFUND -已退款; + * REFUND_ING - 退款中; + * TRADE_CLOSE -订单关闭 + */ + private String status; + + /** + * 支付平台 + */ + private String platform; + + /** + * 订单金额 分 + */ + private Long amount; + + /** + * 扩展数据 + */ + private String extData; + + /** + * 支付成功时间 + */ + private String paySuccessTime; + + /** + * 错误信息 + */ + private String errorMsg; + + /** + * 回调原始数据 + */ + private String originalData; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/merchant/ShopMerchantEditDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/PolyMerchantDTO.java similarity index 50% rename from cash-common/cash-common-service/src/main/java/com/czg/account/dto/merchant/ShopMerchantEditDTO.java rename to cash-common/cash-common-service/src/main/java/com/czg/pay/PolyMerchantDTO.java index 8f52060ab..6107d324a 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/merchant/ShopMerchantEditDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/PolyMerchantDTO.java @@ -1,16 +1,14 @@ -package com.czg.account.dto.merchant; +package com.czg.pay; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import lombok.Data; /** - * @author Administrator + * 聚合支付参数 + * @author ww */ @Data -public class ShopMerchantEditDTO { - @NotNull(message = "店铺id不为空") - private Long shopId; +public class PolyMerchantDTO { @NotEmpty(message = "支付系统商户id不为空") private String storeId; @NotEmpty(message = "支付系统商户名称不为空") @@ -20,12 +18,6 @@ public class ShopMerchantEditDTO { @NotEmpty(message = "商户秘钥不为空") private String appSecret; // 支付密码 - @NotEmpty(message = "支付密码不为空") +// @NotEmpty(message = "支付密码不为空") private String payPassword; - // 微信appid - @NotEmpty(message = "微信appid") - private String wechatSmallAppid; - // 支付宝appid - @NotEmpty(message = "支付宝appid") - private String alipaySmallAppid; } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/pay/QueryOrderRespDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/QueryOrderRespDTO.java new file mode 100644 index 000000000..7c8f0a5e2 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/QueryOrderRespDTO.java @@ -0,0 +1,39 @@ +package com.czg.pay; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 查询订单响应参数 + * @author yjjie + * @date 2026/1/15 13:56 + */ +@Data +@Accessors(chain = true) +public class QueryOrderRespDTO { + + /** + * 订单号 + */ + private String orderNo; + + /** + * 状态 + */ + private String status; + + /** + * 金额 + */ + private Long amount; + + /** + * 错误信息 + */ + private String errorMsg; + + /** + * 原始响应数据 + */ + private String originResp; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/pay/RefundRespDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/pay/RefundRespDTO.java new file mode 100644 index 000000000..c1b8552bc --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/pay/RefundRespDTO.java @@ -0,0 +1,59 @@ +package com.czg.pay; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 退款相应数据 + * @author yjjie + * @date 2026/1/15 11:00 + */ +@Data +@Accessors(chain = true) +public class RefundRespDTO { + + /** + * 退款状态 + * INIT:初始化 + * ING:退款中 + * SUCCESS:退款成功 + * FAIL:退款失败 + * CLOSE:退款关闭 + */ + private String status; + + /** + * 退款金额 + */ + private Long refundAmount; + + /** + * 退款时间 + */ + private String refundTime; + + /** + * 三方退款订单号 + */ + private String thirdRefundNo; + + /** + * 商户退款订单号 + */ + private String merchantRefundNo; + + /** + * 退款失败原因 + */ + private String errMessage; + + /** + * 退款相应原始数据 + */ + private String originalData; + + /** + * 退款平台 + */ + private String platform; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysBankInfo.java b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysBankInfo.java new file mode 100644 index 000000000..de376bb20 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysBankInfo.java @@ -0,0 +1,82 @@ +package com.czg.system.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import java.io.Serializable; +import java.math.BigInteger; +import java.time.LocalDateTime; + +import java.io.Serial; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银行账户信息表 实体类。 + * + * @author ww + * @since 2026-01-06 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("sys_bank_info") +public class SysBankInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @Id(keyType = KeyType.Auto) + private BigInteger id; + + /** + * 银行名称 + */ + private String accountBank; + + /** + * 银行数字编码 + */ + private Integer accountBankCode; + + /** + * 银行别名 + */ + private String bankAlias; + + /** + * 银行别名编码 + */ + private String bankAliasCode; + + /** + * 是否需要支行信息 + */ + private Boolean needBankBranch; + + /** + * 银行英文编码 + */ + private String bankCode; + + /** + * 创建时间 + */ + @Column(onInsertValue = "now()") + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @Column(onInsertValue = "now()", onUpdateValue = "now()") + private LocalDateTime updateTime; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysCategoryInfo.java b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysCategoryInfo.java new file mode 100644 index 000000000..5c6346e46 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysCategoryInfo.java @@ -0,0 +1,81 @@ +package com.czg.system.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import java.io.Serializable; +import java.math.BigInteger; +import java.time.LocalDateTime; + +import java.io.Serial; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 类目信息表 实体类。 + * + * @author ww + * @since 2026-01-06 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("sys_category_info") +public class SysCategoryInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @Id(keyType = KeyType.Auto) + private BigInteger id; + + /** + * 一级类目code + */ + private String firstCategoryCode; + /** + * 一级类目 + */ + private String firstCategory; + + /** + * 二级类目code + */ + private String secondCategoryCode; + + /** + * 二级类目 + */ + private String secondCategory; + + /** + * 适用商家说明 + */ + private String applicableMerchant; + + /** + * 特殊资质(为空则无需提供) + */ + private String specialQualification; + + /** + * 创建时间 + */ + @Column(onInsertValue = "now()") + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @Column(onInsertValue = "now()", onUpdateValue = "now()") + private LocalDateTime updateTime; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysRegion.java b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysRegion.java new file mode 100644 index 000000000..2606e1d97 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysRegion.java @@ -0,0 +1,74 @@ +package com.czg.system.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import java.io.Serializable; + +import java.io.Serial; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 行政区表 实体类。 + * + * @author ww + * @since 2026-01-06 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("sys_region") +public class SysRegion implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 区域编码 + */ + @Id + private String regionId; + + /** + * 上级区域编码 + */ + private String parentRegionId; + + /** + * 区域级别:1=国家,2=省,3=市,4=县 + */ + @Id + private Integer regionLevel; + + /** + * 区域中文全称 + */ + private String regionName; + + /** + * 区域拼音 + */ + private String regionPinyin; + + /** + * 全级别名称(如“中国|北京|北京市|东城区) + */ + private String fullName; + /** + * 微信区域编码 + */ + private String wxProvinceCode; + + /** + * 子级区域 + */ + @Column(ignore = true) + private List children; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysBankInfoService.java b/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysBankInfoService.java new file mode 100644 index 000000000..d4510f052 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysBankInfoService.java @@ -0,0 +1,19 @@ +package com.czg.system.service; + +import com.czg.BaseQueryParam; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.service.IService; +import com.czg.system.entity.SysBankInfo; + +import java.util.List; + +/** + * 银行账户信息表 服务层。 + * + * @author ww + * @since 2026-01-06 + */ +public interface SysBankInfoService extends IService { + + Page bankInfoList(BaseQueryParam param, String bankName); +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysCategoryInfoService.java b/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysCategoryInfoService.java new file mode 100644 index 000000000..78eaee526 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysCategoryInfoService.java @@ -0,0 +1,20 @@ +package com.czg.system.service; + +import com.czg.system.vo.SysCategoryInfoVO; +import com.mybatisflex.core.service.IService; +import com.czg.system.entity.SysCategoryInfo; + +import java.util.List; +import java.util.Map; + +/** + * 类目信息表 服务层。 + * + * @author ww + * @since 2026-01-06 + */ +public interface SysCategoryInfoService extends IService { + + + List categoryList(); +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysRegionService.java b/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysRegionService.java new file mode 100644 index 000000000..d1f2646ec --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/service/SysRegionService.java @@ -0,0 +1,17 @@ +package com.czg.system.service; + +import com.mybatisflex.core.service.IService; +import com.czg.system.entity.SysRegion; + +import java.util.List; + +/** + * 行政区表 服务层。 + * + * @author ww + * @since 2026-01-06 + */ +public interface SysRegionService extends IService { + + List regionList(); +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/vo/SysCategoryInfoVO.java b/cash-common/cash-common-service/src/main/java/com/czg/system/vo/SysCategoryInfoVO.java new file mode 100644 index 000000000..a3d535d7a --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/vo/SysCategoryInfoVO.java @@ -0,0 +1,40 @@ +package com.czg.system.vo; + +import com.czg.system.entity.SysCategoryInfo; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 类目信息表 实体类。 + * + * @author ww + * @since 2026-01-06 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SysCategoryInfoVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 一级类目 + */ + private String firstCategory; + + private List child; +} diff --git a/cash-common/cash-common-tools/pom.xml b/cash-common/cash-common-tools/pom.xml index a4ceb0083..ccbce27f9 100644 --- a/cash-common/cash-common-tools/pom.xml +++ b/cash-common/cash-common-tools/pom.xml @@ -37,6 +37,11 @@ + + + com.aliyun + dysmsapi20170525 + com.google.zxing core @@ -45,10 +50,6 @@ cn.hutool hutool-all - - com.aliyun - dysmsapi20170525 - com.alibaba.fastjson2 fastjson2 diff --git a/cash-common/cash-common-tools/src/main/java/com/czg/constant/PayChannelCst.java b/cash-common/cash-common-tools/src/main/java/com/czg/constant/PayChannelCst.java new file mode 100644 index 000000000..342bc7f99 --- /dev/null +++ b/cash-common/cash-common-tools/src/main/java/com/czg/constant/PayChannelCst.java @@ -0,0 +1,18 @@ +package com.czg.constant; + +/** + * 支付渠道常量 + * @author ww + */ +public interface PayChannelCst { + /** + * 聚合支付 + * 对应 类 PolyPay + */ + String POLY = "poly"; + /** + * 原生支付 + * 对应 类 NativePay + */ + String NATIVE = "native"; +} \ No newline at end of file diff --git a/cash-common/cash-common-tools/src/main/java/com/czg/resp/CzgResult.java b/cash-common/cash-common-tools/src/main/java/com/czg/resp/CzgResult.java index 767e01725..b7559c34b 100644 --- a/cash-common/cash-common-tools/src/main/java/com/czg/resp/CzgResult.java +++ b/cash-common/cash-common-tools/src/main/java/com/czg/resp/CzgResult.java @@ -59,4 +59,8 @@ public class CzgResult implements Serializable { public static CzgResult failure(CzgRespCode respCode) { return new CzgResult<>(respCode.getCode(), respCode.getMsg(), null); } + + public boolean isSuccess() { + return code == 200; + } } diff --git a/cash-dependencies/pom.xml b/cash-dependencies/pom.xml index b5163a004..a598172c8 100644 --- a/cash-dependencies/pom.xml +++ b/cash-dependencies/pom.xml @@ -42,6 +42,8 @@ 2.5.1 2.9.10 4.1.128.Final + 0.2.17 + 3.1.65.ALL @@ -268,6 +270,18 @@ netty-codec-mqtt ${netty.version} + + + com.github.wechatpay-apiv3 + wechatpay-java + ${wechatpay.version} + + + + com.alipay.sdk + alipay-sdk-java-v3 + ${apipay-v3.version} + diff --git a/cash-sdk/aggregation-pay/pom.xml b/cash-sdk/aggregation-pay/pom.xml new file mode 100644 index 000000000..8eb8d2da1 --- /dev/null +++ b/cash-sdk/aggregation-pay/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.czg + cash-sdk + 1.0.0 + + + aggregation-pay + jar + 聚合支付 + + + + 21 + 21 + UTF-8 + + + + + com.github.wechatpay-apiv3 + wechatpay-java + + + + com.alipay.sdk + alipay-sdk-java-v3 + + + com.aliyun + ocr_api20210707 + 3.1.2 + + + + \ No newline at end of file diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java new file mode 100644 index 000000000..4c50b2ba0 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java @@ -0,0 +1,493 @@ +package com.czg; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.dto.req.*; +import com.czg.dto.resp.*; +import com.czg.exception.CzgException; +import com.czg.third.alipay.AlipayEntryManager; +import com.czg.third.alipay.AlipayIsvEntryManager; +import com.czg.third.wechat.WechatEntryManager; +import com.czg.utils.AssertUtil; +import com.czg.utils.AsyncTaskExecutor; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * 进件管理 + * + * @author yjjie + * @date 2026/1/6 13:56 + */ +public class EntryManager { + + /** + * 查询银行支行列表 + * + * @param province 省份 陕西省 + * @param city 城市 西安市 + * @param instId 顶级机构ID CMB + */ + public static List queryBankBranchList(String province, String city, String instId) { + return AlipayEntryManager.queryBankBranchList(null, instId, province, city); + } + + /** + * 查询银行支行列表 + * 走微信查询 + * + * @param bankAliceCode 银行代码 + * @param cityCode 城市编码 + */ + public static WechatBankBranchRespDto queryBankBranchList(String bankAliceCode, String cityCode) { + return WechatEntryManager.queryBankBranchList(null, bankAliceCode, cityCode, 0, 200); + } + + /** + * 查询微信进件状态 + * + * @param merchantCode 商户编号, 进件时返回的批次号 {@link EntryRespDto#wechatApplyId} + * @return 进件状态 + */ + public static QueryStatusResp queryWechatEntryStatus(String merchantCode) { + return WechatEntryManager.queryMerchantEntryStatus(null, merchantCode); + } + + /** + * 查询支付宝进件状态 + * + * @param batchNo 商户编号, 进件时返回的批次号 {@link EntryRespDto#alipayOrderId} + * @return 进件状态 + */ + public static QueryStatusResp queryAlipayEntryStatus(String batchNo) { + return AlipayIsvEntryManager.queryMerchantBatchStatus(null, batchNo); + } + + /** + * 进件 + * 请先执行: + * 1. {@link com.czg.EntryManager#verifyEntryParam(AggregateMerchantDto)} 验证进件参数 + * 2. {@link com.czg.EntryManager#uploadParamImage(AggregateMerchantDto)} 上传图片至第三方 + * + * @param reqDto 进件参数 + * @param platform 平台 {@link com.czg.PayCst.Platform} + */ + public static EntryRespDto entryMerchant(AggregateMerchantDto reqDto, String... platform) { + List> tasks = new ArrayList<>(); + + if (platform == null || platform.length == 0) { + platform = new String[]{PayCst.Platform.WECHAT, PayCst.Platform.ALIPAY}; + } + + if (ArrayUtil.contains(platform, PayCst.Platform.WECHAT)) { + tasks.add(() -> WechatEntryManager.entryMerchant(null, reqDto)); + } + if (ArrayUtil.contains(platform, PayCst.Platform.ALIPAY)) { + tasks.add(() -> AlipayIsvEntryManager.entryMerchant(null, reqDto)); + } + + // 执行所有任务 + List> results = AsyncTaskExecutor.executeAll(tasks); + + return getEntryRespDto(results); + } + + @NotNull + private static EntryRespDto getEntryRespDto(List> results) { + EntryRespDto entryRespDto = new EntryRespDto(); + for (AsyncTaskExecutor.TaskResult result : results) { + // 合并两个进件结果 + EntryThirdRespDto respDto = result.result(); + if (PayCst.Platform.WECHAT.equals(respDto.getPlatform())) { + entryRespDto.setWechatApplyId(respDto.getEntryId()); + entryRespDto.setWechatStatus(respDto.getStatus()); + entryRespDto.setWechatErrorMsg(respDto.getErrorMsg()); + } else if (PayCst.Platform.ALIPAY.equals(respDto.getPlatform())) { + entryRespDto.setAlipayOrderId(respDto.getEntryId()); + entryRespDto.setAlipayStatus(respDto.getStatus()); + entryRespDto.setAlipayErrorMsg(respDto.getErrorMsg()); + entryRespDto.setAlipayAuthInfo(respDto.getAlipayAuthInfo()); + } + } + return entryRespDto; + } + + /** + * 上传图片至第三方 + * 请先执行 {@link com.czg.EntryManager#verifyEntryParam(AggregateMerchantDto)} 验证进件参数 + * + * @param reqDto 进件参数 + */ + public static void uploadParamImage(AggregateMerchantDto reqDto) { + List> tasks = new ArrayList<>(); + + MerchantBaseInfoDto baseInfo = reqDto.getMerchantBaseInfo(); + // 联系人身份证反面 + tasks.add(() -> { + uploadImageToThird(baseInfo.getContactIdCardBackPic()); + return null; + }); + // 联系人身份证正面 + tasks.add(() -> { + uploadImageToThird(baseInfo.getContactIdCardFrontPic()); + return null; + }); + + LegalPersonInfoDto legalPersonInfo = reqDto.getLegalPersonInfo(); + // 法人身份证反面 + tasks.add(() -> { + uploadImageToThird(legalPersonInfo.getIdCardBackPic()); + return null; + }); + // 法人身份证正面 + tasks.add(() -> { + uploadImageToThird(legalPersonInfo.getIdCardFrontPic()); + return null; + }); + // 法人手持身份证 + tasks.add(() -> { + uploadImageToThird(legalPersonInfo.getIdCardHandPic()); + return null; + }); + + BusinessLicenceInfoDto businessLicenceInfo = reqDto.getBusinessLicenceInfo(); + // 营业执照 + tasks.add(() -> { + uploadImageToThird(businessLicenceInfo.getLicensePic()); + return null; + }); + + SettlementInfoDto settlementInfo = reqDto.getSettlementInfo(); + // 银行卡背面 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getBankCardBackPic()); + return null; + }); + // 银行卡正面 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getBankCardFrontPic()); + return null; + }); + // 开户许可证 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getOpenAccountLicencePic()); + return null; + }); + // 非法人手持授权函 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getNoLegalHandSettleAuthPic()); + return null; + }); + // 非法人授权函 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getNoLegalSettleAuthPic()); + return null; + }); + // 非法人身份证反面 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getNoLegalIdCardBackPic()); + return null; + }); + // 非法人身份证正面 + tasks.add(() -> { + uploadImageToThird(settlementInfo.getNoLegalIdCardFrontPic()); + return null; + }); + + StoreInfoDto storeInfo = reqDto.getStoreInfo(); + // 店内图片 + tasks.add(() -> { + uploadImageToThird(storeInfo.getInsidePic()); + return null; + }); + // 门店门头图片 + tasks.add(() -> { + uploadImageToThird(storeInfo.getDoorPic()); + return null; + }); + // 收银台图片 + tasks.add(() -> { + uploadImageToThird(storeInfo.getCashierDeskPic()); + return null; + }); + + // 执行所有任务 + AsyncTaskExecutor.executeAll(tasks); + } + + private static void uploadImageToThird(ImageDto dto) { + if (dto != null && StrUtil.isNotBlank(dto.getUrl())) { + if (StrUtil.isBlank(dto.getWechatId())) { + String image = WechatEntryManager.uploadImage(null, dto.getUrl()); + dto.setWechatId(image); + } +// if (StrUtil.isBlank(dto.getAlipayId())) { +// String image = AlipayEntryManager.uploadImage(null, dto.getUrl()); +// dto.setAlipayId(image); +// } + } + } + + /** + * 验证进件参数 + * + * @param reqDto 进件参数 + */ + public static void verifyEntryParam(AggregateMerchantDto reqDto) { + AssertUtil.isBlank(reqDto.getMerchantCode(), "商户编号不能为空"); + + AssertUtil.isNull(reqDto.getMerchantBaseInfo(), "商户基础信息不能为空"); + + MerchantBaseInfoDto baseInfo = reqDto.getMerchantBaseInfo(); + AssertUtil.isBlank(baseInfo.getUserType(), "商户类型不能为空"); + AssertUtil.isBlank(baseInfo.getShortName(), "商户简称不能为空"); + AssertUtil.isBlank(baseInfo.getMccCode(), "商户行业编码不能为空"); + AssertUtil.isBlank(baseInfo.getAlipayAccount(), "支付宝账号不能为空"); + AssertUtil.isBlank(baseInfo.getContactPersonType(), "联系人类型不能为空"); + if (!PayCst.ContactPersonType.SUPER.equals(baseInfo.getContactPersonType()) && !PayCst.ContactPersonType.LEGAL.equals(baseInfo.getContactPersonType())) { + throw new CzgException("联系人类型错误"); + } + if (PayCst.ContactPersonType.SUPER.equals(baseInfo.getContactPersonType())) { + AssertUtil.isBlank(baseInfo.getCertType(), "证件类型不能为空"); + if (!"0".equals(baseInfo.getCertType())) { + throw new CzgException("证件类型错误"); + } + AssertUtil.isBlank(baseInfo.getContactName(), "联系人姓名不能为空"); + AssertUtil.isBlank(baseInfo.getContactPersonId(), "联系人身份证号不能为空"); + AssertUtil.isBlank(baseInfo.getContactPhone(), "联系人电话不能为空"); + AssertUtil.isBlank(baseInfo.getContactAddr(), "联系人地址不能为空"); + AssertUtil.isBlank(baseInfo.getContactEmail(), "联系人邮箱不能为空"); + AssertUtil.isBlank(baseInfo.getContactPersonIdStartDate(), "联系人身份证开始日期不能为空"); + AssertUtil.isBlank(baseInfo.getContactPersonIdEndDate(), "联系人身份证到期日期不能为空"); + AssertUtil.isNull(baseInfo.getContactIdCardBackPic(), "联系人身份证反面不能为空"); + AssertUtil.isBlank(baseInfo.getContactIdCardBackPic().getUrl(), "联系人身份证反面不能为空"); + AssertUtil.isNull(baseInfo.getContactIdCardFrontPic(), "联系人身份证正面不能为空"); + AssertUtil.isBlank(baseInfo.getContactIdCardFrontPic().getUrl(), "联系人身份证正面不能为空"); + } + AssertUtil.isBlank(baseInfo.getCompanyChildType(), "商户类型不能为空"); + if ("0".equals(baseInfo.getUserType())) { + // 个体商户, 暂无其他校验 + } else if ("1".equals(baseInfo.getUserType())) { + if (!"1".equals(baseInfo.getCompanyChildType()) && !"2".equals(baseInfo.getCompanyChildType()) && !"3".equals(baseInfo.getCompanyChildType())) { + throw new CzgException("商户类型错误"); + } + } else { + throw new CzgException("商户类型错误"); + } + + AssertUtil.isNull(reqDto.getBusinessLicenceInfo(), "营业执照信息不能为空"); + BusinessLicenceInfoDto businessLicenceInfo = reqDto.getBusinessLicenceInfo(); + AssertUtil.isBlank(businessLicenceInfo.getLicenceNo(), "营业执照编号不能为空"); + AssertUtil.isBlank(businessLicenceInfo.getLicenceName(), "营业执照名称不能为空"); + AssertUtil.isBlank(businessLicenceInfo.getRegisteredAddress(), "营业执照注册地址不能为空"); + AssertUtil.isBlank(businessLicenceInfo.getLicenceStartDate(), "营业执照开始日期不能为空"); + AssertUtil.isBlank(businessLicenceInfo.getLicenceEndDate(), "营业执照到期日期不能为空"); + AssertUtil.isNull(businessLicenceInfo.getLicensePic(), "营业执照名称不能为空"); + AssertUtil.isBlank(businessLicenceInfo.getLicensePic().getUrl(), "营业执照名称不能为空"); + + + AssertUtil.isNull(reqDto.getLegalPersonInfo(), "法人信息不能为空"); + LegalPersonInfoDto legalPersonInfo = reqDto.getLegalPersonInfo(); + AssertUtil.isBlank(legalPersonInfo.getLegalPersonName(), "法人姓名不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalPersonId(), "法人身份证号不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalIdPersonStartDate(), "法人身份证开始日期不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalPersonIdEndDate(), "法人身份证到期日期不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalPersonPhone(), "法人电话不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalPersonEmail(), "法人邮箱不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalGender(), "法人性别不能为空"); + AssertUtil.isBlank(legalPersonInfo.getLegalAddress(), "法人地址不能为空"); + AssertUtil.isNull(legalPersonInfo.getIdCardHandPic(), "法人身份证手持不能为空"); + AssertUtil.isBlank(legalPersonInfo.getIdCardHandPic().getUrl(), "法人身份证手持不能为空"); + AssertUtil.isNull(legalPersonInfo.getIdCardFrontPic(), "法人身份证正面不能为空"); + AssertUtil.isBlank(legalPersonInfo.getIdCardFrontPic().getUrl(), "法人身份证正面不能为空"); + AssertUtil.isNull(legalPersonInfo.getIdCardBackPic(), "法人身份证反面不能为空"); + AssertUtil.isBlank(legalPersonInfo.getIdCardBackPic().getUrl(), "法人身份证反面不能为空"); + + AssertUtil.isNull(reqDto.getStoreInfo(), "门店信息不能为空"); + StoreInfoDto storeInfo = reqDto.getStoreInfo(); + AssertUtil.isBlank(storeInfo.getMercProvCode(), "门店省ID不能为空"); + AssertUtil.isBlank(storeInfo.getMercCityCode(), "门店市ID不能为空"); + AssertUtil.isBlank(storeInfo.getMercAreaCode(), "门店区ID不能为空"); + AssertUtil.isBlank(storeInfo.getMercProv(), "门店省不能为空"); + AssertUtil.isBlank(storeInfo.getMercCity(), "门店市不能为空"); + AssertUtil.isBlank(storeInfo.getMercArea(), "门店区不能为空"); + AssertUtil.isBlank(storeInfo.getBusinessAddress(), "门店营业地址不能为空"); + AssertUtil.isNull(storeInfo.getInsidePic(), "门店营业地址不能为空"); + AssertUtil.isBlank(storeInfo.getInsidePic().getUrl(), "门店营业地址不能为空"); + AssertUtil.isNull(storeInfo.getDoorPic(), "门店门头照不能为空"); + AssertUtil.isBlank(storeInfo.getDoorPic().getUrl(), "门店门头照不能为空"); + AssertUtil.isNull(storeInfo.getCashierDeskPic(), "门店收银台照不能为空"); + AssertUtil.isBlank(storeInfo.getCashierDeskPic().getUrl(), "门店收银台照不能为空"); + + + AssertUtil.isNull(reqDto.getSettlementInfo(), "结算信息不能为空"); + SettlementInfoDto settlementInfo = reqDto.getSettlementInfo(); + AssertUtil.isBlank(settlementInfo.getSettlementCardNo(), "结算卡号不能为空"); + AssertUtil.isBlank(settlementInfo.getSettlementName(), "结算账户户名不能为空"); + AssertUtil.isBlank(settlementInfo.getBankName(), "结算银行名称不能为空"); + AssertUtil.isBlank(settlementInfo.getBankInstId(), "结算银行缩写不能为空"); + AssertUtil.isBlank(settlementInfo.getBankMobile(), "结算银行预留手机号不能为空"); + AssertUtil.isBlank(settlementInfo.getSettlementCardType(), "结算卡类型不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccProvinceId(), "结算开户行省ID不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccCityId(), "结算开户行市ID不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccAreaId(), "结算开户行区ID不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccProvince(), "结算开户行省不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccCity(), "结算开户行市不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccArea(), "结算开户行区不能为空"); + if ("11".equals(settlementInfo.getSettlementCardType())) { + // 11 对私借记卡(结算卡正面照、结算卡反面照图片必传) + AssertUtil.isNull(settlementInfo.getBankCardFrontPic(), "结算卡正面照不能为空"); + AssertUtil.isBlank(settlementInfo.getBankCardFrontPic().getUrl(), "结算卡正面照不能为空"); + AssertUtil.isNull(settlementInfo.getBankCardBackPic(), "结算卡反面照不能为空"); + AssertUtil.isBlank(settlementInfo.getBankCardBackPic().getUrl(), "结算卡反面照不能为空"); + AssertUtil.isBlank(settlementInfo.getSettlementType(), "结算类型不能为空"); + if ("0".equals(settlementInfo.getSettlementType())) { + // 非法人结算 + AssertUtil.isBlank(settlementInfo.getNoLegalName(), "非法人姓名不能为空"); + AssertUtil.isBlank(settlementInfo.getNoLegalId(), "非法人身份证号不能为空"); + AssertUtil.isNull(settlementInfo.getNoLegalIdCardFrontPic(), "非法人身份证正面不能为空"); + AssertUtil.isBlank(settlementInfo.getNoLegalIdCardFrontPic().getUrl(), "非法人身份证正面不能为空"); + AssertUtil.isNull(settlementInfo.getNoLegalIdCardBackPic(), "非法人身份证反面不能为空"); + AssertUtil.isBlank(settlementInfo.getNoLegalIdCardBackPic().getUrl(), "非法人身份证反面不能为空"); + AssertUtil.isNull(settlementInfo.getNoLegalSettleAuthPic(), "非法人结算授权书不能为空"); + AssertUtil.isBlank(settlementInfo.getNoLegalSettleAuthPic().getUrl(), "非法人结算授权书不能为空"); + } else if ("1".equals(settlementInfo.getSettlementType())) { + // 法人结算 暂无处理 + } else { + throw new CzgException("结算类型错误"); + } + } else if ("21".equals(settlementInfo.getSettlementCardType())) { + // 21 对公借记卡(只须结算卡正面照片) + AssertUtil.isNull(settlementInfo.getOpenAccountLicencePic(), "开户许可证不能为空"); + AssertUtil.isBlank(settlementInfo.getOpenAccountLicencePic().getUrl(), "开户许可证不能为空"); + } else { + throw new CzgException("结算卡类型错误"); + } + + } + + public static void main(String[] args) { + +// WechatBankBranchRespDto respDto = queryBankBranchList("1000009547", "931"); +// System.out.println(respDto); + + + AggregateMerchantDto merchantDto = getTestMerchantEntryData(); +// + verifyEntryParam(merchantDto); + uploadParamImage(merchantDto); +// +// verifyEntryParam(merchantDto); +// uploadParamImage(merchantDto); +//// System.out.println(merchantDto); + EntryRespDto respDto = entryMerchant(merchantDto, PayCst.Platform.WECHAT); +// entryMerchant(merchantDto, PayCst.Platform.ALIPAY); +// entryMerchant(merchantDto, PayCst.Platform.WECHAT, PayCst.Platform.ALIPAY); + System.out.println(respDto); + } + + private static AggregateMerchantDto getTestMerchantEntryData() { + AggregateMerchantDto merchantDto = new AggregateMerchantDto(); + merchantDto.setMerchantCode("CZG20260112151202011"); + + String baseInfo = "{\"alipayAccount\":\"18191655977\",\"certType\":\"0\",\"companyChildType\":\"1\",\"contactAddr\":\"西安市沣东新城石化大道西段106号沣东科技园5号楼1层5-037室\",\"contactEmail\":\"892675422@qq.com\",\"contactIdCardBackPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/ce731859ee644df8a4a46104b49c7696.png\",\"wechatId\":\"V1_S-iU_YpBpSKoSZFhQw3plh53G4PXHmNlhi1s88_VTk7A943tAxrkIcKErRcpFPkxG7vkMzhkT4ecN3IWxv71JwLJOzY9eQhmv6giwKWUAjk\"},\"contactIdCardFrontPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/e0d259f9fbcf4897b7f93f3f7d1b8e9a.png\",\"wechatId\":\"V1_oYXFrsrx4sZqiHXURjDBMB53G4PXHmPB0jH4yl2JSowqJDinBqRYIZmHrRcpFPkxsGFO-6Ci3dGvLAs-X2g2DQLJOzY9eQhmv6giwKWUAjk\"},\"contactName\":\"\",\"contactPersonId\":\"612501199212187875\",\"contactPersonIdEndDate\":\"2039-02-01\",\"contactPersonIdStartDate\":\"2019-02-01\",\"contactPersonType\":\"LEGAL\",\"contactPhone\":\"18191655977\",\"mccCode\":\"A0001_B0001\",\"shortName\":\"菲慕斯博\",\"userType\":\"1\"}"; + merchantDto.setMerchantBaseInfo(JSONObject.parseObject(baseInfo, MerchantBaseInfoDto.class)); + + String legalPersonInfo = "{\"idCardBackPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/bb0f77b0b3404bbd9975a4f8497494df.png\",\"wechatId\":\"V1_Kt7wB6u_YhTp_V97CU_V_x53G4PXHmOeC1YpTSt8JenBIbhCzXjNIZWHrRcpFPkxHwsAu6WQfWU6BYJ3glMBnALJOzY9eQhmv6giwKWUAjk\"},\"idCardFrontPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/196a6f37c5954fc395d621e5b0289cb5.png\",\"wechatId\":\"V1__G9UoJplQHSY_ychzdk7QR53G4PXHmMm845pnkw_19i0CeVtQ34dIbOHrRcpFPkxsIEMvoVq19SdOFHTsw6b3wLJOzY9eQhmv6giwKWUAjk\"},\"idCardHandPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/eaa5e0f9352d4e77bbf5fa133ed54564.jpg\",\"wechatId\":\"V1_mWQpc12PTPL7P7kkdUOR6h53G4PXHmPbBEOZTTwh_KJ8RdecGr9kIZuGrRcpFPkx-jdQCaZuUWIQRmxr8pm-7QLJOzY9eQhmv6giwKWUAjk\"},\"legalAddress\":\"西安市沣东新城石化大道西段106号沣东科技园5号楼1层5-037室\",\"legalGender\":\"0\",\"legalIdPersonStartDate\":\"2019-02-01\",\"legalPersonEmail\":\"892675422@qq.com\",\"legalPersonId\":\"612501199212187875\",\"legalPersonIdEndDate\":\"2039-02-01\",\"legalPersonName\":\"蔺佳佳\",\"legalPersonPhone\":\"18191655977\"}"; + merchantDto.setLegalPersonInfo(JSONObject.parseObject(legalPersonInfo, LegalPersonInfoDto.class)); + + String businessInfo = "{\"licenceEndDate\":\"2099-12-31\",\"licenceName\":\"西安菲慕斯博市场营销策划有限公司\",\"licenceNo\":\"91610131MA6TXACP28\",\"licenceStartDate\":\"2021-02-25\",\"licensePic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/81b60f3a25494c8586159a193389aa2d.jpg\",\"wechatId\":\"V1_-Vbealekn9NZb3mT37ggpB53G4PXHmNEw41rkFGHdR1KJkYqgc5QId-HrRcpFPkxJLlN6u4U4s24lzVyTVoxPgLJOzY9eQhmv6giwKWUAjk\"},\"registeredAddress\":\"陕西省西安市高新区科技路37号海星城市广场B座2002-080室\"}"; + merchantDto.setBusinessLicenceInfo(JSONObject.parseObject(businessInfo, BusinessLicenceInfoDto.class)); + + String storeInfo = "{\"businessAddress\":\"西安市沣东新城石化大道西段106号沣东科技园5号楼1层5-037室\",\"cashierDeskPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/4e52859aac754670aaf9be1c923da827.jpg\",\"wechatId\":\"V1_qFmvcdaX1XSUmknkcwKzMh53G4PXHmNYVJzGUiEs-qE-2ERQMr6XIemErRcpFPkxyCyY2DFU5QIBiaW6WamZmwLJOzY9eQhmv6giwKWUAjk\"},\"doorPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/2c207c6f4a764ad18e501ed10fbfad59.png\",\"wechatId\":\"V1_JPI6iQUmEXiUL2JogtdaJR53G4PXHmO3QpILvqWkLccmvi-vX4OJIdmBrRcpFPkxwNCa-m1w5XissHoA_ynwKQLJOzY9eQhmv6giwKWUAjk\"},\"insidePic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/394b4834698a47e9b75419a5fd7f7de7.jpg\",\"wechatId\":\"V1_djcTvjUKESKADtVCmtLqmR53G4PXHmOLw9uEV3te3o7OebPqjdklIdGBrRcpFPkxy6JbIGyYEZT6XatGXNQ2YQLJOzY9eQhmv6giwKWUAjk\"},\"mercArea\":\"雁塔区\",\"mercAreaCode\":\"610113\",\"mercCity\":\"西安市\",\"mercCityCode\":\"610100\",\"mercProv\":\"陕西省\",\"mercProvCode\":\"610000\"}"; + merchantDto.setStoreInfo(JSONObject.parseObject(storeInfo, StoreInfoDto.class)); + + String settlementInfo = "{\"bankBranchCode\":\"313791000427\",\"bankBranchName\":\"西安银行高新四路支行\",\"bankCardBackPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/3f3bb30f89fd4441985808f4a451c9c7.jpg\",\"wechatId\":\"V1_KAo5J49VNaCiC7twEi6NfB53G4PXHmNqgVl38ZojoETk3a9m_ZLvIaeHrRcpFPkxmQ3bRK2ytADSD8Fe8v-mpALJOzY9eQhmv6giwKWUAjk\"},\"bankCardFrontPic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/413f9170ca1d4f0893e05fd72b5e2b0e.jpg\",\"wechatId\":\"V1_GNhNtE1UYUCeRAuLsxdimR53G4PXHmNn9A4-gDbYgq47MiHxjki-IarByRcpFPkxfqldVa9taQ7W5KtWL35ppwLJOzY9eQhmv6giwKWUAjk\"},\"bankInstId\":\"XAB\",\"bankMobile\":\"18191655977\",\"bankName\":\"其他银行\",\"bankType\":\"\",\"noLegalHandSettleAuthPic\":{\"alipayId\":\"\",\"url\":\"\",\"wechatId\":\"\"},\"noLegalId\":\"\",\"noLegalIdCardBackPic\":{\"alipayId\":\"\",\"url\":\"\",\"wechatId\":\"\"},\"noLegalIdCardFrontPic\":{\"alipayId\":\"\",\"url\":\"\",\"wechatId\":\"\"},\"noLegalName\":\"\",\"noLegalSettleAuthPic\":{\"alipayId\":\"\",\"url\":\"\",\"wechatId\":\"\"},\"openAccArea\":\"雁塔区\",\"openAccAreaId\":\"610113\",\"openAccCity\":\"西安市\",\"openAccCityId\":\"610100\",\"openAccProvince\":\"陕西省\",\"openAccProvinceId\":\"610000\",\"openAccountLicencePic\":{\"alipayId\":\"\",\"url\":\"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/6e5dca0822f742649ecf1666f91ad1b3.jpg\",\"wechatId\":\"V1_1S62SpAFYecG5T-DC9ewHR53G4PXHmMA49fy0uVVk5j35lE5L5njIfSGrRcpFPkxU9Fm6P6bYLY3t010FYkmAQLJOzY9eQhmv6giwKWUAjk\"},\"settlementCardNo\":\"611011580000054186\",\"settlementCardType\":\"21\",\"settlementName\":\"西安菲慕斯博市场营销策划有限公司\",\"settlementType\":\"1\"}"; + merchantDto.setSettlementInfo(JSONObject.parseObject(settlementInfo, SettlementInfoDto.class)); + + +// merchantDto.setMerchantCode("20220106000000000001"); +// +// MerchantBaseInfoDto baseInfoDto = new MerchantBaseInfoDto(); +// baseInfoDto.setUserType("1"); +// baseInfoDto.setCompanyChildType("1"); +// baseInfoDto.setShortName("测试商户"); +// baseInfoDto.setAlipayAccount("1157756119@qq.com"); +// baseInfoDto.setMccCode("A0001_B0001"); +// baseInfoDto.setContactPersonType(PayCst.ContactPersonType.SUPER); +// baseInfoDto.setContactName("张三"); +// baseInfoDto.setContactPersonId("110101199001011234"); +// baseInfoDto.setContactPersonIdStartDate("2021-01-01"); +// baseInfoDto.setContactPersonIdEndDate("2025-01-01"); +// baseInfoDto.setContactPhone("13800000000"); +// baseInfoDto.setContactEmail("1157756119@qq.com"); +// baseInfoDto.setContactAddr("广东省深圳市南山区"); +// baseInfoDto.setContactIdCardFrontPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// baseInfoDto.setContactIdCardBackPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// merchantDto.setMerchantBaseInfo(baseInfoDto); +// +// BusinessLicenceInfoDto businessLicenceInfoDto = new BusinessLicenceInfoDto(); +// businessLicenceInfoDto.setLicenceName("测试商户"); +// businessLicenceInfoDto.setLicenceNo("110101199001011234"); +// businessLicenceInfoDto.setLicenceStartDate("2021-01-01"); +// businessLicenceInfoDto.setLicenceEndDate("2052-01-01"); +// businessLicenceInfoDto.setRegisteredAddress("广东省深圳市南山区"); +// businessLicenceInfoDto.setLicensePic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// merchantDto.setBusinessLicenceInfo(businessLicenceInfoDto); +// +// LegalPersonInfoDto legalPersonInfoDto = new LegalPersonInfoDto(); +// legalPersonInfoDto.setLegalPersonName("张三"); +// legalPersonInfoDto.setLegalPersonId("110101199001011234"); +// legalPersonInfoDto.setLegalIdPersonStartDate("2021-01-01"); +// legalPersonInfoDto.setLegalPersonIdEndDate("2055-01-01"); +// legalPersonInfoDto.setLegalPersonPhone("13800000000"); +// legalPersonInfoDto.setLegalPersonEmail("1157756119@qq.com"); +// legalPersonInfoDto.setLegalGender("1"); +// legalPersonInfoDto.setLegalAddress("广东省深圳市南山区"); +// legalPersonInfoDto.setIdCardHandPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// legalPersonInfoDto.setIdCardFrontPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// legalPersonInfoDto.setIdCardBackPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// merchantDto.setLegalPersonInfo(legalPersonInfoDto); +// +// StoreInfoDto storeInfoDto = new StoreInfoDto(); +// storeInfoDto.setMercProvCode("440000"); +// storeInfoDto.setMercCityCode("440300"); +// storeInfoDto.setMercAreaCode("440303"); +// storeInfoDto.setMercProv("广东省"); +// storeInfoDto.setMercCity("深圳市"); +// storeInfoDto.setMercArea("南山区"); +// storeInfoDto.setBusinessAddress("广东省深圳市南山区"); +// storeInfoDto.setInsidePic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// storeInfoDto.setDoorPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// storeInfoDto.setCashierDeskPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// merchantDto.setStoreInfo(storeInfoDto); +// +// SettlementInfoDto settlementInfoDto = new SettlementInfoDto(); +// settlementInfoDto.setSettlementType("1"); +// settlementInfoDto.setSettlementCardType("21"); +// settlementInfoDto.setSettlementName("张三"); +// settlementInfoDto.setSettlementCardNo("110101199001011234"); +// settlementInfoDto.setBankMobile("13800000000"); +// settlementInfoDto.setOpenAccProvinceId("440000"); +// settlementInfoDto.setOpenAccCityId("440300"); +// settlementInfoDto.setOpenAccAreaId("440303"); +// settlementInfoDto.setOpenAccProvince("广东省"); +// settlementInfoDto.setOpenAccCity("深圳市"); +// settlementInfoDto.setOpenAccArea("南山区"); +// settlementInfoDto.setBankName("中国工商银行"); +// settlementInfoDto.setBankInstId("ICBC"); +// settlementInfoDto.setBankType("1"); +// settlementInfoDto.setBankBranchName("广东省深圳市南山区"); +// settlementInfoDto.setBankBranchCode("440300"); +// settlementInfoDto.setBankCardFrontPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// settlementInfoDto.setBankCardBackPic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// settlementInfoDto.setOpenAccountLicencePic(new ImageDto("https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg")); +// merchantDto.setSettlementInfo(settlementInfoDto); + + return merchantDto; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/OcrUtils.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/OcrUtils.java new file mode 100644 index 000000000..6719d4f71 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/OcrUtils.java @@ -0,0 +1,68 @@ +package com.czg; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.ocr_api20210707.models.RecognizeAllTextResponse; +import com.aliyun.tea.*; +import com.czg.exception.CzgException; +import lombok.extern.slf4j.Slf4j; + +/** + * @author ww + */ +@Slf4j +public class OcrUtils { + /** + * 阿里 ocr识别图片 + * 本接口支持:PNG、JPG、JPEG、BMP、GIF、TIFF、WebP、PDF。 + * 图片长宽需要大于 15 像素,小于 8192 像素。 + * 长宽比需要小于 50。长宽均大于 500px。 + * 图片二进制文件不能超过 10MB。 + * 图片过大会影响接口响应速度,建议使用小于 1.5M 图片进行识别, + * + * @param url 图片地址 + * @param type IdCard 身份证 + * BankCard 银行卡 + * BusinessLicense 营业执照 + * 目前使用的角色为 czg-oss@1413456038175003.onaliyun.com + * 需要 AliyunOCRFullAccess 权限 + * AliyunBSSContractFullAccess 权限 + * 文档地址 + */ + public static JSONObject getInfoByImg(String accessKeyId, String accessKeySecret, + String url, String type) throws Exception { + com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + .setAccessKeyId(accessKeyId) + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + .setAccessKeySecret(accessKeySecret); + config.endpoint = "ocr-api.cn-hangzhou.aliyuncs.com"; + + com.aliyun.ocr_api20210707.Client client = new com.aliyun.ocr_api20210707.Client(config); + com.aliyun.ocr_api20210707.models.RecognizeAllTextRequest recognizeAllTextRequest = new com.aliyun.ocr_api20210707.models.RecognizeAllTextRequest() + .setUrl(url).setType(type); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + try { + RecognizeAllTextResponse resp = client.recognizeAllTextWithOptions(recognizeAllTextRequest, runtime); + return JSONObject.from(resp.body.data); + } catch (TeaException error) { +// // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 +// // 错误 message +// System.out.println(error.getMessage()); +// // 诊断地址 +// System.out.println(error.getData().get("Recommend")); +// com.aliyun.teautil.Common.assertAsString(error.message); + log.error("识别失败, msg: {}", error.getMessage()); + throw new CzgException(error.getMessage()); + } catch (Exception error) { +// TeaException error = new TeaException(_error.getMessage(), _error); +// // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 +// // 错误 message +// System.out.println(error.getMessage()); +// // 诊断地址 +// System.out.println(error.getData().get("Recommend")); +// com.aliyun.teautil.Common.assertAsString(error.message); + log.error("识别失败, msg: {}", error.getMessage()); + throw new CzgException(error.getMessage()); + } + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/PayCst.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayCst.java new file mode 100644 index 000000000..3602ba2a2 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayCst.java @@ -0,0 +1,92 @@ +package com.czg; + +import java.util.List; + +/** + * 支付相关常量 + * + * @author yjjie + * @date 2026/1/7 09:20 + */ +public interface PayCst { + + /** + * 长期有效时间 + */ + String LONG_TERM_DATE = "2099-12-31"; + + /** + * 支付宝异常信息获取 key + */ + String ALIPAY_ERROR_MSG_KEY = "message"; + + /** + * 平台 + */ + class Platform { + /** + * 微信 + */ + public static final String WECHAT = "WECHAT"; + /** + * 支付宝 + */ + public static final String ALIPAY = "ALIPAY"; + } + + /** + * 进件状态 + */ + class EntryStatus { + /** + * 待提交 + */ + public static final String WAIT = "WAIT"; + + /** + * 待处理 + */ + public static final String INIT = "INIT"; + /** + * 审核中 + */ + public static final String AUDIT = "AUDIT"; + + /** + * 待签约 + */ + public static final String SIGN = "SIGN"; + + /** + * 已完成 + */ + public static final String FINISH = "FINISH"; + + /** + * 失败 + */ + public static final String REJECTED = "REJECTED"; + + + /** + * 需要查询的状态列表 + * 待处理、待签约、待审核的进件 + */ + public static final List NEED_QUERY_LIST = List.of(INIT, AUDIT, SIGN); + } + + /** + * 联系人类型 + */ + class ContactPersonType { + /** + * 法人 + */ + public static final String LEGAL = "LEGAL"; + + /** + * 经办人 + */ + public static final String SUPER = "SUPER"; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java new file mode 100644 index 000000000..6dc32e0b1 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java @@ -0,0 +1,77 @@ +package com.czg; + +import com.czg.constants.SystemConstants; +import com.czg.exception.CzgException; +import com.czg.pay.*; +import com.czg.third.alipay.AlipayIsvPayManager; +import com.czg.third.wechat.WechatPayManager; + +import java.util.Map; + +/** + * @author yjjie + * @date 2026/1/9 11:24 + */ +public class PayManager { + + /** + * jsapi支付 + * + * @param paramsDto 参数 + * @return 结果 + */ + public static Map jsapiPay(CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) { + if (SystemConstants.PayType.WECHAT.equals(paramsDto.getPayType())) { + return WechatPayManager.jsapiPay(null, paramsDto, merchantDTO); + } else if (SystemConstants.PayType.ALIPAY.equals(paramsDto.getPayType())) { + return AlipayIsvPayManager.jsapiPay(null, paramsDto, merchantDTO); + } else { + throw new CzgException("不支持的支付平台"); + } + } + + /** + * 条码支付 + * + * @param paramsDto 参数 + * @return 结果 + */ + public static Map barPay(CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) { + if (SystemConstants.PayType.WECHAT.equals(paramsDto.getPayType())) { + return WechatPayManager.barPay(null, paramsDto, merchantDTO); + } else if (SystemConstants.PayType.ALIPAY.equals(paramsDto.getPayType())) { + return AlipayIsvPayManager.barPay(null, paramsDto, merchantDTO); + } else { + throw new CzgException("不支持的支付平台"); + } + } + + /** + * 查询订单状态 + */ + public static QueryOrderRespDTO queryOrderStatus(String platform, String orderNo, NativeMerchantDTO merchantDTO) { + if (SystemConstants.PayType.WECHAT.equals(platform)) { + return WechatPayManager.queryOrder(null, orderNo, merchantDTO); + } else if (SystemConstants.PayType.ALIPAY.equals(platform)) { + return AlipayIsvPayManager.queryOrder(null, orderNo, merchantDTO); + } else { + throw new CzgException("不支持的支付平台"); + } + } + + /** + * 退款 + */ + public static RefundRespDTO refund(CzgRefundReq paramsDto, String notifyUrl, NativeMerchantDTO merchantDTO) { + if (PayCst.Platform.WECHAT.equals(paramsDto.getPlatform())) { + return WechatPayManager.refundOrder(null, paramsDto, notifyUrl, merchantDTO); + } else if (PayCst.Platform.ALIPAY.equals(paramsDto.getPlatform())) { + return AlipayIsvPayManager.refundOrder(null, paramsDto, notifyUrl, merchantDTO); + } else { + throw new CzgException("不支持的支付平台"); + } + } + + public static void main(String[] args) { + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/AggregateMerchantDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/AggregateMerchantDto.java new file mode 100644 index 000000000..e72937618 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/AggregateMerchantDto.java @@ -0,0 +1,54 @@ +package com.czg.dto.req; + +import lombok.Data; + +/** + * 统一进件 + * + * @author yjjie + * @date 2026/1/6 10:02 + */ +@Data +public class AggregateMerchantDto { + /** + * 商户Id + */ + private Long shopId; + + /** + * 【必填】 + * 自己生成 CZGyyyy-MM-dd HH:mm:ss.SSS + * 商户编号(在当前系统唯一) + */ + private String merchantCode; + + /** + * 【必填】 + * 商户基础信息 + */ + private MerchantBaseInfoDto merchantBaseInfo; + + /** + * 【必填】 + * 法人信息 + */ + private LegalPersonInfoDto legalPersonInfo; + + /** + * 【必填】 + * 营业执照信息 + */ + private BusinessLicenceInfoDto businessLicenceInfo; + + /** + * 【必填】 + * 门店信息 + */ + private StoreInfoDto storeInfo; + + /** + * 【必填】 + * 结算信息 + */ + private SettlementInfoDto settlementInfo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/BusinessLicenceInfoDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/BusinessLicenceInfoDto.java new file mode 100644 index 000000000..bd6380ca2 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/BusinessLicenceInfoDto.java @@ -0,0 +1,42 @@ +package com.czg.dto.req; + +import lombok.Data; + +/** + * 营业执照信息 + * @author yjjie + * @date 2026/1/6 10:17 + */ +@Data +public class BusinessLicenceInfoDto { + + /** + * 营业执照全称--非小微必填 + */ + private String licenceName; + + /** + * 营业执照号码--非小微必填 + */ + private String licenceNo; + + /** + * 营业执照开始日期--非小微必填 + */ + private String licenceStartDate; + + /** + * 营业执照结束日期--非小微必填 + */ + private String licenceEndDate; + + /** + * 营业执照注册地址--非小微必填 + */ + private String registeredAddress; + + /** + * 营业执照 + */ + private ImageDto licensePic; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/ImageDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/ImageDto.java new file mode 100644 index 000000000..b46a6c05a --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/ImageDto.java @@ -0,0 +1,34 @@ +package com.czg.dto.req; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author yjjie + * @date 2026/1/6 16:53 + */ +@Data +@Accessors(chain = true) +public class ImageDto { + + /** + * 图片 url + */ + private String url; + + /** + * 微信 图片 mediaId + */ + private String wechatId; + + /** + * 支付宝 图片 mediaId + */ + private String alipayId; + + public ImageDto() {} + + public ImageDto(String url) { + this.url = url; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/LegalPersonInfoDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/LegalPersonInfoDto.java new file mode 100644 index 000000000..8f631d00e --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/LegalPersonInfoDto.java @@ -0,0 +1,76 @@ +package com.czg.dto.req; + +import lombok.Data; + +/** + * 法人信息 + * @author yjjie + * @date 2026/1/6 10:13 + */ +@Data +public class LegalPersonInfoDto { + + /** + * 【必填】 + * 法人姓名 + */ + private String legalPersonName; + + /** + * 【必填】 + * 法人身份证号 + */ + private String legalPersonId; + + /** + * 【必填】 + * 法人身份证开始日期 + */ + private String legalIdPersonStartDate; + + /** + * 【必填】 + * 法人身份证到期日期 + */ + private String legalPersonIdEndDate; + + /** + * 【必填】 + * 法人电话 + */ + private String legalPersonPhone; + + /** + * 【必填】 + * 法人邮箱 + */ + private String legalPersonEmail; + + /** + * 法人性别(0男 1女) + */ + private String legalGender; + + /** + * 【必填】 + * 法人地址 + */ + private String legalAddress; + + /** + * 身份证手持 图片 + */ + private ImageDto idCardHandPic; + + /** + * 【必填】 + * 身份证正面 + */ + private ImageDto idCardFrontPic; + + /** + * 【必填】 + * 身份证反面 + */ + private ImageDto idCardBackPic; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/MerchantBaseInfoDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/MerchantBaseInfoDto.java new file mode 100644 index 000000000..e249e55a4 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/MerchantBaseInfoDto.java @@ -0,0 +1,105 @@ +package com.czg.dto.req; + +import lombok.Data; + +/** + * 商户基础信息 + * + * @author yjjie + * @date 2026/1/6 10:09 + */ +@Data +public class MerchantBaseInfoDto { + + /** + * 【必填】 + * 商户类型 + * 0: 个体商户; + * 1: 企业商户; + * 3: 小微商户 暂不支持 + */ + private String userType; + + /** + * 【必填】 + * 商户简称--企业、个体必填 + */ + private String shortName; + + /** + * 【必填】 + * 行业编码 + * 一级类目code_二级类目code + * 【示例值】A0001_B0199 + */ + private String mccCode; + + /** + * 【必填】 + * 支付宝账号 + */ + private String alipayAccount; + + /** + * 联系人类型 + * LEGAL: 经营者/法定代表人 + * SUPER: 经办人 + */ + private String contactPersonType = "LEGAL"; + + /** + * 联系人姓名 + */ + private String contactName; + + /** + * 证件类型 + * 目前只支持身份证 传值:0 + */ + private String certType = "0"; + + /** + * 联系人身份证号 + */ + private String contactPersonId; + + /** + * 联系人身份证开始日期 + */ + private String contactPersonIdStartDate; + + /** + * 联系人身份证到期日期 + */ + private String contactPersonIdEndDate; + + /** + * 联系人身份证背面 + */ + private ImageDto contactIdCardBackPic; + + /** + * 联系人身份证正面 + */ + private ImageDto contactIdCardFrontPic; + + /** + * 联系人电话 + */ + private String contactPhone; + + /** + * 联系人通讯地址 + */ + private String contactAddr; + + /** + * 联系人邮箱 + */ + private String contactEmail; + + /** + * 企业类型,1:普通企业,2:事业单位,3:政府机关,4:社会组织 + */ + private String companyChildType = "1"; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java new file mode 100644 index 000000000..20553c16a --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java @@ -0,0 +1,120 @@ +package com.czg.dto.req; + +import com.alibaba.fastjson2.JSONObject; +import com.czg.PayCst; +import com.czg.exception.CzgException; +import com.czg.pay.AlipayAuthInfoDto; +import com.czg.utils.AssertUtil; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 支付参数 + * + * @author yjjie + * @date 2026/1/9 11:12 + */ +@Data +@Accessors(chain = true) +public class PayParamsDto { + + /** + * 【必填】 + * 支付方式 + * {@link com.czg.PayCst.Platform} + */ + private String platform; + + /** + * 【必填】 + * 订单号 + */ + private String orderNo; + + /** + * 【必填】 + * 金额,单位:分 + */ + private Long amount; + + /** + * 【必填】 + * 订单标题 + */ + private String title; + + /** + * 【必填】 + * 回调地址 + */ + private String notifyUrl; + + /** + * 【必填】 + * appId + */ + private String appId; + + /** + * 用户唯一标识 + */ + private String openId; + + /** + * 【微信必填】 + * 商户ID + */ + private String merchantId; + + /** + * 【支付宝必填】 + * 支付参数 + */ + private String payParams; + + /** + * 商品描述 + */ + private String body; + + /** + * 扩展数据 + */ + private String extData; + + /** + * 设备 ip + */ + private String clientIp; + + /** + * 支付条码 + * 微信:18位纯数字,前缀以10、11、12、13、14、15开头 + */ + private String barCode; + + /** + * 支付宝 授权信息解析,不用传 + */ + private AlipayAuthInfoDto alipayAuthInfo; + + public void verifyParams() { + AssertUtil.isBlank(platform, "请选择支付方式"); + AssertUtil.isBlank(orderNo, "订单号不能为空"); + AssertUtil.isBlank(title, "订单标题不能为空"); + AssertUtil.isBlank(notifyUrl, "回调地址不能为空"); + AssertUtil.isBlank(appId, "appId不能为空"); + AssertUtil.isBlank(openId, "用户唯一标识不能为空"); + + if (PayCst.Platform.WECHAT.equals(platform)) { + AssertUtil.isBlank(merchantId, "商户ID不能为空"); + } else if (PayCst.Platform.ALIPAY.equals(platform)) { + AssertUtil.isBlank(payParams, "支付参数不能为空"); + alipayAuthInfo = JSONObject.parseObject(payParams, AlipayAuthInfoDto.class); + AssertUtil.isNull(alipayAuthInfo, "支付参数错误"); + AssertUtil.isBlank(alipayAuthInfo.getAppAuthToken(), "授权信息错误"); + } else { + throw new CzgException("支付平台错误"); + } + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/RefundParamsDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/RefundParamsDto.java new file mode 100644 index 000000000..b4fad4657 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/RefundParamsDto.java @@ -0,0 +1,69 @@ +package com.czg.dto.req; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 退款参数 + * @author yjjie + * @date 2026/1/14 11:37 + */ +@Data +@Accessors(chain = true) +public class RefundParamsDto { + + /** + * 【必填】 + * 平台 + * {@link com.czg.PayCst.Platform} + */ + private String platform; + + /** + * 【必填】 + * 订单号 + */ + private String orderNo; + + /** + * 【必填】 + * 退款单号 + */ + private String refundOrderNo; + + /** + * 【必填】 + * 退款金额 + */ + private Long refundAmount; + + /** + * 【必填】 + * 订单原金额 + */ + private Long orderAmount; + + /** + * 【必填】 + * 退款原因 + */ + private String refundReason; + + /** + * 【必填】 + * 退款回调地址 + */ + private String refundNotifyUrl; + + /** + * 【选填】 + * 微信商户号 + */ + private String merchantId; + + /** + * 【选填】 + * 支付宝授权信息 + */ + private String alipayAuthInfo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/SettlementInfoDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/SettlementInfoDto.java new file mode 100644 index 000000000..c0b9c6dc4 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/SettlementInfoDto.java @@ -0,0 +1,138 @@ +package com.czg.dto.req; + +import lombok.Data; + +/** + * 结算信息 + * + * @author yjjie + * @date 2026/1/6 10:22 + */ +@Data +public class SettlementInfoDto { + + /** + * 结算类型 0:非法人结算, 1:法人结算 + */ + private String settlementType; + + /** + * 非法人姓名 + */ + private String noLegalName; + + /** + * 非法人身份证号码 + */ + private String noLegalId; + + /** + * 结算卡类型 必填 11 对私借记卡(结算卡正面照、结算卡反面照图片必传) 21 对公借记卡(只须结算卡正面照片) + */ + private String settlementCardType; + + /** + * 结算账户卡号 + */ + private String settlementCardNo; + + /** + * 结算账户户名 + */ + private String settlementName; + + /** + * 结算银行预留手机号 + */ + private String bankMobile; + + /** + * 开户行省ID + */ + private String openAccProvinceId; + + /** + * 开户行市ID + */ + private String openAccCityId; + + /** + * 开户行区ID + */ + private String openAccAreaId; + + /** + * 开户行省 + */ + private String openAccProvince; + + /** + * 开户行市 + */ + private String openAccCity; + + /** + * 开户行区 + */ + private String openAccArea; + + /** + * 开户行行别名称 + */ + private String bankName; + + /** + * 开户行缩写 + */ + private String bankInstId; + + /** + * 开户行编号 + */ + private String bankType; + + /** + * 支行开户行行别名称 + */ + private String bankBranchName; + + /** + * 支行开户行编号 + */ + private String bankBranchCode; + + /** + * 银行卡正面 + */ + private ImageDto bankCardFrontPic; + + /** + * 银行卡反面 + */ + private ImageDto bankCardBackPic; + + /** + * 开户许可证 + */ + private ImageDto openAccountLicencePic; + + /** + * 非法人手持结算授权书 + */ + private ImageDto noLegalHandSettleAuthPic; + + /** + * 非法人结算授权书 + */ + private ImageDto noLegalSettleAuthPic; + + /** + * 非法人身份证正面 + */ + private ImageDto noLegalIdCardFrontPic; + + /** + * 非法人身份证反面 + */ + private ImageDto noLegalIdCardBackPic; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/StoreInfoDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/StoreInfoDto.java new file mode 100644 index 000000000..09b4f18e8 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/StoreInfoDto.java @@ -0,0 +1,66 @@ +package com.czg.dto.req; + +import lombok.Data; + +/** + * 门店信息 + * @author yjjie + * @date 2026/1/6 10:19 + */ +@Data +public class StoreInfoDto { + + /** + * 【必填】 + * 商户归属省Code + */ + private String mercProvCode; + + /** + * 【必填】 + * 商户归属市Code + */ + private String mercCityCode; + + /** + * 【必填】 + * 商户归属区Code + */ + private String mercAreaCode; + + /** + * 商户归属省 + */ + private String mercProv; + + /** + * 商户归属市 + */ + private String mercCity; + + /** + * 商户归属区 + */ + private String mercArea; + + /** + * 【必填】 + * 营业地址 + */ + private String businessAddress; + + /** + * 经营场所内设照片 + */ + private ImageDto insidePic; + + /** + * 门头照 + */ + private ImageDto doorPic; + + /** + * 收银台照片 + */ + private ImageDto cashierDeskPic; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/WechatNotifyReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/WechatNotifyReqDto.java new file mode 100644 index 000000000..71efa434e --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/WechatNotifyReqDto.java @@ -0,0 +1,33 @@ +package com.czg.dto.req; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @author yjjie + * @date 2025/12/23 21:47 + */ +@Data +public class WechatNotifyReqDto { + + private String id; + @JSONField(name = "create_time") + private String createTime; + @JSONField(name = "resource_type") + private String resourceType; + @JSONField(name = "event_type") + private String eventType; + private String summary; + private Resource resource; + + @Data + public static class Resource { + @JSONField(name = "resource_type") + private String originalType; + private String algorithm; + private String ciphertext; + @JSONField(name = "associated_data") + private String associatedData; + private String nonce; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/WechatPayNotifyDataDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/WechatPayNotifyDataDto.java new file mode 100644 index 000000000..44a5576c3 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/WechatPayNotifyDataDto.java @@ -0,0 +1,157 @@ +package com.czg.dto.req; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.czg.PayCst; +import com.czg.pay.PayNotifyRespDTO; +import lombok.Data; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @author yjjie + * @date 2025/12/23 22:21 + */ +@Data +public class WechatPayNotifyDataDto { + + /** + * 公众账号ID + */ + private String appid; + + /** + * 商户号 + */ + private String mchid; + + /** + * 商户订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + + /** + * 微信支付订单号 + */ + @JSONField(name = "transaction_id") + private String transactionId; + + /** + * 交易类型 + * JSAPI:公众号支付、小程序支付 + * NATIVE:Native支付 + * APP:APP支付 + * MICROPAY:付款码支付 + * MWEB:H5支付 + * FACEPAY:刷脸支付 + */ + @JSONField(name = "trade_type") + private String tradeType; + + /** + * 交易状态 + * SUCCESS—支付成功 + * REFUND—转入退款 + * NOTPAY—未支付 + * CLOSED—已关闭 + * REVOKED—已撤销(刷卡支付) + * USERPAYING--用户支付中 + * PAYERROR--支付失败(其他原因,如银行返回失败) + */ + @JSONField(name = "trade_state") + private String tradeState; + + /** + * 交易状态描述 + */ + @JSONField(name = "trade_state_desc") + private String tradeStateDesc; + + /** + * 付款银行 + */ + @JSONField(name = "bank_type") + private String bankType; + + /** + * 商户数据包 + */ + private String attach; + + /** + * 支付完成时间 + */ + @JSONField(name = "success_time") + private String successTime; + + /** + * 金额 + */ + private Amount amount; + + @Data + public static class Amount { + /** + * 用户支付金额 + */ + @JSONField(name = "payer_total") + private Integer payerTotal; + + /** + * 货币类型 + */ + private String currency; + + /** + * 总金额 + */ + private Integer total; + } + + public boolean isSuccess() { + return "SUCCESS".equals(tradeState); + } + + public boolean isClosed() { + return "CLOSED".equals(tradeState); + } + + public Long getPayAmount() { + return Long.valueOf(amount.getTotal()); + } + + public PayNotifyRespDTO convertToPayNotifyRespDTO() { + ZonedDateTime zonedDateTime = ZonedDateTime.parse(successTime); + DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String time = zonedDateTime.format(outputFormatter); + PayNotifyRespDTO respDTO = new PayNotifyRespDTO() + .setMchOrderNo(outTradeNo) + .setThirdOrderNo(transactionId) + .setAmount(getPayAmount()) + .setPlatform(PayCst.Platform.WECHAT) + .setExtData(attach) + .setPaySuccessTime(time) + .setErrorMsg(tradeStateDesc); + + switch (tradeState) { + case "SUCCESS": + respDTO.setStatus("TRADE_SUCCESS"); + break; + case "CLOSED": + respDTO.setStatus("TRADE_CLOSE"); + break; + case "USERPAYING": + respDTO.setStatus("TRADE_AWAIT"); + break; + case "NOTPAY": + respDTO.setStatus("TRADE_CANCEL"); + break; + default: + respDTO.setStatus("TRADE_FAIL"); + break; + } + + return respDTO; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/BankBranchDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/BankBranchDto.java new file mode 100644 index 000000000..abc878ee3 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/BankBranchDto.java @@ -0,0 +1,70 @@ +package com.czg.dto.resp; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 银行支行信息 + * @author yjjie + * @date 2026/1/6 15:37 + */ +@Data +public class BankBranchDto { + /** + * 机构ID + */ + private String instId; + + /** + * 银行代码 + */ + private String bankCode; + + /** + * 城市 + */ + private String city; + + /** + * 银行简称 + */ + private String simpleName; + + /** + * 省份 + */ + @JSONField(name = "provice") + private String province; + + /** + * IBPS代码 + */ + private String ibpsCode; + + /** + * 承担机构 + */ + private String undertakeInst; + + /** + * 分支机构名称 + */ + private String branchName; + + /** + * 机构全称 + */ + private String instName; + + /** + * 联行支行边行 + */ + @JSONField(name = "bank_branch_id") + private String bankBranchId; + + /** + * 联行支行名称 + */ + @JSONField(name = "bank_branch_name") + private String bankBranchName; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryRespDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryRespDto.java new file mode 100644 index 000000000..ade747ce3 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryRespDto.java @@ -0,0 +1,51 @@ +package com.czg.dto.resp; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 进件相应结果 + * @author yjjie + * @date 2026/1/6 18:15 + */ +@Data +@Accessors(chain = true) +public class EntryRespDto { + + /** + * 微信申请 Id + */ + private String wechatApplyId; + + /** + * 微信状态 + * {@link com.czg.PayCst.EntryStatus} + */ + private String wechatStatus; + + /** + * 微信进件错误信息 + */ + private String wechatErrorMsg; + + /** + * 支付宝订单 Id + */ + private String alipayOrderId; + + /** + * 支付宝授信息 + */ + private String alipayAuthInfo; + + /** + * 支付宝状态 + * {@link com.czg.PayCst.EntryStatus} + */ + private String alipayStatus; + + /** + * 支付宝进件错误信息 + */ + private String alipayErrorMsg; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryThirdRespDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryThirdRespDto.java new file mode 100644 index 000000000..63b3cde9a --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryThirdRespDto.java @@ -0,0 +1,39 @@ +package com.czg.dto.resp; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author yjjie + * @date 2026/1/7 09:07 + */ +@Data +@Accessors(chain = true) +public class EntryThirdRespDto { + + /** + * 录入id + */ + private String entryId; + + /** + * 支付宝授信息 + */ + private String alipayAuthInfo; + + /** + * 平台 + */ + private String platform; + + /** + * 状态 + * {@link com.czg.PayCst.EntryStatus} + */ + private String status; + + /** + * 错误信息 + */ + private String errorMsg; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/QueryStatusResp.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/QueryStatusResp.java new file mode 100644 index 000000000..79bf5cada --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/QueryStatusResp.java @@ -0,0 +1,51 @@ +package com.czg.dto.resp; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 查询进件状态 + * @author yjjie + * @date 2026/1/7 17:12 + */ +@Data +@Accessors(chain = true) +public class QueryStatusResp { + + /** + * 平台 + * {@link com.czg.PayCst.Platform} + */ + private String platform; + + /** + * 商户编号 + */ + private String merchantCode; + + /** + * 进件编号 + */ + private String applyId; + + /** + * 三方商户id + */ + private String thirdMerchantId; + + /** + * 进件状态 + * {@link com.czg.PayCst.EntryStatus} + */ + private String status; + + /** + * 签约地址 + */ + private String signUrl; + + /** + * 失败原因 + */ + private String failReason; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/WechatBankBranchRespDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/WechatBankBranchRespDto.java new file mode 100644 index 000000000..8cca740f0 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/WechatBankBranchRespDto.java @@ -0,0 +1,61 @@ +package com.czg.dto.resp; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; + +/** + * 微信银行支行列表 + * @author yjjie + * @date 2026/1/12 09:32 + */ +@Data +public class WechatBankBranchRespDto { + + /** + * 开户银行 + */ + @JSONField(name = "account_bank") + private String accountBank; + + /** + * 开户银行编码 + */ + @JSONField(name = "account_bank_code") + private Integer accountBankCode; + + /** + * 银行别名 + */ + @JSONField(name = "bank_alias") + private String bankAlias; + + /** + * 银行别名编码 + */ + @JSONField(name = "bank_alias_code") + private String bankAliasCode; + + /** + * 银行支行列表 + */ + @JSONField(name = "data") + private List data; + + + @Data + public static class WechatBankBranchList { + /** + * 银行支行名称 + */ + @JSONField(name = "bank_branch_name") + private String bankBranchName; + + /** + * 银行支行编号 + */ + @JSONField(name = "bank_branch_id") + private String bankBranchId; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayClient.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayClient.java new file mode 100644 index 000000000..93c6527c4 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayClient.java @@ -0,0 +1,61 @@ +package com.czg.third.alipay; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayConfig; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.v3.ApiClient; +import com.alipay.v3.Configuration; +import com.czg.third.alipay.dto.config.AlipayConfigDto; +import lombok.extern.slf4j.Slf4j; + +/** + * @author yjjie + * @date 2026/1/4 13:48 + */ +@Slf4j +public class AlipayClient { + + public static DefaultAlipayClient getDefaultClient(AlipayConfigDto configDto) { + try { + return new DefaultAlipayClient(getAlipayConfig(configDto)); + } catch (AlipayApiException e) { + log.error("创建支付宝客户端失败", e); + return null; + } + } + + public static void setApiClient(AlipayConfigDto configDto) { + if (configDto == null) { + configDto = AlipayConfigDto.getDefaultConfig(); + } + try { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + // 初始化alipay参数(全局设置一次) + com.alipay.v3.util.model.AlipayConfig alipayConfig = new com.alipay.v3.util.model.AlipayConfig(); + alipayConfig.setServerUrl(configDto.getDomain()); + alipayConfig.setAppId(configDto.getAppId()); + alipayConfig.setAlipayPublicKey(configDto.getAlipayPublicKey()); + alipayConfig.setPrivateKey(configDto.getPrivateKey()); + defaultClient.setAlipayConfig(alipayConfig); + } catch (Exception e) { + log.error("创建支付宝客户端失败", e); + } + } + + public static AlipayConfig getAlipayConfig(AlipayConfigDto configDto) { + if (configDto == null) { + configDto = AlipayConfigDto.getDefaultConfig(); + } + + AlipayConfig alipayConfig = new AlipayConfig(); + alipayConfig.setServerUrl(configDto.getDomain()); + alipayConfig.setAppId(configDto.getAppId()); + alipayConfig.setPrivateKey(configDto.getPrivateKey()); + alipayConfig.setFormat("json"); + alipayConfig.setAlipayPublicKey(configDto.getAlipayPublicKey()); + alipayConfig.setCharset("UTF-8"); + alipayConfig.setSignType("RSA2"); + + return alipayConfig; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayEntryManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayEntryManager.java new file mode 100644 index 000000000..926d01265 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayEntryManager.java @@ -0,0 +1,385 @@ +package com.czg.third.alipay; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alipay.api.AlipayApiException; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.FileItem; +import com.alipay.api.domain.*; +import com.alipay.api.request.AlipayFinancialnetAuthPbcnameQueryRequest; +import com.alipay.api.request.AntMerchantExpandIndirectImageUploadRequest; +import com.alipay.api.request.AntMerchantExpandIndirectZftCreateRequest; +import com.alipay.api.request.AntMerchantExpandIndirectZftorderQueryRequest; +import com.alipay.api.response.AlipayFinancialnetAuthPbcnameQueryResponse; +import com.alipay.api.response.AntMerchantExpandIndirectImageUploadResponse; +import com.alipay.api.response.AntMerchantExpandIndirectZftCreateResponse; +import com.alipay.api.response.AntMerchantExpandIndirectZftorderQueryResponse; +import com.alipay.v3.ApiClient; +import com.alipay.v3.ApiException; +import com.alipay.v3.Configuration; +import com.alipay.v3.api.AlipayOpenAgentApi; +import com.alipay.v3.model.AlipayOpenAgentCreateDefaultResponse; +import com.alipay.v3.model.AlipayOpenAgentCreateModel; +import com.alipay.v3.model.AlipayOpenAgentCreateResponseModel; +import com.alipay.v3.model.ContactModel; +import com.alipay.v3.util.model.AlipayConfig; +import com.czg.PayCst; +import com.czg.dto.req.*; +import com.czg.dto.resp.BankBranchDto; +import com.czg.dto.resp.EntryThirdRespDto; +import com.czg.dto.resp.QueryStatusResp; +import com.czg.exception.CzgException; +import com.czg.third.alipay.dto.config.AlipayConfigDto; +import com.czg.third.wechat.dto.resp.WechatQueryStateResp; +import com.czg.utils.UploadFileUtil; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * 支付宝进件管理 + * ... + * + * @author yjjie + * @date 2025/12/29 14:11 + */ +@Slf4j +public class AlipayEntryManager { + + /** + * 查询商户进件状态 + * + * @param configDto 配置信息 + * @param merchantCode 自系统的商户编号 + * @return 进件状态 + */ + public static QueryStatusResp queryMerchantEntryStatus(AlipayConfigDto configDto, String merchantCode) { + QueryStatusResp queryStatusResp = new QueryStatusResp(); + queryStatusResp.setPlatform(PayCst.Platform.ALIPAY); + queryStatusResp.setMerchantCode(merchantCode); + + AntMerchantExpandIndirectZftorderQueryRequest request = new AntMerchantExpandIndirectZftorderQueryRequest(); + + AntMerchantExpandIndirectZftorderQueryModel model = new AntMerchantExpandIndirectZftorderQueryModel(); + model.setExternalId(merchantCode); + + request.setBizModel(model); + + try { + DefaultAlipayClient defaultAlipayClient = AlipayClient.getDefaultClient(configDto); + AntMerchantExpandIndirectZftorderQueryResponse response = defaultAlipayClient.execute(request); + log.info("支付宝查询进件状态结果:{}", response.getBody()); + + if (response.isSuccess()) { + List orders = response.getOrders(); + if (orders != null && !orders.isEmpty()) { + ZftSubMerchantOrder first = orders.getFirst(); + queryStatusResp.setApplyId(first.getExternalId()); + + if ("99".equals(first.getStatus())) { + // 已完成 + queryStatusResp.setStatus(PayCst.EntryStatus.FINISH); + queryStatusResp.setThirdMerchantId(first.getSmid()); + queryStatusResp.setFailReason(""); + return queryStatusResp; + } + if ("-1".equals(first.getStatus())) { + // 拒绝 + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + queryStatusResp.setFailReason(first.getReason()); + return queryStatusResp; + } + + if (StrUtil.isNotBlank(first.getFkAudit())) { + if ("REJECT".equals(first.getFkAudit())) { + // 风控拒绝 + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + queryStatusResp.setFailReason(first.getFkAuditMemo()); + return queryStatusResp; + } + + if ("CREATE".equals(first.getFkAudit())) { + queryStatusResp.setStatus(PayCst.EntryStatus.AUDIT); + queryStatusResp.setFailReason(first.getFkAuditMemo()); + return queryStatusResp; + } + } + + if (StrUtil.isNotBlank(first.getKzAudit())) { + if ("REJECT".equals(first.getKzAudit())) { + // 风控拒绝 + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + queryStatusResp.setFailReason(first.getKzAuditMemo()); + return queryStatusResp; + } + + if ("CREATE".equals(first.getKzAudit())) { + queryStatusResp.setStatus(PayCst.EntryStatus.AUDIT); + queryStatusResp.setFailReason(first.getKzAuditMemo()); + return queryStatusResp; + } + } + + if (StrUtil.isNotBlank(first.getSubConfirm()) && "FAIL".equals(first.getSubConfirm())) { + // 签约失败 + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + queryStatusResp.setFailReason("签约失败"); + return queryStatusResp; + } + + queryStatusResp.setStatus(PayCst.EntryStatus.SIGN); + queryStatusResp.setSignUrl(first.getSubSignQrCodeUrl()); + return queryStatusResp; + } + } else { + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + queryStatusResp.setFailReason(response.getSubMsg()); + } + } catch (AlipayApiException e) { + log.error("支付宝查询进件状态报错:{}", e.getMessage(), e); + queryStatusResp.setStatus(PayCst.EntryStatus.INIT); + } + + + return queryStatusResp; + } + + /** + * 进件商户 + * + * @param configDto 配置信息 + * @param reqDto 请求信息 + */ + public static EntryThirdRespDto entryMerchant(AlipayConfigDto configDto, AggregateMerchantDto reqDto) { + AntMerchantExpandIndirectZftCreateModel entryReqDto = buildEntryParams(reqDto); + EntryThirdRespDto respDto = new EntryThirdRespDto() + .setPlatform(PayCst.Platform.ALIPAY); + + try { + AntMerchantExpandIndirectZftCreateRequest request = new AntMerchantExpandIndirectZftCreateRequest(); + DefaultAlipayClient defaultAlipayClient = AlipayClient.getDefaultClient(configDto); + request.setBizModel(entryReqDto); + AntMerchantExpandIndirectZftCreateResponse response = defaultAlipayClient.execute(request); + log.info("支付宝进件结果:{}", response.getBody()); + if (response.isSuccess()) { + respDto.setStatus(PayCst.EntryStatus.INIT); + respDto.setEntryId(response.getOrderId()); + respDto.setErrorMsg(""); + } else { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setEntryId(""); + respDto.setErrorMsg(response.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝进件报错:{}", e.getMessage(), e); + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setEntryId(""); + respDto.setErrorMsg(e.getMessage()); + } + + return respDto; + } + + /** + * 上传图片 + * + * @param configDto 配置信息 + * @param url 图片地址 + * @return 图片ID + */ + public static String uploadImage(AlipayConfigDto configDto, String url) { + try { + byte[] bytes = UploadFileUtil.downloadImage(url); + + Path tempFile = Files.createTempFile("image_", ".png"); + + // 写入数据 + Files.write(tempFile, bytes); + + File file = tempFile.toFile(); + + AntMerchantExpandIndirectImageUploadRequest request = new AntMerchantExpandIndirectImageUploadRequest(); + request.setImageType(UploadFileUtil.extractImageExtension(url)); + + // 设置图片内容 + FileItem imageContent = new FileItem(file); + request.setImageContent(imageContent); + + DefaultAlipayClient defaultAlipayClient = AlipayClient.getDefaultClient(configDto); + AntMerchantExpandIndirectImageUploadResponse response = defaultAlipayClient.execute(request); + + return response.getImageId(); + } catch (Exception e) { + log.error("支付宝上传图片报错,URL:{},错误信息:{}", "url", e.getMessage(), e); + return ""; + } + } + + /** + * 查询支行 + * + * @param instId 顶级机构ID CMB + * @param province 省份 陕西省 + * @param city 城市 西安市 + */ + public static List queryBankBranchList(AlipayConfigDto configDto, String instId, String province, String city) { + try { + AlipayFinancialnetAuthPbcnameQueryRequest request = new AlipayFinancialnetAuthPbcnameQueryRequest(); + + AlipayFinancialnetAuthPbcnameQueryModel model = new AlipayFinancialnetAuthPbcnameQueryModel(); + model.setInstId(instId); + model.setProvice(province); + model.setCity(city); + + request.setBizModel(model); + DefaultAlipayClient defaultAlipayClient = AlipayClient.getDefaultClient(configDto); + AlipayFinancialnetAuthPbcnameQueryResponse response = defaultAlipayClient.execute(request); + + String data = response.getPbcQueryResult(); + return JSONArray.parseArray(data, BankBranchDto.class); + } catch (Exception e) { + log.error("支付宝查询支行报错,错误信息:{}", e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * 构建进件参数 + * + * @param reqDto 请求参数 + * @return 进件参数 + */ + private static AntMerchantExpandIndirectZftCreateModel buildEntryParams(AggregateMerchantDto reqDto) { + AntMerchantExpandIndirectZftCreateModel entryParams = new AntMerchantExpandIndirectZftCreateModel(); + + MerchantBaseInfoDto baseInfo = reqDto.getMerchantBaseInfo(); + LegalPersonInfoDto legalPersonInfo = reqDto.getLegalPersonInfo(); + BusinessLicenceInfoDto businessLicenceInfo = reqDto.getBusinessLicenceInfo(); + StoreInfoDto storeInfo = reqDto.getStoreInfo(); + SettlementInfoDto settlementInfo = reqDto.getSettlementInfo(); + + entryParams.setExternalId(reqDto.getMerchantCode()); + if ("0".equals(baseInfo.getUserType())) { + entryParams.setMerchantType("07"); + } else if ("1".equals(baseInfo.getUserType())) { + switch (baseInfo.getCompanyChildType()) { + case "1" -> entryParams.setMerchantType("01"); + case "2" -> entryParams.setMerchantType("02"); + case "3" -> entryParams.setMerchantType("05"); + case "4" -> entryParams.setMerchantType("04"); + default -> throw new CzgException("主体类型错误"); + } + } else { + throw new CzgException("用户类型错误"); + } + entryParams.setName(businessLicenceInfo.getLicenceName()); + entryParams.setAliasName(baseInfo.getShortName()); + entryParams.setMcc(baseInfo.getMccCode()); + entryParams.setService(List.of("当面付", "jsapi支付", "小程序支付")); + entryParams.setInDoorImages(List.of(storeInfo.getInsidePic().getAlipayId(), storeInfo.getCashierDeskPic().getAlipayId())); + entryParams.setOutDoorImages(List.of(storeInfo.getDoorPic().getAlipayId())); + + // 证件类型(目前只支持个体和企业,所以都是营业执照) + entryParams.setCertType("201"); + entryParams.setCertImage(businessLicenceInfo.getLicensePic().getAlipayId()); + entryParams.setCertNo(businessLicenceInfo.getLicenceNo()); + + entryParams.setLegalName(legalPersonInfo.getLegalPersonName()); + entryParams.setLegalCertNo(legalPersonInfo.getLegalPersonId()); + entryParams.setLegalCertFrontImage(legalPersonInfo.getIdCardFrontPic().getAlipayId()); + entryParams.setLegalCertBackImage(legalPersonInfo.getIdCardBackPic().getAlipayId()); + + entryParams.setServicePhone(PayCst.ContactPersonType.LEGAL.equals(baseInfo.getContactPersonType()) ? legalPersonInfo.getLegalPersonPhone() : baseInfo.getContactPhone()); + + com.alipay.api.domain.AddressInfo addressInfo = new com.alipay.api.domain.AddressInfo(); + addressInfo.setCityCode(storeInfo.getMercCityCode()); + addressInfo.setDistrictCode(storeInfo.getMercAreaCode()); + addressInfo.setProvinceCode(storeInfo.getMercProvCode()); + addressInfo.setAddress(storeInfo.getBusinessAddress()); + entryParams.setBusinessAddress(addressInfo); + + com.alipay.api.domain.SettleCardInfo settleCardInfo = new com.alipay.api.domain.SettleCardInfo(); + settleCardInfo.setAccountHolderName(settlementInfo.getSettlementName()); + settleCardInfo.setAccountNo(settlementInfo.getSettlementCardNo()); + settleCardInfo.setAccountInstProvince(settlementInfo.getOpenAccProvince()); + settleCardInfo.setAccountInstCity(settlementInfo.getOpenAccCity()); + settlementInfo.setBankBranchName(settlementInfo.getBankBranchName()); + settleCardInfo.setUsageType("21".equals(settlementInfo.getSettlementCardType()) ? "01" : "02"); + settleCardInfo.setAccountType("DC"); + settleCardInfo.setAccountInstName(settlementInfo.getBankName()); + settleCardInfo.setAccountInstId(settlementInfo.getBankInstId()); + settleCardInfo.setBankCode(settlementInfo.getBankBranchCode()); + entryParams.setBizCards(List.of(settleCardInfo)); + + com.alipay.api.domain.ContactInfo contactInfo = new com.alipay.api.domain.ContactInfo(); + if (PayCst.ContactPersonType.SUPER.equals(baseInfo.getContactPersonType())) { + contactInfo.setName(baseInfo.getContactName()); + contactInfo.setMobile(baseInfo.getContactPhone()); + contactInfo.setEmail(baseInfo.getContactEmail()); + contactInfo.setIdCardNo(baseInfo.getContactPersonId()); + } else if (PayCst.ContactPersonType.LEGAL.equals(baseInfo.getContactPersonType())) { + contactInfo.setName(legalPersonInfo.getLegalPersonName()); + contactInfo.setMobile(legalPersonInfo.getLegalPersonPhone()); + contactInfo.setEmail(legalPersonInfo.getLegalPersonEmail()); + contactInfo.setIdCardNo(legalPersonInfo.getLegalPersonId()); + } else { + throw new CzgException("联系人类型错误"); + } + entryParams.setContactInfos(List.of(contactInfo)); + + entryParams.setLicenseAuthLetterImage(businessLicenceInfo.getLicensePic().getAlipayId()); + + // 小程序 + SiteInfo miniAppInfo = new SiteInfo(); + miniAppInfo.setSiteType("06"); + miniAppInfo.setSiteUrl("https://sxczgkj.cn"); + miniAppInfo.setSiteName("银收客"); + miniAppInfo.setTinyAppId("2021004145625815"); + entryParams.setSites(List.of(miniAppInfo)); + + return entryParams; + } + + public static void main(String[] args) throws ApiException { + + queryBankBranchList(null, "CMB", "陕西省", "西安市"); +// uploadImage(null, "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg"); + +// test(); + } + + // https://opendocs.alipay.com/solution/0dec7x?pathHash=caec4753 直付通 + public static void test() throws ApiException { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + // 初始化alipay参数(全局设置一次) + AlipayConfig alipayConfig = new AlipayConfig(); + alipayConfig.setServerUrl("https://openapi.alipay.com"); + alipayConfig.setAppId("2021006121646825"); + alipayConfig.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB"); + alipayConfig.setPrivateKey("MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCz3FkdffxZibdwis9W7eOW1dEjGAbSvRDL2ikfeCIW5KZNoIjUqxI0mIoUlLRRKO71QLHZS1Vb2aJp8jeOAqIPa8e76HTneQEzk3FGA8gpraSGvbadHWzvxnmYKsts1TBiEZQL82ySJXhQTJvZ6jyDM7s6wHHUnrH+Qi29QpppQ1sxsoJeCtajUgRg3btD6XbBcyFAX3pzM56Kw9eaIjZoD8WToZOM/Y3sqNL2uo8lLqcIpTrI7Pq5ZOspmBQ+t8v3rS9IdDZZMvd0trzS67AXwHz8rKPBT/lL1A4iHnXCHUvktusX1fPs3/RGY/a7PIddaBLnfY0GcueE16K7QcrXAgMBAAECggEAbSdT2eckp75BWoaTcGEs1tRqeM7TDT/6moyKmnOQ1K3tE31SrSYpBUxxuC3LBNo/sw2RIZtrcTOyMnPyLTgB3DP/4lUf5X51MTTQ8LnI1ypvh+pIki9Sdm3QS33lOOZk149tdpdDk6ozyx/DEcvq74EMpoo2SuAIi5LkKVDrXuehvGA/WeXtpmuPgqRFdIA+JBlA3knHk5XEQY/k2Y31gq5oCwNL9iT9OAZqVkukE6EnvCXE9t2rAV4/snYilaf/UaO+ktgEwSbPBQ8YKlovDAarMBbGtgr6E174A9djlPyR+W/fgx8rlTwSWtieb9MkO8LN3KSxgVs0kY5U8OHg8QKBgQDljJq9kTFHare+W/fAXdUy3tJprfNQCAii6s+GuDfTQiviVQDtWmdtHAN+xU3to7MepvVhwHsqtQnZXKTtZuwwxn82FNl7A5RYD3GVFW+wG6AsGLIdESrWxySoL6Kx8QmNpMEVg8acT/ywzW/RnUXS5vU7GIi8GA0vtyBo24R9KQKBgQDIlf/R9+iNk9oXlbB/k4um9eVvBBS7l5cx4E5Id5Dpp4kGZfPZEa7oDsEUstZZM9mgQLJK/noNWbcf0+BohCR5ux7SC12qIoxwN3k4BzTDqrS8BzFuVVp5PELUsf/uCbRn05iMzpiDUhj3Vde04wvjHYIobfKlZO2HeSWXCpUH/wKBgQC8wSuU6ck90pEY5QMKmZ3wYK1g3PsQOirv3Gmde+nbu7PePsuuYQJfBAQTwCZeXJezgtKP+PjOm2Nn6vhrhpB9YxvD2s0ijET1TG23i5L1myHQYNZFdJJnXgXUjqcX7v5ODMYA7QTqEBPXRnbGRK7fx66rU3dMQ/LD46+wyaFeUQKBgA4QTk53dkuu6SSsLyLSwoDjTsHY5Gc+urAZjQORtoxbXcUgEtfOYJgOqMT9wP+iHgkZYCbX7tDO0IMfxOUvFqueTgvmFhwergAUM6CVCMMLTf689l9JBr3nVrw4+rvC3G5HLLP6rEDQ2cVFtIkPPj8fS4fwJYopKGpOOS9843QbAoGBAMoHH8LqoZ50FLsospx/hJe24Cd8wCgQTXSa/hMqNZ1999JDhftMt7R0ZdB1he2LReACe0K9ntBU4H4u225zZ3wZlyOfoyerAHuLK/ysNlgIIzblZlOxbBJ64Kul8leXzlYy3tOZuZ997KqBcWPCE3LUBBNvM6E3blJUnlmJAVoi"); + defaultClient.setAlipayConfig(alipayConfig); + log.info("dd: {}", JSONObject.toJSONString(alipayConfig)); + AlipayOpenAgentApi api = new AlipayOpenAgentApi(); + AlipayOpenAgentCreateModel data = new AlipayOpenAgentCreateModel(); + ContactModel contactInfo = new ContactModel(); + contactInfo.setContactEmail("zhangsan@alipy.com"); + contactInfo.setContactName("张三"); + contactInfo.setContactMobile("18866668888"); + data.setContactInfo(contactInfo); +// data.setOrderTicket("00ee2d475f374ad097ee0f1ac223fX00"); + data.setAccount("1157756119@qq.com"); + try { + AlipayOpenAgentCreateResponseModel response = api.create(data); + System.out.println(response); + } catch (ApiException e) { + AlipayOpenAgentCreateDefaultResponse errorObject = (AlipayOpenAgentCreateDefaultResponse) e.getErrorObject(); + System.out.println("调用失败:" + errorObject); + System.out.println("调用失败:" + e.getMessage()); + } + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvEntryManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvEntryManager.java new file mode 100644 index 000000000..32d5aef14 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvEntryManager.java @@ -0,0 +1,263 @@ +package com.czg.third.alipay; + +import com.alibaba.fastjson2.JSONObject; +import com.alipay.v3.ApiException; +import com.alipay.v3.api.*; +import com.alipay.v3.model.*; +import com.czg.PayCst; +import com.czg.dto.req.*; +import com.czg.dto.resp.EntryThirdRespDto; +import com.czg.dto.resp.QueryStatusResp; +import com.czg.exception.CzgException; +import com.czg.pay.AlipayAuthInfoDto; +import com.czg.third.alipay.dto.config.AlipayConfigDto; +import com.czg.utils.UploadFileUtil; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; + +/** + * 支付宝服务商进件管理 + * ... + * @author yjjie + * @date 2026/1/8 13:34 + */ +@Slf4j +public class AlipayIsvEntryManager { + + public static QueryStatusResp queryMerchantBatchStatus(AlipayConfigDto configDto, String batchNo) { + if (configDto == null) { + configDto = AlipayConfigDto.getThirdDefaultConfig(); + } + QueryStatusResp respDto = new QueryStatusResp() + .setPlatform(PayCst.Platform.ALIPAY); + AlipayClient.setApiClient(configDto); + + try { + AlipayOpenAgentOrderApi api = new AlipayOpenAgentOrderApi(); + AlipayOpenAgentOrderQueryResponseModel response = api.query(batchNo); + + log.info("支付宝查询进件状态: 响应={}", response); + respDto.setApplyId(batchNo); + + switch (response.getOrderStatus()) { + case "MERCHANT_INFO_HOLD" -> { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setFailReason("三方异常,请重新提交"); + return respDto; + } + case "MERCHANT_CONFIRM_TIME_OUT" -> { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setFailReason("商户确认超时,请重新提交"); + return respDto; + } + case "MERCHANT_AUDITING" -> { + respDto.setStatus(PayCst.EntryStatus.AUDIT); + respDto.setFailReason(""); + return respDto; + } + case "MERCHANT_CONFIRM" -> { + respDto.setStatus(PayCst.EntryStatus.SIGN); + respDto.setFailReason(""); + respDto.setSignUrl(response.getConfirmUrl()); + return respDto; + } + case "MERCHANT_APPLY_ORDER_CANCELED" -> { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setFailReason(response.getRejectReason()); + return respDto; + } + case "MERCHANT_CONFIRM_SUCCESS" -> { + respDto.setStatus(PayCst.EntryStatus.FINISH); + respDto.setFailReason(""); + respDto.setThirdMerchantId(response.getMerchantPid()); + return respDto; + } + case null -> { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setFailReason("查询失败"); + return respDto; + } + default -> { + return respDto; + } + } + + } catch (ApiException e) { + String body = e.getResponseBody(); + log.error("支付宝查询进件状态异常: {}", body); + JSONObject object = JSONObject.parseObject(body); + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setFailReason(object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + return respDto; + } + } + + /** + * 创建商户 + * @param configDto 配置信息 + * @param reqDto 请求信息 + */ + public static EntryThirdRespDto entryMerchant(AlipayConfigDto configDto, AggregateMerchantDto reqDto) { + if (configDto == null) { + configDto = AlipayConfigDto.getThirdDefaultConfig(); + } + AlipayClient.setApiClient(configDto); + EntryThirdRespDto respDto = new EntryThirdRespDto() + .setPlatform(PayCst.Platform.ALIPAY); + try { + String batchNo = createRequest(configDto, reqDto); + respDto.setEntryId(batchNo); + + AlipayOpenAgentFacetofaceSignModel signModel = buildFaceToFaceModel(reqDto, batchNo); + File businessLicensePic = UploadFileUtil.getFileByUrl(reqDto.getBusinessLicenceInfo().getLicensePic().getUrl()); + File shopScenePic = UploadFileUtil.getFileByUrl(reqDto.getStoreInfo().getInsidePic().getUrl()); + File shopSignBoardPic = UploadFileUtil.getFileByUrl(reqDto.getStoreInfo().getDoorPic().getUrl()); + + log.info("支付宝开启代商户签约: 请求={}", JSONObject.toJSONString(signModel)); + + // 构造请求参数以调用接口 + AlipayOpenAgentFacetofaceApi api = new AlipayOpenAgentFacetofaceApi(); + Object response = api.sign(null, businessLicensePic, signModel, shopScenePic, shopSignBoardPic, null); + log.info("支付宝开启代商户签约: 响应={}", JSONObject.toJSONString(response)); + + try { + String authInfo = confirmRequest(configDto, batchNo); + respDto.setAlipayAuthInfo(authInfo); + respDto.setStatus(PayCst.EntryStatus.INIT); + respDto.setErrorMsg(""); + return respDto; + } catch (CzgException e) { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setErrorMsg(e.getMessage()); + return respDto; + } + } catch (ApiException e) { + String body = e.getResponseBody(); + log.error("支付宝开启代商户签约,创建应用事务异常: {}", body); + JSONObject object = JSONObject.parseObject(body); + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setErrorMsg(object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + return respDto; + } catch (Exception e) { + log.error("支付宝开启代商户签约,创建应用事务异常2", e); + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setErrorMsg(e.getMessage()); + return respDto; + } + } + + /** + * 开启代商户签约、创建应用事务 + * @param reqDto 请求信息 + * @return 请求ID + */ + public static String createRequest(AlipayConfigDto configDto, AggregateMerchantDto reqDto) { + if (configDto == null) { + configDto = AlipayConfigDto.getThirdDefaultConfig(); + } + AlipayClient.setApiClient(configDto); + AlipayOpenAgentApi api = new AlipayOpenAgentApi(); + AlipayOpenAgentCreateModel openModel = new AlipayOpenAgentCreateModel(); + + MerchantBaseInfoDto info = reqDto.getMerchantBaseInfo(); + LegalPersonInfoDto legalPersonInfo = reqDto.getLegalPersonInfo(); + String contactPersonType = info.getContactPersonType(); + boolean isSuper = PayCst.ContactPersonType.SUPER.equals(contactPersonType); + + ContactModel contactModel = new ContactModel(); + // 联系人名称 + contactModel.setContactName(isSuper ? info.getContactName() : legalPersonInfo.getLegalPersonName()); + // 联系人手机号码 + contactModel.setContactMobile(isSuper ? info.getContactPhone() : legalPersonInfo.getLegalPersonPhone()); + // 联系人邮箱 + contactModel.setContactEmail(isSuper ? info.getContactEmail() : legalPersonInfo.getLegalPersonEmail()); + openModel.setContactInfo(contactModel); + + openModel.setAccount(reqDto.getMerchantBaseInfo().getAlipayAccount()); + + try { + AlipayOpenAgentCreateResponseModel responseModel = api.create(openModel); + log.info("支付宝开启代商户签约,开启事务: 结果={}, batchNo={}", responseModel.getBatchStatus(), responseModel.getBatchNo()); + return responseModel.getBatchNo(); + } catch (ApiException e) { + String body = e.getResponseBody(); + log.error("支付宝开启代商户签约,开启事务异常: {}", body); + JSONObject object = JSONObject.parseObject(body); + throw new CzgException("支付宝开启代商户签约,开启事务异常: " + object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + } + } + + public static String confirmRequest(AlipayConfigDto configDto, String batchNo) { + if (configDto == null) { + configDto = AlipayConfigDto.getThirdDefaultConfig(); + } + AlipayClient.setApiClient(configDto); + // 构造请求参数以调用接口 + AlipayOpenAgentApi api = new AlipayOpenAgentApi(); + AlipayOpenAgentConfirmModel data = new AlipayOpenAgentConfirmModel(); + + // 设置ISV 代商户操作事务编号 + data.setBatchNo(batchNo); + try { + AlipayOpenAgentConfirmResponseModel response = api.confirm(data); + log.info("支付宝开启代商户签约,确认事务: 响应={}", response); + + AlipayAuthInfoDto authInfoDto = new AlipayAuthInfoDto() + .setUserId(response.getUserId()) + .setOpenId(response.getOpenId()) + .setAuthAppId(response.getAuthAppId()) + .setAppAuthToken(response.getAppAuthToken()) + .setExpiresIn(response.getExpiresIn()) + .setAppRefreshToken(response.getAppRefreshToken()) + .setReExpiresIn(response.getReExpiresIn()) + .setOrderNo(response.getOrderNo()); + return JSONObject.toJSONString(authInfoDto); + } catch (ApiException e) { + String body = e.getResponseBody(); + log.error("支付宝开启代商户签约,确认事务异常: {}", body); + JSONObject object = JSONObject.parseObject(body); + throw new CzgException(object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + } + } + + /** + * 构建 当面付参数 + */ + public static AlipayOpenAgentFacetofaceSignModel buildFaceToFaceModel(AggregateMerchantDto reqDto, String batchNo) { + AlipayOpenAgentFacetofaceSignModel signModel = new AlipayOpenAgentFacetofaceSignModel(); + signModel.setBatchNo(batchNo); + + MerchantBaseInfoDto baseInfo = reqDto.getMerchantBaseInfo(); + LegalPersonInfoDto legalPersonInfo = reqDto.getLegalPersonInfo(); + BusinessLicenceInfoDto licenceInfo = reqDto.getBusinessLicenceInfo(); + StoreInfoDto storeInfo = reqDto.getStoreInfo(); + + signModel.setMccCode(baseInfo.getMccCode()); + signModel.setRate("0.38"); + signModel.setSignAndAuth(true); + + signModel.setBusinessLicenseNo(licenceInfo.getLicenceNo()); + signModel.setBusinessLicenseMobile(legalPersonInfo.getLegalPersonPhone()); + signModel.setLongTerm(PayCst.LONG_TERM_DATE.equals(licenceInfo.getLicenceEndDate())); + signModel.setDateLimitation(licenceInfo.getLicenceStartDate()); + + signModel.setShopName(baseInfo.getShortName()); + + // 设置店铺地址 + SignAddressInfo shopAddress = new SignAddressInfo(); + shopAddress.setCountryCode("156"); + shopAddress.setDistrictCode(storeInfo.getMercAreaCode()); + shopAddress.setDetailAddress(storeInfo.getBusinessAddress()); + shopAddress.setProvinceCode(storeInfo.getMercProvCode()); + shopAddress.setCityCode(storeInfo.getMercCityCode()); + signModel.setShopAddress(shopAddress); + + return signModel; + } + + public static void main(String[] args) { +// confirmRequest("2026010815384505500018243"); + queryMerchantBatchStatus(null, "2026010815384505500018243"); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java new file mode 100644 index 000000000..159f0fb88 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java @@ -0,0 +1,120 @@ +package com.czg.third.alipay; + +import com.alibaba.fastjson2.JSONObject; +import com.alipay.v3.ApiException; +import com.alipay.v3.api.AlipayTradeApi; +import com.alipay.v3.model.AlipayTradeCreateModel; +import com.alipay.v3.model.AlipayTradeCreateResponseModel; +import com.alipay.v3.model.AlipayTradePayModel; +import com.alipay.v3.model.ExtendParams; +import com.alipay.v3.util.model.CustomizedParams; +import com.czg.PayCst; +import com.czg.exception.CzgException; +import com.czg.pay.*; +import com.czg.third.alipay.dto.config.AlipayConfigDto; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +/** + * @author yjjie + * @date 2026/1/9 09:30 + */ +@Slf4j +public class AlipayIsvPayManager { + + /** + * H5支付 + * + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map jsapiPay(AlipayConfigDto configDto, CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) { + try { + AlipayClient.setApiClient(configDto); + AlipayTradeApi api = new AlipayTradeApi(); + AlipayTradeCreateModel model = new AlipayTradeCreateModel(); + model.setOutTradeNo(paramsDto.getMchOrderNo()); + model.setProductCode("JSAPI_PAY"); + model.setOpAppId(merchantDTO.getAlipayMerchantId()); + model.setOpBuyerOpenId(paramsDto.getUserId()); + model.setTotalAmount(getYuanAmountByFen(paramsDto.getAmount())); + model.setSubject(paramsDto.getSubject()); + model.setBody(paramsDto.getBody()); + model.setNotifyUrl(paramsDto.getNotifyUrl() + "/" + PayCst.Platform.ALIPAY); + + model.setExtendParams(new ExtendParams()); + CustomizedParams customizedParams = new CustomizedParams(); + customizedParams.setAppAuthToken(merchantDTO.getAlipayAuthInfo().getAppAuthToken()); + + AlipayTradeCreateResponseModel responseModel = api.create(model, customizedParams); + log.info("支付宝 jsapi 支付结果: {}", responseModel); + return new HashMap<>(); + } catch (ApiException e) { + String body = e.getResponseBody(); + log.error("支付宝 H5 api 支付异常: {}", body); + JSONObject object = JSONObject.parseObject(body); + throw new CzgException(object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + } catch (Exception e) { + log.error("支付宝 H5支付异常: {}", e.getMessage()); + throw new CzgException(e.getMessage()); + } + } + + /** + * 条码支付 + * + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map barPay(AlipayConfigDto configDto, CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) { + try { + AlipayClient.setApiClient(configDto); + + // 构造请求参数以调用接口 + AlipayTradeApi api = new AlipayTradeApi(); + AlipayTradePayModel data = new AlipayTradePayModel(); + + data.setOutTradeNo(paramsDto.getMchOrderNo()); + data.setTotalAmount(getYuanAmountByFen(paramsDto.getAmount())); + data.setSubject(paramsDto.getSubject()); + // 设置支付授权码 + data.setAuthCode(merchantDTO.getAlipayAuthInfo().getAppAuthToken()); + // 设置支付场景 + data.setScene("bar_code"); + // 设置产品码 + data.setProductCode("FACE_TO_FACE_PAYMENT"); + data.setAuthCode(paramsDto.getAuthCode()); +// AlipayTradeCreateResponseModel responseModel = api.create(data); + + return new JSONObject(); +// } catch (ApiException e) { +// String body = e.getResponseBody(); +// log.error("支付宝 条码支付异常: {}", body); +// JSONObject object = JSONObject.parseObject(body); +// throw new CzgException(object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + } catch (Exception e) { + log.error("支付宝 条码支付异常: {}", e.getMessage()); + throw new CzgException(e.getMessage()); + } + } + + public static QueryOrderRespDTO queryOrder(AlipayConfigDto configDto, String orderNo, NativeMerchantDTO merchantDTO) { + return new QueryOrderRespDTO(); + } + + public static RefundRespDTO refundOrder(AlipayConfigDto configDto, CzgRefundReq paramsDto, String notifyUrl, NativeMerchantDTO merchantDTO) { + return new RefundRespDTO(); + } + + /** + * 金额转换 + */ + private static String getYuanAmountByFen(Long amount) { + BigDecimal yuanAmount = new BigDecimal(amount).divide(new BigDecimal(100)); + return yuanAmount.toString(); + } + +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/config/AlipayConfigDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/config/AlipayConfigDto.java new file mode 100644 index 000000000..bcf7d497b --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/config/AlipayConfigDto.java @@ -0,0 +1,76 @@ +package com.czg.third.alipay.dto.config; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author yjjie + * @date 2026/1/4 13:49 + */ +@Data +@Accessors(chain = true) +public class AlipayConfigDto { + + /** + * 支付宝 AppId + */ + private String appId; + + /** + * 商户私钥 + */ + private String privateKey; + + /** + * 支付宝公钥 + */ + private String alipayPublicKey; + + /** + * 支付宝支付域名 + * + */ + private String domain; + + /** + * 第三方应用 签约当面付 + */ + public static AlipayConfigDto getThirdDefaultConfig() { + return new AlipayConfigDto() + .setAppId("2021006121646825") + .setPrivateKey("MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCz3FkdffxZibdwis9W7eOW1dEjGAbSvRDL2ikfeCIW5KZNoIjUqxI0mIoUlLRRKO71QLHZS1Vb2aJp8jeOAqIPa8e76HTneQEzk3FGA8gpraSGvbadHWzvxnmYKsts1TBiEZQL82ySJXhQTJvZ6jyDM7s6wHHUnrH+Qi29QpppQ1sxsoJeCtajUgRg3btD6XbBcyFAX3pzM56Kw9eaIjZoD8WToZOM/Y3sqNL2uo8lLqcIpTrI7Pq5ZOspmBQ+t8v3rS9IdDZZMvd0trzS67AXwHz8rKPBT/lL1A4iHnXCHUvktusX1fPs3/RGY/a7PIddaBLnfY0GcueE16K7QcrXAgMBAAECggEAbSdT2eckp75BWoaTcGEs1tRqeM7TDT/6moyKmnOQ1K3tE31SrSYpBUxxuC3LBNo/sw2RIZtrcTOyMnPyLTgB3DP/4lUf5X51MTTQ8LnI1ypvh+pIki9Sdm3QS33lOOZk149tdpdDk6ozyx/DEcvq74EMpoo2SuAIi5LkKVDrXuehvGA/WeXtpmuPgqRFdIA+JBlA3knHk5XEQY/k2Y31gq5oCwNL9iT9OAZqVkukE6EnvCXE9t2rAV4/snYilaf/UaO+ktgEwSbPBQ8YKlovDAarMBbGtgr6E174A9djlPyR+W/fgx8rlTwSWtieb9MkO8LN3KSxgVs0kY5U8OHg8QKBgQDljJq9kTFHare+W/fAXdUy3tJprfNQCAii6s+GuDfTQiviVQDtWmdtHAN+xU3to7MepvVhwHsqtQnZXKTtZuwwxn82FNl7A5RYD3GVFW+wG6AsGLIdESrWxySoL6Kx8QmNpMEVg8acT/ywzW/RnUXS5vU7GIi8GA0vtyBo24R9KQKBgQDIlf/R9+iNk9oXlbB/k4um9eVvBBS7l5cx4E5Id5Dpp4kGZfPZEa7oDsEUstZZM9mgQLJK/noNWbcf0+BohCR5ux7SC12qIoxwN3k4BzTDqrS8BzFuVVp5PELUsf/uCbRn05iMzpiDUhj3Vde04wvjHYIobfKlZO2HeSWXCpUH/wKBgQC8wSuU6ck90pEY5QMKmZ3wYK1g3PsQOirv3Gmde+nbu7PePsuuYQJfBAQTwCZeXJezgtKP+PjOm2Nn6vhrhpB9YxvD2s0ijET1TG23i5L1myHQYNZFdJJnXgXUjqcX7v5ODMYA7QTqEBPXRnbGRK7fx66rU3dMQ/LD46+wyaFeUQKBgA4QTk53dkuu6SSsLyLSwoDjTsHY5Gc+urAZjQORtoxbXcUgEtfOYJgOqMT9wP+iHgkZYCbX7tDO0IMfxOUvFqueTgvmFhwergAUM6CVCMMLTf689l9JBr3nVrw4+rvC3G5HLLP6rEDQ2cVFtIkPPj8fS4fwJYopKGpOOS9843QbAoGBAMoHH8LqoZ50FLsospx/hJe24Cd8wCgQTXSa/hMqNZ1999JDhftMt7R0ZdB1he2LReACe0K9ntBU4H4u225zZ3wZlyOfoyerAHuLK/ysNlgIIzblZlOxbBJ64Kul8leXzlYy3tOZuZ997KqBcWPCE3LUBBNvM6E3blJUnlmJAVoi") + .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB") + .setDomain("https://openapi.alipay.com"); +// return new AlipayConfigDto() +// .setAppId("2021004174605036") +// .setPrivateKey("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD48HaUoV7OH7os+9L01kHgxzwIhJick4OkFq4aHsntsXEJ J3gedhuEZtV8oHKZ30DPW12IJ4S8NXtpr8OWaqrAPFonf4wVaRY1d0yIAea57kfLEn9oOEEy4FzARgMNDkyxC+/3OUdGbLHpTjfVX3gusXsEhUDy1/WewihAkoNYF37+W3W/uVLzeWoPq0EcUbRv/G/t/p6gL69ltsMAiVFG4Q/Yk24YAN6lYgBPNLXUEwQ1Q+T+1omjfavHgvarKOp33z3JOUH+aGOmDsJ5Y9gyGtJzOCipAd8Zcv+T1ygsEzZYO1/gzcbPnfO1ShqStCHzssuw8FBVx2JdfQKXKMMNAgMBAAECggEAVTrO/pg5Q00titU1Jspsh67u6OOs9H605Ws2dI7yB8VmtAGlaJh7V1t14FN2qSP8poHbhhAxq9aLyGV7C3a9u09udnN+3J28EtYjh7VO732bavWMVXxdJjQWzWWrCb9JlpxFrlkYBA6W4w/6ob0sAqCVQ7jzwbEa0R4cde8ztOa5nysKSfr4YTSs0gqvoiC6fmg8eiRJraEQBoYz9VkKFtOhhh/4w5FhVcYQ2gQvZ3kK3QVuD1eJIQKlCtz8qaox9lXKDiZT4SCmnKshdUL0u5TYIcYeBjZmhJz0Q50KHcpZrCs5y7I0+vRBH3hU+TKSQt7ureymwhbwWMHScLV2gQKBgQD+58SHXhr5M8NGagAmTdsgmCnNv2kOYMd4STyPMY10SVwCv1Bk808ZuP+7e558J1b5/OuDLI5dLq6xrZ/1wLv1G++XqxI00hlFuWS5mUGJVcXotT1mw20rVeUILc7Qe3mLvbMGgfyKf4A7Qa5SSZ4bDeDTJYaFxyiQ281hMzDuPQKBgQD6AiL/Na2/uPH4CG6juwpjYvYVUcjK+7gbRwf3wWsWMpk90Z4ju2iUiP5c1J/oK9P+1T3PIr6M4Xjza8JJj+r9KC/PVB0gBv6vVM96cDpKUEy/UMpcn/T81vqj/Z+WEOODU8Ms6NiTTm+u9ldvpCjbu0u8M+9c0JeIyadJvSTFEQKBgQCsxmFyM3nq8YfpgU2qqNjfBeRH3faSVUy+nj1a/YZYjKS+A/i1BCnYUImeBVNN6chNV342ggvY4xxruDiU9Vcw8wd58O09Oi8BEIFSP6upL6cebUI6Fjo3xlegLJRiwV6INkNTJOYM5hD/mSxUACwXQFfkJipBINXBIgraWD1RLQKBgQCj49axWq0F6+WjZVOyPaD3uh37p9trRUxRhWTxw3fB23WdktaKMgbCqHOmwzP4bRLSEVQtf2dOz1gMqu14b8HqJvgAf/F/11YJ9hz09LEhmjZVjE68HZfqT7uK2W5OX8/lfXmK7TFcj6SjG5YB96lZMhTZ0WnufEd6QkdKDZYXIQKBgQD9GDTcIMbFwbEaKHnfZaTD3f876EGRgsgrCxwdEk7LBCRPwWo7yI929M4psIlpNwNeiyjBkBunWIVkpznp6qPtJqagIPUYesU4f5v6/okq5wcpaNKSkWbIvWVLaLGOiA1aeGJtbpMpyClbSr52puHpRRdvAiIEQ74yYh0JX8q96g==") +// .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB") +// .setDomain("https://openapi.alipay.com/gateway.do"); +// return new AlipayConfigDto() +// .setAppId("2021004145625815") +// .setPrivateKey("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCAjDBuS8K/IJb9ui+KuNm/sTUdEiaji4BNpZ92avO1N5JpNlGmac6ec4p3tNFT950sBLcQkClcUpQxUHQzAT6DYNNXOKyvfI/EmcqwCw6PaMNLs/8cV//J2WWZBUhLaOsjKurpm9/3W5MnTh4BGxIfBoeBMA8f8K3BgKdmyKtvIEV2h2cyjsMskdn+g6oNZcmWcms0pvpPHyH46mRaGFhpp0v19wX3WsamGldh1L2VntmaDN3C2XbSrXv90XYp5bEUqwTbLwXpMAlzTibF56d/iqv9oYi8cpAKougUFLOymnbutLNs2tLrEDSFwHcmG2/wbZHybZyYcIgFgv4arf+tAgMBAAECggEAf7hKKlw1y6Z6vvAtalxNZUuRZSfyog3p1bwYWxTavZPQcZ7Zs0lvVDmiO1u5m/7q96BbryY9IhCeUv0H5uF2lhwu/3s9AEL3qTPQkeb6eXxyhhX6A9RfPdM1Qbtg4CQHdHKg4qjP9znSVHwmDZ0y/QaEvdPdQzPjv92u9c2tn4N4x6XyBYcU5gzxiJNnIugCmBgcJo/3H2fgV+XXEhORPvy5of9b4oATHEaLS/8dAS2wuOjhzaGS4MXp3VkXn3XaYjwSzaL03qYWA+xm+aO5sJv8bmqZW7sNVck5o3sPo7cQ4VkBFVzyrRdmJcxcSRJ9MsB9JsrhoKI8pgaXrVie4QKBgQDU2vai0lpBIK/0jzRpPNoqdT8lnafnnWni8nU4kfAh+gCLi+HBPhQRT0kv4unQc2q2/gALE7sgZVO00JGY5a3R0orsojPoUSZlpypGW7GGqKy576NHn0nw4o/PdfysT92VWgt1hlfTf6qfCDhfE9APU+RGvlSWXcT8nxVel3iUaQKBgQCamoJN6+4v+chJvL2nqV8NVVRLp0vDIHxs1QOtKwUodx8Qp1D6CJYtavCXn8aNUFVNQJPJ7TQPpJjXP2rI4SN01weDwx+I+wh8PBGHV6/234R+6TvFgY1PrYgCdfNP4i/E7B4uyEhAxdU73PB8qkqRAeJGok05p7oG71KCOBiYpQKBgEZfGflcuDAeAW5GRhqg3rP4zWa/R7qgZVh9tll8jjp9b96y4XFE99d9MgId8BVVgyt6sEL5Q/2C4ni+F9TH4n6jMADp42VkJuCmsqhOOlP9whU67+2G8Sgtj0QUivPg964f9ffl8XVgGOW5DwIIB9p5btggptCLscufQK5kP545AoGADBvf6tR4wl8w9b2HqTMV08iEIqzGvVC1Dh0c/Zop/EJgN4CzUfIMOSBwGaAVAApzs+pD6QPgGP2OTwWTioo/qa4R05sbxDHNN1XJFa2jhZV6HiqMWOrNs5jm1zJ/zRjtHuJTdtyO9CvKiLbESy9XScY4/8lEfSiK5HIoJzTXkFUCgYAkYkvkW6psJpWj05XWq44UN0n6QOU/Igl35Um/iuOMVhsTmIt09STQVTuzJzfH82+sCqoRsD1blE5unKNUC1DK77aNKTv3Z0dxN9R7FAyfZRiYQXTrbBPBqWjay6FCNxn8e8UsJN4Z6FIV2LGlQI114krSap1MALKLVvnld0NaUQ==") +// .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB") +// .setDomain("https://openapi.alipay.com/gateway.do"); + } + + /** + * 直付通商户配置 + */ + public static AlipayConfigDto getDefaultConfig() { +// return new AlipayConfigDto() +// .setAppId("2021006121646825") +// .setPrivateKey("MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCz3FkdffxZibdwis9W7eOW1dEjGAbSvRDL2ikfeCIW5KZNoIjUqxI0mIoUlLRRKO71QLHZS1Vb2aJp8jeOAqIPa8e76HTneQEzk3FGA8gpraSGvbadHWzvxnmYKsts1TBiEZQL82ySJXhQTJvZ6jyDM7s6wHHUnrH+Qi29QpppQ1sxsoJeCtajUgRg3btD6XbBcyFAX3pzM56Kw9eaIjZoD8WToZOM/Y3sqNL2uo8lLqcIpTrI7Pq5ZOspmBQ+t8v3rS9IdDZZMvd0trzS67AXwHz8rKPBT/lL1A4iHnXCHUvktusX1fPs3/RGY/a7PIddaBLnfY0GcueE16K7QcrXAgMBAAECggEAbSdT2eckp75BWoaTcGEs1tRqeM7TDT/6moyKmnOQ1K3tE31SrSYpBUxxuC3LBNo/sw2RIZtrcTOyMnPyLTgB3DP/4lUf5X51MTTQ8LnI1ypvh+pIki9Sdm3QS33lOOZk149tdpdDk6ozyx/DEcvq74EMpoo2SuAIi5LkKVDrXuehvGA/WeXtpmuPgqRFdIA+JBlA3knHk5XEQY/k2Y31gq5oCwNL9iT9OAZqVkukE6EnvCXE9t2rAV4/snYilaf/UaO+ktgEwSbPBQ8YKlovDAarMBbGtgr6E174A9djlPyR+W/fgx8rlTwSWtieb9MkO8LN3KSxgVs0kY5U8OHg8QKBgQDljJq9kTFHare+W/fAXdUy3tJprfNQCAii6s+GuDfTQiviVQDtWmdtHAN+xU3to7MepvVhwHsqtQnZXKTtZuwwxn82FNl7A5RYD3GVFW+wG6AsGLIdESrWxySoL6Kx8QmNpMEVg8acT/ywzW/RnUXS5vU7GIi8GA0vtyBo24R9KQKBgQDIlf/R9+iNk9oXlbB/k4um9eVvBBS7l5cx4E5Id5Dpp4kGZfPZEa7oDsEUstZZM9mgQLJK/noNWbcf0+BohCR5ux7SC12qIoxwN3k4BzTDqrS8BzFuVVp5PELUsf/uCbRn05iMzpiDUhj3Vde04wvjHYIobfKlZO2HeSWXCpUH/wKBgQC8wSuU6ck90pEY5QMKmZ3wYK1g3PsQOirv3Gmde+nbu7PePsuuYQJfBAQTwCZeXJezgtKP+PjOm2Nn6vhrhpB9YxvD2s0ijET1TG23i5L1myHQYNZFdJJnXgXUjqcX7v5ODMYA7QTqEBPXRnbGRK7fx66rU3dMQ/LD46+wyaFeUQKBgA4QTk53dkuu6SSsLyLSwoDjTsHY5Gc+urAZjQORtoxbXcUgEtfOYJgOqMT9wP+iHgkZYCbX7tDO0IMfxOUvFqueTgvmFhwergAUM6CVCMMLTf689l9JBr3nVrw4+rvC3G5HLLP6rEDQ2cVFtIkPPj8fS4fwJYopKGpOOS9843QbAoGBAMoHH8LqoZ50FLsospx/hJe24Cd8wCgQTXSa/hMqNZ1999JDhftMt7R0ZdB1he2LReACe0K9ntBU4H4u225zZ3wZlyOfoyerAHuLK/ysNlgIIzblZlOxbBJ64Kul8leXzlYy3tOZuZ997KqBcWPCE3LUBBNvM6E3blJUnlmJAVoi") +// .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB") +// .setDomain("https://openapi.alipay.com"); + return new AlipayConfigDto() + .setAppId("2021004174605036") + .setPrivateKey("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD48HaUoV7OH7os+9L01kHgxzwIhJick4OkFq4aHsntsXEJ J3gedhuEZtV8oHKZ30DPW12IJ4S8NXtpr8OWaqrAPFonf4wVaRY1d0yIAea57kfLEn9oOEEy4FzARgMNDkyxC+/3OUdGbLHpTjfVX3gusXsEhUDy1/WewihAkoNYF37+W3W/uVLzeWoPq0EcUbRv/G/t/p6gL69ltsMAiVFG4Q/Yk24YAN6lYgBPNLXUEwQ1Q+T+1omjfavHgvarKOp33z3JOUH+aGOmDsJ5Y9gyGtJzOCipAd8Zcv+T1ygsEzZYO1/gzcbPnfO1ShqStCHzssuw8FBVx2JdfQKXKMMNAgMBAAECggEAVTrO/pg5Q00titU1Jspsh67u6OOs9H605Ws2dI7yB8VmtAGlaJh7V1t14FN2qSP8poHbhhAxq9aLyGV7C3a9u09udnN+3J28EtYjh7VO732bavWMVXxdJjQWzWWrCb9JlpxFrlkYBA6W4w/6ob0sAqCVQ7jzwbEa0R4cde8ztOa5nysKSfr4YTSs0gqvoiC6fmg8eiRJraEQBoYz9VkKFtOhhh/4w5FhVcYQ2gQvZ3kK3QVuD1eJIQKlCtz8qaox9lXKDiZT4SCmnKshdUL0u5TYIcYeBjZmhJz0Q50KHcpZrCs5y7I0+vRBH3hU+TKSQt7ureymwhbwWMHScLV2gQKBgQD+58SHXhr5M8NGagAmTdsgmCnNv2kOYMd4STyPMY10SVwCv1Bk808ZuP+7e558J1b5/OuDLI5dLq6xrZ/1wLv1G++XqxI00hlFuWS5mUGJVcXotT1mw20rVeUILc7Qe3mLvbMGgfyKf4A7Qa5SSZ4bDeDTJYaFxyiQ281hMzDuPQKBgQD6AiL/Na2/uPH4CG6juwpjYvYVUcjK+7gbRwf3wWsWMpk90Z4ju2iUiP5c1J/oK9P+1T3PIr6M4Xjza8JJj+r9KC/PVB0gBv6vVM96cDpKUEy/UMpcn/T81vqj/Z+WEOODU8Ms6NiTTm+u9ldvpCjbu0u8M+9c0JeIyadJvSTFEQKBgQCsxmFyM3nq8YfpgU2qqNjfBeRH3faSVUy+nj1a/YZYjKS+A/i1BCnYUImeBVNN6chNV342ggvY4xxruDiU9Vcw8wd58O09Oi8BEIFSP6upL6cebUI6Fjo3xlegLJRiwV6INkNTJOYM5hD/mSxUACwXQFfkJipBINXBIgraWD1RLQKBgQCj49axWq0F6+WjZVOyPaD3uh37p9trRUxRhWTxw3fB23WdktaKMgbCqHOmwzP4bRLSEVQtf2dOz1gMqu14b8HqJvgAf/F/11YJ9hz09LEhmjZVjE68HZfqT7uK2W5OX8/lfXmK7TFcj6SjG5YB96lZMhTZ0WnufEd6QkdKDZYXIQKBgQD9GDTcIMbFwbEaKHnfZaTD3f876EGRgsgrCxwdEk7LBCRPwWo7yI929M4psIlpNwNeiyjBkBunWIVkpznp6qPtJqagIPUYesU4f5v6/okq5wcpaNKSkWbIvWVLaLGOiA1aeGJtbpMpyClbSr52puHpRRdvAiIEQ74yYh0JX8q96g==") + .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB") + .setDomain("https://openapi.alipay.com/gateway.do"); +// return new AlipayConfigDto() +// .setAppId("2021004145625815") +// .setPrivateKey("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCAjDBuS8K/IJb9ui+KuNm/sTUdEiaji4BNpZ92avO1N5JpNlGmac6ec4p3tNFT950sBLcQkClcUpQxUHQzAT6DYNNXOKyvfI/EmcqwCw6PaMNLs/8cV//J2WWZBUhLaOsjKurpm9/3W5MnTh4BGxIfBoeBMA8f8K3BgKdmyKtvIEV2h2cyjsMskdn+g6oNZcmWcms0pvpPHyH46mRaGFhpp0v19wX3WsamGldh1L2VntmaDN3C2XbSrXv90XYp5bEUqwTbLwXpMAlzTibF56d/iqv9oYi8cpAKougUFLOymnbutLNs2tLrEDSFwHcmG2/wbZHybZyYcIgFgv4arf+tAgMBAAECggEAf7hKKlw1y6Z6vvAtalxNZUuRZSfyog3p1bwYWxTavZPQcZ7Zs0lvVDmiO1u5m/7q96BbryY9IhCeUv0H5uF2lhwu/3s9AEL3qTPQkeb6eXxyhhX6A9RfPdM1Qbtg4CQHdHKg4qjP9znSVHwmDZ0y/QaEvdPdQzPjv92u9c2tn4N4x6XyBYcU5gzxiJNnIugCmBgcJo/3H2fgV+XXEhORPvy5of9b4oATHEaLS/8dAS2wuOjhzaGS4MXp3VkXn3XaYjwSzaL03qYWA+xm+aO5sJv8bmqZW7sNVck5o3sPo7cQ4VkBFVzyrRdmJcxcSRJ9MsB9JsrhoKI8pgaXrVie4QKBgQDU2vai0lpBIK/0jzRpPNoqdT8lnafnnWni8nU4kfAh+gCLi+HBPhQRT0kv4unQc2q2/gALE7sgZVO00JGY5a3R0orsojPoUSZlpypGW7GGqKy576NHn0nw4o/PdfysT92VWgt1hlfTf6qfCDhfE9APU+RGvlSWXcT8nxVel3iUaQKBgQCamoJN6+4v+chJvL2nqV8NVVRLp0vDIHxs1QOtKwUodx8Qp1D6CJYtavCXn8aNUFVNQJPJ7TQPpJjXP2rI4SN01weDwx+I+wh8PBGHV6/234R+6TvFgY1PrYgCdfNP4i/E7B4uyEhAxdU73PB8qkqRAeJGok05p7oG71KCOBiYpQKBgEZfGflcuDAeAW5GRhqg3rP4zWa/R7qgZVh9tll8jjp9b96y4XFE99d9MgId8BVVgyt6sEL5Q/2C4ni+F9TH4n6jMADp42VkJuCmsqhOOlP9whU67+2G8Sgtj0QUivPg964f9ffl8XVgGOW5DwIIB9p5btggptCLscufQK5kP545AoGADBvf6tR4wl8w9b2HqTMV08iEIqzGvVC1Dh0c/Zop/EJgN4CzUfIMOSBwGaAVAApzs+pD6QPgGP2OTwWTioo/qa4R05sbxDHNN1XJFa2jhZV6HiqMWOrNs5jm1zJ/zRjtHuJTdtyO9CvKiLbESy9XScY4/8lEfSiK5HIoJzTXkFUCgYAkYkvkW6psJpWj05XWq44UN0n6QOU/Igl35Um/iuOMVhsTmIt09STQVTuzJzfH82+sCqoRsD1blE5unKNUC1DK77aNKTv3Z0dxN9R7FAyfZRiYQXTrbBPBqWjay6FCNxn8e8UsJN4Z6FIV2LGlQI114krSap1MALKLVvnld0NaUQ==") +// .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB") +// .setDomain("https://openapi.alipay.com/gateway.do"); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatAesUtil.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatAesUtil.java new file mode 100644 index 000000000..4e83075cd --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatAesUtil.java @@ -0,0 +1,60 @@ +package com.czg.third.wechat; + +import javax.crypto.AEADBadTagException; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +/** + * 微信参数 AES 解密 + * @author GYJoker + */ +public class WechatAesUtil { + static final int KEY_LENGTH_BYTE = 32; + static final int TAG_LENGTH_BIT = 128; + private final byte[] aesKey; + public WechatAesUtil(byte[] key) { + if (key.length != KEY_LENGTH_BYTE) { + throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); + } + this.aesKey = key; + } + public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) + throws GeneralSecurityException { + try { + + // 调试信息:打印参数信息 + System.out.println("associatedData长度: " + (associatedData != null ? associatedData.length : 0)); + System.out.println("nonce长度: " + (nonce != null ? nonce.length : 0)); + System.out.println("ciphertext长度: " + ciphertext.length()); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); + GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + + // 处理 associatedData,确保使用空数组而非 null + cipher.updateAAD(associatedData != null ? associatedData : new byte[0]); + + byte[] decodedCiphertext = Base64.getDecoder().decode(ciphertext); + System.out.println("解码后密文长度: " + decodedCiphertext.length); + + byte[] plaintext = cipher.doFinal(decodedCiphertext); + return new String(plaintext, StandardCharsets.UTF_8); + } catch (AEADBadTagException e) { + // 捕获标签不匹配异常,提供更详细的错误信息 + throw new GeneralSecurityException("解密失败: 标签不匹配,可能是密钥错误、数据被篡改或参数不匹配", e); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException("无效的密钥或参数", e); + } + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatConfig.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatConfig.java new file mode 100644 index 000000000..9dc172994 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatConfig.java @@ -0,0 +1,50 @@ +package com.czg.third.wechat; + +import com.czg.third.wechat.dto.config.WechatPayConfigDto; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAPublicKeyConfig; +import com.wechat.pay.java.service.file.FileUploadService; + +/** + * 微信支付 配置 + * + * @author yjjie + * @date 2025/12/26 09:33 + */ +public class WechatConfig { + + /** + * 获取微信支付配置 + * @param dto 微信支付配置 + * @return 微信支付配置 + */ + public static Config getRsaConfig(WechatPayConfigDto dto) { + if (dto == null) { + dto = WechatPayConfigDto.getDefaultConfig(); + } + return new RSAPublicKeyConfig.Builder() + .merchantId(dto.getMerchantId()) + .privateKey(dto.getPrivateKey()) + .publicKey(dto.getPublicKey()) + .publicKeyId(dto.getPublicKeyId()) + .merchantSerialNumber(dto.getSerialNumber()) + .apiV3Key(dto.getApiV3Key()) + .build(); + } + + public static FileUploadService getFileUploadServiceByConfig(Config config) { + return new FileUploadService.Builder() + .config(config) + .build(); + } + + /** + * 获取文件上传服务 + * @param dto 微信支付配置 + * @return 文件上传服务 + */ + public static FileUploadService getFileUploadService(WechatPayConfigDto dto) { + return getFileUploadServiceByConfig(getRsaConfig(dto)); + } + +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEncrypt.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEncrypt.java new file mode 100644 index 000000000..24ca2a939 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEncrypt.java @@ -0,0 +1,77 @@ +package com.czg.third.wechat; + +import lombok.extern.slf4j.Slf4j; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.Base64; + +/** + * 微信支付加解密 + * @author yjjie + * @date 2025/12/26 10:58 + */ +@Slf4j +public class WechatEncrypt { + + public static String rsaEncryptOaep(String message, PublicKey publicKey) { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] data = message.getBytes(StandardCharsets.UTF_8); + byte[] cipherData = cipher.doFinal(data); + return Base64.getEncoder().encodeToString(cipherData); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + log.error("当前Java环境不支持RSA v1.5/OAEP"); + } catch (InvalidKeyException e) { + log.error("无效的证书"); + } catch (IllegalBlockSizeException | BadPaddingException e) { + log.error("加密原串的长度不能超过214字节"); + } + return ""; + } + + public static String getFileBytesSha256(byte[] input) throws NoSuchAlgorithmException { + return bytesToHex(calculateSha256(input)); + } + + /** + * 核心方法:计算字节数组的 SHA-256 哈希值 + * @param input 待计算的字节数组 + * @return SHA-256 哈希值(32 字节的字节数组) + */ + private static byte[] calculateSha256(byte[] input) throws NoSuchAlgorithmException { + // 获取 SHA-256 消息摘要实例(Java 21 完全兼容) + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + // 计算哈希值 + return digest.digest(input); + } + + /** + * 辅助方法:将字节数组转换为十六进制字符串(SHA-256 结果常用格式) + * @param bytes 待转换的字节数组 + * @return 十六进制字符串(SHA-256 对应 64 位字符串) + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + // Java 21 可使用 var 简化声明 + for (var b : bytes) { + // 将字节转换为两位十六进制数,不足补 0 + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEntryManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEntryManager.java new file mode 100644 index 000000000..74e647650 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEntryManager.java @@ -0,0 +1,444 @@ +package com.czg.third.wechat; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.czg.PayCst; +import com.czg.dto.req.*; +import com.czg.dto.resp.EntryThirdRespDto; +import com.czg.dto.resp.QueryStatusResp; +import com.czg.dto.resp.WechatBankBranchRespDto; +import com.czg.exception.CzgException; +import com.czg.third.wechat.dto.config.WechatPayConfigDto; +import com.czg.third.wechat.dto.req.entry.*; +import com.czg.third.wechat.dto.req.entry.business.WechatEntryBusinessReqDto; +import com.czg.third.wechat.dto.req.entry.business.WechatEntryIdentityReqDto; +import com.czg.third.wechat.dto.req.entry.business.WechatEntryLicenseReqDto; +import com.czg.third.wechat.dto.req.entry.business.sales.*; +import com.czg.third.wechat.dto.req.entry.id.WechatEntryIdCardReqDto; +import com.czg.third.wechat.dto.resp.WechatAuditDetail; +import com.czg.third.wechat.dto.resp.WechatQueryStateResp; +import com.czg.utils.UploadFileUtil; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.cipher.PrivacyEncryptor; +import com.wechat.pay.java.core.exception.MalformedMessageException; +import com.wechat.pay.java.service.file.FileUploadService; +import com.wechat.pay.java.service.file.model.FileUploadResponse; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 微信支付进件 管理 + * 参考地址 ... + * + * @author yjjie + * @date 2025/12/26 10:57 + */ +@Slf4j +public class WechatEntryManager { + + /** + * 查询商户进件状态 + * + * @param configDto 配置信息 + * @param applyId 自系统的商户编号 + * @return 进件状态 + */ + public static QueryStatusResp queryMerchantEntryStatus(WechatPayConfigDto configDto, String applyId) { + QueryStatusResp queryStatusResp = new QueryStatusResp(); + queryStatusResp.setPlatform(PayCst.Platform.WECHAT); + queryStatusResp.setMerchantCode(applyId); + + String resp = WechatReqUtils.getReq(configDto, "/v3/applyment4sub/applyment/applyment_id/" + applyId, Map.of()); + JSONObject object = JSONObject.parseObject(resp); + if (resp.contains("message")) { + log.error("微信查询进件状态失败:{}", resp); + queryStatusResp.setFailReason(object.getString("message")); + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + return queryStatusResp; + } + log.info("微信查询进件状态:{}", resp); + WechatQueryStateResp stateResp = JSONObject.parseObject(resp, WechatQueryStateResp.class); + + queryStatusResp.setApplyId(stateResp.getApplyId()); + switch (stateResp.getApplyState()) { + case "APPLYMENT_STATE_EDITTING" -> { + queryStatusResp.setStatus(PayCst.EntryStatus.WAIT); + queryStatusResp.setFailReason(""); + } + case "APPLYMENT_STATE_AUDITING" -> { + queryStatusResp.setStatus(PayCst.EntryStatus.AUDIT); + queryStatusResp.setFailReason(""); + queryStatusResp.setSignUrl(stateResp.getSignUrl()); + if (StrUtil.isNotBlank(queryStatusResp.getSignUrl())) { + queryStatusResp.setStatus(PayCst.EntryStatus.SIGN); + } + } + case "APPLYMENT_STATE_REJECTED" -> { + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + StringBuilder msg = new StringBuilder(); + for (WechatAuditDetail auditDetail : stateResp.getAuditDetail()) { + msg.append(auditDetail.getRejectReason()).append(","); + } + queryStatusResp.setFailReason(msg.toString()); + } + case "APPLYMENT_STATE_TO_BE_CONFIRMED", "APPLYMENT_STATE_TO_BE_SIGNED" -> { + queryStatusResp.setStatus(PayCst.EntryStatus.SIGN); + queryStatusResp.setFailReason(""); + queryStatusResp.setSignUrl(stateResp.getSignUrl()); + } + case "APPLYMENT_STATE_SIGNING", "APPLYMENT_STATE_FINISHED" -> { + queryStatusResp.setStatus(PayCst.EntryStatus.FINISH); + queryStatusResp.setFailReason(""); + queryStatusResp.setThirdMerchantId(stateResp.getSubMchId()); + } + } + + return queryStatusResp; + } + + /** + * 进件商户 + * + * @param configDto 配置信息 + * @param reqDto 请求信息 + */ + public static EntryThirdRespDto entryMerchant(WechatPayConfigDto configDto, AggregateMerchantDto reqDto) { + EntryThirdRespDto respDto = new EntryThirdRespDto() + .setPlatform(PayCst.Platform.WECHAT); + try { + WechatEntryReqDto entryReqDto = buildEntryParams(configDto, reqDto); + log.info("微信进件参数:{}", JSONObject.toJSONString(entryReqDto)); + + String params = JSONObject.toJSONString(entryReqDto, JSONWriter.Feature.IgnoreEmpty); + String respBody = WechatReqUtils.postReq(configDto, "/v3/applyment4sub/applyment/", params); + JSONObject object = JSONObject.parseObject(respBody); + log.info("微信进件结果:{}", respBody); + if (respBody.contains("applyment_id")) { + respDto.setStatus(PayCst.EntryStatus.INIT); + respDto.setEntryId(object.getString("applyment_id")); + respDto.setErrorMsg(""); + } else { + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setEntryId(""); + respDto.setErrorMsg(object.getString("message")); + } + } catch (Exception e) { + log.error("微信进件报错:{}", e.getMessage()); + respDto.setStatus(PayCst.EntryStatus.REJECTED); + respDto.setEntryId(""); + respDto.setErrorMsg(e.getMessage()); + } + + return respDto; + } + + public static JSONObject queryBankList(WechatPayConfigDto configDto, Integer offset, Integer limit) { + String resp = WechatReqUtils.getReq(configDto, "/v3/capital/capitallhh/banks/corporate-banking", Map.of("offset", offset, "limit", limit)); + log.info("微信查询银行列表:{}", resp); + return JSONObject.parseObject(resp); + } + + public static WechatBankBranchRespDto queryBankBranchList(WechatPayConfigDto configDto, String bankAliceCode, String cityCode, Integer offset, Integer limit) { + String resp = WechatReqUtils.getReq(configDto, "/v3/capital/capitallhh/banks/" + bankAliceCode + "/branches", Map.of("city_code", cityCode, "offset", offset, "limit", limit)); + log.info("微信查询银行支行列表:{}", resp); + return JSONObject.parseObject(resp, WechatBankBranchRespDto.class); + } + + public static JSONObject queryProvinceList(WechatPayConfigDto configDto) { + String resp = WechatReqUtils.getReq(configDto, "/v3/capital/capitallhh/areas/provinces", Map.of()); + log.info("微信查询省份列表:{}", resp); + return JSONObject.parseObject(resp); + } + + public static JSONObject queryCityList(WechatPayConfigDto configDto, String provinceCode) { + String resp = WechatReqUtils.getReq(configDto, "/v3/capital/capitallhh/areas/provinces/" + provinceCode + "/cities", Map.of()); + log.info("微信查询城市列表:{}", resp); + return JSONObject.parseObject(resp); + } + + + /** + * 上传图片 + * + * @param url 图片URL + * @return 图片ID + */ + public static String uploadImage(WechatPayConfigDto configDto, String url) { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + // 校验入参 + if (url == null || url.trim().isEmpty()) { + log.error("上传图片失败:URL参数为空"); + return ""; + } + + FileUploadService service = WechatConfig.getFileUploadService(configDto); + + try { + // 获取图片字节数组 + byte[] bytes = UploadFileUtil.downloadImage(url); + if (bytes.length == 0) { + log.error("下载的图片内容为空,URL:{}", url); + return ""; + } + + // 2. 计算SHA256 + String sha256Hex = WechatEncrypt.getFileBytesSha256(bytes); + + // 3. 构造元数据(从URL提取文件名,或自定义) + JSONObject meta = new JSONObject(); + meta.put("sha256", sha256Hex); + // 从URL提取文件名,若提取失败则使用默认名 + String fileName = UploadFileUtil.extractFileNameFromUrl(url); + meta.put("filename", fileName); + + // 4. 上传图片到微信接口 + FileUploadResponse uploadResponse = service.uploadImage( + configDto.getDomain() + "/v3/merchant/media/upload", + meta.toJSONString(), + fileName, + bytes + ); + + return uploadResponse.getMediaId(); + } catch (MalformedMessageException e) { + log.error("微信上传图片报错1,URL:{}", url); + } catch (Exception e) { + log.error("微信上传图片报错2,URL:{}", url); + } + return ""; + } + + /** + * 构建进件参数 + * + * @param reqDto 请求参数 + * @return 进件参数 + */ + private static WechatEntryReqDto buildEntryParams(WechatPayConfigDto configDto, AggregateMerchantDto reqDto) { + WechatEntryReqDto entryParams = new WechatEntryReqDto(); + + Config config = WechatConfig.getRsaConfig(configDto); + PrivacyEncryptor encryptor = config.createEncryptor(); + + entryParams.setBusinessCode(reqDto.getMerchantCode()); + + MerchantBaseInfoDto baseInfo = reqDto.getMerchantBaseInfo(); + LegalPersonInfoDto legalPersonInfo = reqDto.getLegalPersonInfo(); + BusinessLicenceInfoDto businessLicenceInfo = reqDto.getBusinessLicenceInfo(); + StoreInfoDto storeInfo = reqDto.getStoreInfo(); + SettlementInfoDto settlementInfo = reqDto.getSettlementInfo(); + + WechatEntryContactReqDto contactInfo = new WechatEntryContactReqDto(); + contactInfo.setContactType(baseInfo.getContactPersonType()); + // 默认都使用身份证 暂不支持其他证件 + contactInfo.setContactIdDocCopy("IDENTIFICATION_TYPE_IDCARD"); + if (PayCst.ContactPersonType.SUPER.equals(baseInfo.getContactPersonType())) { + contactInfo.setContactName(encryptor.encrypt(baseInfo.getContactName())); + contactInfo.setContactIdNumber(encryptor.encrypt(baseInfo.getContactPersonId())); + contactInfo.setContactIdDocCopy(baseInfo.getContactIdCardFrontPic().getWechatId()); + contactInfo.setContactIdDocCopyBack(baseInfo.getContactIdCardBackPic().getWechatId()); + contactInfo.setContactPeriodBegin(baseInfo.getContactPersonIdStartDate()); + contactInfo.setContactPeriodEnd(PayCst.LONG_TERM_DATE.equals(baseInfo.getContactPersonIdEndDate()) ? "长期" : baseInfo.getContactPersonIdEndDate()); + contactInfo.setMobilePhone(encryptor.encrypt(baseInfo.getContactPhone())); + contactInfo.setContactEmail(encryptor.encrypt(baseInfo.getContactEmail())); + } else if (PayCst.ContactPersonType.LEGAL.equals(baseInfo.getContactPersonType())) { + contactInfo.setContactName(encryptor.encrypt(legalPersonInfo.getLegalPersonName())); + contactInfo.setContactIdNumber(encryptor.encrypt(legalPersonInfo.getLegalPersonId())); + contactInfo.setContactIdDocCopy(legalPersonInfo.getIdCardFrontPic().getWechatId()); + contactInfo.setContactIdDocCopyBack(legalPersonInfo.getIdCardBackPic().getWechatId()); + contactInfo.setContactPeriodBegin(legalPersonInfo.getLegalIdPersonStartDate()); + contactInfo.setContactPeriodEnd(PayCst.LONG_TERM_DATE.equals(legalPersonInfo.getLegalPersonIdEndDate()) ? "长期" : legalPersonInfo.getLegalPersonIdEndDate()); + contactInfo.setMobilePhone(encryptor.encrypt(legalPersonInfo.getLegalPersonPhone())); + contactInfo.setContactEmail(encryptor.encrypt(legalPersonInfo.getLegalPersonEmail())); + } else { + throw new CzgException("联系人类型错误"); + } + entryParams.setContactInfo(contactInfo); + + WechatEntrySubjectReqDto subjectInfo = new WechatEntrySubjectReqDto(); + if ("0".equals(baseInfo.getUserType())) { + subjectInfo.setSubjectType("SUBJECT_TYPE_INDIVIDUAL"); + } else if ("1".equals(baseInfo.getUserType())) { + switch (baseInfo.getCompanyChildType()) { + case "1" -> subjectInfo.setSubjectType("SUBJECT_TYPE_ENTERPRISE"); + case "2" -> subjectInfo.setSubjectType("SUBJECT_TYPE_INSTITUTIONS"); + case "3" -> subjectInfo.setSubjectType("SUBJECT_TYPE_GOVERNMENT"); + case "4" -> subjectInfo.setSubjectType("SUBJECT_TYPE_OTHERS"); + default -> throw new CzgException("主体类型错误"); + } + } else { + throw new CzgException("用户类型错误"); + } + subjectInfo.setFinanceInstitution(false); + + WechatEntryLicenseReqDto licenseReqDto = new WechatEntryLicenseReqDto(); + licenseReqDto.setLicenseCopy(businessLicenceInfo.getLicensePic().getWechatId()); + licenseReqDto.setLicenseNumber(businessLicenceInfo.getLicenceNo()); + licenseReqDto.setMerchantName(businessLicenceInfo.getLicenceName()); + licenseReqDto.setLegalPerson(legalPersonInfo.getLegalPersonName()); + licenseReqDto.setLicenseAddress(businessLicenceInfo.getRegisteredAddress()); + licenseReqDto.setPeriodBegin(businessLicenceInfo.getLicenceStartDate()); + licenseReqDto.setPeriodEnd(PayCst.LONG_TERM_DATE.equals(businessLicenceInfo.getLicenceEndDate()) ? "长期" : businessLicenceInfo.getLicenceEndDate()); + subjectInfo.setBusinessLicenseInfo(licenseReqDto); + WechatEntryIdentityReqDto identityInfo = new WechatEntryIdentityReqDto(); + identityInfo.setIdHolderType(PayCst.ContactPersonType.LEGAL); + identityInfo.setIdDocType("IDENTIFICATION_TYPE_IDCARD"); + WechatEntryIdCardReqDto idCardInfo = new WechatEntryIdCardReqDto(); + idCardInfo.setIdCardCopy(legalPersonInfo.getIdCardFrontPic().getWechatId()); + idCardInfo.setIdCardNational(legalPersonInfo.getIdCardFrontPic().getWechatId()); + idCardInfo.setIdCardName(encryptor.encrypt(legalPersonInfo.getLegalPersonName())); + idCardInfo.setIdCardNumber(encryptor.encrypt(legalPersonInfo.getLegalPersonId())); + idCardInfo.setIdCardAddress(encryptor.encrypt(legalPersonInfo.getLegalAddress())); + idCardInfo.setCardPeriodBegin(legalPersonInfo.getLegalIdPersonStartDate()); + idCardInfo.setCardPeriodEnd(legalPersonInfo.getLegalPersonIdEndDate()); + identityInfo.setIdCardInfo(idCardInfo); + subjectInfo.setIdentityInfo(identityInfo); + entryParams.setSubjectInfo(subjectInfo); + + WechatEntryBusinessReqDto businessReqInfo = new WechatEntryBusinessReqDto(); + businessReqInfo.setMerchantShortname(baseInfo.getShortName()); + businessReqInfo.setServicePhone(PayCst.ContactPersonType.LEGAL.equals(baseInfo.getContactPersonType()) ? legalPersonInfo.getLegalPersonPhone() : baseInfo.getContactPhone()); + WechatEntrySalesInfoReqDto salesInfo = new WechatEntrySalesInfoReqDto(); + salesInfo.setSalesScenesType(List.of("SALES_SCENES_STORE", "SALES_SCENES_MINI_PROGRAM")); + WechatEntryStoreInfoReqDto bizStoreInfo = new WechatEntryStoreInfoReqDto(); + bizStoreInfo.setBizStoreName(baseInfo.getShortName()); + bizStoreInfo.setBizAddressCode(storeInfo.getMercAreaCode()); + bizStoreInfo.setBizStoreAddress(storeInfo.getBusinessAddress()); + bizStoreInfo.setStoreEntrancePic(storeInfo.getDoorPic().getWechatId()); + bizStoreInfo.setIndoorPic(storeInfo.getInsidePic().getWechatId()); + salesInfo.setBizStoreInfo(bizStoreInfo); + WechatEntryMiniProgramReqDto miniProgramInfo = new WechatEntryMiniProgramReqDto(); + miniProgramInfo.setMiniProgramAppid("wxd88fffa983758a30"); + salesInfo.setMiniProgramInfo(miniProgramInfo); + WechatEntryWebInfoReqDto webInfo = new WechatEntryWebInfoReqDto(); + webInfo.setDomain("https://invoice.sxczgkj.cn/pay/"); + salesInfo.setWebInfo(webInfo); + businessReqInfo.setSalesInfo(salesInfo); + entryParams.setBusinessInfo(businessReqInfo); + + WechatEntrySettleReqDto settlementReqInfo = new WechatEntrySettleReqDto(); + settlementReqInfo.setSettlementId("0".equals(baseInfo.getUserType()) ? "719" : "716"); + settlementReqInfo.setQualificationType("餐饮"); + settlementReqInfo.setActivitiesId("20191030111cff5b5e"); + settlementReqInfo.setActivitiesRate("0.38"); + entryParams.setSettlementInfo(settlementReqInfo); + + WechatEntryBankAccountReqDto bankAccountReqInfo = new WechatEntryBankAccountReqDto(); + bankAccountReqInfo.setBankAccountType("21".equals(settlementInfo.getSettlementCardType()) ? "BANK_ACCOUNT_TYPE_CORPORATE" : "BANK_ACCOUNT_TYPE_PERSONAL"); + bankAccountReqInfo.setAccountName(encryptor.encrypt(settlementInfo.getSettlementName())); + bankAccountReqInfo.setBankAddressCode(settlementInfo.getOpenAccCityId()); + bankAccountReqInfo.setAccountBank(settlementInfo.getBankName()); + bankAccountReqInfo.setBankBranchId(settlementInfo.getBankBranchCode()); + bankAccountReqInfo.setBankName(settlementInfo.getBankBranchName()); + bankAccountReqInfo.setAccountNumber(encryptor.encrypt(settlementInfo.getSettlementCardNo())); + entryParams.setBankAccountInfo(bankAccountReqInfo); + + return entryParams; + } + + public static void main(String[] args) throws IOException { + + +// queryMerchantEntryStatus(null, "20220106000000000001"); + + int offset = 0; + Integer limit = 100; +// JSONObject resp = queryBankList(null, offset, limit); + +// queryProvinceList(null); +// queryCityList(null, "28"); +// queryBankBranchList(null, "1000009547", "931", offset, limit); +// queryBankBranchList(null, "1000009561", "29", offset, limit); + queryBankBranchList(null, "1000005241", "29", offset, limit); + +// uploadImage(null, "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/5/ec5bafd00f96466fb3efe545a058f08b.png"); + +// queryProvinceList(dto); +// queryCityList(dto, "28"); + + +// String string = uploadImage(dto, "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png"); +// log.info("图片上传成功:{}", string); + +// Config config = WechatConfig.getRsaConfig(dto); +// PrivacyEncryptor encryptor = config.createEncryptor(); +// +// WechatEntryReqDto reqDto = new WechatEntryReqDto() +// .setBusinessCode("MER_20231025110010000010000000000001"); +// +// WechatEntryContactReqDto contactInfo = new WechatEntryContactReqDto() +// .setContactType("LEGAL") +// .setContactName(encryptor.encrypt("张三")) +// .setContactIdType("IDCARD") +// .setContactIdNumber(encryptor.encrypt("110101199001011234")) +// .setContactIdDocCopy("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png") +// .setContactIdDocCopyBack("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png") +// .setContactPeriodBegin("2023-10-25") +// .setContactPeriodEnd("2024-10-25") +// .setMobilePhone(encryptor.encrypt("13888888888")) +// .setContactEmail(encryptor.encrypt("123456@qq.com")); +// +// reqDto.setContactInfo(contactInfo); +// +// WechatEntrySubjectReqDto subjectInfo = new WechatEntrySubjectReqDto() +// .setSubjectType("SUBJECT_TYPE_INDIVIDUAL"); +// +// WechatEntryLicenseReqDto licenseInfo = new WechatEntryLicenseReqDto() +// .setLicenseCopy("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png") +// .setLicenseNumber("110101199001011234") +// .setMerchantName("张三商行") +// .setLegalPerson(encryptor.encrypt("张三")) +// .setLicenseAddress("北京") +// .setPeriodBegin("2023-10-25") +// .setPeriodEnd("2024-10-25"); +// subjectInfo.setBusinessLicenseInfo(licenseInfo); +// +// WechatEntryIdentityReqDto identityInfo = new WechatEntryIdentityReqDto() +// .setIdHolderType("LEGAL"); +// subjectInfo.setIdentityInfo(identityInfo); +// +// reqDto.setSubjectInfo(subjectInfo); +// +// WechatEntryBusinessReqDto businessInfo = new WechatEntryBusinessReqDto() +// .setMerchantShortname("张三商行") +// .setServicePhone("13888888888"); +// WechatEntrySalesInfoReqDto salesInfo = new WechatEntrySalesInfoReqDto() +// .setSalesScenesType(List.of("SALES_SCENES_STORE")); +// WechatEntryStoreInfoReqDto storeInfo = new WechatEntryStoreInfoReqDto() +// .setBizStoreName("张三商行") +// .setBizAddressCode("110101") +// .setBizStoreAddress("北京") +// .setStoreEntrancePic(List.of("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png")) +// .setIndoorPic(List.of("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png")); +// salesInfo.setBizStoreInfo(storeInfo); +// businessInfo.setSalesInfo(salesInfo); +// +// reqDto.setBusinessInfo(businessInfo); +// +// WechatEntrySettleReqDto settleInfo = new WechatEntrySettleReqDto() +// .setSettlementId("719") +// .setQualificationType("IDCARD") +// .setQualifications(List.of("110101199001011234")) +// .setActivitiesId("20191030111cff5b5e"); +// +// reqDto.setSettlementInfo(settleInfo); +// +// WechatEntryBankAccountReqDto bankAccountInfo = new WechatEntryBankAccountReqDto() +// .setBankAccountType("BANK_ACCOUNT_TYPE_CORPORATE") +// .setAccountBank("ICBC") +// .setBankName("中国工商银行") +// .setAccountName(encryptor.encrypt("张三")) +// .setAccountNumber(encryptor.encrypt("110101199001011234")); +// +// reqDto.setBankAccountInfo(bankAccountInfo); + +// entryMerchant(dto, reqDto); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java new file mode 100644 index 000000000..85d2b71cd --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java @@ -0,0 +1,385 @@ +package com.czg.third.wechat; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.PayCst; +import com.czg.exception.CzgException; +import com.czg.pay.*; +import com.czg.third.wechat.dto.config.WechatPayConfigDto; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.cipher.Signer; +import com.wechat.pay.java.core.util.NonceUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +/** + * @author yjjie + * @date 2025/12/26 09:15 + */ +@Slf4j +public class WechatPayManager { + + private static final String PAY_SUCCESS = "SUCCESS"; + private static final String BAR_PAY_URL = "https://api.mch.weixin.qq.com/pay/micropay"; + + /** + * jsapi 小程序支付 + * + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map jsapiPay(WechatPayConfigDto configDto, CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + + try { + JSONObject reqData = new JSONObject(); + reqData.put("sp_appid", paramsDto.getSubAppid()); + reqData.put("sp_mchid", configDto.getMerchantId()); + reqData.put("sub_mchid", merchantDTO.getWechatMerchantId()); + reqData.put("description", paramsDto.getSubject()); + reqData.put("out_trade_no", paramsDto.getMchOrderNo()); + reqData.put("notify_url", paramsDto.getNotifyUrl() + "/" + PayCst.Platform.WECHAT); + reqData.put("attach", paramsDto.getExtParam()); + + JSONObject amount = new JSONObject(); + amount.put("total", paramsDto.getAmount()); + reqData.put("amount", amount); + + JSONObject payer = new JSONObject(); + payer.put("sp_openid", paramsDto.getUserId()); + reqData.put("payer", payer); + + String string = WechatReqUtils.postReq(configDto, "/v3/pay/partner/transactions/jsapi", reqData.toJSONString()); + log.info("微信支付请求结果: orderNo = {}, res = {}", paramsDto.getMchOrderNo(), string); + + JSONObject object = JSONObject.parseObject(string); + + long timestamp = Instant.now().getEpochSecond(); + String nonceStr = NonceUtil.createNonce(32); + String packageVal = "prepay_id=" + object.getString("prepay_id"); + String message = + paramsDto.getSubAppid() + "\n" + timestamp + "\n" + nonceStr + "\n" + packageVal + "\n"; + + Config config = WechatConfig.getRsaConfig(configDto); + Signer signer = config.createSigner(); + String sign = signer.sign(message).getSign(); + + JSONObject res = new JSONObject(); + res.put("appId", paramsDto.getSubAppid()); + res.put("timeStamp", String.valueOf(timestamp)); + res.put("nonceStr", nonceStr); + res.put("package", packageVal); + res.put("signType", "RSA"); + res.put("paySign", sign); + log.info("微信支付签名结果: orderNo = {}, res = {}", paramsDto.getMchOrderNo(), res); + return res; + } catch (Exception e) { + log.error("微信支付异常: orderNo = {}, e = {}", paramsDto.getMchOrderNo(), e.getMessage()); + throw new CzgException("微信支付异常"); + } + } + + /** + * 条码支付 + * 用户付款码规则:18位纯数字,前缀以10、11、12、13、14、15开头 + * + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map barPay(WechatPayConfigDto configDto, CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) { + try { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + + String nonceStr = NonceUtil.createNonce(32); + + Map payMap = new HashMap<>(); + Document document = DocumentHelper.createDocument(); + + // 添加根元素 + Element xml = document.addElement("xml"); + + addElementIfNotEmpty(xml, "appid", paramsDto.getSubAppid()); + payMap.put("appid", paramsDto.getSubAppid()); + addElementIfNotEmpty(xml, "mch_id", configDto.getMerchantId()); + payMap.put("mch_id", configDto.getMerchantId()); + addElementIfNotEmpty(xml, "sub_mch_id", merchantDTO.getWechatMerchantId()); + payMap.put("sub_mch_id", merchantDTO.getWechatMerchantId()); + addElementIfNotEmpty(xml, "nonce_str", nonceStr); + payMap.put("nonce_str", nonceStr); + addElementIfNotEmpty(xml, "body", paramsDto.getSubject()); + payMap.put("body", paramsDto.getSubject()); + addElementIfNotEmpty(xml, "out_trade_no", paramsDto.getMchOrderNo()); + payMap.put("out_trade_no", paramsDto.getMchOrderNo()); + addElementIfNotEmpty(xml, "total_fee", paramsDto.getAmount()); + payMap.put("total_fee", paramsDto.getAmount()); + addElementIfNotEmpty(xml, "spbill_create_ip", paramsDto.getClientIp()); + payMap.put("spbill_create_ip", paramsDto.getClientIp()); + addElementIfNotEmpty(xml, "auth_code", paramsDto.getAuthCode()); + payMap.put("auth_code", paramsDto.getAuthCode()); + + if (StrUtil.isNotBlank(paramsDto.getExtParam())) { + addElementIfNotEmpty(xml, "attach", paramsDto.getExtParam()); + payMap.put("attach", paramsDto.getExtParam()); + } + + String sign = signBarPayParam(configDto, payMap); + addElementIfNotEmpty(xml, "sign", sign); + + OutputFormat format = OutputFormat.createPrettyPrint(); + XMLWriter writer = new XMLWriter(System.out, format); + writer.write(document); + + String xmlStr = document.asXML(); + log.info("微信条码支付参数:{}", xmlStr); + + String post = HttpUtil.post(BAR_PAY_URL, xmlStr); + log.info("微信条码支付请求结果: orderNo = {}, res = {}", paramsDto.getMchOrderNo(), post); + + JSONObject res = new JSONObject(); + res.put("orderNo", paramsDto.getMchOrderNo()); + + SAXReader saxReader = new SAXReader(); + Document read = saxReader.read(stringToInputStream(post)); + Element rootElement = read.getRootElement(); + String returnCode = rootElement.elementText("return_code"); + + String errCode = rootElement.elementText("err_code"); + String errCodeDes = rootElement.elementText("err_code_des"); + res.put("message", errCodeDes); + + if (StrUtil.isNotBlank(errCode)) { + if ("SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode) || "USERPAYING".equals(errCode)) { + // 等待结果 + log.info("微信条码支付等待结果: orderNo = {}, err {}", paramsDto.getMchOrderNo(), errCodeDes); + res.put("status", "TRADE_AWAIT"); + return res; + } + + res.put("status", "TRADE_FAIL"); + return res; + } + + if (!PAY_SUCCESS.equals(returnCode)) { + log.error("微信条码支付失败: orderNo = {}, msg = {}", paramsDto.getMchOrderNo(), rootElement.elementText("return_msg")); + res.put("status", "TRADE_FAIL"); + return res; + } + + // 支付成功 + log.info("微信条码支付成功: orderNo = {}, msg = {}", paramsDto.getMchOrderNo(), rootElement.elementText("return_msg")); + res.put("status", "TRADE_SUCCESS"); + return res; + } catch (CzgException e) { + throw e; + } catch (Exception e) { + log.error("微信条码支付异常: orderNo = {}, e = {}", paramsDto.getMchOrderNo(), e.getMessage()); + throw new CzgException("微信支付异常"); + } + } + + /** + * 查询订单 + * + * @param configDto 配置 + * @param orderNo 订单号 + * @param merchantDTO 支付商户信息 + * SUCCESS:支付成功 + * REFUND:转入退款 + * NOTPAY:未支付 + * CLOSED:已关闭 + * REVOKED:已撤销(仅付款码支付会返回) + * USERPAYING:用户支付中(仅付款码支付会返回) + * PAYERROR:支付失败(仅付款码支付会返回) + */ + public static QueryOrderRespDTO queryOrder(WechatPayConfigDto configDto, String orderNo, NativeMerchantDTO merchantDTO) { + try { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + + String resp = WechatReqUtils.getReq(configDto, "/v3/pay/partner/transactions/out-trade-no/" + orderNo, + Map.of("sp_mchid", configDto.getMerchantId(), "sub_mchid", merchantDTO.getWechatMerchantId())); + log.info("微信查询订单,订单号:{}, 响应:{}", orderNo, resp); + + QueryOrderRespDTO queryOrderRespDTO = new QueryOrderRespDTO() + .setOrderNo(orderNo) + .setOriginResp(resp); + + JSONObject object = JSONObject.parseObject(resp); + String tradeState = object.getString("trade_state"); + queryOrderRespDTO.setErrorMsg(object.getString("trade_state_desc")); + if (PAY_SUCCESS.equals(tradeState)) { + queryOrderRespDTO.setStatus("TRADE_SUCCESS"); + return queryOrderRespDTO; + } + + if ("USERPAYING".equals(tradeState)) { + queryOrderRespDTO.setStatus("TRADE_AWAIT"); + return queryOrderRespDTO; + } + + queryOrderRespDTO.setStatus("TRADE_FAIL"); + return queryOrderRespDTO; + } catch (Exception e) { + log.error("微信查询订单异常: orderNo = {}, e = {}", orderNo, e.getMessage()); + throw new CzgException("微信查询订单异常"); + } + } + + /** + * 退款 + * + * @param configDto 配置 + * @param paramsDto 参数 + * SUCCESS: 退款成功 + * CLOSED: 退款关闭 + * PROCESSING: 退款处理中 + * ABNORMAL: 退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往服务商平台-交易中心,手动处理此笔退款 + */ + public static RefundRespDTO refundOrder(WechatPayConfigDto configDto, CzgRefundReq paramsDto, String notifyUrl, NativeMerchantDTO merchantDTO) { + try { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + + JSONObject refundParam = new JSONObject(); + refundParam.put("sub_mchid", merchantDTO.getWechatMerchantId()); + refundParam.put("out_trade_no", paramsDto.getMchOrderNo()); + refundParam.put("out_refund_no", paramsDto.getMchRefundNo()); + refundParam.put("reason", paramsDto.getRefundReason()); + refundParam.put("notify_url", notifyUrl + "/" + PayCst.Platform.WECHAT); + + JSONObject amount = new JSONObject(); + amount.put("total", paramsDto.getOrderTotalAmount()); + amount.put("refund", paramsDto.getRefundAmount()); + amount.put("currency", "CNY"); + refundParam.put("amount", amount); + + String resp = WechatReqUtils.postReq(configDto, "/v3/refund/domestic/refunds", refundParam.toJSONString()); + log.info("微信退款,订单号:{}, 响应:{}", paramsDto.getMchOrderNo(), resp); + + JSONObject object = JSONObject.parseObject(resp); + + String code = object.getString("code"); + String status = object.getString("status"); + if ("INVALID_REQUEST".equalsIgnoreCase(code) || StrUtil.isNotBlank(status)) { + throw new CzgException("微信退款失败:" + object.getString("message")); + } + + RefundRespDTO respDTO = new RefundRespDTO() + .setMerchantRefundNo(object.getString("out_refund_no")) + .setThirdRefundNo(object.getString("refund_id")) + .setRefundTime(object.getString("success_time")) + .setOriginalData(resp) + .setPlatform(PayCst.Platform.WECHAT); + + JSONObject resAmount = object.getJSONObject("amount"); + if (resAmount != null) { + respDTO.setRefundAmount(resAmount.getLong("refund")); + } + + switch (status) { + case "SUCCESS": + case "PROCESSING": + respDTO.setStatus("SUCCESS"); + break; + case "CLOSED": + respDTO.setStatus("CLOSED"); + break; + case "ABNORMAL": + respDTO.setStatus("INIT"); + break; + default: + respDTO.setStatus("FAIL"); + break; + } + + return respDTO; + } catch (CzgException e) { + throw e; + } catch (Exception e) { + log.error("微信退款异常: orderNo = {} ", paramsDto.getMchOrderNo(), e); + throw new CzgException("微信退款异常"); + } + } + + /** + * 将String转换为InputStream + * + * @param str 待转换的字符串 + * @return 转换后的InputStream + */ + private static InputStream stringToInputStream(String str) { + if (str == null || str.isEmpty()) { + return new ByteArrayInputStream(new byte[0]); + } + // 核心:先转字节数组,再封装为ByteArrayInputStream + byte[] byteArray = str.getBytes(StandardCharsets.UTF_8); + return new ByteArrayInputStream(byteArray); + } + + private static String signBarPayParam(WechatPayConfigDto configDto, Map params) { + TreeMap sortedParams = new TreeMap<>(params); + String sortParam = sortedParams.entrySet().stream() + // 将每个键值对转换为 "key=value" 格式的字符串 + .map(entry -> entry.getKey() + "=" + (entry.getValue() == null ? "" : entry.getValue())) + // 使用 "&" 连接所有转换后的字符串 + .collect(Collectors.joining("&")); + log.info("微信条码支付 加密前参数: {}", sortParam); + + String signStr = sortParam + "&key=" + configDto.getApiV2Key(); + return SecureUtil.md5(signStr).toUpperCase(); + } + + /** + * 添加元素,包括空值 + */ + private static void addElementIfNotEmpty(Element parent, String name, Object value) { + Element element = parent.addElement(name); + element.addText(value == null ? "" : String.valueOf(value)); + } + + public static void main(String[] args) { +// barPay(null, new PayParamsDto() +// .setMerchantId("1738216504") +// .setOrderNo("sa101293120sss1") +// .setTitle("1213") +// .setBody("1213") +// .setAmount(1L) +// .setAppId("wxd88fffa983758a30") +// .setClientIp("127.0.0.1") +// .setBarCode("130013153082460022")); + +// Map sss1 = queryOrder(null, "sa101293120sss1", "1738216504"); +// System.out.println(sss1); + +// refundOrder(null, new RefundParamsDto() +// .setMerchantId("1738216504") +// .setOrderNo("sa101293120sss1") +// .setRefundOrderNo("sa101293120sss1") +// .setOrderAmount(1L) +// .setRefundAmount(1L) +// .setRefundReason("测试") +// .setRefundNotifyUrl("https://www.baidu.com")); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java new file mode 100644 index 000000000..8c1ed17cd --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java @@ -0,0 +1,138 @@ +package com.czg.third.wechat; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import com.czg.dto.req.WechatNotifyReqDto; +import com.czg.exception.CzgException; +import com.czg.third.wechat.dto.config.WechatPayConfigDto; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.cipher.Signer; +import com.wechat.pay.java.core.util.NonceUtil; +import lombok.extern.slf4j.Slf4j; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 微信请求工具类 + * + * @author yjjie + * @date 2025/12/29 10:02 + */ +@Slf4j +public class WechatReqUtils { + + public static String postReq(WechatPayConfigDto configDto, String url, String body) { + return req(configDto, url, "POST", body); + } + + public static String getReq(WechatPayConfigDto configDto, String url, Map params) { + url = Arrays.stream(params.entrySet().toArray(new Map.Entry[0])) + .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)) + .collect(Collectors.joining("&", url + "?", "")); + + // 如果最后没有参数,则去掉多余的 "?" + url = url.endsWith("?") ? url.substring(0, url.length() - 1) : url; + + return req(configDto, url, "GET", ""); + } + + private static String req(WechatPayConfigDto configDto, String url, String method, String body) { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + long timestamp = getTimestamp(); + String nonce = getNonceStr(); + String signature = encryptReqParam(configDto, method, url, body, timestamp, nonce); + String authorization = String.format("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\",timestamp=\"%d\",serial_no=\"%s\"", + configDto.getMerchantId(), nonce, signature, timestamp, configDto.getSerialNumber()); + + HttpRequest request = switch (method) { + case "POST" -> HttpUtil.createPost(configDto.getDomain() + url) + .header("Authorization", authorization) + .header("Wechatpay-Serial", configDto.getPublicKeyId()) + .charset(StandardCharsets.UTF_8) + .contentType("application/json") + .body(body); + + case "GET" -> HttpUtil.createGet(configDto.getDomain() + url) + .header("Authorization", authorization) + .header("Wechatpay-Serial", configDto.getPublicKeyId()) + .charset(StandardCharsets.UTF_8) + .contentType("application/json"); + default -> throw new CzgException("不支持的请求方法"); + }; + log.info("微信支付请求:url = {} \nauthorization: {}", request.getUrl(), authorization); + HttpResponse response = request.execute(); + String s = response.body(); + log.info("微信支付请求:\n\turl = {}\n\tmethod: {}\n\tbody: {}\n\tresp: {}", url, method, body, s); + return s; + } + + /** + * 加密请求参数 + * + * @param configDto 配置 + * @param method 请求方法 + * @param url 请求地址 + * @param body 请求报文主体 + * @return 加密后的报文 + *

+ * 签名方法 + * HTTP请求方法\n + * URL\n + * 请求时间戳\n + * 请求随机串\n + * 请求报文主体\n + */ + public static String encryptReqParam(WechatPayConfigDto configDto, String method, String url, String body, long timestamp, String nonce) { + String encryptStr = String.format("%s\n%s\n%d\n%s\n%s\n", + method, url, timestamp, nonce, body); + + Config config = WechatConfig.getRsaConfig(configDto); + Signer signer = config.createSigner(); + String signature = signer.sign(encryptStr).getSign(); + log.info("微信签名 encryptStr = \n{} \nsignature:{}", encryptStr, signature); + return signature; + } + + /** + * 解密微信回调报文 + * + * @param configDto 配置 + * @param reqDto 请求报文 + * @return 解密后的报文 + */ + public static String decryptRespParam(WechatPayConfigDto configDto, WechatNotifyReqDto reqDto) { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + WechatAesUtil aesUtil = new WechatAesUtil(configDto.getApiV3Key().getBytes(StandardCharsets.UTF_8)); + try { + return aesUtil.decryptToString( + reqDto.getResource().getAssociatedData() == null ? new byte[0] : reqDto.getResource().getAssociatedData().getBytes(StandardCharsets.UTF_8), + reqDto.getResource().getNonce().getBytes(StandardCharsets.UTF_8), reqDto.getResource().getCiphertext()); + } catch (Exception e) { + log.error("【微信支付回调】解密失败 {}", reqDto); + throw new CzgException("解密失败"); + } + } + + /** + * 获取随机串 + */ + private static String getNonceStr() { + return NonceUtil.createNonce(32); + } + + /** + * 获取时间戳 + */ + private static long getTimestamp() { + return System.currentTimeMillis() / 1000; + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java new file mode 100644 index 000000000..d1f53173b --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java @@ -0,0 +1,102 @@ +package com.czg.third.wechat.dto.config; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author yjjie + * @date 2025/12/26 09:21 + */ +@Data +@Accessors(chain = true) +public class WechatPayConfigDto { + + /** + * 商户号 + */ + private String merchantId; + + /** + * Api V3密钥 + */ + private String apiV3Key; + + /** + * Api V2 密钥 + */ + private String apiV2Key; + + /** + * 商户证书序列号 + */ + private String serialNumber; + + /** + * 商户私钥 + */ + private String privateKey; + + /** + * 商户公钥 + */ + private String publicKey; + + /** + * 商户公钥 ID + */ + private String publicKeyId; + + /** + * 微信支付域名 + * + */ + private String domain; + + public static WechatPayConfigDto getDefaultConfig() { + return new WechatPayConfigDto() + .setMerchantId("1643779408") + .setApiV3Key("a92baac5eb7a36ed8ec198113e769a03") + .setSerialNumber("4DE9BAC2EA584C3F274F694C9753CA814C4E9BF4") + .setPublicKey(""" + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbeWXEEjBaYtw2OyM+SC + aCEMbryRXi4duKxx3vYG4rUVix+d5/Jz7Khev4Upml9zC+Xxvv/G9bHWAUyolzqD + wefahGurIxr43r4GJVnQ4i5G6BbBVw5d4Vuz0y/9Zd14zmc/EmBpT0Ml26H7tOZl + n1LSbQ4xNFkrRKrNEcExBLxkCd+K5K2TREZznywIi0izbHImvuzM8IneuR51FiqK + pdFnAjTwb126EIj6ECkL6KLCl8RWqpfiX8SFiolSQLs1/w79O0sIUk96X2zWpnoW + rTDFatPif/VEKl3y2dTlxxDxoZtVi48yTDW00OMzVl5D67oX3FVK0KsjHJSCfGlZ + 6wIDAQAB + -----END PUBLIC KEY-----""") + .setPrivateKey(""" + -----BEGIN PRIVATE KEY----- + MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqnAZhTxT572fo + 6wvSr8Rt0vRXg+EFKC6UvUiNE24oocQU5fjedX9KL/+fmoqay/xqIxvxvCNFTs4J + zlMqavSl6bMWCpjvf/Ry82JmN1v2kJEO4lgP8BsEiqlUpObCH8BMAVUOn1j+9q4Q + uZJJcbtRvec2fNweDM8EJp4B7RlUdDbHm86lfcDVp8iini7VjDp6D7aHT+C8A8OT + ugxQIquDec778wVd9r2Sv3+t6rAzFs+n+Zu++2xtFEPhO5N0wjrLHaukl+9crU+1 + lktjDzcPd07SwGZ+A+3BTmW3UCramI3506e/3MWBECB7ge+gM4URAV0nJJCLH/Im + WgEvPMr5AgMBAAECggEBAKv+wraoMWqiVv1tA/froAgbtcJLDranJK8qrXuvmPz0 + yzm+91qvrSgIVFEADUk67swo/R2Vng37nhWWS2Y3jy/rSr2H+2Lp3Z5ATA0/3I3A + onfU/FaC4mvL9CP32KzMdj/CYkccDzSsSCQ+x75MQNXGcTGDDCSDo2kZnpEu73j3 + aqvO1jbqTGWigRfjOIaIhStjQIT8nEm/3mJ4f5dM9M6FMz33mhax8EahSgvdahYB + t45iaGOWw81OJhmry47EvpwjXBl7jtO2HX3LiLbq5Ebcwu1zqDz5NM7ttnnGAqWC + 6y7JN5Vt4wPYrCydiUDe7dj0looffr2yw6MkNfYjLGECgYEA+FAvbEInQEi9YguS + CQtLHngqvYeai66tvyykog9o38KHnPGx2Myf+rn/Hcp7KNRfjd5G34CCNg7KLnrx + YIYh6+2bY3jirzdYUxuNKGbvM4gky/6M/P9zHF/uALKOE02yArdqemf4UxUvrYCc + JdXsAMqvbpdvW1aGgNRB32YCkG0CgYEA8d89vawsCjNCEUh0VWTMoBLFoex3qBMZ + rfzYQeBo6bDCRlAlUVzl+ouBUxSYxP/U8rzeNaRzGUwRLmlGMjyIr58FBlHsg2cR + DlsX3HVCUjpS6sgPXOqakdiLfhMcHZAspislSyVfeS3TbUWiA45+5PuNUq+EZYwl + ESsB1+yfRT0CgYEA0Ct49ksnWM8iZbXJgeeD3FFlk2rBd2TDqEem5W4Bv8T3p+0/ + 6b7yR2HyrGj5gys3yFmWFP1JLESN3xWWkhMhEQcrg+LuN3Iwi8vHNR3GXu892f7W + 96q4OAt8Hf2S+j/igkB99YyANDbIt63gOh/zMF67X/14j5wkOpC3gK+maqkCgYEA + s7nIrPoUt3ejLiiCmTmPe5q3VDzcJP4cZNau8zSHgK6hjZHcSPsYwPWMoWl6o1fe + qoiBLacHB9MoKS58xLOKdcVZ/Ho/ntylJd+2eVCAeY1xM5h5IfgJ5znbXVFh4O3S + 357L1Wzt5qOQqW/GlZH65LevKbPWU4axvHISqpnfN5kCgYEAqiqLuAPu84VSsqsE + GFh25t+1f0JY1sNfilE3/t9AdQeeCFv/5z9KB1kMt3a5ZFeVonsFIvCyaOJjhSkj + 4HCB94vWS7NuN5G9r874lMaPbZYQGwrcVaf265tN7cYYr3gUx1qB6+u+fh/kcft1 + BBmTzhb0zp5k8ngwAkA1g/LK204= + -----END PRIVATE KEY-----""") + .setPublicKeyId("PUB_KEY_ID_0116437794082025111000382377001000") + .setDomain("https://api.mch.weixin.qq.com"); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryAdditionReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryAdditionReqDto.java new file mode 100644 index 000000000..c7c7865e7 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryAdditionReqDto.java @@ -0,0 +1,51 @@ +package com.czg.third.wechat.dto.req.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 微信进件-补充信息 + * + * @author yjjie + * @date 2025/12/26 13:43 + */ +@Data +@Accessors(chain = true) +public class WechatEntryAdditionReqDto { + + /** + * 【选填】 + * 法定代表人开户承诺函 + * 模板下载地址 ... + * 通过图片上传完成后 MediaID + */ + @JSONField(name = "legal_person_commitment") + private String legalPersonCommitment; + + /** + * 【选填】 + * 法定代表人开户意愿视频 + * 通过视频上传完成后 MediaID + */ + @JSONField(name = "legal_person_video") + private String legalPersonVideo; + + /** + * 【选填】 + * 补充材料 + * 最多可上传5张照片 + * 通过图片上传完成后 MediaID + */ + @JSONField(name = "business_addition_pics") + private List businessAdditionPics; + + /** + * 【选填】 + * 补充说明 512字以内 + */ + @JSONField(name = "business_addition_msg") + private String businessAdditionMsg; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryBankAccountReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryBankAccountReqDto.java new file mode 100644 index 000000000..ab84037a7 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryBankAccountReqDto.java @@ -0,0 +1,78 @@ +package com.czg.third.wechat.dto.req.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 微信进件-银行账号 + * + * @author yjjie + * @date 2025/12/26 13:42 + */ +@Data +@Accessors(chain = true) +public class WechatEntryBankAccountReqDto { + + /** + * 【必填】 + * 账户类型 + * 1、若主体为企业/政府机关/事业单位/社会组织,可填写:对公银行账户; + * 2、若主体为个体户,可选择填写:对公银行账户或经营者个人银行卡 + * 可选取值 BANK_ACCOUNT_TYPE_CORPORATE: 对公银行账户 BANK_ACCOUNT_TYPE_PERSONAL: 经营者个人银行卡 + */ + @JSONField(name = "bank_account_type") + private String bankAccountType; + + /** + * 【必填】 + * 开户名称 + * 1、选择“经营者个人银行卡”时,开户名称必须与“经营者证件姓名”一致; + * 2、选择“对公银行账户”时,开户名称必须与营业执照上的“商户名称”一致; + * 3、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + * ... + */ + @JSONField(name = "account_name") + private String accountName; + + @JSONField(name = "bank_address_code") + private String bankAddressCode; + + /** + * 【必填】 + * 开户银行 + * 对私银行调用:查询支持个人业务的银行列表API + * 对公银行调用:查询支持对公业务的银行列表API + */ + @JSONField(name = "account_bank") + private String accountBank; + + /** + * 【选填】 + * 开户银行银行号 + * 1、根据开户银行查询接口中的“是否需要填写支行”判断是否需要填写。如为其他银行,开户银行全称(含支行)和开户银行联行号二选一; + * 2、详细需调用查询支行列表API查看查询结果。 ... + */ + @JSONField(name = "bank_branch_id") + private String bankBranchId; + + /** + * 【选填】 + * 开户银行全称(含支行) + * 1、根据开户银行查询接口中的“是否需要填写支行”判断是否需要填写。如为其他银行,开户银行全称(含支行)和开户银行联行号二选一; + * 2、详细需调用查询支行列表API查看查询结果。 ... + */ + @JSONField(name = "bank_name") + private String bankName; + + /** + * 【必填】 + * 开户银行账号 + * 1、选择“经营者个人银行卡”时,开户账号为经营者个人银行卡号; + * 2、选择“对公银行账户”时,开户账号为对公银行账号; + * 3、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + * ... + */ + @JSONField(name = "account_number") + private String accountNumber; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryContactReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryContactReqDto.java new file mode 100644 index 000000000..b7327dfe8 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryContactReqDto.java @@ -0,0 +1,139 @@ +package com.czg.third.wechat.dto.req.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 进件 联系人信息 + * + * @author yjjie + * @date 2025/12/26 13:38 + */ +@Data +@Accessors(chain = true) +public class WechatEntryContactReqDto { + + /** + * 【必填】 + * 超级管理员类型 + * 主体为“个体工商户/企业/政府机关/事业单位/社会组织”,可选择:LEGAL:经营者/法定代表人,SUPER:经办人 。(经办人:经商户授权办理微信支付业务的人员)。 + * 可选取值 + * LEGAL: 经营者/法定代表人 + * SUPER: 经办人 + */ + @JSONField(name = "contact_type") + private String contactType; + + /** + * 【必填】 + * 超级管理员姓名 + * 1、长度为2-100个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符。 + * 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "contact_name") + private String contactName; + + /** + * 【选填】 + * 超级管理员证件类型 当超级管理员类型是经办人时,请上传超级管理员证件类型。 + * 可选取值 + * IDENTIFICATION_TYPE_IDCARD: 中国大陆居民-身份证 + * IDENTIFICATION_TYPE_OVERSEA_PASSPORT: 其他国家或地区居民-护照 + * IDENTIFICATION_TYPE_HONGKONG_PASSPORT: 中国香港居民-来往内地通行证 + * IDENTIFICATION_TYPE_MACAO_PASSPORT: 中国澳门居民-来往内地通行证 + * IDENTIFICATION_TYPE_TAIWAN_PASSPORT: 中国台湾居民-来往大陆通行证 + * IDENTIFICATION_TYPE_FOREIGN_RESIDENT: 外国人居留证 + * IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT: 港澳居民居住证 + * IDENTIFICATION_TYPE_TAIWAN_RESIDENT: 台湾居民居住证 + */ + @JSONField(name = "contact_id_type") + private String contactIdType; + + /** + * 【选填】 + * 超级管理员身份证件号码 + * 1、当超级管理员类型是经办人时,请上传超级管理员证件号码; + * 2、可传身份证、来往内地通行证、来往大陆通行证、护照等证件号码,号码规范如下: + * 身份证(限中国大陆居民):17位数字+1位数字|X; + * 护照(限境外人士):4-15位 数字|字母|连字符; + * 中国香港居民--来往内地通行证:H/h开头+8或10位数字/字母; + * 中国澳门居民--来往内地通行证:M/m开头+8或10位数字/字母; + * 中国台湾居民--来往大陆通行证:8位数字或10位数字; + * 外国人居留证:15位 数字|字母; + * 台湾居民居住证/港澳居民居住证:17位数字+1位数字|X; + * 3、超级管理员签约时,校验微信号绑定的银行卡实名信息,是否与该证件号码一致; + * 4、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "contact_id_number") + private String contactIdNumber; + + /** + * 【选填】 + * 超级管理员证件正面照片 + * 1、当超级管理员类型是经办人时,请上传超级管理员证件的正面照片; + * 2、若证件类型为身份证,请上传人像面照片; + * 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 4、请上传彩色照片或彩色扫描件或复印件(需加盖公章鲜章),可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "contact_id_doc_copy") + private String contactIdDocCopy; + + /** + * 【选填】 + * 超级管理员证件反面照片 + * 1、当超级管理员类型是经办人时,请上传超级管理员证件的反面照片; + * 2、若证件类型为护照,无需上传反面照片; + * 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 4、请上传彩色照片或彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "contact_id_doc_copy_back") + private String contactIdDocCopyBack; + + /** + * 【选填】 + * 超级管理员证件有效期开始时间 + * 1、当超级管理员类型是经办人时,请上传证件有效期开始时间; + * 2、请按照示例值填写,日期格式应满足合法的YYYY-MM-DD格式; + * 3、开始时间不能小于1900-01-01,开始时间不能大于等于当前日期。 + */ + @JSONField(name = "contact_period_begin") + private String contactPeriodBegin; + + /** + * 【选填】 + * 超级管理员证件有效期截止时间 + * 1、当超级管理员类型是经办人时,请上传证件有效期结束时间; + * 2、请按照示例值填写,日期格式应满足合法的YYYY-MM-DD格式,若证件有效期为长期,请填写:长期; + * 3、结束时间大于开始时间。 + */ + @JSONField(name = "contact_period_end") + private String contactPeriodEnd; + + /** + * 【必填】 + * 联系手机 + * 1、前后不能有空格、制表符、换行符; + * 2、需满足以下任一条件: + * 11位数字的手机号码; + * 5-20位数字、连字符“-”、加号“+”; + * 3、用于接收微信支付的重要管理信息及日常操作验证码; + * 4、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "mobile_phone") + private String mobilePhone; + + /** + * 【必填】 + * 联系邮箱 + * 1、用于接收微信支付的开户邮件及日常业务通知; + * 2、需要带@,遵循邮箱格式校验 ,该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "contact_email") + private String contactEmail; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryReqDto.java new file mode 100644 index 000000000..0555fdda3 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryReqDto.java @@ -0,0 +1,69 @@ +package com.czg.third.wechat.dto.req.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.czg.third.wechat.dto.req.entry.business.WechatEntryBusinessReqDto; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 微信进件请求参数 + * 参考地址 ... + * @author yjjie + * @date 2025/12/26 13:36 + */ +@Data +@Accessors(chain = true) +public class WechatEntryReqDto { + + /** + * 【必填】 + * 业务申请编号 + */ + @JSONField(name = "business_code") + private String businessCode; + + /** + * 【必填】 + * 超级管理员信息 + */ + @JSONField(name = "contact_info") + private WechatEntryContactReqDto contactInfo; + + /** + * 【必填】 + * 主体资料 + */ + @JSONField(name = "subject_info") + private WechatEntrySubjectReqDto subjectInfo; + + /** + * 【必填】 + * 经营资料 + * 商家的经营业务信息、售卖商品/提供服务场景信息 + */ + @JSONField(name = "business_info") + private WechatEntryBusinessReqDto businessInfo; + + /** + * 【必填】 + * 结算规则 + * 请填写商家的结算费率规则、特殊资质等信息 + */ + @JSONField(name = "settlement_info") + private WechatEntrySettleReqDto settlementInfo; + + /** + * 【必填】 + * 结算银行账户 + */ + @JSONField(name = "bank_account_info") + private WechatEntryBankAccountReqDto bankAccountInfo; + + /** + * 【选填】 + * 补充材料 + * 根据实际审核情况,会额外要求商家提供指定的补充资料 + */ + @JSONField(name = "addition_info") + private WechatEntryAdditionReqDto additionInfo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntrySettleReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntrySettleReqDto.java new file mode 100644 index 000000000..637cdfd00 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntrySettleReqDto.java @@ -0,0 +1,91 @@ +package com.czg.third.wechat.dto.req.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件 结算信息 + * @author yjjie + * @date 2025/12/26 13:38 + */ +@Data +@Accessors(chain = true) +public class WechatEntrySettleReqDto { + + /** + * 【必填】 + * 入驻结算规则ID + * 请选择结算规则ID,详细参见费率结算规则对照表 + * ... + */ + @JSONField(name = "settlement_id") + private String settlementId; + + /** + * 【必填】 + * 所属行业 + * ... + */ + @JSONField(name = "qualification_type") + private String qualificationType; + + /** + * 【选填】 + * 特殊资质图片 + * 1、仅当商户选择了必需提交特殊资质的行业时,需要提供该项资料;若商户选择了无需特殊资质的行业,或未选择行业时,无需提交该项资料,详情查看《费率结算规则对照表》; + * 2、最多可上传5张照片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "qualifications") + private List qualifications; + + /** + * 【选填】 + * 优惠费率活动ID + * 如果商户有意向报名优惠费率活动,该字段【必填】。详细参见《优惠费率活动对照表》 + * ... + */ + @JSONField(name = "activities_id") + private String activitiesId; + + /** + * 【选填】 + * 优惠费率活动值 + * 根据优惠费率活动规则,由合作伙伴自定义填写,支持两个小数点,需在优惠费率活动ID指定费率范围内 + * (1)2023年7月17日-9月17日,各合作伙伴平台可选择只传入“活动费率值”,或分别传入“信用卡优惠活动费率值” 与 “非信用卡优惠活动费率值”,只传入“活动费率值”的情况下,平台将协助将申请单中的 “优惠活动费率值” 回填入新增的 “信用卡优惠活动费率值” 与 “非信用卡优惠活动费率值”中 ; + * (2)2023年9月18日起,平台将不再提供如上兼容能力,届时仅能分别传入“信用卡优惠活动费率值” 与 “非信用卡优惠活动费率值”,否则接口将会报错。为避免影响正常进件,请在兼容期间完成相关调整。 + */ + @JSONField(name = "activities_rate") + private String activitiesRate; + + /** + * 【选填】 + * 优惠费率活动补充材料 + * 1、根据所选优惠费率活动,提供相关材料,详细参见《优惠费率活动对照表》;... + * 2、最多可上传5张照片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "activities_additions") + private List activitiesAdditions; + + /** + * 【选填】 + * 非信用卡活动费率值 + * 用户支付方式为借记卡时收取的手续费费率: + * 1、若填写了优惠费率活动ID,则该字段【必填】; + * 2、仅能填入2位以内小数,且在优惠费率活动ID指定费率范围内。 + */ + @JSONField(name = "debit_activities_rate") + private String debitActivitiesRate; + + /** + * 【选填】 + * 信用卡活动费率值 + * 用户支付方式为信用卡时收取的手续费费率: + * 1、若填写了优惠费率活动ID,则该字段【必填】; + * 2、仅能填入2位以内小数,且在优惠费率活动ID指定费率范围内。 + */ + @JSONField(name = "credit_activities_rate") + private String creditActivitiesRate; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntrySubjectReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntrySubjectReqDto.java new file mode 100644 index 000000000..1010b4969 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntrySubjectReqDto.java @@ -0,0 +1,103 @@ +package com.czg.third.wechat.dto.req.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.czg.third.wechat.dto.req.entry.business.*; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件 主体资料 + * @author yjjie + * @date 2025/12/26 13:40 + */ +@Data +@Accessors(chain = true) +public class WechatEntrySubjectReqDto { + + /** + * 【必填】 + * 主体类型 主体类型需与营业执照/登记证书上一致,可参考选择主体指引。... + * 可选取值 + * SUBJECT_TYPE_INDIVIDUAL: (个体户)营业执照上的主体类型一般为个体户、个体工商户、个体经营 + * SUBJECT_TYPE_ENTERPRISE: (企业)营业执照上的主体类型一般为有限公司、有限责任公司 + * SUBJECT_TYPE_GOVERNMENT: (政府机关)包括各级、各类政府机关,如机关党委、税务、民政、人社、工商、商务、市监等 + * SUBJECT_TYPE_INSTITUTIONS: (事业单位)包括国内各类事业单位,如:医疗、教育、学校等单位 + * SUBJECT_TYPE_OTHERS:(社会组织) 包括社会团体、民办非企业、基金会、基层群众性自治组织、农村集体经济组织等组织 + */ + @JSONField(name = "subject_type") + private String subjectType; + + /** + * 【选填】 + * 是否是金融机构 + * 【是否是金融机构】 【选填】,请根据申请主体的实际情况填写,可参考选择金融机构指引: + * 1、若商户主体是金融机构,则填写:true; + * 2、若商户主体不是金融机构,则填写:false。 + * 若未传入将默认填写:false。 + */ + @JSONField(name = "finance_institution") + private Boolean financeInstitution; + + /** + * 【必填】 + * 营业执照 当前不允许小微注册,所以【必填】 + * 1、主体为个体户/企业,【必填】; + * 2、请上传“营业执照”,需年检章齐全,当年注册除外; + */ + @JSONField(name = "business_license_info") + private WechatEntryLicenseReqDto businessLicenseInfo; + + /** + * 【选填】 + * 登记证书 主体为政府机关/事业单位/其他组织时,【必填】。 + */ + @JSONField(name = "certificate_info") + private WechatEntryCertificateReqDto certificateInfo; + + /** + * 【选填】 + * 单位证明函照片 + * 1、主体类型为政府机关、事业单位选传: + * (1)若上传,则审核通过后即可签约,无需汇款验证; + * (2)若未上传,则审核通过后,需汇款验证。 + * 2、主体为个体户、企业、其他组织等,不需要上传本字段; + * 3、请参照示例图打印单位证明函,全部信息需打印,不支持手写商户信息,并加盖公章; ... + * 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "certificate_letter_copy") + private String certificateLetterCopy; + + /** + * 【选填】 + * 金融机构许可证信息 当主体是金融机构时,【必填】。 + */ + @JSONField(name = "finance_institution_info") + private WechatEntryFinanceInstitutionReqDto financeInstitutionInfo; + + /** + * 【必填】 + * 经营者/法定代表人身份证件 + * 1、个体户:请上传经营者的身份证件; + * 2、企业/社会组织:请上传法定代表人的身份证件; + * 3、政府机关/事业单位:请上传法定代表人/经办人的身份证件。 + */ + @JSONField(name = "identity_info") + private WechatEntryIdentityReqDto identityInfo; + + /** + * 【选填】 + * 最终受益人信息列表(UBO) + * 1、主体类型个体户/社会组织/政府机关/事业单位时,无需填写 + * 2、主体类型为企业时,按照下述要求填写 + * 1)若经营者/法定代表人不是最终受益所有人,则需提填写受益所有人信息,最多上传4个。 + * 2)若经营者/法定代表人是最终受益所有人之一,可在此填写其他受益所有人信息,最多上传3个。 + * 根据国家相关法律法规,需要提供公司受益所有人信息,受益所有人需符合至少以下条件之一: + * ▪️直接或者间接拥有超过25%公司股权或者表决权的自然人。 + * ▪️通过人事、财务等其他方式对公司进行控制的自然人。 + * ▪️公司的高级管理人员,包括公司的经理、副经理、财务负责人、上市公司董事会秘书和公司章程规定的其他人员。 + */ + @JSONField(name = "ubo_info_list") + private List uboInfoList; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryBusinessReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryBusinessReqDto.java new file mode 100644 index 000000000..d2b5f773b --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryBusinessReqDto.java @@ -0,0 +1,50 @@ +package com.czg.third.wechat.dto.req.entry.business; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.czg.third.wechat.dto.req.entry.business.sales.WechatEntrySalesInfoReqDto; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 微信进件 商户信息 经营资料 + * + * @author yjjie + * @date 2025/12/26 13:41 + */ +@Data +@Accessors(chain = true) +public class WechatEntryBusinessReqDto { + + /** + * 【必填】 + * 商户简称 在支付完成页向买家展示,需与微信经营类目相关 + * 1、请输入1-64个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式; + * 6、简称要求 + * (1)不支持单纯以人名来命名,若为个体户经营,可用“个体户+经营者名称”或“经营者名称+业务”命名,如“个体户张三”或“张三餐饮店”; + * (2)不支持无实际意义的文案,如“XX特约商户”、“800”、“XX客服电话XXX”。 + */ + @JSONField(name = "merchant_shortname") + private String merchantShortname; + + /** + * 【必填】 + * 客服电话 将在交易记录中向买家展示,请确保电话畅通以便平台回拨确认 + * 1、前后不能有空格、制表符、换行符; + * 2、需满足以下任一条件; + * 11位数字的手机号码; + * 5-20位数字、连字符“-”、加号“+”。 + */ + @JSONField(name = "service_phone") + private String servicePhone; + + /** + * 【必填】 + * 经营场景 + */ + @JSONField(name = "sales_info") + private WechatEntrySalesInfoReqDto salesInfo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryCertificateReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryCertificateReqDto.java new file mode 100644 index 000000000..cd422bf05 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryCertificateReqDto.java @@ -0,0 +1,111 @@ +package com.czg.third.wechat.dto.req.entry.business; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 登记证书 + * @author yjjie + * @date 2025/12/26 15:07 + */ +@Data +@Accessors(chain = true) +public class WechatEntryCertificateReqDto { + + /** + * 【必填】 + * 登记证书照片 + * 1、照片应正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 2、上传彩色照片、彩色扫描件,复印件需加盖公章鲜章; + * 3、水印仅限于微信支付业务相关; + * 4、指引与示例可参考【指引文档】; + * 5、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "cert_copy") + private String certCopy; + + /** + * 【必填】 + * 登记证书类型 + * 1、主体为“政府机关/事业单位/社会组织”时,请上传登记证书类型; + * 2、主体为“个体工商户/企业”时,不填; + * 当主体为事业单位时,选择此枚举值: + * CERTIFICATE_TYPE_2388:事业单位法人证书 + * 当主体为政府机关,选择此枚举值: + * CERTIFICATE_TYPE_2389:统一社会信用代码证书 + * 当主体为社会组织,选择以下枚举值之一: + * CERTIFICATE_TYPE_2389:统一社会信用代码证书 + * CERTIFICATE_TYPE_2394:社会团体法人登记证书 + * CERTIFICATE_TYPE_2395:民办非企业单位登记证书 + * CERTIFICATE_TYPE_2396:基金会法人登记证书 + * CERTIFICATE_TYPE_2520:执业许可证/执业证 + * CERTIFICATE_TYPE_2521:基层群众性自治组织特别法人统一社会信用代码证 + * CERTIFICATE_TYPE_2522:农村集体经济组织登记证 + * CERTIFICATE_TYPE_2399:宗教活动场所登记证 + * CERTIFICATE_TYPE_2400:政府部门下发的其他有效证明文件 + */ + @JSONField(name = "cert_type") + private String certType; + + /** + * 【必填】 + * 证书号 请输入与所选证书类型相匹配且符合国家标准规范的证书号,其中除政府证明文件外,需满足18位阿拉伯数字或大写英文字母(不得包含英文字母I/O/Z/S/V) + */ + @JSONField(name = "cert_number") + private String certNumber; + + /** + * 【必填】 + * 商户名称 请填写登记证书上的商户名称 + * 1、长度为2-128个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式。 + */ + @JSONField(name = "merchant_name") + private String merchantName; + + /** + * 【必填】 + * 注册地址 请填写登记证书的注册地址 + * 1、长度为4-128个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式。 + */ + @JSONField(name = "company_address") + private String companyAddress; + + /** + * 【必填】 + * 法定代表人 请填写登记证书上的法定代表人姓名 + * 1、长度为2-100个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符。 + */ + @JSONField(name = "legal_person") + private String legalPerson; + + /** + * 【选填】 + * 有效期限开始日期 建议填写营业执照的有效期限开始时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。 + * 1、日期格式应满足合法的YYYY-MM-DD格式; + * 2、开始时间不能小于1900-01-01; + * 3、开始时间不能大于等于当前日期。 + */ + @JSONField(name = "period_begin") + private String periodBegin; + + /** + * 【选填】 + * 营业期限结束日期 建议填写营业执照的有效期限结束时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。 + * 1、日期格式应满足合法的YYYY-MM-DD格式或长期; + * 2、结束时间需大于开始时间。 + */ + @JSONField(name = "period_end") + private String periodEnd; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryFinanceInstitutionReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryFinanceInstitutionReqDto.java new file mode 100644 index 000000000..b300cd666 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryFinanceInstitutionReqDto.java @@ -0,0 +1,42 @@ +package com.czg.third.wechat.dto.req.entry.business; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 金融机构许可证信息 + * @author yjjie + * @date 2025/12/26 15:21 + */ +@Data +@Accessors(chain = true) +public class WechatEntryFinanceInstitutionReqDto { + + /** + * 【必填】 + * 金融机构类型 金融机构类型需与营业执照/登记证书上一致,可参考选择金融机构指引。 + * BANK_AGENT:银行业, 适用于商业银行、政策性银行、农村合作银行、村镇银行、开发性金融机构等 + * PAYMENT_AGENT:支付机构, 适用于非银行类支付机构 + * INSURANCE:保险业, 适用于保险、保险中介、保险代理、保险经纪等保险类业务 + * TRADE_AND_SETTLE:交易及结算类金融机构, 适用于交易所、登记结算类机构、银行卡清算机构、资金清算中心等 + * OTHER:其他金融机构, 适用于财务公司、信托公司、金融资产管理公司、金融租赁公司、汽车金融公司、贷款公司、货币经纪公司、消费金融公司、证券业、金融控股公司、股票、期货、货币兑换、小额贷款公司、金融资产管理、担保公司、商业保理公司、典当行、融资租赁公司、财经咨询等其他金融业务 + */ + @JSONField(name = "finance_type") + private String financeType; + + /** + * 必填 + * 金融机构许可证图片 + * 1、照片应正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 2、上传彩色照片、彩色扫描件,复印件需加盖公章鲜章; + * 3、水印仅限于微信支付业务相关; + * 4、根据所属金融机构类型的许可证要求提供,详情查看金融机构指引; ... + * 5、请提供为“申请商家主体”所属的许可证,可授权使用总公司/分公司的特殊资质; + * 6、最多可上传5张照片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "finance_license_pics") + private List financeLicensePics; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryIdentityReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryIdentityReqDto.java new file mode 100644 index 000000000..3d694aeb6 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryIdentityReqDto.java @@ -0,0 +1,72 @@ +package com.czg.third.wechat.dto.req.entry.business; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.czg.third.wechat.dto.req.entry.id.WechatEntryIdCardReqDto; +import com.czg.third.wechat.dto.req.entry.id.WechatEntryIdDocInfoReqDto; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 经营者/法定代表人身份证件 + * @author yjjie + * @date 2025/12/26 15:24 + */ +@Data +@Accessors(chain = true) +public class WechatEntryIdentityReqDto { + + /** + * 【选填】 + * 证件持有人类型 + * 1、主体类型为政府机关/事业单位时选传: + * (1)若上传的是法定代表人证件,则不需要上传该字段。 + * (2)若因政策保密等原因,无法提供法定代表人证件时,可上传经办人。 (经办人:经商户授权办理微信支付业务的人员,授权范围包括但不限于签约,入驻过程需完成账户验证)。 + * 2、主体类型为小微/个人卖家/企业/个体户/社会组织时,默认为经营者/法定代表人,不需要上传该字段。 + * LEGAL: 经营者/法定代表人 + * SUPER: 经办人 + */ + @JSONField(name = "id_holder_type") + private String idHolderType; + + /** + * 【选填】 + * 证件类型 + * 1、当证件持有人类型为法定代表人时,填写。其他情况,无需上传; + * 2、个体户/企业/事业单位/社会组织:可选择任一证件类型,政府机关仅支持中国大陆居民-身份证类型。 + * 可选取值 + * IDENTIFICATION_TYPE_IDCARD: 中国大陆居民-身份证 + * IDENTIFICATION_TYPE_OVERSEA_PASSPORT: 其他国家或地区居民-护照 + * IDENTIFICATION_TYPE_HONGKONG_PASSPORT: 中国香港居民-来往内地通行证 + * IDENTIFICATION_TYPE_MACAO_PASSPORT: 中国澳门居民-来往内地通行证 + * IDENTIFICATION_TYPE_TAIWAN_PASSPORT: 中国台湾居民-来往大陆通行证 + * IDENTIFICATION_TYPE_FOREIGN_RESIDENT: 外国人居留证 + * IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT: 港澳居民居住证 + * IDENTIFICATION_TYPE_TAIWAN_RESIDENT: 台湾居民居住证 + */ + @JSONField(name = "id_doc_type") + private String idDocType; + + /** + * 【选填】 + * 法定代表人说明函 + * 1、当证件持有人类型为经办人时,必须上传。其他情况,无需上传; + * 2、若因特殊情况,无法提供法定代表人证件时,请参照示例图打印法定代表人说明函,全部信息需打印,不支持手写商户信息,并加盖公章; ... + * 3、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "authorize_letter_copy") + private String authorizeLetterCopy; + + /** + * 【选填】 + * 身份证信息 当证件持有人类型为经营者/法定代表人且证件类型为“身份证”时填写。 + */ + @JSONField(name = "id_card_info") + private WechatEntryIdCardReqDto idCardInfo; + + /** + * 【选填】 + * 其他类型证件信息 当证件持有人类型为经营者/法定代表人且证件类型不为“身份证”时填写。 + */ + @JSONField(name = "id_doc_info") + private WechatEntryIdDocInfoReqDto idDocInfo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryLicenseReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryLicenseReqDto.java new file mode 100644 index 000000000..0e76f06c5 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryLicenseReqDto.java @@ -0,0 +1,90 @@ +package com.czg.third.wechat.dto.req.entry.business; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 营业执照信息 + * @author yjjie + * @date 2025/12/26 14:59 + */ +@Data +@Accessors(chain = true) +public class WechatEntryLicenseReqDto { + + /** + * 【必填】 + * 营业执照照片 + * 1、照片应正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 2、上传彩色照片、彩色扫描件,复印件需加盖公章鲜章; + * 3、水印仅限于微信支付业务相关; + * 4、指引与示例可参考【指引文档】; + * 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "license_copy") + private String licenseCopy; + + /** + * 【必填】 + * 注册号/统一社会信用代码 + */ + @JSONField(name = "license_number") + private String licenseNumber; + + /** + * 【必填】 + * 商户名称 + * 1、长度为2-128个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式; + * 6、个体户证件为以下情况时,按照个体户XXX命名(XXX是营业执照经营人姓名):营业执照登记名称为空、仅含数字、仅含特殊字符、“无”、“无字号”; + * 7、个体户不能使用“企业”“公司”或“农民专业合作社”结尾。 + */ + @JSONField(name = "merchant_name") + private String merchantName; + + /** + * 【必填】 + * 个体户经营者/法定代表人姓名 请填写营业执照的经营者/法定代表人姓名 + * 1、长度为2-100个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符。 + */ + @JSONField(name = "legal_person") + private String legalPerson; + + /** + * 【选填】 + * 注册地址 建议填写营业执照的注册地址,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。 + * 1、长度为4-128个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式。 + */ + @JSONField(name = "license_address") + private String licenseAddress; + + /** + * 【选填】 + * 有效期限开始日期 建议填写营业执照的有效期限开始时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。 + * 1、日期格式应满足合法的YYYY-MM-DD格式; + * 2、开始时间不能小于1900-01-01; + * 3、开始时间不能大于等于当前日期。 + */ + @JSONField(name = "period_begin") + private String periodBegin; + + /** + * 【选填】 + * 营业期限结束日期 建议填写营业执照的有效期限结束时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。 + * 1、日期格式应满足合法的YYYY-MM-DD格式或长期; + * 2、结束时间需大于开始时间。 + */ + @JSONField(name = "period_end") + private String periodEnd; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryUboInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryUboInfoReqDto.java new file mode 100644 index 000000000..8f7014e2c --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/WechatEntryUboInfoReqDto.java @@ -0,0 +1,113 @@ +package com.czg.third.wechat.dto.req.entry.business; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 最终受益人信息 + * @author yjjie + * @date 2025/12/26 15:50 + */ +@Data +@Accessors(chain = true) +public class WechatEntryUboInfoReqDto { + + /** + * 【必填】 + * 证件类型 请填写受益人的证件类型。枚举值: + * 可选取值 + * IDENTIFICATION_TYPE_IDCARD: 中国大陆居民-身份证 + * IDENTIFICATION_TYPE_OVERSEA_PASSPORT: 其他国家或地区居民-护照 + * IDENTIFICATION_TYPE_HONGKONG_PASSPORT: 中国香港居民-来往内地通行证 + * IDENTIFICATION_TYPE_MACAO_PASSPORT: 中国澳门居民-来往内地通行证 + * IDENTIFICATION_TYPE_TAIWAN_PASSPORT: 中国台湾居民-来往大陆通行证 + * IDENTIFICATION_TYPE_FOREIGN_RESIDENT: 外国人居留证 + * IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT: 港澳居民居住证 + * IDENTIFICATION_TYPE_TAIWAN_RESIDENT: 台湾居民居住证 + */ + @JSONField(name = "ubo_id_doc_type") + private String uboIdDocType; + + /** + * 必填 + * 证件正面照片 + * 1、请上传受益人证件的正面照片; + * 2、若证件类型为身份证,请上传人像面照片; + * 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 4、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "ubo_id_doc_copy") + private String uboIdDocCopy; + + /** + * 【选填】 + * 证件反面照片 + * 1、请上传受益人证件的反面照片; + * 2、若证件类型为身份证,请上传国徽面照片; + * 3、若证件类型为护照,无需上传反面照片; + * 4、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 5、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 6、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "ubo_id_doc_copy_back") + private String uboIdDocCopyBack; + + /** + * 必填 + * 证件姓名 + * 1、长度为2-100个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "ubo_id_doc_name") + private String uboIdDocName; + + /** + * 必填 + * 证件号码 + * 1、证件号码为证件正反面中一致的号码; + * 2、证件号码为18位或15位,或港澳居民来往内地通行证为9位; + * 3、证件号码为英文字母X或x,或港澳居民来往内地通行证为英文字母X或x; + * 4、证件号码为英文字母x或X,或港澳居民来往内地通行证为英文字母x或X; + * 5、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥 + */ + @JSONField(name = "ubo_id_doc_number") + private String uboIdDocNumber; + + /** + * 必填 + * 证件居住地址 + * 1、请按照身份证住址填写,如广东省深圳市南山区xx路xx号xx室; + * 2、长度为4-128个字符; + * 3、前后不能有空格、制表符、换行符; + * 4、不能仅含数字、特殊字符; + * 5、仅能填写数字、英文字母、汉字及特殊字符; + * 6、仅支持utf-8格式; + * 7、 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "ubo_id_doc_address") + private String uboIdDocAddress; + + /** + * 必填 + * 证件有效期开始时间 + * 1、日期格式应满足合法的YYYY-MM-DD格式; + * 2、开始时间不能小于1900-01-01; + * 3、开始时间不能大于等于当前日期。 + */ + @JSONField(name = "ubo_period_begin") + private String uboPeriodBegin; + + /** + * 必填 + * 证件有效期结束时间 + * 1、日期格式应满足合法的YYYY-MM-DD格式或长期; + * 2、结束时间需大于开始时间。 + */ + @JSONField(name = "ubo_period_end") + private String uboPeriodEnd; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryAppInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryAppInfoReqDto.java new file mode 100644 index 000000000..bfb652f7e --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryAppInfoReqDto.java @@ -0,0 +1,45 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件-经营资料-经营场景-App场景 + * @author yjjie + * @date 2025/12/26 14:26 + */ +@Data +@Accessors(chain = true) +public class WechatEntryAppInfoReqDto { + + /** + * 【必填】 + * App截图 + * 1、请提供APP首页截图、尾页截图、应用内截图、支付页截图各1张; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "app_pics") + private List appPics; + + /** + * 【选填】 + * 商家应用AppID + * 1、服务商应用AppID与商家应用AppID,二选一【必填】; + * 2、可填写与商家主体一致且已认证的应用AppID,需是已认证的App; + * 3、审核通过后,系统将发起特约商家商户号与该AppID的绑定(即配置为sub_appid),服务商随后可在发起支付时选择传入该AppID,以完成支付,并获取sub_openid用于数据统计,营销等业务场景。 + */ + @JSONField(name = "app_sub_appid") + private String appSubAppid; + + /** + * 【选填】 + * 服务商应用AppID + * 1、服务商应用AppID与商家应用AppID,二选一【必填】; + * 2、可填写当前服务商商户号已绑定的应用AppID。 + */ + @JSONField(name = "app_appid") + private String appAppid; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryMiniProgramReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryMiniProgramReqDto.java new file mode 100644 index 000000000..fbf428a46 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryMiniProgramReqDto.java @@ -0,0 +1,47 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件-经营资料-经营场景-小程序场景 + * @author yjjie + * @date 2025/12/26 14:23 + */ +@Data +@Accessors(chain = true) +public class WechatEntryMiniProgramReqDto { + + /** + * 【选填】 + * 服务商小程序AppID + * 1、服务商小程序AppID与商家小程序AppID,二选一必填; + * 2、可填写当前服务商商户号已绑定的小程序AppID。 + */ + @JSONField(name = "mini_program_appid") + private String miniProgramAppid; + + /** + * 【选填】 + * 商家小程序AppID + * 1、服务商小程序AppID与商家小程序AppID,二选一必填; + * 2、请填写已认证的小程序AppID; + * 3、完成进件后,系统发起特约商户号与该AppID的绑定(即配置为sub_appid可在发起支付时传入) + * (1)若AppID主体与商家主体/服务商主体一致,则直接完成绑定; + * (2)若AppID主体与商家主体/服务商主体不一致,则商户签约时显示《联合营运承诺函》,并且AppID的管理员需登录公众平台确认绑定意愿。 + */ + @JSONField(name = "mini_program_sub_appid") + private String miniProgramSubAppid; + + /** + * 【选填】 + * 小程序截图 + * 1、请提供展示商品/服务的页面截图/设计稿(最多5张),若小程序未建设完善或未上线 请务必提供; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "mini_program_pics") + private List miniProgramPics; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryMpInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryMpInfoReqDto.java new file mode 100644 index 000000000..e6a1bebfb --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryMpInfoReqDto.java @@ -0,0 +1,45 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件-经营资料-经营场景-服务号或公众号景 + * @author yjjie + * @date 2025/12/26 14:20 + */ +@Data +@Accessors(chain = true) +public class WechatEntryMpInfoReqDto { + + /** + * 【必填】 + * 服务号或公众号页面截图 + * 1、请提供展示商品/服务的页面截图/设计稿(最多5张),若服务号或公众号未建设完善或未上线请务必提供; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "mp_pics") + private List mpPics; + + /** + * 【选填】 + * 商家服务号或公众号AppID + * 1、服务商服务号或公众号AppID、商家服务号或公众号AppID,二选一【必填】; + * 2、可填写与商家主体一致且已认证的服务号或公众号AppID,需是已认证的服务号、政府或媒体类型的公众号; + * 3、审核通过后,系统将发起特约商家商户号与该AppID的绑定(即配置为sub_appid),服务商随后可在发起支付时选择传入该appid,以完成支付,并获取sub_openid用于数据统计,营销等业务场景。 + */ + @JSONField(name = "mp_sub_appid") + private String mpSubAppid; + + /** + * 【选填】 + * 服务商服务号或公众号AppID + * 1、服务商服务号或公众号AppID、商家服务号或公众号AppID,二选一【必填】; + * 2、可填写当前服务商商户号已绑定的服务号或公众号AppID。 + */ + @JSONField(name = "mp_appid") + private String mpAppid; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntrySalesInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntrySalesInfoReqDto.java new file mode 100644 index 000000000..b1d6979de --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntrySalesInfoReqDto.java @@ -0,0 +1,87 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件-经营场景 + * @author yjjie + * @date 2025/12/26 14:07 + */ +@Data +@Accessors(chain = true) +public class WechatEntrySalesInfoReqDto { + + /** + * 【必填】 + * 经营场景类型 + * 1、请勾选实际售卖商品/提供服务场景(至少一项),以便为你开通需要的支付权限; + * 2、建议只勾选目前必须的场景,以便尽快通过入驻审核,其他支付权限可在入驻后再根据实际需要发起申请 + * 可选取值 + * SALES_SCENES_STORE: 线下场所 + * SALES_SCENES_MP: 服务号与公众号 + * SALES_SCENES_MINI_PROGRAM: 小程序 + * SALES_SCENES_WEB: 互联网网站 + * SALES_SCENES_APP: App + * SALES_SCENES_WEWORK: 企业微信 + */ + @JSONField(name = "sales_scenes_type") + private List salesScenesType; + + /** + * 【选填】 + * 线下场所场景 + * 1、审核通过后,服务商可帮商户发起付款码支付、JSAPI支付; + * 2、当"经营场景类型"选择"SALES_SCENES_STORE",该场景资料【必填】。 + */ + @JSONField(name = "biz_store_info") + private WechatEntryStoreInfoReqDto bizStoreInfo; + + /** + * 【选填】 + * 服务号或公众号场景 + * 1、审核通过后,服务商可帮商家发起JSAPI支付; + * 2、当"经营场景类型"选择"SALES_SCENES_MP",该场景资料【必填】。 + */ + @JSONField(name = "mp_info") + private WechatEntryMpInfoReqDto mpInfo; + + /** + * 【选填】 + * 小程序场景 + * 1、审核通过后,服务商可帮商家发起JSAPI支付; + * 2、当"经营场景类型"选择"SALES_SCENES_MINI_PROGRAM",该场景资料必填。 + */ + @JSONField(name = "mini_program_info") + private WechatEntryMiniProgramReqDto miniProgramInfo; + + /** + * 【选填】 + * App场景 + * 1、审核通过后,服务商可帮商家发起App支付; + * 2、当"经营场景类型"选择"SALES_SCENES_APP",该场景资料必填。 + */ + @JSONField(name = "app_info") + private WechatEntryAppInfoReqDto appInfo; + + /** + * 【选填】 + * 互联网网站场景 + * 1、审核通过后,服务商可帮商家发起JSAPI支付; + * 2、当"经营场景类型"选择"SALES_SCENES_WEB",该场景资料必填。 + */ + @JSONField(name = "web_info") + private WechatEntryWebInfoReqDto webInfo; + + /** + * 【选填】 + * 企业微信场景 + * 1、审核通过后,服务商可帮商家发起JSAPI支付; + * 2、当"经营场景类型"选择"SALES_SCENES_WEWORK",该场景资料必填。 + */ + @JSONField(name = "wework_info") + private WechatEntryWeworkInfoReqDto weworkInfo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryStoreInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryStoreInfoReqDto.java new file mode 100644 index 000000000..298741d7e --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryStoreInfoReqDto.java @@ -0,0 +1,80 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 进件-经营资料-经营场景-线下场所场景 + * @author yjjie + * @date 2025/12/26 14:10 + */ +@Data +@Accessors(chain = true) +public class WechatEntryStoreInfoReqDto { + + /** + * 【必填】 + * 线下场所名称 请填写门店名称 + * 1、长度为1-50个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式。 + */ + @JSONField(name = "biz_store_name") + private String bizStoreName; + + /** + * 【必填】 + * 线下场所省市编码 + * 1、只能由数字组成; + * 2、详细参见微信支付提供的省市对照表。 ... + */ + @JSONField(name = "biz_address_code") + private String bizAddressCode; + + /** + * 【必填】 + * 线下场所地址 请填写详细的经营场所信息,如有多个场所,选择一个主要场所填写即可。 + * 1、长度为4-512个字符; + * 2、前后不能有空格、制表符、换行符; + * 3、不能仅含数字、特殊字符; + * 4、仅能填写数字、英文字母、汉字及特殊字符; + * 5、仅支持utf-8格式 + */ + @JSONField(name = "biz_store_address") + private String bizStoreAddress; + + /** + * 【必填】 + * 线下场所门头照片 + * 1、请上传门头正面照片(要求门店招牌、门框完整、清晰、可辨识);若为停车场等无固定门头照片的经营场所,可上传岗亭/出入闸口。具体参考【指引文档】; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "store_entrance_pic") +// private List storeEntrancePic; + private String storeEntrancePic; + + /** + * 【必填】 + * 线下场所内部照片 + * 1、请上传门店内部环境照片(可辨识经营内容)。若为停车场等无固定门头的经营场所,可上传停车场内部照片。具体参考【指引文档】; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "indoor_pic") +// private List indoorPic; + private String indoorPic; + + /** + * 【选填】 + * 线下场所对应的商家AppID + * 1、可填写与商家主体一致且已认证的服务号或公众号、小程序、APP的AppID,其中服务号或公众号AppID需是已认证的服务号、政府或媒体类型的公众号; + * 2、审核通过后,系统将额外为商家开通付款码支付、JSAPI支付的自有交易权限,并完成商家商户号与该AppID的绑定。 + */ + @JSONField(name = "biz_sub_appid") + private String bizSubAppid; + +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryWebInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryWebInfoReqDto.java new file mode 100644 index 000000000..bc087205a --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryWebInfoReqDto.java @@ -0,0 +1,44 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 进件-经营资料-经营场景-互联网网站场景 + * @author yjjie + * @date 2025/12/26 14:29 + */ +@Data +@Accessors(chain = true) +public class WechatEntryWebInfoReqDto { + + /** + * 【必填】 + * 互联网网站域名 + * 1、如为PC端商城、智能终端等场景,可上传官网链接; + * 2、网站域名需ICP备案,若备案主体与申请主体不同,请上传加盖公章的网站授权函。 + */ + @JSONField(name = "domain") + private String domain; + + /** + * 【必填】 + * 网站授权函 + * 1、若备案主体与申请主体不同,请务必上传加盖公章的网站授权函.doc; ... + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "web_authorisation") + private String webAuthorisation; + + /** + * 【选填】 + * 互联网网站对应的商家AppID + * 1、可填写已认证的服务号或公众号、小程序、APP的AppID,其中服务号或公众号AppID需是已认证的服务号、政府或媒体类型的公众号; + * 2、完成进件后,系统发起特约商户号与该AppID的绑定(即配置为sub_appid,可在发起支付时传入) + * (1)若APPID主体与商家主体一致,则直接完成绑定; + * (2)若APPID主体与商家主体不一致,则商户签约时显示《联合营运承诺函》,并且 AppID的管理员需登录公众平台确认绑定意愿;( 暂不支持绑定异主体的应用APPID)。 + */ + @JSONField(name = "web_appid") + private String webAppid; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryWeworkInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryWeworkInfoReqDto.java new file mode 100644 index 000000000..4706b9d46 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryWeworkInfoReqDto.java @@ -0,0 +1,33 @@ +package com.czg.third.wechat.dto.req.entry.business.sales; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 进件-经营资料-经营场景-企业微信场景 + * @author yjjie + * @date 2025/12/26 14:32 + */ +@Data +@Accessors(chain = true) +public class WechatEntryWeworkInfoReqDto { + + /** + * 【必填】 + * 商家企业微信CorpID + * 1、可填写与商家主体一致且已认证的企业微信CorpID; + * 2、审核通过后,系统将为商家开通企业微信专区的自有交易权限,并完成商家商户号与该AppID的绑定,商家可自行发起交易。 + */ + @JSONField(name = "sub_corp_id") + private String subCorpId; + + /** + * 必填 + * 企业微信页面截图 + * 1、最多可上传5张照片; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "wework_pics") + private String[] weworkPics; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/id/WechatEntryIdCardReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/id/WechatEntryIdCardReqDto.java new file mode 100644 index 000000000..02c1e3c18 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/id/WechatEntryIdCardReqDto.java @@ -0,0 +1,96 @@ +package com.czg.third.wechat.dto.req.entry.id; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 身份证信息 + * @author yjjie + * @date 2025/12/26 15:29 + */ +@Data +@Accessors(chain = true) +public class WechatEntryIdCardReqDto { + + /** + * 【必填】 + * 身份证人像面照片 + * 1、请上传个体户经营者/法定代表人的身份证人像面照片; + * 2、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 3、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "id_card_copy") + private String idCardCopy; + + /** + * 【必填】 + * 身份证国徽面照片 + * 、请上传个体户经营者/法定代表人的身份证国徽面照片; + * 2、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 3、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "id_card_national") + private String idCardNational; + + /** + * 【必填】 + * 身份证姓名 + * 1、请填写个体户经营者/法定代表人对应身份证的姓名; + * 2、长度为2-100个字符; + * 3、前后不能有空格、制表符、换行符; + * 4、不能仅含数字、特殊字符; + * 5、仅能填写数字、英文字母、汉字及特殊字符; + * 6、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + * 加密 + */ + @JSONField(name = "id_card_name") + private String idCardName; + + /** + * 必填 + * 身份证号码 + * 1、请填写个体户经营者/法定代表人对应身份证的号码; + * 2、17位数字+1位数字|X ,该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + * 加密 + */ + @JSONField(name = "id_card_number") + private String idCardNumber; + + /** + * 【选填】 + * 身份证居住地址 + * 1、主体类型为企业时,需要填写。其他主体类型,无需上传; + * 2、请按照身份证住址填写,如广东省深圳市南山区xx路xx号xx室; + * 3、长度为4-128个字符; + * 4、前后不能有空格、制表符、换行符; + * 5、不能仅含数字、特殊字符; + * 6、仅能填写数字、英文字母、汉字及特殊字符; + * 7、仅支持utf-8格式; + * 8、 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + * 加密 + */ + @JSONField(name = "id_card_address") + private String idCardAddress; + + /** + * 必填 + * 身份证有效期开始时间 + * 1、请填写身份证有效期开始时间,格式为yyyy-MM-dd; + * 2、开始时间不能小于1900-01-01; + * 3、开始时间不能大于等于当前日期。 + */ + @JSONField(name = "card_period_begin") + private String cardPeriodBegin; + + /** + * 必填 + * 身份证有效期截止时间 + * 1、日期格式应满足合法的YYYY-MM-DD格式或长期; + * 2、结束时间需大于开始时间。 + */ + @JSONField(name = "card_period_end") + private String cardPeriodEnd; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/id/WechatEntryIdDocInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/id/WechatEntryIdDocInfoReqDto.java new file mode 100644 index 000000000..b5cae26ba --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/id/WechatEntryIdDocInfoReqDto.java @@ -0,0 +1,100 @@ +package com.czg.third.wechat.dto.req.entry.id; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 其他类型证件信息 + * @author yjjie + * @date 2025/12/26 15:42 + */ +@Data +@Accessors(chain = true) +public class WechatEntryIdDocInfoReqDto { + + /** + * 【必填】 + * 证件正面照片 + * 1、证件类型不为“身份证”时,上传证件正面照片; + * 2、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 3、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "id_doc_copy") + private String idDocCopy; + + /** + * 【选填】 + * 证件反面照片 + * 1、若证件类型为往来通行证、外国人居留证、港澳居民居住证、台湾居民居住证时,上传证件反面照片; + * 2、若证件类型为护照,无需上传反面照片; + * 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS; + * 4、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】; + * 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "id_doc_copy_back") + private String idDocCopyBack; + + /** + * 【必填】 + * 证件姓名 + * 1、请填写经营者/法定代表人的证件姓名; + * 2、长度为2-100个字符; + * 3、前后不能有空格、制表符、换行符; + * 4、不能仅含数字、特殊字符; + * 5、仅能填写数字、英文字母、汉字及特殊字符; + * 6、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "id_doc_name") + private String idDocName; + + /** + * 【必填】 + * 证件号码 + * 1、请填写经营者/法定代表人的证件号码: + * 护照(限境外人士):4-15位 数字|字母|连字符; + * 中国香港居民--来往内地通行证:H/h开头+8或10位数字/字母; + * 中国澳门居民--来往内地通行证:M/m开头+8或10位数字/字母; + * 中国台湾居民--来往大陆通行证:8位数字或10位数字; + * 外国人居留证:15位 数字|字母; + * 台湾居民居住证/港澳居民居住证:17位数字+1位数字|X; + * 2、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "id_doc_number") + private String idDocNumber; + + /** + * 【选填】 + * 证件居住地址 + * 1、主体类型为企业时,需要填写。其他主体类型,无需上传; + * 2、请按照身份证住址填写,如广东省深圳市南山区xx路xx号xx室; + * 3、长度为4-128个字符; + * 4、前后不能有空格、制表符、换行符; + * 5、不能仅含数字、特殊字符; + * 6、仅能填写数字、英文字母、汉字及特殊字符; + * 7、仅支持utf-8格式; + * 8、 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。 + */ + @JSONField(name = "id_doc_address") + private String idDocAddress; + + /** + * 必填 + * 证件有效期开始时间 + * 1、日期格式应满足合法的YYYY-MM-DD格式; + * 2、开始时间不能小于1900-01-01; + * 3、开始时间不能大于等于当前日期。 + */ + @JSONField(name = "doc_period_begin") + private String docPeriodBegin; + + /** + * 必填 + * 证件有效期结束时间 + * 1、日期格式应满足合法的YYYY-MM-DD格式或长期; + * 2、结束时间需大于开始时间。 + */ + @JSONField(name = "doc_period_end") + private String docPeriodEnd; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/resp/WechatAuditDetail.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/resp/WechatAuditDetail.java new file mode 100644 index 000000000..6e9deea6f --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/resp/WechatAuditDetail.java @@ -0,0 +1,29 @@ +package com.czg.third.wechat.dto.resp; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @author yjjie + * @date 2026/1/7 15:46 + */ +@Data +public class WechatAuditDetail { + /** + * 字段名 + */ + @JSONField(name = "field") + public String field; + + /** + * 字段名称 + */ + @JSONField(name = "field_name") + public String fieldName; + + /** + * 驳回原因 + */ + @JSONField(name = "reject_reason") + public String rejectReason; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/resp/WechatQueryStateResp.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/resp/WechatQueryStateResp.java new file mode 100644 index 000000000..c351d3f3f --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/resp/WechatQueryStateResp.java @@ -0,0 +1,70 @@ +package com.czg.third.wechat.dto.resp; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 微信进件状态查询返回参数 + * @author yjjie + * @date 2026/1/7 15:42 + */ +@Data +@Accessors(chain = true) +public class WechatQueryStateResp { + + /** + * 业务申请编号 + */ + @JSONField(name = "business_code") + public String businessCode; + + /** + * 微信支付申请单号 + */ + @JSONField(name = "applyment_id") + public String applyId; + + /** + * 特约商户号 + * 当申请单状态为APPLYMENT_STATE_TO_BE_SIGNED、APPLYMENT_STATE_SIGNING、APPLYMENT_STATE_FINISHED时才返回。 + */ + @JSONField(name = "sub_mchid") + public String subMchId; + + /** + * 超级管理员签约链接 + * 1、超级管理员用微信扫码,关注“微信支付商家助手”公众号后,公众号将自动发送签约消息;超管需点击消息,根据指引完成核对联系信息、账户验证、签约等操作; + * 2、超管完成核对联系信息,后续申请单进度可通过公众号自动通知超级管理员。 + */ + @JSONField(name = "sign_url") + public String signUrl; + + /** + * 申请单状态 + * APPLYMENT_STATE_EDITTING:(编辑中)提交申请发生错误导致,请尝试重新提交。 + * APPLYMENT_STATE_AUDITING:(审核中)申请单正在审核中,超级管理员用微信打开“签约链接”,完成绑定微信号后,申请单进度将通过微信公众号通知超级管理员,引导完成后续步骤。 + * APPLYMENT_STATE_REJECTED:(已驳回)请按照驳回原因修改申请资料,超级管理员用微信打开“签约链接”,完成绑定微信号,后续申请单进度将通过微信公众号通知超级管理员。 + * APPLYMENT_STATE_TO_BE_CONFIRMED:(待账户验证)请超级管理员使用微信打开返回的“签约链接”,根据页面指引完成账户验证。 + * APPLYMENT_STATE_TO_BE_SIGNED:(待签约)请超级管理员使用微信打开返回的“签约链接”,根据页面指引完成签约。 + * APPLYMENT_STATE_SIGNING:(开通权限中)系统开通相关权限中,请耐心等待。 + * APPLYMENT_STATE_FINISHED:(已完成)商户入驻申请已完成。 + * APPLYMENT_STATE_CANCELED:(已作废)申请单已被撤销。 + */ + @JSONField(name = "applyment_state") + public String applyState; + + /** + * 申请状态描述 + */ + @JSONField(name = "applyment_state_msg") + public String applyStateMsg; + + /** + * 驳回原因详情 + */ + @JSONField(name = "audit_detail") + public List auditDetail; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/AsyncTaskExecutor.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/AsyncTaskExecutor.java new file mode 100644 index 000000000..619acdf11 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/AsyncTaskExecutor.java @@ -0,0 +1,157 @@ +package com.czg.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Java 21 异步多任务执行工具类 + * 功能:异步执行多个任务,等待所有任务完成后统一返回结果(包含成功/失败信息) + * 特性:基于虚拟线程、支持泛型、完善的异常处理、可自定义线程池 + * + * @author yjjie + * @date 2026/1/6 18:21 + */ +public class AsyncTaskExecutor { + + // 默认线程池:Java 21 虚拟线程池(轻量级、高并发) + private static final ExecutorService DEFAULT_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor(); + + /** + * 执行多个异步任务,等待所有任务完成后统一返回结果 + * + * @param tasks 任务列表(每个任务是一个 Supplier 函数式接口,封装具体业务逻辑) + * @param 任务返回值类型 + * @return 所有任务的执行结果(包含成功/失败信息) + */ + public static List> executeAll(List> tasks) { + return executeAll(tasks, DEFAULT_EXECUTOR); + } + + /** + * 执行多个异步任务(自定义线程池),等待所有任务完成后统一返回结果 + * + * @param tasks 任务列表 + * @param executor 自定义线程池(如需要使用传统线程池可传入) + * @param 任务返回值类型 + * @return 所有任务的执行结果 + */ + public static List> executeAll(List> tasks, ExecutorService executor) { + // 校验入参 + if (tasks == null || tasks.isEmpty()) { + return List.of(); + } + if (executor == null) { + throw new IllegalArgumentException("线程池不能为null"); + } + + // 1. 提交所有异步任务,获取CompletableFuture列表 + List>> futureList = tasks.stream() + .map(task -> CompletableFuture.supplyAsync( + () -> executeSingleTask(task), + executor + )) + .collect(Collectors.toList()); + + // 2. 等待所有任务完成(无超时) + CompletableFuture allFutures = CompletableFuture.allOf( + futureList.toArray(new CompletableFuture[0]) + ); + + try { + // 阻塞等待所有任务完成(可根据业务需求添加超时,如 allFutures.get(10, TimeUnit.SECONDS)) + allFutures.get(); + } catch (Exception e) { + // 全局等待异常(如超时、中断),标记所有未完成的任务为失败 + handleGlobalException(futureList, e); + } + + // 3. 收集所有任务结果 + return futureList.stream() + // 显式指定泛型类型,让编译器明确知道map的返回类型是TaskResult + .>map(future -> { + try { + // 这里强制指定泛型,避免类型推断模糊 + return future.get(); + } catch (Exception e) { + // 理论上不会走到这里,因为singleTask已捕获异常,allOf已等待完成 + return new TaskResult<>(null, false, "结果收集异常:" + e.getMessage()); + } + }) + .collect(Collectors.toList()); + } + + /** + * 执行单个任务,捕获任务执行过程中的异常 + */ + private static TaskResult executeSingleTask(Supplier task) { + try { + T result = task.get(); + return new TaskResult<>(result, true, null); + } catch (Exception e) { + // 捕获单个任务的所有异常,封装为失败结果 + return new TaskResult<>(null, false, "任务执行失败:" + e.getMessage()); + } + } + + /** + * 处理全局等待过程中的异常(如超时、中断),标记未完成任务为失败 + */ + private static void handleGlobalException(List>> futureList, Exception e) { + String errorMsg = "全局等待异常:" + e.getMessage(); + futureList.forEach(future -> { + if (!future.isDone()) { + // 取消未完成的任务,并标记为失败 + future.complete(new TaskResult<>(null, false, errorMsg)); + } + }); + // 恢复线程中断状态(如果是中断异常) + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + } + + /** + * 任务结果封装类 + * 包含:返回值、是否成功、失败信息 + * + * @param 结果类型 + * @param result Getter 方法 任务返回值(成功时非null) + * @param success 是否执行成功 + * @param errorMsg 失败信息(失败时非null) + */ + public record TaskResult(T result, boolean success, String errorMsg) { + + // 重写toString,方便打印结果 + @NotNull + @Override + public String toString() { + if (success) { + return "TaskResult{success=true, result=" + result + "}"; + } else { + return "TaskResult{success=false, errorMsg='" + errorMsg + "'}"; + } + } + } + + /** + * 关闭默认线程池(可选,如应用退出时调用) + */ + public static void shutdownDefaultExecutor() { + DEFAULT_EXECUTOR.shutdown(); + try { + if (!DEFAULT_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { + DEFAULT_EXECUTOR.shutdownNow(); + } + } catch (InterruptedException e) { + DEFAULT_EXECUTOR.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/UploadFileUtil.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/UploadFileUtil.java new file mode 100644 index 000000000..485436290 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/UploadFileUtil.java @@ -0,0 +1,106 @@ +package com.czg.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; + +/** + * @author yjjie + * @date 2026/1/4 13:58 + */ +@Slf4j +public class UploadFileUtil { + + // 匹配常见的图片扩展名 + private static final Pattern PATTERN = Pattern.compile("\\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)(?:[\\?#]|$)", Pattern.CASE_INSENSITIVE); + + /** + * 使用正则表达式提取图片后缀 + */ + public static String extractImageExtension(String imageUrl) { + java.util.regex.Matcher matcher = PATTERN.matcher(imageUrl); + + if (matcher.find()) { + String extension = matcher.group(1).toLowerCase(); + // 处理jpeg的情况 + return "jpeg".equals(extension) ? "jpg" : extension; + } + // 默认后缀 + return "png"; + } + + /** + * 根据图片URL获取图片文件 + * + * @param url 图片URL + * @return 图片文件 + */ + public static File getFileByUrl(String url) throws IOException { + byte[] bytes = downloadImage(url); + + Path tempFile = Files.createTempFile("image_", "." + extractImageExtension(url)); + Files.write(tempFile, bytes); + + return tempFile.toFile(); + } + + /** + * 下载图片 + * @param url 图片地址 + * @return 图片字节数组 + */ + public static byte[] downloadImage(String url) { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .GET() + .build(); + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); + + if (response.statusCode() != 200) { + throw new RuntimeException("Failed to download image, status code: " + response.statusCode()); + } + + return response.body(); + } catch (Exception e) { + log.error("Failed to download image: {}", e.getMessage()); + return new byte[0]; + } + } + + /** + * 从URL中提取文件名 + * + * @param url 图片URL + * @return 提取的文件名,失败则返回默认名 + */ + public static String extractFileNameFromUrl(String url) { + try { + if (url.contains("/")) { + String fileName = url.substring(url.lastIndexOf("/") + 1); + // 如果文件名包含参数,截取?之前的部分 + if (fileName.contains("?")) { + fileName = fileName.substring(0, fileName.indexOf("?")); + } + // 如果提取的文件名有效,直接返回 + if (fileName.contains(".")) { + return fileName; + } + } + } catch (Exception e) { + log.warn("提取文件名失败,使用默认文件名", e); + } + // 默认文件名 + return "upload_" + System.currentTimeMillis() + ".png"; + } +} diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgBaseReq.java b/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgBaseReq.java deleted file mode 100644 index 1303047d1..000000000 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgBaseReq.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.czg.entity.req; - -import lombok.Data; -import lombok.NonNull; - -/** - * @author ww - */ -@Data -public class CzgBaseReq { - //必填范围 - /** - * 订单标题 - */ - private String subject; - - /** - * 订单描述 String(256) - */ - private String body; - /** - * 交易金额 - */ - private Long amount; - /** - * 货币类型 cny - */ - private String currency = "cny"; - /** - * 商户订单号 String(30) - * 操作方式+雪花算法 - * 收银机客户端 PC+雪花ID - * 微信小程序 WX+雪花ID - * 支付宝小程序 ALI+雪花ID - * PC管理端 WEB+雪花ID - * APP管理端 APP+雪花ID - *

- * 退款 re+雪花ID - */ - private String mchOrderNo; - /** - * 门店编号 tb_shop_merchant 的 store_id - */ - private String storeId; - - - //非必填项 - /** - * 订单备注 String(50) - */ - private String buyerRemark; - /** - * 异步通知地址 String(128) - * 支付结果异步回调URL,只有传了该值才会发起回调 - */ - private String notifyUrl; - /** - * 失效时间 int 15 - * 订单失效时间,单位分钟,默认15小时. - * 取值范围 5-1440 分钟 - * 订单在(创建时间+失效时间)后失效 - */ - private Integer expiredTime; - - - /** - * 分账模式: - * 0-该笔订单不允许分账[默认], - * 1-实时分账 - * 2-延时分账 - */ - private Integer divisionMode; - /** - * 分账详情 - */ - private String divisionDetail; - /** - * 扩展参数 String(512) - * 商户扩展参数,回调时会原样返回 - * * 扩展参数 - * * { - * * "pay_type": "order/vip" - * * } - */ - private String extParam; - - /** - * 花呗分期数 - * 支付宝交易时可传 而且金额需要大于或等于 100.00元时生效 - * 3/6/12 - */ - private Integer hbFqNum; - /** - * 卖家是否承担手续费 - * 100,卖家承担手续费 - * 0,买家承担手续费 - */ - private Integer hbFqPercent; - /** - * 是否禁用信用卡 - * -1:不禁用 1:禁用 - * 不传默认为不限制 - */ - private Integer limitPay; - - public CzgBaseReq() { - - } - - public CzgBaseReq(String mchOrderNo, Long amount, String body, - String buyerRemark, String extParam) { - this.mchOrderNo = mchOrderNo; - this.body = body; - this.amount = amount; - this.buyerRemark = buyerRemark; - this.extParam = extParam; - } - - public void assignMerchant(@NonNull String storeId, @NonNull String subject, @NonNull String notifyUrl) { - this.storeId = storeId; - this.subject = subject; - this.notifyUrl = notifyUrl; - } -} diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgH5PayReq.java b/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgH5PayReq.java deleted file mode 100644 index c3259e2f5..000000000 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgH5PayReq.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.czg.entity.req; - - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; - -/** - * h5支付请求参数 - * - * @author ww - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class CzgH5PayReq extends CzgBaseReq { - //必填范围 - /** - * 用户IP 支付的用户IP - */ - private String clientIp; - private String payType; - //? - private String openId; - - //非必填范围 - /** - * 跳转通知地址 - * 支付结果同步跳转通知URL - */ - private String returnUrl; - - - public CzgH5PayReq(@NonNull String mchOrderNo, @NonNull Long amount, String body, @NonNull String clientIp, - String returnUrl, String buyerRemark, @NonNull String extParam) { - super(mchOrderNo, amount, body, buyerRemark, extParam); - this.clientIp = clientIp; - this.returnUrl = returnUrl; - } -} diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgJsPayReq.java b/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgJsPayReq.java deleted file mode 100644 index 87b19033d..000000000 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgJsPayReq.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.czg.entity.req; - - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; - -/** - * 公众号/生活号/银联js支付 - * - * @author ww - *

- * ● 码牌、台卡、立牌等 - * 用户通过扫描微信/支付宝/云闪付APP静态二维码,输入订单金额进行付款动作。 - * ● 公众号 / 生活号商城 - * 用户通过公众号下单,输入密码完成付款动作。适用于自有公众号主体交易 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class CzgJsPayReq extends CzgBaseReq { - //必填范围 - /** - * 用户唯一标识 String(30) - * 微信支付时,传入用户的openId; - * 支付宝支付和银联支付时,传入用户的userId - */ - private String userId; - /** - * 用户IP - * 需要传付款用户客户端IP地址 - */ - private String clientIp; - /** - * 微信 WECHAT; - * 支付宝 ALIPAY; - * 银联云闪付 UNIONPAY - */ - private String payType; - - - //非必填范围 - /** - * 子商户appid ,微信付款支付的时候 需要上送 - */ - private String subAppid; - /** - * 跳转通知地址 - * 支付结果同步跳转通知URL - */ - private String returnUrl; - - /** - * 银联js支付成功前端跳转 - */ - private String frontUrl; - /** - * 银联js支付失败前端跳转地址 - */ - private String frontFailUrl; - - public CzgJsPayReq(@NonNull String mchOrderNo, - @NonNull Long amount, String body, - @NonNull String openId, @NonNull String clientIp, - String returnUrl, String buyerRemark,@NonNull String extParam) { - super(mchOrderNo, amount, body, buyerRemark, extParam); - this.userId = openId; - this.clientIp = clientIp; - this.returnUrl = returnUrl; - } -} - diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgLtPayReq.java b/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgLtPayReq.java deleted file mode 100644 index fb88d8163..000000000 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgLtPayReq.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.czg.entity.req; - - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; - - -/** - * 小程序支付 - * - * @author ww - * 自有小程序拉起支付 - * 自有小程序拉起服务商小程序,(微信)半屏小程序支付 - * 半屏小程序需要在商户自己的小程序中的半屏小程序配置里面申请 否则会拉起全屏小程序支付 - *

- * 半屏小程序支付适用场景是商户自己的小程序被纳入微信实物电商类型小程序,导致间联商户无法绑定这类的小程序APPID - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class CzgLtPayReq extends CzgBaseReq { - //必填范围 - /** - * 用户唯一标识 String(30) - * 微信支付时,传入用户的 openId; - * 支付宝支付和银联支付时,传入用户的 userId - */ - private String userId; - private String payType; - /** - * 用户 IP 支付的用户 IP - */ - private String clientIp; - - - //非必填范围 - /** - * 跳转通知地址 - * 支付结果同步跳转通知 URL - */ - private String returnUrl; - /** - * 子商户 appid ,微信付款支付的时候 需要上送 - * isScreen 为 false 的情况下需要传入 - */ - private String subAppid; - /** - * 是否半屏 - * 是否使用半屏小程序,默认为 false - * 前提小程序被纳入了实物电商类型的小程序,间联商户无法绑定这类的小程序的 appid - */ - private boolean isScreen; - - public CzgLtPayReq(@NonNull String mchOrderNo, @NonNull Long amount, @NonNull String payType, - String body, @NonNull String openId, @NonNull String clientIp, - String returnUrl, String buyerRemark, @NonNull String extParam) { - super(mchOrderNo, amount, body, buyerRemark, extParam); - this.userId = openId; - this.payType = "aliPay".equals(payType) ? "ALIPAY" : "WECHAT"; - this.clientIp = clientIp; - this.returnUrl = returnUrl; - } -} diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgMicroPayReq.java b/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgMicroPayReq.java deleted file mode 100644 index d53dbebb1..000000000 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgMicroPayReq.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.czg.entity.req; - - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; - -/** - * 反扫请求参数 - * - * @author ww - *

- * 被扫免密同步返回支付结果,不推送异步通知。 - * 被扫输密,推送异步通知。 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class CzgMicroPayReq extends CzgBaseReq { - //必填范围 - /** - * 付款码信息 String(30) - * 扫码设备获取微信或支付宝上付款的信息 - */ - private String authCode; - - //非必填范围 - /** - * 子商户appid ,微信付款支付的时候 需要上送 - */ - private String subAppid; - - public CzgMicroPayReq(@NonNull String mchOrderNo, @NonNull Long amount, String body, - @NonNull String authCode, String buyerRemark, @NonNull String extParam) { - super(mchOrderNo, amount, body, buyerRemark, extParam); - this.authCode = authCode; - } -} diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgScanPayReq.java b/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgScanPayReq.java deleted file mode 100644 index 4727dcb19..000000000 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/req/CzgScanPayReq.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.czg.entity.req; - - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; - -/** - * 正扫支付请求参数 - * - * @author ww - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class CzgScanPayReq extends CzgBaseReq { - // 必填项 - /** - * 用户IP 支付的用户IP - */ - private String clientIp; - - //非必填项 - /** - * 跳转通知地址 - * String(128) - */ - private String returnUrl; - - public CzgScanPayReq(@NonNull String mchOrderNo, @NonNull Long amount, String body, - @NonNull String clientIp, String returnUrl, String buyerRemark, @NonNull String extParam) { - super(mchOrderNo, amount, body, buyerRemark, extParam); - this.clientIp = clientIp; - this.returnUrl = returnUrl; - } -} diff --git a/cash-sdk/czg-pay/pom.xml b/cash-sdk/poly-pay/pom.xml similarity index 93% rename from cash-sdk/czg-pay/pom.xml rename to cash-sdk/poly-pay/pom.xml index 4763f4eb4..6e9aa3184 100644 --- a/cash-sdk/czg-pay/pom.xml +++ b/cash-sdk/poly-pay/pom.xml @@ -9,7 +9,7 @@ 1.0.0 - czg-pay + poly-pay 21 diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/CzgPayUtils.java b/cash-sdk/poly-pay/src/main/java/com/czg/PolyPayUtils.java similarity index 52% rename from cash-sdk/czg-pay/src/main/java/com/czg/CzgPayUtils.java rename to cash-sdk/poly-pay/src/main/java/com/czg/PolyPayUtils.java index 8574ee21b..c39a3cd7a 100644 --- a/cash-sdk/czg-pay/src/main/java/com/czg/CzgPayUtils.java +++ b/cash-sdk/poly-pay/src/main/java/com/czg/PolyPayUtils.java @@ -8,26 +8,31 @@ import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.TypeReference; -import com.czg.entity.CzgBaseReqParams; -import com.czg.entity.CzgBaseRespParams; -import com.czg.entity.req.*; +import com.czg.entity.PolyBaseReq; +import com.czg.entity.PolyBaseResp; +import com.czg.entity.notify.CzgPayNotifyDTO; import com.czg.entity.resp.*; import com.czg.enums.CzgPayEnum; +import com.czg.pay.CzgPayBaseReq; +import com.czg.pay.CzgRefundReq; +import com.czg.pay.PayNotifyRespDTO; +import com.czg.resp.CzgRespCode; import com.czg.resp.CzgResult; import com.czg.utils.AssertUtil; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; /** * @author ww - * @description 超掌柜支付类 + * @description 聚合支付类 */ @Slf4j -public class CzgPayUtils { +public class PolyPayUtils { /** * h5支付 @@ -35,9 +40,10 @@ public class CzgPayUtils { * @param domain 域名 * @param appId 应用id tb_shop_merchant 表中的 app_id * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret + * CzgResult */ - public static CzgResult h5Pay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgH5PayReq bizData) { - return sendCzg(domain.concat(CzgPayEnum.H5PAY.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, bizData), CzgH5PayResp.class); + public static CzgResult> h5Pay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) { + return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.H5_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgH5PayResp.class)); } /** @@ -46,9 +52,10 @@ public class CzgPayUtils { * @param domain 域名 * @param appId 应用id tb_shop_merchant 表中的 app_id * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret + * CzgResult */ - public static CzgResult jsPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgJsPayReq bizData) { - return sendCzg(domain.concat(CzgPayEnum.JSPAY.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, bizData), CzgJsPayResp.class); + public static CzgResult> jsPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) { + return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.JS_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgJsPayResp.class)); } /** @@ -57,9 +64,10 @@ public class CzgPayUtils { * @param domain 域名 * @param appId 应用id tb_shop_merchant 表中的 app_id * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret + * CzgResult */ - public static CzgResult ltPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgLtPayReq bizData) { - return sendCzg(domain.concat(CzgPayEnum.LTPAY.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, bizData), CzgLtPayResp.class); + public static CzgResult> ltPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) { + return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.LT_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgLtPayResp.class)); } /** @@ -68,9 +76,10 @@ public class CzgPayUtils { * @param domain 域名 * @param appId 应用id tb_shop_merchant 表中的 app_id * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret + * CzgResult */ - public static CzgResult scanPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgScanPayReq bizData) { - return sendCzg(domain.concat(CzgPayEnum.SCANPAY.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, bizData), CzgScanPayResp.class); + public static CzgResult> scanPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) { + return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.SCAN_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgScanPayResp.class)); } /** @@ -79,9 +88,10 @@ public class CzgPayUtils { * @param domain 域名 * @param appId 应用id tb_shop_merchant 表中的 app_id * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret + * CzgResult */ - public static CzgResult microPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgMicroPayReq bizData) { - return sendCzg(domain.concat(CzgPayEnum.MICROPAY.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, bizData), CzgMicroPayResp.class); + public static CzgResult> microPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) { + return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.MICRO_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgMicroPayResp.class)); } /** @@ -98,7 +108,7 @@ public class CzgPayUtils { JSONObject queryPayOrder = new JSONObject(); queryPayOrder.put("payOrderId", payOrderId); queryPayOrder.put("mchOrderNo", mchOrderNo); - return sendCzg(domain.concat(CzgPayEnum.TRADE.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, queryPayOrder), CzgBaseResp.class); + return sendPolyPay(domain.concat(CzgPayEnum.TRADE.getUri()), PolyBaseReq.getInstance(appId, appSecret, queryPayOrder), CzgBaseResp.class); } @@ -110,7 +120,7 @@ public class CzgPayUtils { * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret */ public static CzgResult refundOrder(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgRefundReq bizData) { - return sendCzg(domain.concat(CzgPayEnum.REFUND.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, bizData), CzgRefundResp.class); + return sendPolyPay(domain.concat(CzgPayEnum.REFUND.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgRefundResp.class); } /** @@ -127,29 +137,55 @@ public class CzgPayUtils { JSONObject queryPayOrder = new JSONObject(); queryPayOrder.put("mchRefundNo", mchRefundNo); queryPayOrder.put("refundOrderId", refundOrderId); - return sendCzg(domain.concat(CzgPayEnum.QUERY_REFUND.getUri()), CzgBaseReqParams.getInstance(appId, appSecret, queryPayOrder), CzgRefundResp.class); + return sendPolyPay(domain.concat(CzgPayEnum.QUERY_REFUND.getUri()), PolyBaseReq.getInstance(appId, appSecret, queryPayOrder), CzgRefundResp.class); } /** * 回调数据处理 */ - public static JSONObject getCzg(CzgBaseRespParams respParams) { - AssertUtil.isNull(respParams, "超掌柜交易 回调数据为空"); - log.info("超掌柜交易请求响应,{}", respParams); + public static JSONObject getCzg(PolyBaseResp respParams) { + AssertUtil.isNull(respParams, "聚合交易 回调数据为空"); + log.info("聚合交易请求响应,{}", respParams); if (!"000000".equals(respParams.getCode())) { - log.error("超掌柜回调响应失败,{}", respParams); + log.error("聚合回调响应失败,{}", respParams); return null; } // if (StrUtil.isNotBlank(respParams.getSign())) { // if (validateSign(respParams.getSign(), respParams.getBizData())) { -// log.error("超掌柜回调 验签失败"); +// log.error("聚合回调 验签失败"); // } // } return JSONObject.parse(respParams.getBizData()); } + + /** + * 回调数据处理 + */ + public static PayNotifyRespDTO getNotifyResp(PolyBaseResp respParams) { + AssertUtil.isNull(respParams, "聚合交易 回调数据为空"); + log.info("聚合交易请求响应,{}", respParams); + if (!"000000".equals(respParams.getCode())) { + log.error("聚合回调响应失败,{}", respParams); + return null; + } + + CzgPayNotifyDTO dto = JSONObject.parseObject(respParams.getBizData(), CzgPayNotifyDTO.class); + + return new PayNotifyRespDTO() + .setMchOrderNo(dto.getMchOrderNo()) + .setThirdOrderNo(dto.getPayOrderId()) + .setAmount(dto.getAmount()) + .setPlatform(dto.getPayType()) + .setExtData(dto.getExtParam()) + .setPaySuccessTime(dto.getPayTime()) + .setErrorMsg("") + .setStatus(dto.getState()) + .setOriginalData(respParams.getBizData()); + } + /** * 默认Post * @@ -161,19 +197,19 @@ public class CzgPayUtils { * "data": "返回Json数据", * } */ - private static CzgResult sendCzg(String url, CzgBaseReqParams params, Class clazz) { + private static CzgResult sendPolyPay(String url, PolyBaseReq params, Class clazz) { CzgResult result = CzgResult.success(); Map reqMap = BeanUtil.beanToMap(params, false, false); params.setSign(MD5.create().digestHex(sortFields(new TreeMap<>(reqMap)))); - log.info("超掌柜交易请求参数,{}", JSONObject.toJSONString(params)); + log.info("聚合交易请求参数,{}", JSONObject.toJSONString(params)); try (HttpResponse resp = HttpRequest.post(url).body(JSONObject.toJSONString(params)).execute()) { if (resp.isOk()) { // 获取响应体 String respStr = resp.body(); if (StrUtil.isNotEmpty(respStr)) { - log.info("超掌柜交易请求响应元数据,{}", respStr); - CzgBaseRespParams respParams = JSONObject.parseObject(respStr, CzgBaseRespParams.class); - log.info("超掌柜交易请求响应,{}", respParams); + log.info("聚合交易请求响应元数据,{}", respStr); + PolyBaseResp respParams = JSONObject.parseObject(respStr, PolyBaseResp.class); + log.info("聚合交易请求响应,{}", respParams); result.setCode("000000".equals(respParams.getCode()) ? 200 : Integer.parseInt(respParams.getCode())); result.setMsg(respParams.getMsg()); @@ -187,15 +223,15 @@ public class CzgPayUtils { } } else { result.setCode(resp.getStatus()); - result.setMsg("超掌柜交易请求失败"); - log.error("超掌柜交易请求失败,状态码: {}", resp.getStatus()); + result.setMsg("聚合交易请求失败"); + log.error("聚合交易请求失败,状态码: {}", resp.getStatus()); } } else { result.setCode(resp.getStatus()); - result.setMsg("超掌柜交易请求失败"); + result.setMsg("聚合交易请求失败"); } } catch (Exception e) { - log.error("超掌柜交易请求异常", e); + log.error("聚合交易请求异常", e); } return result; } @@ -214,7 +250,7 @@ public class CzgPayUtils { private static String sortFields(TreeMap map) { if (CollectionUtil.isEmpty(map)) { - log.error("超掌柜支付参数为空!!!"); + log.error("聚合支付参数为空!!!"); throw new RuntimeException("参数为空"); } String sortParam = map.entrySet().stream() @@ -229,51 +265,41 @@ public class CzgPayUtils { } return sortParam; } + private static CzgResult> execPayResult(CzgResult res) { + CzgResult> result = CzgResult.success(); + if (res.getCode() != 200 || res.getData() == null) { + result.setCode(500); + result.setMsg(res.getMsg()); + return result; + } + CzgBaseResp data = res.getData(); -// public static void main(String[] args) { -//// String appId = "66691a6afdf641f0bf1dc701"; -// String appId = "66e3dd399a7621f45a6293c1"; -//// String appSecret = "jikd52TefZcSPI5hRWrfPSpQcXZrbqshbnLmqH6UattqspIDEzjbGvZmfwTW58RMf1XuPhN4zE1GbIjKy3b1oabgOx5n79faT93Si6i7g2IPSQJAln2NNsCSNynHIJ8"; -// String appSecret = "2p7TCixkN3FuhTqJyr23GNAfrqAqUt84T1IOSunCEEqFBP9gVkSO8CqrfNwNAJqLYuWmQou7lbwdW0Lb4zJVkBEdV7CPq3VhnbGDRIpQXpBNPOjJbor1IFGuLLOA7oll"; -// CzgJsPayReq bizData = new CzgJsPayReq("20250428150248328893", 100L, -// "订单支付", "or1l867cx6JFbLgmppwtG46AUhwg","1.80.211.145", "http://store.sxczgkj.com/h5/#/pages/user-order/user-order", "", ""); -// bizData.setCurrency("cny"); -// bizData.setPayType("WECHAT"); -// bizData.setSubAppid("wxd88fffa983758a30"); -// -// bizData.setSubject("订单支付"); -//// bizData.setStoreId("S2409148611"); -// bizData.setStoreId("S2406120331"); -// -// CzgBaseReqParams params = CzgBaseReqParams.getInstance(appId, appSecret, bizData); -// params.setVersion("1.0"); -// params.setReqId("57143686759273485473"); -// params.setReqTime("20250428150248"); -// params.setSignType("MD5"); -// CzgResult czgH5PayRespCzgResult = sendCzg("https://paymentapi.sxczgkj.cn/api/open/payment/jspay", params, CzgH5PayResp.class); -// System.out.println(czgH5PayRespCzgResult); -// } + Map map = new HashMap<>(); + switch (data) { + case CzgMicroPayResp ignored -> { + if ("TRADE_SUCCESS".equals(data.getState())) { + return result; + } else if ("TRADE_AWAIT".equals(data.getState())) { + result.setCode(CzgRespCode.WAIT_PAY.getCode()); + result.setMsg("等待用户付款"); + } else { + result.setCode(CzgRespCode.FAILURE.getCode()); + } + } + case CzgH5PayResp h5PayResp -> + map = JSONObject.parseObject(JSONObject.toJSONString(h5PayResp.getPayInfo())); + case CzgJsPayResp jsPayResp -> + map = JSONObject.parseObject(JSONObject.toJSONString(jsPayResp.getPayInfo())); + case CzgLtPayResp ltPayResp -> + map = JSONObject.parseObject(JSONObject.toJSONString(ltPayResp.getPayInfo())); + case CzgScanPayResp scanPayResp -> + map = JSONObject.parseObject(JSONObject.toJSONString(scanPayResp.getPayInfo())); + default -> throw new IllegalStateException("Unexpected value: " + data); + } + result.setData(map); -// public static void main(String[] args) { -// CzgResult result = CzgResult.success(); -// CzgBaseRespParams respParams = new CzgBaseRespParams(); -// respParams.setCode("000000"); -// respParams.setMsg("成功"); -// respParams.setSign("6c0f1e11b0d3a16298c2dfeee8c1491a"); -// respParams.setBizData("{\"amount\":500,\"currency\":\"cny\",\"ifCode\":\"lklspay\",\"mchOrderNo\":\"WX1889977729515615615\",\"mercNo\":\"B240612563201\",\"note\":\"成功\",\"payOrderId\":\"202502151890598483156443138V6W\",\"payType\":\"WECHAT\",\"settlementType\":\"D1\",\"state\":\"TRADE_SUCCESS\",\"storeId\":\"S2406125309\",\"subject\":\"超掌柜\",\"tradeFee\":2}"); -// respParams.setTimestamp("20250215110620"); -// log.info("超掌柜交易请求响应,{}", respParams); -// -// result.setCode("000000".equals(respParams.getCode()) ? 200 : Integer.parseInt(respParams.getCode())); -// result.setMsg(respParams.getMsg()); -// if ("000000".equals(respParams.getCode()) && StrUtil.isNotBlank(respParams.getSign())) { -// if (validateSign(respParams.getSign(), JSONObject.toJSONString(respParams))) { -// log.info("验签失败"); -// } -// result.setData(JSONObject.parseObject(respParams.getBizData(), CzgMicroPayResp.class)); -// } -// System.out.println(JSONObject.toJSONString(result)); -// } + return result; + } } diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/CzgBaseReqParams.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/PolyBaseReq.java similarity index 86% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/CzgBaseReqParams.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/PolyBaseReq.java index a255b26b8..fc9e4dfc1 100644 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/CzgBaseReqParams.java +++ b/cash-sdk/poly-pay/src/main/java/com/czg/entity/PolyBaseReq.java @@ -12,7 +12,7 @@ import lombok.Data; * @author ww */ @Data -public class CzgBaseReqParams { +public class PolyBaseReq { /** * 应用ID tb_shop_merchant 表中的 app_id @@ -69,7 +69,7 @@ public class CzgBaseReqParams { * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret * @param bizData JSON 形式的字符串 */ - public CzgBaseReqParams(String appId, String bizData, String appSecret) { + public PolyBaseReq(String appId, String bizData, String appSecret) { this.appId = appId; this.bizData = bizData; this.appSecret = appSecret; @@ -82,8 +82,8 @@ public class CzgBaseReqParams { * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret * @param bizData JSON 形式的字符串 */ - public static CzgBaseReqParams getInstance(String appId, String appSecret, Object bizData) { - return new CzgBaseReqParams(appId, ObjectUtil.isNotEmpty(bizData) ? JSONObject.toJSONString(bizData) : "", appSecret); + public static PolyBaseReq getInstance(String appId, String appSecret, Object bizData) { + return new PolyBaseReq(appId, ObjectUtil.isNotEmpty(bizData) ? JSONObject.toJSONString(bizData) : "", appSecret); } } diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/CzgBaseRespParams.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/PolyBaseResp.java similarity index 96% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/CzgBaseRespParams.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/PolyBaseResp.java index 74ab343ef..206c03843 100644 --- a/cash-sdk/czg-pay/src/main/java/com/czg/entity/CzgBaseRespParams.java +++ b/cash-sdk/poly-pay/src/main/java/com/czg/entity/PolyBaseResp.java @@ -6,7 +6,7 @@ import lombok.Data; * @author ww */ @Data -public class CzgBaseRespParams { +public class PolyBaseResp { /** diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/notify/CzgPayNotifyDTO.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/notify/CzgPayNotifyDTO.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/notify/CzgPayNotifyDTO.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/notify/CzgPayNotifyDTO.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/notify/CzgRefundNotifyDTO.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/notify/CzgRefundNotifyDTO.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/notify/CzgRefundNotifyDTO.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/notify/CzgRefundNotifyDTO.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgBaseResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgBaseResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgBaseResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgBaseResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgH5PayResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgH5PayResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgH5PayResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgH5PayResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgJsPayResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgJsPayResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgJsPayResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgJsPayResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgLtPayResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgLtPayResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgLtPayResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgLtPayResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgMicroPayResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgMicroPayResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgMicroPayResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgMicroPayResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgRefundResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgRefundResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgRefundResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgRefundResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgScanPayResp.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgScanPayResp.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/CzgScanPayResp.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/CzgScanPayResp.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgH5PayInfo.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgH5PayInfo.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgH5PayInfo.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgH5PayInfo.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgLitePayInfo.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgLitePayInfo.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgLitePayInfo.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgLitePayInfo.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgPayInfo.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgPayInfo.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgPayInfo.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgPayInfo.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgScanPayInfo.java b/cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgScanPayInfo.java similarity index 100% rename from cash-sdk/czg-pay/src/main/java/com/czg/entity/resp/pay/CzgScanPayInfo.java rename to cash-sdk/poly-pay/src/main/java/com/czg/entity/resp/pay/CzgScanPayInfo.java diff --git a/cash-sdk/czg-pay/src/main/java/com/czg/enums/CzgPayEnum.java b/cash-sdk/poly-pay/src/main/java/com/czg/enums/CzgPayEnum.java similarity index 58% rename from cash-sdk/czg-pay/src/main/java/com/czg/enums/CzgPayEnum.java rename to cash-sdk/poly-pay/src/main/java/com/czg/enums/CzgPayEnum.java index 20a61e52d..c835799ec 100644 --- a/cash-sdk/czg-pay/src/main/java/com/czg/enums/CzgPayEnum.java +++ b/cash-sdk/poly-pay/src/main/java/com/czg/enums/CzgPayEnum.java @@ -8,11 +8,11 @@ import lombok.Getter; @Getter public enum CzgPayEnum { - SCANPAY("/api/open/payment/scanpay", "PC扫码支付"), - MICROPAY("/api/open/payment/micropay", "聚合反扫(B扫C)/ 快捷收款"), - JSPAY("/api/open/payment/jspay", "公众号/生活号/银联js支付"), - LTPAY("/api/open/payment/ltpay", "小程序支付"), - H5PAY("/api/open/payment/h5pay", "手机网页支付"), + SCAN_PAY("/api/open/payment/scanpay", "PC扫码支付"), + MICRO_PAY("/api/open/payment/micropay", "聚合反扫(B扫C)/ 快捷收款"), + JS_PAY("/api/open/payment/jspay", "公众号/生活号/银联js支付"), + LT_PAY("/api/open/payment/ltpay", "小程序支付"), + H5_PAY("/api/open/payment/h5pay", "手机网页支付"), TRADE("/api/open/query/trade", "订单状态查询"), REFUND("/api/open/order/refund", "统一退款 D0退款需要使用平台户退款"), diff --git a/cash-sdk/pom.xml b/cash-sdk/pom.xml index 55ee0e62b..022803d97 100644 --- a/cash-sdk/pom.xml +++ b/cash-sdk/pom.xml @@ -12,7 +12,8 @@ third sdk 第三方内容 - czg-pay + poly-pay + aggregation-pay cash-sdk @@ -28,5 +29,10 @@ cash-common-tools ${project.version} + + com.czg + cash-common-service + ${project.version} + \ No newline at end of file diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/mapper/QuickMenuMapper.java b/cash-service/account-service/src/main/java/com/czg/service/account/mapper/QuickMenuMapper.java new file mode 100644 index 000000000..eedbb8b62 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/mapper/QuickMenuMapper.java @@ -0,0 +1,14 @@ +package com.czg.service.account.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.czg.account.entity.QuickMenu; + +/** + * 悬浮窗配置 映射层。 + * + * @author ww + * @since 2025-12-29 + */ +public interface QuickMenuMapper extends BaseMapper { + +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/CallTableServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/CallTableServiceImpl.java index 53d467fe9..6f46a4fcc 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/CallTableServiceImpl.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/CallTableServiceImpl.java @@ -14,9 +14,9 @@ import com.czg.config.RedisCst; import com.czg.constants.ParamCodeCst; import com.czg.exception.CzgException; import com.czg.resp.CzgResult; +import com.czg.service.RedisService; import com.czg.service.account.mapper.CallQueueMapper; import com.czg.service.account.mapper.CallTableMapper; -import com.czg.service.account.util.FunUtil; import com.czg.service.account.util.WechatMiniMsgUtil; import com.czg.system.dto.SysParamsDTO; import com.czg.system.service.SysParamsService; @@ -29,6 +29,7 @@ import jakarta.annotation.Resource; import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.config.annotation.DubboService; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.util.*; @@ -41,7 +42,7 @@ import java.util.stream.Collectors; * @author zs * @since 2025-02-21 */ -@DubboService +@Service public class CallTableServiceImpl extends ServiceImpl implements CallTableService { @DubboReference private SysParamsService sysParamsService; @@ -51,10 +52,10 @@ public class CallTableServiceImpl extends ServiceImpl { + return redisService.runFunAndCheckKey(() -> { String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId()); String value = stringRedisTemplate.opsForValue().get(callNumKey); AtomicReference newVal = new AtomicReference<>(""); // 初始化 if (StrUtil.isBlank(value)) { - Boolean setFlag = FunUtil.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag, + Boolean setFlag = RedisService.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag, _ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey))); if (setFlag) { diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/QuickMenuServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/QuickMenuServiceImpl.java new file mode 100644 index 000000000..d9b76cad7 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/QuickMenuServiceImpl.java @@ -0,0 +1,18 @@ +package com.czg.service.account.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.czg.account.entity.QuickMenu; +import com.czg.account.service.QuickMenuService; +import com.czg.service.account.mapper.QuickMenuMapper; +import org.springframework.stereotype.Service; + +/** + * 悬浮窗配置 服务层实现。 + * + * @author ww + * @since 2025-12-29 + */ +@Service +public class QuickMenuServiceImpl extends ServiceImpl implements QuickMenuService{ + +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopMerchantServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopMerchantServiceImpl.java deleted file mode 100644 index 72c178e79..000000000 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopMerchantServiceImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.czg.service.account.service.impl; - -import cn.hutool.core.bean.BeanUtil; -import com.czg.account.dto.merchant.ShopMerchantEditDTO; -import com.czg.account.entity.ShopMerchant; -import com.czg.account.service.ShopMerchantService; -import com.czg.sa.StpKit; -import com.czg.service.account.mapper.ShopMerchantMapper; -import com.czg.utils.AssertUtil; -import com.mybatisflex.spring.service.impl.ServiceImpl; -import org.apache.dubbo.config.annotation.DubboService; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; - -import java.io.Serializable; - -/** - * 第三方商户进件 服务层实现。 - * - * @author Administrator - * @since 2025-02-11 - */ -@CacheConfig(cacheNames = "shop:merchant") -@DubboService -public class ShopMerchantServiceImpl extends ServiceImpl implements ShopMerchantService { - - @Override - public ShopMerchant detail(Integer shopId) { - ShopMerchant one = queryChain().eq(ShopMerchant::getShopId, shopId).one(); - return one == null ? new ShopMerchant() : one; - } - - @CacheEvict(key = "#shopMerchantEditDTO.shopId") - @Override - public Boolean edit(ShopMerchantEditDTO shopMerchantEditDTO) { - ShopMerchant shopMerchant = queryChain().eq(ShopMerchant::getShopId, shopMerchantEditDTO.getShopId()).one(); - if (shopMerchant == null) { - shopMerchant = new ShopMerchant(); - shopMerchant.setShopId(shopMerchantEditDTO.getShopId()); - BeanUtil.copyProperties(shopMerchantEditDTO, shopMerchant); - return save(shopMerchant); - } - BeanUtil.copyProperties(shopMerchantEditDTO, shopMerchant); - return updateById(shopMerchant); - } - - @Cacheable(key = "#id") - @Override - public ShopMerchant getById(Serializable id) { - ShopMerchant shopMerchant = getMapper().selectOneById(id); - AssertUtil.isNull(shopMerchant, "暂未开通支付"); - return shopMerchant; - } -} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopUserServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopUserServiceImpl.java index b7dd81f9c..5669504ce 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopUserServiceImpl.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopUserServiceImpl.java @@ -22,7 +22,6 @@ import com.czg.market.vo.InviteUserVO; import com.czg.market.vo.MemberConfigVO; import com.czg.order.entity.OrderInfo; import com.czg.service.account.mapper.ShopUserMapper; -import com.czg.service.account.util.FunUtil; import com.czg.utils.FunUtils; import com.czg.utils.PageUtil; import com.github.pagehelper.PageHelper; diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/util/FunUtil.java b/cash-service/account-service/src/main/java/com/czg/service/account/util/FunUtil.java deleted file mode 100644 index c7d7f2d62..000000000 --- a/cash-service/account-service/src/main/java/com/czg/service/account/util/FunUtil.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.czg.service.account.util; - -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.script.DefaultRedisScript; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * @author Administrator - */ -@Slf4j -@Component -public class FunUtil { - @Resource - private RedisTemplate redisTemplate; - public static int retryCount = 5; - - /** - * 执行任务并保证锁唯一 - * @param supplier 业务逻辑 - * @param lockKey Redis锁的Key - * @return 业务逻辑返回值 - */ - public T runFunAndCheckKey(Supplier supplier, String lockKey) { - String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId()); - try { - // 尝试获取锁,超时时间 5 秒,防止死锁 - boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)); - int count = 0; - // 初始等待 10ms - int retryDelay = 10; - - while (!lock) { - // 最多重试 10 次,大约 10 秒 - if (count++ > 50) { - throw new RuntimeException("系统繁忙, 稍后再试"); - } - Thread.sleep(retryDelay); - // 指数退避,最大等待 200ms - retryDelay = Math.min(retryDelay * 2, 200); - lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)); - } - - // 执行任务 - return supplier.get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("线程被中断", e); - } catch (Exception e) { - log.error("执行出错:{}", e.getMessage(), e); - throw e; - } finally { - // 释放锁(使用 Lua 脚本确保原子性) - unlock(lockKey, lockValue); - } - } - - /** - * 使用 Lua 脚本确保释放锁的原子性 - * @param lockKey 锁的 Key - * @param lockValue 当前线程的锁值 - */ - private void unlock(String lockKey, String lockValue) { - String luaScript = - "if redis.call('get', KEYS[1]) == ARGV[1] then " + - "return redis.call('del', KEYS[1]) " + - "else return 0 end"; - redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), - Collections.singletonList(lockKey), lockValue); - } - - public static R runFunAndRetry( - Supplier function, - Function check, Consumer errFun) { - log.info("工具类开始执行函数"); - R result = function.get(); - boolean flag = check.apply(result); - - log.info("执行结果: {}", result); - - while (flag && retryCount-- > 0) { - log.info("执行函数失败, 剩余尝试次数{}", retryCount); - result = function.get(); - log.info("执行结果: {}", result); - flag = check.apply(result); - } - - if (flag) { - errFun.accept(result); - } - return result; - } -} diff --git a/cash-service/account-service/src/main/resources/mapper/QuickMenuMapper.xml b/cash-service/account-service/src/main/resources/mapper/QuickMenuMapper.xml new file mode 100644 index 000000000..ee950d745 --- /dev/null +++ b/cash-service/account-service/src/main/resources/mapper/QuickMenuMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/cash-service/market-service/pom.xml b/cash-service/market-service/pom.xml index 97577907d..0cf98da5d 100644 --- a/cash-service/market-service/pom.xml +++ b/cash-service/market-service/pom.xml @@ -18,9 +18,8 @@ ${project.version} - com.czg - pay-service - 1.0.0 + com.github.javen205 + IJPay-All diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AppWxServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AppWxServiceImpl.java index 6650c5f62..c5037ba50 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AppWxServiceImpl.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AppWxServiceImpl.java @@ -1,12 +1,12 @@ package com.czg.service.market.service.impl; +import cn.hutool.crypto.SecureUtil; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson2.JSONObject; import com.czg.constants.ParamCodeCst; import com.czg.exception.CzgException; import com.czg.service.RedisService; import com.czg.system.service.SysParamsService; -import com.ijpay.core.kit.RsaKit; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.beans.factory.annotation.Autowired; @@ -14,7 +14,6 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import java.util.Map; -import java.util.Set; /** * 微信支付service @@ -80,7 +79,7 @@ public class AppWxServiceImpl extends BaseWx { // api支付证书公钥 config.apiCert = payKeyMap.get(ParamCodeCst.Wechat.Pay.WX_API_CLIENT_CERT); try { - config.privateKey = RsaKit.loadPrivateKey(config.apiCertKey); + config.privateKey = SecureUtil.rsa(config.apiCertKey, null).getPrivateKey(); } catch (Exception e) { log.warn("微信加载api证书失败"); } @@ -98,10 +97,6 @@ public class AppWxServiceImpl extends BaseWx { config.refundNotifyUrl = ""; config.transferNotifyUrl = payKeyMap.get(ParamCodeCst.System.NATIVE_NOTIFY_URL) + "/wx/transfer"; } - - public BaseWx getAppService() { - return this; - } } diff --git a/cash-service/order-service/pom.xml b/cash-service/order-service/pom.xml index 6ca559a34..ee523373e 100644 --- a/cash-service/order-service/pom.xml +++ b/cash-service/order-service/pom.xml @@ -20,12 +20,12 @@ com.czg - pay-service + market-service ${project.version} com.czg - market-service + pay-service ${project.version} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/dto/AggregateMerchantVO.java b/cash-service/order-service/src/main/java/com/czg/service/order/dto/AggregateMerchantVO.java new file mode 100644 index 000000000..a39f49346 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/dto/AggregateMerchantVO.java @@ -0,0 +1,81 @@ +package com.czg.service.order.dto; + +import com.czg.dto.req.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * @author ww + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AggregateMerchantVO extends AggregateMerchantDto{ + + private String shopName; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + private String errorMsg; + + /** + * {@link com.czg.PayCst.EntryStatus} + * 微信状态 + * WAIT 待提交 INIT 待处理 AUDIT 审核中 SIGN 待签约 REJECTED 失败 FINISH 已完成 + */ + private String wechatStatus; + + /** + * 支付宝账号 + */ + private String alipayAccount; + /** + * 商户编号(在当前系统唯一) + */ + private String merchantCode; + /** + * 【必填】 + * 商户类型 + * 0: 个体商户; + * 1: 企业商户; + * 3: 小微商户 暂不支持 + */ + private String userType; + + /** + * 【必填】 + * 商户简称--企业、个体必填 + */ + private String shortName; + + /** + * 微信进件错误信息 + */ + private String wechatErrorMsg; + /** + * 微信进件签名地址 + */ + private String wechatSignUrl; + private String wechatApplyId; + private String alipayOrderId; + + /** + * {@link com.czg.PayCst.EntryStatus} + * 支付宝状态 + * WAIT 待提交 INIT 待处理 AUDIT 审核中 SIGN 待签约 REJECTED 失败 FINISH 已完成 + */ + private String alipayStatus; + + /** + * 支付宝进件错误信息 + */ + private String alipayErrorMsg; + /** + * 支付宝进件签名地址 + */ + private String alipaySignUrl; + +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/dto/CreateOrderDTO.java b/cash-service/order-service/src/main/java/com/czg/service/order/dto/CreateOrderDTO.java deleted file mode 100644 index 16a71a84c..000000000 --- a/cash-service/order-service/src/main/java/com/czg/service/order/dto/CreateOrderDTO.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.czg.service.order.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/** - * 创建订单 - * @author ww - */ -@Data -public class CreateOrderDTO { - @NotBlank(message = "桌号不能为空") - private String tableCode; - @NotBlank(message = "用餐模式 堂食 dine-in 外带 take-out 外卖 take-away") - private String dineMode; - /** - * 平台类型 - * 微信小程序 WX - * 支付宝小程序 ALI - * 收银机客户端 PC - * PC管理端 APC - * APP管理端 APP - */ - @NotBlank(message = "平台类型不能为空") - private String platformType; - - /** - * 是否使用了霸王餐 - */ - private boolean isFreeDine = false; - - private String remark; - - // 使用的积分抵扣数量 - private Integer pointsNum; - - // 使用的优惠券 - @Valid - private List userCouponInfos = new ArrayList<>(); -} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/dto/MerchantQueryDTO.java b/cash-service/order-service/src/main/java/com/czg/service/order/dto/MerchantQueryDTO.java new file mode 100644 index 000000000..7f0ed3147 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/dto/MerchantQueryDTO.java @@ -0,0 +1,58 @@ +package com.czg.service.order.dto; + +import com.czg.BaseQueryParam; +import com.czg.utils.CzgStrUtils; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +/** + * 进件查询参数 + * @author ww + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class MerchantQueryDTO extends BaseQueryParam { + + /** + * 商户类型 + * 0: 个体商户; + * 1: 企业商户; + */ + private String userType; + /** + * 店铺名称 模糊查询 + */ + private String shopName; + /** + * 进件状态 + * {@link com.czg.PayCst.EntryStatus} + * WAIT 待提交 + * INIT 待处理 + * AUDIT 待审核 + * SIGN 待签约 + * FINISH 已完成 + * REJECTED 失败 + */ + private String status; + /** + * 支付宝账号 + */ + private String alipayAccount; + + public String getUserType() { + return CzgStrUtils.getStrOrNull(userType); + } + + public String getShopName() { + return CzgStrUtils.getStrOrNull(shopName); + } + + public String getStatus() { + return CzgStrUtils.getStrOrNull(status); + } + + public String getAlipayAccount() { + return CzgStrUtils.getStrOrNull(alipayAccount); + } +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/dto/RechargeDTO.java b/cash-service/order-service/src/main/java/com/czg/service/order/dto/RechargeDTO.java deleted file mode 100644 index 668bd5690..000000000 --- a/cash-service/order-service/src/main/java/com/czg/service/order/dto/RechargeDTO.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.czg.service.order.dto; - -import jakarta.validation.constraints.DecimalMin; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.math.BigDecimal; - -/** - * 支付接收参数 实体类 - * - * @author ww - * @description - */ -@Data -public class RechargeDTO { - @NotNull(message = "店铺不能为空") - private Long shopId; - private Long shopUserId; - - /** - * 充值金额id - */ - private Long rechargeDetailId; - @DecimalMin("0.01") - private BigDecimal amount; - private Integer allPack; - - /** - * 跳转地址 - */ - private String returnUrl; - /** - * 平台类型 pc 收银机客户端 wechat 微信小程序 alipay 支付宝小程序 admin-pc PC管理端 admin-app APP管理端 - */ - private String platformType; - - private String openId; - - private String buyerRemark; - - private String payType; - - private Long orderId; - private Integer seatNum; - /** - * 用户端 使用 全打包 或者 全不打包 - */ - private Integer userAllPack; - - public boolean isAllPack() { - return allPack != null && allPack == 1; - } - - -} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/dto/UserCouponInfoDTO.java b/cash-service/order-service/src/main/java/com/czg/service/order/dto/UserCouponInfoDTO.java deleted file mode 100644 index bf0aac5b1..000000000 --- a/cash-service/order-service/src/main/java/com/czg/service/order/dto/UserCouponInfoDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.czg.service.order.dto; - -import jakarta.validation.constraints.Min; -import lombok.Data; - - -@Data -public class UserCouponInfoDTO { - private Long userCouponId; - @Min(1) - private Integer num; -} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopDirectMerchantMapper.java b/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopDirectMerchantMapper.java new file mode 100644 index 000000000..a56042505 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopDirectMerchantMapper.java @@ -0,0 +1,19 @@ +package com.czg.service.order.mapper; + +import com.czg.service.order.dto.MerchantQueryDTO; +import com.mybatisflex.core.BaseMapper; +import com.czg.order.entity.ShopDirectMerchant; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商户进件 映射层。 + * + * @author ww + * @since 2026-01-07 + */ +public interface ShopDirectMerchantMapper extends BaseMapper { + + List getEntryList(@Param("queryParam") MerchantQueryDTO queryParam); +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/mapper/ShopMerchantMapper.java b/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopMerchantMapper.java similarity index 71% rename from cash-service/account-service/src/main/java/com/czg/service/account/mapper/ShopMerchantMapper.java rename to cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopMerchantMapper.java index c10ade4ad..e0f6f0a34 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/mapper/ShopMerchantMapper.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopMerchantMapper.java @@ -1,6 +1,6 @@ -package com.czg.service.account.mapper; +package com.czg.service.order.mapper; -import com.czg.account.entity.ShopMerchant; +import com.czg.order.entity.ShopMerchant; import com.mybatisflex.core.BaseMapper; /** diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/print/FeiPrinter.java b/cash-service/order-service/src/main/java/com/czg/service/order/print/FeiPrinter.java index 3665d34dd..caae46bed 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/print/FeiPrinter.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/print/FeiPrinter.java @@ -1,17 +1,18 @@ package com.czg.service.order.print; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.text.UnicodeUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import com.czg.account.dto.HandoverRecordDTO; -import com.czg.account.entity.HandoverRecord; import com.czg.account.entity.PrintMachine; import com.czg.account.entity.ShopInfo; -import com.czg.account.service.ShopInfoService; import com.czg.order.entity.OrderDetail; import com.czg.order.entity.OrderInfo; import com.czg.service.order.enums.OrderStatusEnums; -import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.http.HttpEntity; @@ -20,24 +21,21 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Administrator + * 接口文档 */ @Component @Slf4j public class FeiPrinter extends PrinterHandler implements PrinterImpl { - @Resource - private RestTemplate restTemplate; - - @Resource - private ShopInfoService shopInfoService; // API 地址 private static final String URL = "http://api.feieyun.cn/Api/Open/"; @@ -72,10 +70,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl { String remark = orderDetail.getRemark(); String content = buildDishPrintData(false, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); - Object o = sendPrintRequest(machine.getAddress(), content, null, "1"); - printMachineLogService.save(machine, "新订单", content, o); - -// printMachineLogService.save(machine, "新订单", , ); + String o = sendPrintRequest(machine.getAddress(), content, null, "1"); + printMachineLogService.save(orderInfo.getId(), machine, "新订单", content, o); } @@ -84,8 +80,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl { String remark = orderDetail.getRemark(); String content = buildDishPrintData(true, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); - Object o = sendPrintRequest(machine.getAddress(), content, null, "1"); - printMachineLogService.save(machine, "退款单", content, o); + String o = sendPrintRequest(machine.getAddress(), content, null, "1"); + printMachineLogService.save(orderInfo.getId(), machine, "退款单", content, o); } @@ -106,8 +102,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl { .setRemark(orderInfo.getRemark()) .setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString()); String string = buildOrderPrintData(printInfoDTO, detailList); - Object o = sendPrintRequest(machine.getAddress(), string, null, printerNum); - printMachineLogService.save(machine, "退款单", string, o); + String o = sendPrintRequest(machine.getAddress(), string, null, printerNum); + printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, o); } @@ -134,24 +130,24 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl { .setRemark(orderInfo.getRemark()) .setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString()); printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle()); - if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){ + if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) { printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString()); printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString()); } - if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){ + if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) { printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString()); } String string = buildOrderPrintData(printInfoDTO, detailList); - Object resp = sendPrintRequest(machine.getAddress(), string, null, printerNum); - printMachineLogService.save(machine, "结算单", string, resp); + String resp = sendPrintRequest(machine.getAddress(), string, null, printerNum); + printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, resp); } @Override protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) { String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一条新的排号记录\"}"; String data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime); - Object resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1"); + String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1"); printMachineLogService.save(machine, "叫号单", data, resp); } @@ -170,7 +166,7 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl { } @Override - public R sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) { + public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) { log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData); String time = String.valueOf(System.currentTimeMillis() / 1000); MultiValueMap formData = new LinkedMultiValueMap<>(); @@ -189,12 +185,68 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl { HttpEntity> requestEntity = new HttpEntity<>(formData, headers); String result = restTemplate.postForObject(URL, requestEntity, String.class); - log.info("打印结果: {}", result); - return (R) result; + log.info("飞鹅打印结果: {}", result); + return result; } catch (Exception e) { log.error("打印请求失败: {}", e.getMessage()); throw new RuntimeException("飞蛾打印请求失败", e); } } + /** + * 检查飞鹅打印机 打印任务是否已打印 + * + * @param printOrderId 打印订单编号 + * @return null-未知错误,true-已打印,false-未打印 + */ + public Boolean checkFPrintStatus(String printOrderId) { + String time = String.valueOf(System.currentTimeMillis() / 1000); + Map paramMap = new HashMap<>(); + paramMap.put("user", USER); + paramMap.put("stime", time); + paramMap.put("sig", signature(time)); + paramMap.put("apiname", "Open_queryOrderState"); + paramMap.put("orderid", printOrderId); + Boolean ret; + try { + String resp = HttpUtil.post(URL, paramMap, 1000 * 5); + //成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4} + //失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4} + JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp)); + log.info("飞鹅打印机 打印任务状态响应: {}", json); + ret = json.getBool("data"); + } catch (Exception e) { + ret = null; + } + return ret; + } + + /** + * 检查飞鹅打印机是否在线 + * + * @param sn 设备编号 + * @return 在线,工作状态正常。/离线。/未知错误 + */ + public String checkOnline(String sn) { + String time = String.valueOf(System.currentTimeMillis() / 1000); + Map paramMap = new HashMap<>(); + paramMap.put("user", USER); + paramMap.put("stime", time); + paramMap.put("sig", signature(time)); + paramMap.put("apiname", "Open_queryPrinterStatus"); + paramMap.put("sn", sn); + String msg; + try { + String resp = HttpUtil.post(URL, paramMap, 1000 * 5); + //成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4} + //成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7} + JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp)); + log.info("飞鹅打印机状态响应: {}", json); + msg = json.getStr("data"); + } catch (Exception e) { + msg = "未知错误"; + } + return msg; + } + } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterHandler.java b/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterHandler.java index bd9caf027..9af2c670a 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterHandler.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterHandler.java @@ -34,6 +34,7 @@ import lombok.ToString; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.web.client.RestTemplate; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -49,10 +50,9 @@ public abstract class PrinterHandler { @Setter protected PrinterHandler nextPrinter; protected String printerBrand; - // 创建 ThreadLocal 变量 - private static final ThreadLocal ERR_MSG = ThreadLocal.withInitial(() -> ""); - + @Resource + protected RestTemplate restTemplate; @Resource protected OrderDetailService orderDetailService; @Resource @@ -84,7 +84,11 @@ public abstract class PrinterHandler { @Getter public enum PrintTypeEnum { HANDOVER("交班", "handover"), - ORDER("订单", "order"), ONE("菜品", "one"), CALL("叫号", "call"), ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), PRE_ORDER("预结算单", "preOrder"); + ORDER("订单", "order"), + ONE("菜品", "one"), + CALL("叫号", "call"), + ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), + PRE_ORDER("预结算单", "preOrder"); private final String name; private final String code; @@ -157,7 +161,7 @@ public abstract class PrinterHandler { if (StrUtil.isNotEmpty(printMethod)) { List arrayList = switch (printMethod) { - case "all" ->Arrays.asList("one", "normal", "all"); + case "all" -> Arrays.asList("one", "normal", "all"); case "one" -> Arrays.asList("one", "all"); case "normal" -> Arrays.asList("normal", "all"); default -> new ArrayList<>(); @@ -173,10 +177,6 @@ public abstract class PrinterHandler { wrapper.like(PrintMachine::getPrintType, printType); } List list = printMachineService.list(wrapper); -// for (PrintMachine item : list) { -// //实际打印以传递的参数为准 -// item.setPrintMethod(printMethod); -// } if (list.isEmpty()) { log.error("店铺未配置打印机,店铺id: {}", shopId); return list; @@ -188,7 +188,8 @@ public abstract class PrinterHandler { /** * 处理打印 - * @param data 传递的数据 + * + * @param data 传递的数据 * @param printTypeEnum order returnOrder preOrder one call handover */ public void handler(String data, PrintTypeEnum printTypeEnum) { @@ -314,7 +315,7 @@ public abstract class PrinterHandler { item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum())); item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum())); orderDetails.add(item); - }else { + } else { orderDetails.add(item); } @@ -334,34 +335,37 @@ public abstract class PrinterHandler { log.info("准备开始打印交班"); if (data instanceof HandoverRecordDTO record) { handoverPrint(machine, record); - }else { + } else { throw new RuntimeException("传递数据类型有误"); } break; case PrintTypeEnum.ORDER: log.info("准备开始打印订单"); if (data instanceof OrderInfo orderInfo) { + redisService.set("order:print:" + orderInfo.getId(),"", 180); List orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); onlyFrontDesk(machine, false, orderInfo, orderDetailList); - }else { + } else { throw new RuntimeException("传递数据类型有误"); } break; case PrintTypeEnum.PRE_ORDER: log.info("准备开始打印预结算订单"); if (data instanceof OrderInfo orderInfo) { + redisService.set("order:print:" + orderInfo.getId(),"", 180); List orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); onlyFrontDesk(machine, true, orderInfo, orderDetailList); - }else { + } else { throw new RuntimeException("传递数据类型有误"); } break; case PrintTypeEnum.ONE: log.info("准备开始打印菜品单"); if (data instanceof OrderInfo orderInfo) { + redisService.set("order:print:" + orderInfo.getId(),"", 180); List orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); onlyKitchen(machine, orderInfo, orderDetailList); - }else { + } else { throw new RuntimeException("传递数据类型有误"); } break; @@ -369,13 +373,14 @@ public abstract class PrinterHandler { log.info("准备开始打印叫号单"); if (data instanceof CallQueue queue) { onlyCallNumPrint(machine, queue); - }else { + } else { throw new RuntimeException("传递数据类型有误"); } break; case PrintTypeEnum.ONE_AND_ORDER: log.info("准备开始打印菜品以及结算单"); if (data instanceof OrderInfo orderInfo) { + redisService.set("order:print:" + orderInfo.getId(),"", 180); List orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); switch (machine.getPrintMethod()) { case "all": @@ -393,7 +398,7 @@ public abstract class PrinterHandler { default: throw new RuntimeException("打印方法有误"); } - }else { + } else { throw new RuntimeException("传递数据类型有误"); } @@ -448,17 +453,7 @@ public abstract class PrinterHandler { log.info("台位费商品,不打印"); return; } -// ProdSku sku = prodSkuService.getById(item.getSkuId()); -// if (isTemporary == 0 && sku == null) { -// log.error("商品不存在, id: {}", item.getSkuId()); -// return; -// } else if (isTemporary == 1) { -// sku = new ProdSku(); -// } - -// String remark = StrUtil.isNotBlank(sku.getSpecInfo()) ? sku.getSpecInfo() : ""; -// item.setRemark(remark); if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) { returnDishesPrint(orderInfo, item, machine); } @@ -469,8 +464,9 @@ public abstract class PrinterHandler { // 保存已打印信息 OrderDetail orderDetail = detailMap.get(item.getId()); - redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId()) - .setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24); + redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), + JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId()) + .setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24); }); @@ -582,17 +578,35 @@ public abstract class PrinterHandler { } + /** + * 菜品打印 + */ protected abstract void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine); + /** + * 菜品 退菜打印 + */ protected abstract void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine); + /** + * 退单打印 + */ protected abstract void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List detailList); + /** + * 订单打印 + */ protected abstract void normalOrderPrint(OrderInfo orderInfo, boolean isPre, PrintMachine machine, String balance, List detailList); + /** + * 叫号打印 + */ protected abstract void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote); + /** + * 交班打印 + */ protected abstract void handoverPrint(PrintMachine machine, HandoverRecordDTO record); } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterImpl.java index 3d0f9c125..33024622b 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/print/PrinterImpl.java @@ -9,7 +9,6 @@ import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.czg.account.dto.HandoverRecordDTO; -import com.czg.account.entity.HandoverRecord; import com.czg.order.entity.OrderDetail; import lombok.Data; import lombok.experimental.Accessors; @@ -69,10 +68,9 @@ public interface PrinterImpl { * @param metaPrintData 打印元数据 * @param voiceData 语音信息 * @param printNum 打印联数 - * @param 返回数据类型 * @return 打印结果 */ - R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum); + String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum); /** * 获取当前打印机标签信息 @@ -560,12 +558,6 @@ public interface PrinterImpl { return str; } - public static void main(String[] args) { - System.out.println("水煮肉片".length()); - System.out.println(StrUtil.repeat(' ', 8)); - System.out.println(StrUtil.fillAfter("水煮肉片", ' ', 21)); - } - /** * 获取填充字符串, 并且换行 * diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/print/YxyPrinter.java b/cash-service/order-service/src/main/java/com/czg/service/order/print/YxyPrinter.java index 3d9f19c70..9513ef90b 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/print/YxyPrinter.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/print/YxyPrinter.java @@ -4,11 +4,10 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; import com.czg.account.dto.HandoverRecordDTO; -import com.czg.account.entity.HandoverRecord; import com.czg.account.entity.PrintMachine; import com.czg.account.entity.ShopInfo; -import com.czg.account.service.ShopInfoService; import com.czg.order.entity.OrderDetail; import com.czg.order.entity.OrderInfo; import com.czg.service.order.enums.OrderStatusEnums; @@ -20,16 +19,14 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; -import javax.annotation.Resource; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.*; /** - * 云享印打印机 - * + * 博实结-云享印打印机 + * 接口文档 * @author Administrator */ @Slf4j @@ -45,12 +42,6 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { //APPSECRET private static final String APP_SECRET = "2022bsjZF544GAH"; - @Resource - private ShopInfoService shopInfoService; - - @Resource - private RestTemplate restTemplate; - public YxyPrinter() { super("云想印"); } @@ -75,7 +66,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { @Override - public R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) { + public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) { log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData); //设备名称 //行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息 @@ -87,10 +78,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { String time = String.valueOf(System.currentTimeMillis()); String uuid = UUID.randomUUID().toString(); - Map param = getToken(time, uuid); //参数 MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); - multiValueMap.add("token", param.get("TOKEN")); + multiValueMap.add("token", getToken(time, uuid)); multiValueMap.add("devName", address); multiValueMap.add("actWay", 3); multiValueMap.add("cn", printNum); @@ -108,18 +98,17 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { httpEntity, String.class); log.info("请求云享印成功,响应数据: {}", httpResponse); - return (R) httpResponse; + return httpResponse; } @Override protected void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) { String buildDishPrintData = buildDishPrintData(false, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); - String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; -// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}"; - Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1"); -// sendPrintRequest(voiceJson, 3, 1, machine.getAddress(), data); - printMachineLogService.save(machine, "新订单", buildDishPrintData, resp); +// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; + String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}"; + String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1"); + printMachineLogService.save(orderInfo.getId(), machine, "新订单", buildDishPrintData, resp); } @@ -127,10 +116,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) { String buildDishPrintData = buildDishPrintData(true, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); -// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}"; - Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1"); - printMachineLogService.save(machine, "退款单", buildDishPrintData, resp); + String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1"); + printMachineLogService.save(orderInfo.getId(), machine, "退款单", buildDishPrintData, resp); } @@ -138,21 +126,21 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { protected void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List detailList) { ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId()); PrintInfoDTO printInfoDTO = new PrintInfoDTO().setShopName(shopInfo.getShopName()).setPrintType("普通打印").setPickupNum(getPickupNum(orderInfo)) - .setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString()) + .setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")) + .setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString()) .setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo)) .setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0") - .setOutNumber(orderInfo.getTakeCode()).setPrintTitle("结算单") + .setOutNumber(orderInfo.getTakeCode()).setPrintTitle("退款单") .setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString()); String data = buildOrderPrintData(printInfoDTO, detailList); String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; -// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}"; String printerNum = "1"; if (StrUtil.isNotBlank(machine.getPrintQty())) { printerNum = machine.getPrintQty().split("\\^")[1]; } String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum); - printMachineLogService.save(machine, "新订单", data, resp); + printMachineLogService.save(orderInfo.getId(), machine, "退款单", data, resp); } @@ -165,7 +153,8 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { .le(OrderInfo::getCreateTime, orderInfo.getCreateTime())); PrintInfoDTO printInfoDTO = new PrintInfoDTO().setShopName(shopInfo.getShopName()) .setPrintType("普通打印").setPickupNum(getPickupNum(orderInfo)) - .setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString()) + .setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")) + .setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString()) .setOriginalAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee())).toPlainString()).setReturn(isReturn(orderInfo)) .setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0") .setOutNumber(orderInfo.getTakeCode()).setPrintTitle(isPre ? "预结算单" : "结算单") @@ -173,33 +162,38 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { .setRemark(orderInfo.getRemark()) .setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString()); printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle()); - if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){ + if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) { printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString()); printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString()); } - if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){ + if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) { printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString()); } String data = buildOrderPrintData(printInfoDTO, detailList); String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; -// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}"; String printerNum = "1"; if (StrUtil.isNotBlank(machine.getPrintQty())) { printerNum = machine.getPrintQty().split("\\^")[1]; } String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum); -// printMachineLogService.save(machine, printType, data, resp); - printMachineLogService.save(machine, "新订单", data, resp); + printMachineLogService.save(orderInfo.getId(), machine, "结算单", data, resp); } + /** + * 叫号单打印 + */ @Override protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) { String resp = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime); sendPrintRequest(machine.getAddress(), resp, null, "1"); } + + /** + * 交班单打印 + */ @Override protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) { String string = buildHandoverData(record); @@ -214,9 +208,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { * @param requestId 请求ID,自定义 * @return token信息 */ - private static Map getToken(String timestamp, String requestId) { + private static String getToken(String timestamp, String requestId) { StringBuilder token = new StringBuilder(); - StringBuilder encode = new StringBuilder(); +// StringBuilder encode = new StringBuilder(); SortedMap map = new TreeMap<>(); map.put("appId", APP_ID); map.put("timestamp", timestamp); @@ -226,13 +220,38 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl { String key = next.getKey(); Object value = next.getValue(); token.append(key).append(value); - encode.append(key).append("=").append(value).append("&"); +// encode.append(key).append("=").append(value).append("&"); } - Map finalMap = new HashMap<>(); - finalMap.put("ENCODE", encode.toString()); - finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase()); - return finalMap; +// Map finalMap = new HashMap<>(); +// finalMap.put("ENCODE", encode.toString()); +// finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase()); +// return finalMap; + return SecureUtil.md5(token + APP_SECRET).toUpperCase(); } + /** + * 检查打印状态 + * + * @param devName 设备名称,(唯一) 对应配置表中的address字段即(IP地址/打印机编号) + * @param taskId 打印任务id,用于复查打印状态,云想印=orderId + */ + public String checkPrintStatus(String devName, String taskId) { + String time = String.valueOf(System.currentTimeMillis()); + String uuid = UUID.randomUUID().toString(); + + Map paramMap = new HashMap<>(); + + paramMap.put("devName", devName); + paramMap.put("orderId", taskId); + paramMap.put("token", getToken(time, uuid)); + paramMap.put("appId", APP_ID); + paramMap.put("timestamp", time); + paramMap.put("requestId", uuid); + paramMap.put("userCode", USER_CODE); + String s = HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5); + log.info("云想印打印机:{},任务:{}状态:{}", devName, taskId, s); + return s; + } + } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java index a8ba97db3..295f85eea 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java @@ -7,33 +7,20 @@ import java.util.Map; /** - * 支付 + * 分销员支付 * * @author ww */ public interface DistributionPayService { - /** - * 现金支付 - */ - CzgResult cashPayOrder(MkDistributionPayDTO payParam); - - /** * 小程序支付 */ CzgResult> ltPayOrder(String clintIp, MkDistributionPayDTO payParam); - /** - * PC扫码支付 - */ - CzgResult> scanPayOrder(String clintIp, MkDistributionPayDTO payParam); /** - * 反扫 + * 运营端小程序余额充值 */ - CzgResult> microPayOrder(MkDistributionPayDTO payParam); - - - Map mchRecharge(String clientIP, MkDistributionPayDTO payParam); + Map mchRecharge(MkDistributionPayDTO payParam); } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/OrderPayService.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/OrderPayService.java new file mode 100644 index 000000000..89cee4ad1 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/OrderPayService.java @@ -0,0 +1,73 @@ +package com.czg.service.order.service; + +import com.czg.order.dto.OrderInfoRefundDTO; +import com.czg.resp.CzgResult; +import com.czg.service.order.dto.OrderPayParamDTO; +import lombok.NonNull; + +import java.util.Map; + +/** + * @author ww + */ +public interface OrderPayService { + + /** + * 挂账 + */ + CzgResult creditPayOrder(OrderPayParamDTO payParam); + + /** + * 现金支付 + */ + CzgResult cashPayOrder(OrderPayParamDTO payParam); + + /** + * 余额支付 + */ + CzgResult vipPayOrder(OrderPayParamDTO payParam); + + /** + * h5支付 + */ + CzgResult> h5PayOrder(String clintIp, OrderPayParamDTO payParam); + + /** + * js支付 + */ + CzgResult> jsPayOrder(String clintIp, OrderPayParamDTO payParam); + + /** + * 空订单支付 + */ + CzgResult> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam); + + /** + * 小程序支付 + */ + CzgResult> ltPayOrder(String clintIp, OrderPayParamDTO payParam); + + /** + * PC扫码支付 + */ + CzgResult> scanPayOrder(String clintIp, OrderPayParamDTO payParam); + + /** + * 聚合反扫 + */ + CzgResult> microPayOrder(OrderPayParamDTO payParam); + //-----------------------------------------------------------------订单+会员 一起支付----------------------------------------------------------- + + /** + * 充值并付款 + */ + CzgResult> rechargePayOrder(String clintIp, OrderPayParamDTO payParam); + + + //-----------------------------------------------------------------退款----------------------------------------------------------------- + + //订单退款 + CzgResult refundOrderBefore(OrderInfoRefundDTO param); + + +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/PayService.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/PayService.java index 66dc3e9c3..265efb883 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/PayService.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/PayService.java @@ -1,15 +1,13 @@ package com.czg.service.order.service; -import com.czg.entity.resp.CzgBaseResp; -import com.czg.entity.resp.CzgRefundResp; +import com.czg.enums.CzgPayEnum; import com.czg.order.dto.LtPayOtherDTO; -import com.czg.order.dto.OrderInfoRefundDTO; import com.czg.order.entity.OrderPayment; +import com.czg.pay.CzgPayBaseReq; +import com.czg.pay.CzgRefundReq; +import com.czg.pay.QueryOrderRespDTO; +import com.czg.pay.RefundRespDTO; import com.czg.resp.CzgResult; -import com.czg.service.order.dto.OrderPayParamDTO; -import com.czg.service.order.dto.VipMemberPayParamDTO; -import com.czg.service.order.dto.VipPayParamDTO; -import com.czg.service.order.dto.VipRefundDTO; import lombok.NonNull; import java.math.BigDecimal; @@ -22,129 +20,41 @@ import java.util.Map; * @author ww */ public interface PayService { - //-----------------------------------------------------------------订单支付-------------------------------------------------------------------- - - /** - * 挂账 - */ - CzgResult creditPayOrder(OrderPayParamDTO payParam); - - /** - * 现金支付 - */ - CzgResult cashPayOrder(OrderPayParamDTO payParam); - - /** - * 余额支付 - */ - CzgResult vipPayOrder(OrderPayParamDTO payParam); - - /** - * h5支付 - */ - CzgResult> h5PayOrder(String clintIp, OrderPayParamDTO payParam); - - /** - * js支付 - */ - CzgResult> jsPayOrder(String clintIp, OrderPayParamDTO payParam); - - /** - * 空订单支付 - */ - CzgResult> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam); - - /** - * 小程序支付 - */ - CzgResult> ltPayOrder(String clintIp, OrderPayParamDTO payParam); - - /** - * PC扫码支付 - */ - CzgResult> scanPayOrder(String clintIp, OrderPayParamDTO payParam); - - /** - * 聚合反扫 - */ - CzgResult> microPayOrder(OrderPayParamDTO payParam); - //-----------------------------------------------------------------订单+会员 一起支付----------------------------------------------------------- - - /** - * 充值并付款 - */ - CzgResult> rechargePayOrder(String clintIp, OrderPayParamDTO payParam); - //-----------------------------------------------------------------会员支付-------------------------------------------------------------------- - - /** - * 现金充值 - */ - CzgResult cashPayVip(VipPayParamDTO payParam); - - /** - * js支付 - */ - CzgResult> jsPayVip(String clintIp, VipPayParamDTO payParam); - - /** - * 小程序支付 - */ - CzgResult> ltPayVip(String clintIp, VipPayParamDTO payParam); - - /** - * PC扫码支付 - */ - CzgResult> scanPayVip(String clintIp, VipPayParamDTO payParam); - - /** - * 聚合反扫 - */ - CzgResult> microPayVip(VipPayParamDTO payParam); - //-----------------------------------------------------------------会员开通购买 ---------------------------------------------------------- - - CzgResult> ltPayMember(String clientIP, VipMemberPayParamDTO payParam); - - CzgResult> recharge(String clientIP, VipPayParamDTO rechargeDTO, Long userId); + BigDecimal MONEY_RATE = new BigDecimal("100"); + Long initPayment(OrderPayment payment); + + //-----------------------------------------------------------------付款 ---------------------------------------------------------- + CzgResult> pay(@NonNull Long shopId, CzgPayEnum payType, CzgPayBaseReq bizData); //-----------------------------------------------------------------积分商品/拼团 付款 ---------------------------------------------------------- /** * * @param param 支付参数 - * @param payType 暂时只有 POINT 积分 和 WARE 拼团商品 + * @param payType {@link com.czg.constants.PayTypeConstants.SourceType} 常量 * @param detail 操作描述 如 积分商品购买 / 拼团商品购买 */ CzgResult> ltPayOther(LtPayOtherDTO param, String payType, String detail); //-----------------------------------------------------------------退款----------------------------------------------------------------- - /** - * 退款前校验 - */ - CzgResult> refundVipBefore(VipRefundDTO payParam); /** - * 会员退款 + * 退款 + * 目前订单 会员 使用 */ - CzgResult refundVip(VipRefundDTO payParam); - - CzgResult refundOrderBefore(OrderInfoRefundDTO param); - - /** - * 订单退款 - */ - void refundOrder(Long shopId, Long orderId, Long payOrderId, String refPayOrderNo, String refundReason, BigDecimal refundAmount); - + CzgResult refund(@NonNull Long shopId, CzgRefundReq bizData); /** * 统一退款接口 - * + * 目前 拼团商品 积分商品 套餐推广 使用 * @param refPayOrderNo 自定义退单号 {@link com.czg.enums.OrderNoPrefixEnum} + 雪花Id */ void unifyRefund(Long shopId, Long sourceId, Long payOrderId, String refPayOrderNo, String refundReason, BigDecimal refundAmount); - /** * 退款补偿使用 + * 目前 拼团商品 积分商品 套餐推广 使用 + * (退款 账户余额不足时 会失败定时任务) */ void unifyRefund(OrderPayment payment, String refPayOrderNo); @@ -157,7 +67,7 @@ public interface PayService { * @param payOrderId 平台订单号 * @param mchOrderNo 商户订单号 */ - CzgResult queryPayOrder(Long shopId, String payOrderId, String mchOrderNo); + CzgResult queryPayOrder(Long shopId, String payOrderId, String mchOrderNo, String platform); /** * 退款查询 @@ -165,5 +75,5 @@ public interface PayService { * @param mchRefundNo 商户退款订单号 二选一 * @param refundOrderId 平台退款订单号 二选一 */ - CzgResult queryRefund(Long shopId, String mchRefundNo, String refundOrderId); + CzgResult queryRefund(Long shopId, String mchRefundNo, String refundOrderId); } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopDirectMerchantService.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopDirectMerchantService.java new file mode 100644 index 000000000..6a9bc72ed --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopDirectMerchantService.java @@ -0,0 +1,38 @@ +package com.czg.service.order.service; +import com.alibaba.fastjson2.JSONObject; +import com.czg.dto.req.AggregateMerchantDto; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.czg.service.order.dto.MerchantQueryDTO; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.service.IService; +import com.czg.order.entity.ShopDirectMerchant; + +/** + * 商户进件 服务层。 + * + * @author ww + * @since 2026-01-07 + */ +public interface ShopDirectMerchantService extends IService { + + /** + * ocr识别图片 + * @param url 图片地址 + * @param type IdCard 身份证 + * BankCard 银行卡 + * BusinessLicense 营业执照 + */ + JSONObject getInfoByImg(String url, String type) throws Exception; + + + Page getEntryList(MerchantQueryDTO queryParam); + /** + * 获取进件信息 + */ + AggregateMerchantVO getEntry(Long shopId); + + /** + * 申请进件 + */ + boolean entryManager(AggregateMerchantDto reqDto); +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopUserPayService.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopUserPayService.java new file mode 100644 index 000000000..5b60080b2 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopUserPayService.java @@ -0,0 +1,63 @@ +package com.czg.service.order.service; + +import com.czg.resp.CzgResult; +import com.czg.service.order.dto.VipMemberPayParamDTO; +import com.czg.service.order.dto.VipPayParamDTO; +import com.czg.service.order.dto.VipRefundDTO; + +import java.math.BigDecimal; +import java.util.Map; + + +/** + * 用户余额 充值/会员购买开通 + * @author ww + */ +public interface ShopUserPayService { + + //-----------------------------------------------------------------会员支付-------------------------------------------------------------------- + + /** + * 现金充值 + */ + CzgResult cashPayVip(VipPayParamDTO payParam); + + /** + * js支付 + */ + CzgResult> jsPayVip(String clintIp, VipPayParamDTO payParam); + + /** + * 小程序支付 + */ + CzgResult> ltPayVip(String clintIp, VipPayParamDTO payParam); + + /** + * PC扫码支付 + */ + CzgResult> scanPayVip(String clintIp, VipPayParamDTO payParam); + + /** + * 聚合反扫 + */ + CzgResult> microPayVip(VipPayParamDTO payParam); + //-----------------------------------------------------------------会员开通购买 ---------------------------------------------------------- + + CzgResult> ltPayMember(String clientIP, VipMemberPayParamDTO payParam); + + CzgResult> recharge(String clientIP, VipPayParamDTO rechargeDTO, Long userId); + + + + //-----------------------------------------------------------------退款----------------------------------------------------------------- + + /** + * 退款前校验 + */ + CzgResult> refundVipBefore(VipRefundDTO payParam); + + /** + * 会员退款 + */ + CzgResult refundVip(VipRefundDTO payParam); +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java index 81b0fd0f1..38601ff4c 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java @@ -3,18 +3,19 @@ package com.czg.service.order.service.impl; import cn.hutool.core.util.IdUtil; import com.czg.account.entity.ShopUser; import com.czg.account.entity.UserInfo; -import com.czg.account.service.ShopInfoService; import com.czg.account.service.ShopUserService; import com.czg.account.service.UserInfoService; import com.czg.constant.TableValueConstant; -import com.czg.entity.req.CzgLtPayReq; +import com.czg.constants.PayTypeConstants; +import com.czg.constants.SystemConstants; +import com.czg.enums.CzgPayEnum; import com.czg.exception.CzgException; import com.czg.market.service.MkDistributionConfigService; import com.czg.market.vo.MkDistributionConfigVO; import com.czg.order.dto.MkDistributionPayDTO; import com.czg.order.entity.OrderPayment; -import com.czg.constants.PayTypeConstants; import com.czg.order.service.OrderPaymentService; +import com.czg.pay.CzgPayBaseReq; import com.czg.resp.CzgResult; import com.czg.service.market.service.impl.WxServiceImpl; import com.czg.service.order.service.DistributionPayService; @@ -44,8 +45,6 @@ public class DistributionPayServiceImpl implements DistributionPayService { @Resource private PayServiceImpl payService; - @DubboReference - private ShopInfoService shopInfoService; @DubboReference private ShopUserService shopUserService; @DubboReference @@ -70,7 +69,7 @@ public class DistributionPayServiceImpl implements DistributionPayService { ShopUser shopUserInfo = shopUserService.getShopUserInfo(payParam.getShopId(), userId); OrderPayment orderPayment = new OrderPayment().setShopId(payParam.getShopId()).setSourceId(isRecharge ? payParam.getShopId() : shopUserInfo.getId()) - .setSourceType(isRecharge ? PayTypeConstants.SourceType.DISTRIBUTION_RECHARGE : PayTypeConstants.SourceType.DISTRIBUTION ) + .setSourceType(isRecharge ? PayTypeConstants.SourceType.DISTRIBUTION_RECHARGE : PayTypeConstants.SourceType.DISTRIBUTION) .setPayType(PayTypeConstants.PayType.PAY) .setOrderNo(payParam.getPlatformType() + IdUtil.getSnowflakeNextId()) .setAmount(isRecharge ? payParam.getAmount() : detail.getPayAmount()); @@ -84,36 +83,22 @@ public class DistributionPayServiceImpl implements DistributionPayService { } else { UserInfo userInfo = userInfoService.getById(userId); initInfo.setPayment(orderPayment).setShopUser(shopUserInfo) - .setOpenId("aliPay".equals(payParam.getPayType()) ? userInfo.getAlipayOpenId() : userInfo.getWechatOpenId()); + .setOpenId(SystemConstants.PayType.ALIPAY.equals(payParam.getPayType()) ? userInfo.getAlipayOpenId() : userInfo.getWechatOpenId()); } return initInfo; } - @Override - public CzgResult cashPayOrder(MkDistributionPayDTO payParam) { - return null; - } - @Override public CzgResult> ltPayOrder(String clintIp, MkDistributionPayDTO payParam) { InitInfo initInfo = initPayment(payParam.getUserId(), payParam, false); - return payService.ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(initInfo.payment.getOrderNo(), initInfo.payment.getAmount().multiply(MONEY_RATE).longValue(), - payParam.getPayType(), "分销员开通", initInfo.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); + return payService.pay(payParam.getShopId(), CzgPayEnum.LT_PAY, + CzgPayBaseReq.ltPayReq(initInfo.payment.getOrderNo(), "分销员开通", initInfo.payment.getAmount().multiply(MONEY_RATE).longValue(), + payParam.getPayType(), initInfo.getOpenId(), clintIp)); } @Override - public Map mchRecharge(String clientIP, MkDistributionPayDTO payParam) { + public Map mchRecharge(MkDistributionPayDTO payParam) { InitInfo initInfo = initPayment(payParam.getUserId() == null ? payParam.getShopId() : payParam.getUserId(), payParam, true); return wxService.v3Pay(initInfo.openId, payParam.getAmount(), "商户运营余额充值", initInfo.payment.getOrderNo(), initInfo.payment.getSourceType()); } - - @Override - public CzgResult> scanPayOrder(String clintIp, MkDistributionPayDTO payParam) { - return null; - } - - @Override - public CzgResult> microPayOrder(MkDistributionPayDTO payParam) { - return null; - } } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java index 3cce395fc..6eeaa0c2c 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java @@ -6,7 +6,10 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.exceptions.ValidateException; import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.*; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; @@ -17,9 +20,8 @@ import com.czg.config.RabbitPublisher; import com.czg.config.RedisCst; import com.czg.constant.MarketConstants; import com.czg.constant.TableValueConstant; -import com.czg.entity.notify.CzgPayNotifyDTO; -import com.czg.entity.notify.CzgRefundNotifyDTO; import com.czg.constants.PayTypeConstants; +import com.czg.entity.notify.CzgRefundNotifyDTO; import com.czg.enums.ShopTableStatusEnum; import com.czg.enums.ShopUserFlowBizEnum; import com.czg.exception.CzgException; @@ -40,6 +42,7 @@ import com.czg.order.entity.OrderPayment; import com.czg.order.enums.PayEnums; import com.czg.order.service.*; import com.czg.order.vo.*; +import com.czg.pay.PayNotifyRespDTO; import com.czg.product.entity.Product; import com.czg.product.service.ProductRpcService; import com.czg.product.service.ProductService; @@ -50,7 +53,6 @@ import com.czg.service.order.enums.OrderStatusEnums; import com.czg.service.order.mapper.OrderInfoCustomMapper; import com.czg.service.order.print.PrinterHandler; import com.czg.utils.*; -import com.czg.utils.PageUtil; import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; @@ -299,6 +301,7 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { orderDetails.forEach(item -> { item.setUrgent(true); item.setSubStatus(TableValueConstant.OrderDetail.SubStatus.READY_TO_SERVE.getCode()); + item.setStartOrderTime(DateUtil.date().toLocalDateTime()); }); } else { switch (EnumUtil.fromString(TableValueConstant.OrderDetail.SubStatus.class, param.getSubStatus())) { @@ -1027,13 +1030,12 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { @Override @Transactional - public void payCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson, int retryCount) { - CzgPayNotifyDTO czgCallBackDto = JSONObject.parseObject(resultJson.toString(), CzgPayNotifyDTO.class); + public void payCallBackOrder(@NotBlank String orderNo, @NotNull PayNotifyRespDTO notifyRespDTO, String channel, int retryCount) { OrderPayment payment = paymentService.getOne(new QueryWrapper().eq(OrderPayment::getOrderNo, orderNo)); if (payment == null) { if (retryCount < MAX_RETRIES) { log.info("支付记录不存在,第 {} 次重试,将在 {} 秒后进行", retryCount + 1, DELAY); - executorService.schedule(() -> payCallBackOrder(orderNo, resultJson, retryCount + 1), DELAY, TimeUnit.SECONDS); + executorService.schedule(() -> payCallBackOrder(orderNo, notifyRespDTO, channel, retryCount + 1), DELAY, TimeUnit.SECONDS); } else { log.error("订单支付回调失败, 达到最大重试次数, 支付记录不存在, orderNo: {}", orderNo); } @@ -1043,10 +1045,12 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { log.info("订单处理过,payment id:{}", payment.getId()); return; } - payment.setTradeNumber(czgCallBackDto.getPayOrderId()); - payment.setRespJson(resultJson.toString()); + payment.setTradeNumber(notifyRespDTO.getThirdOrderNo()); + payment.setRespJson(notifyRespDTO.getOriginalData()); payment.setPayStatus(PayTypeConstants.PayStatus.FAIL); - if ("TRADE_SUCCESS".equals(czgCallBackDto.getState())) { + payment.setPlatformType(notifyRespDTO.getPlatform()); + payment.setChannel(channel); + if ("TRADE_SUCCESS".equals(notifyRespDTO.getStatus())) { payment.setPayStatus(PayTypeConstants.PayStatus.SUCCESS); if (PayTypeConstants.SourceType.ORDER.equals(payment.getSourceType())) { OrderInfo orderInfo = orderInfoService.getById(payment.getSourceId()); @@ -1054,8 +1058,8 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { log.error("订单支付回调失败,订单不存在,支付记录Id,{}", payment.getId()); return; } - upOrderInfo(orderInfo, new BigDecimal(czgCallBackDto.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.DOWN), - DateUtil.parseLocalDateTime(czgCallBackDto.getPayTime()), payment.getId(), null); + upOrderInfo(orderInfo, new BigDecimal(notifyRespDTO.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.DOWN), + DateUtil.parseLocalDateTime(notifyRespDTO.getPaySuccessTime()), payment.getId(), null); if (orderInfo.getUserId() != null) { // 分销员升级等级 distributionUserService.costUpgradeLevelBefore(orderInfo.getUserId(), orderInfo.getShopId()); @@ -1065,14 +1069,14 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { } else if (PayTypeConstants.SourceType.MEMBER_IN.equals(payment.getSourceType()) || PayTypeConstants.SourceType.FREE.equals(payment.getSourceType())) { boolean isFree = PayTypeConstants.SourceType.FREE.equals(payment.getSourceType()); ShopUser shopUser = shopUserService.getById(payment.getSourceId()); - OrderInfo orderInfo = null; + OrderInfo orderInfo; if (shopUser == null) { log.error("会员充值失败,会员不存在,会员id:{}", payment.getSourceId()); } else { ShopUserFlowBizEnum bizEnum; - if ("WECHAT".equals(czgCallBackDto.getPayType())) { + if ("WECHAT".equals(notifyRespDTO.getPlatform())) { bizEnum = ShopUserFlowBizEnum.WECHAT_IN; - } else if ("ALIPAY".equals(czgCallBackDto.getPayType())) { + } else if ("ALIPAY".equals(notifyRespDTO.getPlatform())) { bizEnum = ShopUserFlowBizEnum.ALIPAY_IN; } else { bizEnum = ShopUserFlowBizEnum.CASH_IN; @@ -1088,7 +1092,7 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { .setType(1) .setBizEnum(ShopUserFlowBizEnum.FREE_IN) .setRelationId(orderInfo.getId()) - .setMoney(BigDecimal.valueOf(czgCallBackDto.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN)); + .setMoney(BigDecimal.valueOf(notifyRespDTO.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN)); shopUserService.updateMoney(shopUserMoneyEditDTO); upOrderInfo(orderInfo, BigDecimal.ZERO, LocalDateTime.now(), null, PayEnums.FREE_PAY); @@ -1110,26 +1114,21 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { LocalDateTime.now(), null, PayEnums.VIP_PAY); } shopRechargeService.recharge(payment.getShopId(), payment.getSourceId(), payment.getRelatedId(), - BigDecimal.valueOf(czgCallBackDto.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN), + BigDecimal.valueOf(notifyRespDTO.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN), payment.getId(), payment.getSourceType(), bizEnum, orderInfo == null); } } - } - else if (PayTypeConstants.SourceType.MEMBER_PAY.equals(payment.getSourceType())) { + } else if (PayTypeConstants.SourceType.MEMBER_PAY.equals(payment.getSourceType())) { //购买会员 ShopUser shopUser = shopUserService.getById(payment.getSourceId()); memberConfigService.joinMember(payment.getShopId(), shopUser.getUserId(), payment.getRelatedId()); - } - else if (PayTypeConstants.SourceType.DISTRIBUTION.equals(payment.getSourceType())) { + } else if (PayTypeConstants.SourceType.DISTRIBUTION.equals(payment.getSourceType())) { distributionUserService.open(payment.getSourceId(), payment.getAmount(), payment.getShopId(), payment.getId()); - } - else if (PayTypeConstants.SourceType.POINT.equals(payment.getSourceType())) { + } else if (PayTypeConstants.SourceType.POINT.equals(payment.getSourceType())) { goodPayService.payCallBack(payment.getSourceId(), payment.getId()); - } - else if (PayTypeConstants.SourceType.WARE.equals(payment.getSourceType())) { + } else if (PayTypeConstants.SourceType.WARE.equals(payment.getSourceType())) { gbOrderService.payCallBack(payment.getSourceId(), payment.getId()); - } - else if (PayTypeConstants.SourceType.PP.equals(payment.getSourceType())) { + } else if (PayTypeConstants.SourceType.PP.equals(payment.getSourceType())) { ppPackageOrderService.paySuccess(payment.getSourceId(), payment.getId()); } } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderPayServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderPayServiceImpl.java new file mode 100644 index 000000000..4b3a86d4d --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderPayServiceImpl.java @@ -0,0 +1,567 @@ +package com.czg.service.order.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.MD5; +import com.alibaba.fastjson2.JSONObject; +import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO; +import com.czg.account.entity.ShopInfo; +import com.czg.account.entity.ShopUser; +import com.czg.account.entity.UserInfo; +import com.czg.account.service.ShopInfoService; +import com.czg.account.service.ShopUserService; +import com.czg.account.service.UserInfoService; +import com.czg.config.RabbitPublisher; +import com.czg.config.RedisCst; +import com.czg.constants.PayTypeConstants; +import com.czg.enums.CzgPayEnum; +import com.czg.enums.ShopUserFlowBizEnum; +import com.czg.exception.CzgException; +import com.czg.exception.PaySuccessException; +import com.czg.market.entity.MkShopRechargeDetail; +import com.czg.market.enums.PointsConstant; +import com.czg.market.service.*; +import com.czg.market.vo.MkShopRechargeVO; +import com.czg.order.dto.CheckOrderPay; +import com.czg.order.dto.OrderInfoRefundDTO; +import com.czg.order.entity.OrderDetail; +import com.czg.order.entity.OrderInfo; +import com.czg.order.entity.OrderPayment; +import com.czg.order.enums.PayEnums; +import com.czg.order.service.CreditBuyerOrderService; +import com.czg.order.service.OrderDetailService; +import com.czg.order.service.OrderInfoCustomService; +import com.czg.order.service.OrderPaymentService; +import com.czg.pay.CzgPayBaseReq; +import com.czg.pay.CzgRefundReq; +import com.czg.pay.RefundRespDTO; +import com.czg.resp.CzgResult; +import com.czg.service.RedisService; +import com.czg.service.order.dto.OrderPayParamDTO; +import com.czg.service.order.enums.OrderStatusEnums; +import com.czg.service.order.service.OrderPayService; +import com.czg.service.order.service.PayService; +import com.czg.utils.AssertUtil; +import com.czg.utils.CzgRandomUtils; +import com.czg.utils.FunUtils; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotBlank; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author ww + */ +@Slf4j +@Service +public class OrderPayServiceImpl implements OrderPayService { + @Resource + private PayService payService; + @Resource + private OrderPaymentService paymentService; + @DubboReference + private ShopUserService shopUserService; + @DubboReference + private UserInfoService userInfoService; + @DubboReference + private ShopInfoService shopInfoService; + @Resource + private OrderInfoCustomService orderInfoCustomService; + @Resource + private OrderInfoService orderInfoService; + @Resource + private CreditBuyerOrderService buyerOrderService; + @Resource + private RedisService redisService; + @Resource + private RabbitPublisher rabbitPublisher; + @Resource + private MkShopRechargeService shopRechargeService; + @Resource + private MkShopRechargeDetailService shopRechargeDetailService; + @Resource + private OrderDetailService orderDetailService; + @Resource + private MkDistributionUserService distributionUserService; + @Resource + private MkShopConsumerCouponService consumerCouponService; + @Resource + private MkPointsUserService mkPointsUserService; + @Resource + private MkConsumeCashbackService consumeCashbackService; + @Resource + private MkPointsConfigService pointsConfigService; + + private OrderInfo checkPay(CheckOrderPay checkOrderPay) { + OrderInfo orderInfo = orderInfoCustomService.checkOrderPay(checkOrderPay); + if (orderInfo.getOrderAmount().compareTo(BigDecimal.ZERO) == 0) { + //发送打票信息 + //orderId_0_0 订单ID_先付后付(1先付0后付)_订单状态 0未完成 1完成 + //orderInfo.getId() + "_" + (!"after-pay".equals(orderInfo.getPayMode()) ? 1 : 0) + "_0" + rabbitPublisher.sendOrderPrintMsg(orderInfo.getId() + "_" + (!"after-pay".equals(orderInfo.getPayMode()) ? 1 : 0) + "_1", orderInfo.getIsPrint() == 1); + redisService.del(RedisCst.classKeyExpired.EXPIRED_ORDER + orderInfo.getId()); + throw new PaySuccessException("支付成功"); + } + return orderInfo; + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult creditPayOrder(OrderPayParamDTO payParam) { + AssertUtil.isNull(payParam.getCreditBuyerId(), "请选择挂账人后支付"); + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + orderInfo.setCreditBuyerId(payParam.getCreditBuyerId()); + orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), + LocalDateTime.now(), null, PayEnums.CREDIT_PAY); + //挂账后续逻辑 + buyerOrderService.save(payParam.getCreditBuyerId().toString(), orderInfo.getId()); + return CzgResult.success(); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult cashPayOrder(OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), + LocalDateTime.now(), null, PayEnums.CASH_PAY); + return CzgResult.success(); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult vipPayOrder(OrderPayParamDTO payParam) { + ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); + AssertUtil.isNull(shopInfo, "店铺不存在"); + if (!shopInfo.getIsAccountPay().equals(1)) { + return CzgResult.failure("支付失败,店铺暂未开启会员余额支付。"); + } + ShopUser shopUser = new ShopUser(); + if ("scanCode".equals(payParam.getPayType())) { + AssertUtil.isBlank(payParam.getAuthCode(), "会员码不能为空"); + Object o = redisService.get(RedisCst.SHOP_USER_DYNAMIC_CODE + payParam.getShopId() + ":" + payParam.getAuthCode()); + AssertUtil.isNull(o, "会员码已失效"); + shopUser = shopUserService.getById(o.toString()); + } else { + if ("userPay".equals(payParam.getPayType())) { + AssertUtil.isNull(payParam.getShopUserId(), "请选择付款人后重试"); + shopUser = shopUserService.getById(payParam.getShopUserId()); + } else if ("accountPay".equals(payParam.getPayType())) { + shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "会员不存在"); + UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); + AssertUtil.isNull(userInfo, "用户信息不存在"); + if (userInfo.getUsePayPwd() == 1) { + AssertUtil.isBlank(payParam.getPwd(), "支付密码不能为空"); + if (userInfo.getPayPwd() == null || !userInfo.getPayPwd().equals(MD5.create().digestHex((payParam.getPwd())))) { + return CzgResult.failure("支付密码错误"); + } + } + } + } + if (shopUser == null || shopUser.getId() == null) { + AssertUtil.isNull(shopUser, "会员不存在"); + } + payParam.getCheckOrderPay().setUserId(shopUser.getUserId()); + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + if (shopInfo.getIsHeadShop().equals(1)) { + if (!shopUser.getMainShopId().equals(orderInfo.getShopId())) { + return CzgResult.failure("违规操作,请确认店铺后重试"); + } + } else { + boolean exists = shopInfoService.exists(QueryWrapper.create() + .eq(ShopInfo::getMainId, shopInfo.getMainId()) + .eq(ShopInfo::getId, orderInfo.getShopId())); + if (!exists) { + return CzgResult.failure("违规操作,请确认店铺后重试"); + } + } + if (shopUser.getAmount().compareTo(orderInfo.getOrderAmount()) < 0) { + return CzgResult.failure("会员余额不足"); + } + ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO() + .setId(shopUser.getId()) + .setMoney(orderInfo.getOrderAmount()) + .setType(0) + .setBizEnum(ShopUserFlowBizEnum.ORDER_PAY) + .setRelationId(orderInfo.getId()); + //更新会员余额 并生成流水 + if (payParam.getCheckOrderPay() != null && StrUtil.isNotBlank(payParam.getCheckOrderPay().getRemark())) { + orderInfo.setRemark(payParam.getCheckOrderPay().getRemark()); + } + + Long flowId = shopUserService.updateMoney(shopUserMoneyEditDTO); + orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), + LocalDateTime.now(), flowId, PayEnums.VIP_PAY); + return CzgResult.success(); + } + + @Override + public CzgResult> rechargePayOrder(String clintIp, OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); + AssertUtil.isNull(shopInfo, "店铺不存在"); + AssertUtil.isNull(payParam.getShopUserId(), "请选择付款人后重试"); + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "支付失败 该店铺用户不存在"); + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); + AssertUtil.isNull(payParam.getRechargeId(), "请选择充值配置后重试"); + AssertUtil.isNull(payParam.getRechargeDetailId(), "请选择充值配置后重试"); + //查询活动Id 获取金额字段 + MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getOne( + new QueryWrapper().eq(MkShopRechargeDetail::getId, payParam.getRechargeDetailId()) + .eq(MkShopRechargeDetail::getShopRechargeId, payParam.getRechargeId())); + AssertUtil.isNull(rechargeDetail, "充值配置不存在"); + if (orderInfo.getOrderAmount().compareTo(rechargeDetail.getAmount()) >= 0) { + log.info("充值金额小于订单金额,充值金额:{} 订单金额:{}", rechargeDetail.getAmount(), orderInfo.getOrderAmount()); + return CzgResult.failure("支付失败 充值金额必须大雨订单金额"); + } + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), PayTypeConstants.SourceType.MEMBER_IN, + payOrderNo, rechargeDetail.getAmount(), "", rechargeDetail.getId())); + upOrderPayInfo(orderInfo.getId(), PayEnums.VIP_PAY, paymentId, + payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + return payService.pay(payParam.getShopId(), CzgPayEnum.LT_PAY, + CzgPayBaseReq.ltPayReq( + payOrderNo, "充值并支付", + rechargeDetail.getAmount().multiply(PayService.MONEY_RATE).longValue(), payParam.getPayType(), + "wechatPay".equals(payParam.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId(), clintIp)); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult> h5PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), "")); + upOrderPayInfo(orderInfo.getId(), PayEnums.H5_PAY, paymentId, payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + return payService.pay(payParam.getShopId(), CzgPayEnum.H5_PAY, + CzgPayBaseReq.h5PayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), clintIp)); + } + + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult> jsPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), "")); + upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId, + payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + return payService.pay(payParam.getShopId(), CzgPayEnum.JS_PAY, + CzgPayBaseReq.jsPayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), + payParam.getPayType(), payParam.getOpenId(), clintIp)); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); + OrderInfo orderInfo; + if (payParam.getCheckOrderPay().getOrderId() == null) { + if (payParam.getCheckOrderPay().getOrderAmount() == null || payParam.getCheckOrderPay().getOrderAmount().compareTo(BigDecimal.ZERO) <= 0) { + throw new CzgException("支付金额不合法"); + } + orderInfo = orderInfoCustomService.createPayOrder(payParam.getShopId(), payParam.getCheckOrderPay().getOrderAmount(), + payParam.getCheckOrderPay().getRemark()); + } else { + orderInfo = orderInfoService.getById(payParam.getCheckOrderPay().getOrderId()); + } + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), "")); + upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId, + payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + return payService.pay(payParam.getShopId(), CzgPayEnum.JS_PAY, + CzgPayBaseReq.jsPayReq(payOrderNo, "扫码支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), + payParam.getPayType(), payParam.getOpenId(), clintIp)); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult> ltPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), "")); + upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId, + payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + return payService.pay(payParam.getShopId(), CzgPayEnum.LT_PAY, + CzgPayBaseReq.ltPayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), + payParam.getPayType(), payParam.getOpenId(), clintIp)); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult> scanPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId()); + if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { + throw new CzgException("未开启自定义充值金额"); + } + Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); + BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount()); + payParam.setAmount(amount); + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), "")); + upOrderPayInfo(orderInfo.getId(), PayEnums.MAIN_SCAN, paymentId, + payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + return payService.pay(payParam.getShopId(), CzgPayEnum.SCAN_PAY, + CzgPayBaseReq.scanPayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), clintIp)); + } + + @Override + @Transactional(noRollbackFor = PaySuccessException.class) + public CzgResult> microPayOrder(OrderPayParamDTO payParam) { + OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); + AssertUtil.isBlank(payParam.getAuthCode(), "扫描码不能为空"); + + + if (payParam.getShopUserId() != null) { + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId()); + if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { + throw new CzgException("未开启自定义充值金额"); + } + Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); + BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount()); + payParam.setAmount(amount); + } + + String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); + Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), payParam.getAuthCode())); + CzgResult> mapCzgResult = payService.pay(payParam.getShopId(), CzgPayEnum.MICRO_PAY, + CzgPayBaseReq.microPay(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), payParam.getAuthCode())); + if (mapCzgResult.getCode() == 200) { + orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), + LocalDateTime.now(), paymentId, PayEnums.BACK_SCAN); + } else { + upOrderPayInfo(orderInfo.getId(), PayEnums.BACK_SCAN, paymentId, + payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); + } + return mapCzgResult; + } + + + @Override + @Transactional + public CzgResult refundOrderBefore(OrderInfoRefundDTO param) { + OrderInfo orderInfo = orderInfoService.getById(param.getOrderId()); + if (orderInfo.getStatus().equals(OrderStatusEnums.CANCELLED.getCode())) { + throw new CzgException("订单已过期不可退单"); + } + boolean isFirstRefund = true; + if (orderInfo.getRefundAmount().compareTo(BigDecimal.ZERO) != 0) { + isFirstRefund = false; + } + ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId()); + Map returnProMap = new HashMap<>(); + boolean isPay = true; + String refPayOrderNo = "REFO" + IdUtil.getSnowflakeNextId(); + if (orderInfo.getStatus().equals(OrderStatusEnums.UNPAID.getCode())) { + isPay = false; + refPayOrderNo = ""; + } + if (isPay) { + if (shopInfo.getIsReturnPwd().equals(1)) { + AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用"); + if (!SecureUtil.md5(param.getPwd()).equals(shopInfo.getOperationPwd())) { + throw new CzgException("操作密码错误"); + } + } + orderInfo.setRefundAmount(orderInfo.getRefundAmount().add(param.getRefundAmount())); + if (orderInfo.getRefundAmount().compareTo(orderInfo.getPayAmount()) > 0) { + throw new CzgException("退单失败,可退金额不足"); + } + } + if (CollUtil.isNotEmpty(param.getRefundDetails())) { + for (OrderDetail refundDetail : param.getRefundDetails()) { + AssertUtil.isNull(refundDetail.getNum(), "退单数量不能为空"); + //退款数量 + BigDecimal refNum = refundDetail.getNum(); + OrderDetail orderDetail = orderDetailService.getById(refundDetail.getId()); + //可退数量=订单数量-退单数量-退菜数量 + BigDecimal returnNum = orderDetail.getNum().subtract(orderDetail.getRefundNum()).subtract(orderDetail.getReturnNum()); + if (returnNum.compareTo(BigDecimal.ZERO) <= 0 || returnNum.compareTo(refundDetail.getNum()) < 0) { + throw new CzgException("退单失败," + orderDetail.getProductName() + "可退数量不足"); + } + refundDetail.setReturnAmount(refundDetail.getNum().multiply(orderDetail.getUnitPrice()).setScale(2, RoundingMode.UP)); + if (isPay) { + orderDetail.setRefundNum(orderDetail.getRefundNum().add(refNum)); + if (orderDetail.getNum().compareTo(orderDetail.getRefundNum().add(orderDetail.getReturnNum())) == 0) { + orderDetail.setStatus(OrderStatusEnums.REFUND.getCode()); + } else { + orderDetail.setStatus(OrderStatusEnums.PART_REFUND.getCode()); + } + } else { + orderDetail.setReturnNum(orderDetail.getReturnNum().add(refNum)); + if (orderDetail.getPackNumber().compareTo(BigDecimal.ZERO) > 0 && orderDetail.getPackNumber().compareTo(orderDetail.getNum().subtract(orderDetail.getReturnNum())) > 0) { + orderDetail.setPackNumber(orderDetail.getNum().subtract(orderDetail.getReturnNum())); + } + } + orderDetail.setRefundNo(refPayOrderNo); + orderDetail.setRefundRemark(orderDetail.getRefundRemark() + param.getRefundReason()); +// if (isPay) { + orderDetail.setReturnAmount(orderDetail.getReturnAmount().add(refundDetail.getReturnAmount())); + if (orderDetail.getReturnAmount().compareTo(orderDetail.getPayAmount()) > 0) { + orderDetail.setReturnAmount(orderDetail.getPayAmount()); + } +// } + orderDetailService.updateById(orderDetail); + if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) { + returnProMap.put(Convert.toStr(orderDetail.getProductId()), refundDetail.getNum()); + } + } + long count = orderDetailService.queryChain() + .eq(OrderDetail::getOrderId, orderInfo.getId()) + .ne(OrderDetail::getStatus, OrderStatusEnums.REFUND.getCode()).count(); + if (count > 0 && isPay) { + orderInfo.setStatus(OrderStatusEnums.PART_REFUND.getCode()); + } else if (isPay) { + orderInfo.setStatus(OrderStatusEnums.REFUND.getCode()); + } + } else { + orderInfo.setStatus(OrderStatusEnums.REFUND.getCode()); + List orderDetails = orderDetailService.queryChain() + .select(OrderDetail::getId, OrderDetail::getProductId, OrderDetail::getNum, OrderDetail::getReturnNum, OrderDetail::getPackAmount, OrderDetail::getReturnNum) + .eq(OrderDetail::getOrderId, orderInfo.getId()) + .list(); + for (OrderDetail orderDetail : orderDetails) { + if (isPay) { + if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) { + returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum()).subtract(orderDetail.getRefundNum())); + } + orderDetail.setReturnAmount(orderDetail.getPayAmount()); + orderDetail.setRefundNum(orderDetail.getNum().subtract(orderDetail.getReturnNum())); + orderDetail.setStatus(OrderStatusEnums.REFUND.getCode()); + } else { + if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) { + returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum())); + } + orderDetail.setReturnNum(orderDetail.getNum()); + orderDetail.setStatus(OrderStatusEnums.CANCELLED.getCode()); + } + } + orderDetailService.updateBatch(orderDetails); + } + //总退款金额 + //TODO 退款 券 未处理 + if (isPay) { + orderInfo.setRefundType("cash"); + //非现金退款 + if (!param.isCash()) { + if (orderInfo.getPayType().equals(PayEnums.VIP_PAY.getValue())) { + ShopUser shopUser = shopUserService.getShopUserInfo(orderInfo.getShopId(), orderInfo.getUserId()); + //会员支付 退钱 + ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO() + .setId(shopUser.getId()) + .setMoney(param.getRefundAmount()) + .setType(1) + .setRelationId(orderInfo.getId()) + .setBizEnum(ShopUserFlowBizEnum.ORDER_REFUND); + shopUserService.updateMoney(shopUserMoneyEditDTO); + } else if (orderInfo.getPayType().equals(PayEnums.CREDIT_PAY.getValue())) { + AssertUtil.isNull(orderInfo.getCreditBuyerId(), "挂账单退款失败,未查询到挂账人"); + buyerOrderService.partRefund(orderInfo.getCreditBuyerId().toString(), orderInfo.getId(), param.getRefundAmount()); + } else if (!orderInfo.getPayType().equals(PayEnums.CASH_PAY.getValue())) { + //退款 param.getRefundAmount() + if (orderInfo.getPayOrderId() == null) { + throw new CzgException("退款失败,支付记录不存在"); + } + refundOrder(orderInfo.getShopId(), orderInfo.getId(), orderInfo.getPayOrderId(), + refPayOrderNo, StrUtil.isBlank(param.getRefundReason()) ? "退款" : param.getRefundReason(), param.getRefundAmount()); + } + orderInfo.setRefundType("payBack"); + } + } else { + orderInfo.setOrderAmount(orderInfo.getOrderAmount().subtract(param.getRefundAmount())); + } + orderInfo.setRefundRemark(orderInfo.getRefundRemark() + param.getRefundReason()); + orderInfoService.updateById(orderInfo); + //退款后续 + //退款返还库存 + if (!returnProMap.isEmpty()) { + rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderInfo.getId(), "returnProMap", returnProMap))); + } + refundOrderAfter(orderInfo.getId(), orderInfo.getShopId(), orderInfo.getUserId(), orderInfo.getOrderNo(), + orderInfo.getPointsNum(), isFirstRefund, orderInfo.getStatus().equals(OrderStatusEnums.REFUND.getCode())); + return CzgResult.success(); + } + + private void refundOrder(@NonNull Long shopId, @NonNull Long orderId, @NonNull Long payOrderId, @NonNull String refPayOrderNo, + @NonNull String refundReason, @NonNull BigDecimal refundAmount) { + OrderPayment payment = paymentService.getById(payOrderId); + AssertUtil.isNull(payment, "退款失败支付记录不存在"); + Long refundId = payService.initPayment(OrderPayment.refund(shopId, orderId, PayTypeConstants.SourceType.ORDER, + refPayOrderNo, refundAmount, payment.getId(), payment.getPlatformType())); + + CzgResult refund = payService.refund(shopId, new CzgRefundReq(refPayOrderNo, refundReason, refundAmount.multiply(PayService.MONEY_RATE).longValue(), + payment.getAmount().multiply(PayService.MONEY_RATE).longValue(), payment.getOrderNo(), "", payment.getPlatformType())); + if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getStatus())) { + if (refund.getData() != null && refund.getData().getErrMessage() != null) { + throw new CzgException(refund.getData().getErrMessage()); + } + throw new CzgException(refund.getMsg()); + } else { + paymentService.updateChain() + .eq(OrderPayment::getId, refundId) + .set(OrderPayment::getPayTime, refund.getData().getRefundTime()) + .set(OrderPayment::getTradeNumber, refund.getData().getThirdRefundNo()) + .set(OrderPayment::getRespJson, refund.getData().getOriginalData()) + .update(); + } + } + + //触发订单退款时 后续处理 + @Async + public void refundOrderAfter(@NonNull Long orderId, @NonNull Long shopId, Long userId, String orderNo, + Integer pointsNum, boolean isFirstRefund, boolean isAllRefund) { + if (isFirstRefund) { + // 退款分销还原 + FunUtils.safeRunVoid(() -> distributionUserService.refund(orderId, orderNo), + "订单id:{} 退款,分销处理失败", orderId); + if (userId == null) { + return; + } + FunUtils.safeRunVoid(() -> consumerCouponService.removeConsumerCoupon(shopId, userId, orderId), + "订单id:{} 退款,消费赠券回撤处理失败", orderId); + FunUtils.safeRunVoid(() -> consumeCashbackService.removeCashback(shopId, userId, orderId, orderNo), + "订单id:{} 退款,消费返现扣除处理失败", orderId); + FunUtils.safeRunVoid(() -> pointsConfigService.removeConsumeAwardPoints(shopId, userId, orderId, orderNo), + "订单id:{} 退款,赠送积分扣除失败", orderId); + } + if (isAllRefund && userId != null && pointsNum != null && pointsNum > 0) { + FunUtils.safeRunVoid(() -> mkPointsUserService.alterPoints(userId, null, shopId, PointsConstant.ADD, + pointsNum, orderId, StrUtil.format("订单退款返还{}积分", pointsNum)), + "订单id:{} 退款,赠送积分扣除失败", orderId); + + } + } + + + private void upOrderPayInfo(@NonNull Long orderId, @NonNull PayEnums payType, @NotBlank Long paymentId, String remark) { + if (paymentId == null) { + throw new CzgException("未获取到支付记录"); + } + orderInfoCustomService.updatePayOrderId(orderId, paymentId, payType.getValue(), remark); + } +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java index 0c14f7fc6..098f3842b 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java @@ -1,73 +1,40 @@ package com.czg.service.order.service.impl; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.digest.MD5; import com.alibaba.fastjson2.JSONObject; -import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO; -import com.czg.account.entity.*; -import com.czg.account.service.*; -import com.czg.config.RabbitPublisher; -import com.czg.config.RedisCst; +import com.czg.PayAdapter; +import com.czg.PayAdapterFactory; +import com.czg.constant.PayChannelCst; import com.czg.constants.ParamCodeCst; import com.czg.constants.PayTypeConstants; -import com.czg.entity.req.*; -import com.czg.entity.resp.*; -import com.czg.enums.ShopUserFlowBizEnum; +import com.czg.constants.SystemConstants; +import com.czg.enums.CzgPayEnum; import com.czg.exception.CzgException; -import com.czg.exception.PaySuccessException; -import com.czg.market.dto.MemberOrderDTO; -import com.czg.market.entity.MemberOrder; -import com.czg.market.entity.MkShopCouponRecord; -import com.czg.market.entity.MkShopRechargeDetail; -import com.czg.market.enums.PointsConstant; -import com.czg.market.service.*; -import com.czg.market.vo.MkShopRechargeVO; -import com.czg.order.dto.CheckOrderPay; import com.czg.order.dto.LtPayOtherDTO; -import com.czg.order.dto.OrderInfoRefundDTO; -import com.czg.order.entity.OrderDetail; -import com.czg.order.entity.OrderInfo; import com.czg.order.entity.OrderPayment; -import com.czg.order.enums.PayEnums; -import com.czg.order.service.CreditBuyerOrderService; -import com.czg.order.service.OrderDetailService; -import com.czg.order.service.OrderInfoCustomService; +import com.czg.order.entity.ShopMerchant; import com.czg.order.service.OrderPaymentService; -import com.czg.resp.CzgRespCode; +import com.czg.order.service.ShopMerchantService; +import com.czg.pay.CzgPayBaseReq; +import com.czg.pay.CzgRefundReq; +import com.czg.pay.QueryOrderRespDTO; +import com.czg.pay.RefundRespDTO; import com.czg.resp.CzgResult; -import com.czg.service.CzgPayService; -import com.czg.service.RedisService; -import com.czg.service.order.dto.OrderPayParamDTO; -import com.czg.service.order.dto.VipMemberPayParamDTO; -import com.czg.service.order.dto.VipPayParamDTO; -import com.czg.service.order.dto.VipRefundDTO; -import com.czg.service.order.enums.OrderStatusEnums; import com.czg.service.order.mapper.OrderPaymentMapper; import com.czg.service.order.service.PayService; import com.czg.system.service.SysParamsService; import com.czg.utils.AssertUtil; -import com.czg.utils.CzgRandomUtils; -import com.czg.utils.FunUtils; import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; -import jakarta.validation.constraints.NotBlank; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -78,798 +45,58 @@ import java.util.Map; @Service public class PayServiceImpl implements PayService { - @DubboReference + @Resource private ShopMerchantService shopMerchantService; @DubboReference private SysParamsService sysParamsService; - @DubboReference - private ShopUserService shopUserService; - @DubboReference - private UserInfoService userInfoService; - @DubboReference - private ShopInfoService shopInfoService; - @DubboReference - private ShopUserFlowService userFlowService; - @Resource - private MkShopCouponRecordService recordService; - @DubboReference - private FreeDineConfigService freeConfigService; - @Resource - private CzgPayService czgPayService; - @Resource - private OrderInfoCustomService orderInfoCustomService; - @Resource - private OrderInfoService orderInfoService; - @Resource - private OrderDetailService orderDetailService; @Resource private OrderPaymentService paymentService; @Resource private OrderPaymentMapper paymentMapper; - @Resource - private CreditBuyerOrderService buyerOrderService; - @Resource - private RedisService redisService; - @Resource - private RabbitPublisher rabbitPublisher; - @DubboReference - private MemberOrderService memberOrderService; - @Resource - private MkShopRechargeService shopRechargeService; - @Resource - private MkShopRechargeDetailService shopRechargeDetailService; - @Resource - private MkDistributionUserService distributionUserService; - @Resource - private MkShopConsumerCouponService consumerCouponService; - @Resource - private MkPointsUserService mkPointsUserService; - @Resource - private MkConsumeCashbackService consumeCashbackService; - @Resource - private MkPointsConfigService pointsConfigService; - - private final BigDecimal MONEY_RATE = new BigDecimal("100"); - - private OrderInfo checkPay(CheckOrderPay checkOrderPay) { - OrderInfo orderInfo = orderInfoCustomService.checkOrderPay(checkOrderPay); - if (orderInfo.getOrderAmount().compareTo(BigDecimal.ZERO) == 0) { - //发送打票信息 - //orderId_0_0 订单ID_先付后付(1先付0后付)_订单状态 0未完成 1完成 - //orderInfo.getId() + "_" + (!"after-pay".equals(orderInfo.getPayMode()) ? 1 : 0) + "_0" - rabbitPublisher.sendOrderPrintMsg(orderInfo.getId() + "_" + (!"after-pay".equals(orderInfo.getPayMode()) ? 1 : 0) + "_1", orderInfo.getIsPrint() == 1); - redisService.del(RedisCst.classKeyExpired.EXPIRED_ORDER + orderInfo.getId()); - throw new PaySuccessException("支付成功"); - } - return orderInfo; - } - - /** - * 会员充值 校验 - * - * @return 是否是霸王餐充值 - */ - private boolean checkPayVip(VipPayParamDTO payParam) { - - if (payParam.getAmount().compareTo(BigDecimal.ZERO) <= 0) { - throw new CzgException("充值金额不正确"); - } - if (payParam.getOrderId() != null) { - FreeDineConfig freeConfig = freeConfigService.getById(shopInfoService.getMainIdByShopId(payParam.getShopId())); - AssertUtil.isNull(freeConfig, "该店铺未启用霸王餐"); - if (!freeConfig.getEnable()) { - throw new CzgException("该店铺未启用霸王餐"); - } - CheckOrderPay checkOrderPay = payParam.getCheckOrderPay(); - OrderInfo orderInfo = orderInfoCustomService.checkOrderPay(checkOrderPay.setFreeDine(true).setWithCoupon(freeConfig.getWithCoupon()).setWithPoints(freeConfig.getWithPoints())); - payParam.setAmount(orderInfo.getOrderAmount().multiply(BigDecimal.valueOf(freeConfig.getRechargeTimes()))); - return true; - } - return false; - } - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult creditPayOrder(OrderPayParamDTO payParam) { - AssertUtil.isNull(payParam.getCreditBuyerId(), "请选择挂账人后支付"); - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - orderInfo.setCreditBuyerId(payParam.getCreditBuyerId()); - orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), - LocalDateTime.now(), null, PayEnums.CREDIT_PAY); - //挂账后续逻辑 - buyerOrderService.save(payParam.getCreditBuyerId().toString(), orderInfo.getId()); - return CzgResult.success(); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult cashPayOrder(OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), - LocalDateTime.now(), null, PayEnums.CASH_PAY); - return CzgResult.success(); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult vipPayOrder(OrderPayParamDTO payParam) { - ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); - AssertUtil.isNull(shopInfo, "店铺不存在"); - if (!shopInfo.getIsAccountPay().equals(1)) { - return CzgResult.failure("支付失败,店铺暂未开启会员余额支付。"); + public CzgResult> pay(@NonNull Long shopId, CzgPayEnum payType, CzgPayBaseReq bizData) { + ShopMerchant shopMerchant = getMerchant(shopId); + PayAdapter adapter = PayAdapterFactory.getAdapter(shopMerchant.getChannel()); + String payData = null; + if (shopMerchant.getChannel().equals(PayChannelCst.NATIVE)) { + payData = shopMerchant.getNativePayJson(); + } else if (shopMerchant.getChannel().equals(PayChannelCst.POLY)) { + payData = shopMerchant.getPolyPayJson(); } - ShopUser shopUser = new ShopUser(); - if ("scanCode".equals(payParam.getPayType())) { - AssertUtil.isBlank(payParam.getAuthCode(), "会员码不能为空"); - Object o = redisService.get(RedisCst.SHOP_USER_DYNAMIC_CODE + payParam.getShopId() + ":" + payParam.getAuthCode()); - AssertUtil.isNull(o, "会员码已失效"); - shopUser = shopUserService.getById(o.toString()); - } else { - if ("userPay".equals(payParam.getPayType())) { - AssertUtil.isNull(payParam.getShopUserId(), "请选择付款人后重试"); - shopUser = shopUserService.getById(payParam.getShopUserId()); - } else if ("accountPay".equals(payParam.getPayType())) { - shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "会员不存在"); - UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); - AssertUtil.isNull(userInfo, "用户信息不存在"); - if (userInfo.getUsePayPwd() == 1) { - AssertUtil.isBlank(payParam.getPwd(), "支付密码不能为空"); - if (userInfo.getPayPwd() == null || !userInfo.getPayPwd().equals(MD5.create().digestHex((payParam.getPwd())))) { - return CzgResult.failure("支付密码错误"); - } - } - } + bizData.setSubAppid(SystemConstants.PayType.ALIPAY.equals(bizData.getPayType()) ? shopMerchant.getAlipayAppId() : shopMerchant.getWechatAppId()); + if (payType.equals(CzgPayEnum.MICRO_PAY)) { + checkMicroPay(bizData, shopMerchant); } - if (shopUser == null || shopUser.getId() == null) { - AssertUtil.isNull(shopUser, "会员不存在"); - } - payParam.getCheckOrderPay().setUserId(shopUser.getUserId()); - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - if (shopInfo.getIsHeadShop().equals(1)) { - if (!shopUser.getMainShopId().equals(orderInfo.getShopId())) { - return CzgResult.failure("违规操作,请确认店铺后重试"); - } - } else { - boolean exists = shopInfoService.exists(QueryWrapper.create() - .eq(ShopInfo::getMainId, shopInfo.getMainId()) - .eq(ShopInfo::getId, orderInfo.getShopId())); - if (!exists) { - return CzgResult.failure("违规操作,请确认店铺后重试"); - } - } - if (shopUser.getAmount().compareTo(orderInfo.getOrderAmount()) < 0) { - return CzgResult.failure("会员余额不足"); - } - ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO() - .setId(shopUser.getId()) - .setMoney(orderInfo.getOrderAmount()) - .setType(0) - .setBizEnum(ShopUserFlowBizEnum.ORDER_PAY) - .setRelationId(orderInfo.getId()); - //更新会员余额 并生成流水 - if (payParam.getCheckOrderPay() != null && StrUtil.isNotBlank(payParam.getCheckOrderPay().getRemark())) { - orderInfo.setRemark(payParam.getCheckOrderPay().getRemark()); - } - - Long flowId = shopUserService.updateMoney(shopUserMoneyEditDTO); - orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), - LocalDateTime.now(), flowId, PayEnums.VIP_PAY); - return CzgResult.success(); + return adapter.pay(payType, payData, getDomain(), getNotifyUrl(shopMerchant.getChannel()), bizData); } - @Override - public CzgResult> rechargePayOrder(String clintIp, OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); - AssertUtil.isNull(shopInfo, "店铺不存在"); - AssertUtil.isNull(payParam.getShopUserId(), "请选择付款人后重试"); - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "支付失败 该店铺用户不存在"); - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); - AssertUtil.isNull(payParam.getRechargeId(), "请选择充值配置后重试"); - AssertUtil.isNull(payParam.getRechargeDetailId(), "请选择充值配置后重试"); - //查询活动Id 获取金额字段 - MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getOne( - new QueryWrapper().eq(MkShopRechargeDetail::getId, payParam.getRechargeDetailId()) - .eq(MkShopRechargeDetail::getShopRechargeId, payParam.getRechargeId())); - AssertUtil.isNull(rechargeDetail, "充值配置不存在"); - if (orderInfo.getOrderAmount().compareTo(rechargeDetail.getAmount()) >= 0) { - log.info("充值金额小于订单金额,充值金额:{} 订单金额:{}", rechargeDetail.getAmount(), orderInfo.getOrderAmount()); - return CzgResult.failure("支付失败 充值金额必须大雨订单金额"); - } - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), shopUser.getId(), PayTypeConstants.SourceType.MEMBER_IN, PayTypeConstants.PayType.PAY, payOrderNo, - "", rechargeDetail.getAmount(), rechargeDetail.getId())); - upOrderPayInfo(orderInfo.getId(), PayEnums.VIP_PAY, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - return ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(payOrderNo, rechargeDetail.getAmount().multiply(MONEY_RATE).longValue(), - payParam.getPayType(), "充值并支付", "wechatPay".equals(payParam.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult> h5PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), orderInfo.getId(), - PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.PAY, payOrderNo, "", orderInfo.getOrderAmount())); - upOrderPayInfo(orderInfo.getId(), PayEnums.H5_PAY, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - return h5Pay(payParam.getShopId(), new CzgH5PayReq(payOrderNo, orderInfo.getOrderAmount().multiply(MONEY_RATE).longValue(), - "点餐支付", clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult> jsPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), orderInfo.getId(), - PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.PAY, payOrderNo, "", orderInfo.getOrderAmount())); - upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - return jsPay(payParam.getShopId(), payParam.getPayType(), new CzgJsPayReq(payOrderNo, orderInfo.getOrderAmount().multiply(MONEY_RATE).longValue(), - "点餐支付", payParam.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); - OrderInfo orderInfo; - if (payParam.getCheckOrderPay().getOrderId() == null) { - if (payParam.getCheckOrderPay().getOrderAmount() == null || payParam.getCheckOrderPay().getOrderAmount().compareTo(BigDecimal.ZERO) <= 0) { - throw new CzgException("支付金额不合法"); - } - orderInfo = orderInfoCustomService.createPayOrder(payParam.getShopId(), payParam.getCheckOrderPay().getOrderAmount(), - payParam.getCheckOrderPay().getRemark()); - } else { - orderInfo = orderInfoService.getById(payParam.getCheckOrderPay().getOrderId()); - } - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), orderInfo.getId(), - PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.PAY, payOrderNo, "", orderInfo.getOrderAmount())); - upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - return jsPay(payParam.getShopId(), payParam.getPayType(), new CzgJsPayReq(payOrderNo, orderInfo.getOrderAmount().multiply(MONEY_RATE).longValue(), - "点餐支付", payParam.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult> ltPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), orderInfo.getId(), - PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.PAY, payOrderNo, "", orderInfo.getOrderAmount())); - upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - return ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(payOrderNo, orderInfo.getOrderAmount().multiply(MONEY_RATE).longValue(), - payParam.getPayType(), "点餐支付", payParam.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult> scanPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - - - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId()); - if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { - throw new CzgException("未开启自定义充值金额"); - } - Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); - BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount()); - payParam.setAmount(amount); - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), orderInfo.getId(), - PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.PAY, payOrderNo, "", orderInfo.getOrderAmount())); - upOrderPayInfo(orderInfo.getId(), PayEnums.MAIN_SCAN, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - return scanPay(payParam.getShopId(), new CzgScanPayReq(payOrderNo, orderInfo.getOrderAmount().multiply(MONEY_RATE).longValue(), - "点餐支付", clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional(noRollbackFor = PaySuccessException.class) - public CzgResult> microPayOrder(OrderPayParamDTO payParam) { - OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay()); - AssertUtil.isBlank(payParam.getAuthCode(), "扫描码不能为空"); - - - if (payParam.getShopUserId() != null) { - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId()); - if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { - throw new CzgException("未开启自定义充值金额"); - } - Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); - BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount()); - payParam.setAmount(amount); - } - - String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake(); - Long paymentId = initOrderPayment(new OrderPayment(payParam.getShopId(), orderInfo.getId(), - PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.PAY, payOrderNo, payParam.getAuthCode(), orderInfo.getOrderAmount())); - CzgResult> mapCzgResult = microPay(payParam.getShopId(), new CzgMicroPayReq(payOrderNo, orderInfo.getOrderAmount().multiply(MONEY_RATE).longValue(), - "点餐支付", payParam.getAuthCode(), payParam.getBuyerRemark(), "")); - if (mapCzgResult.getCode() == 200) { - orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(), - LocalDateTime.now(), paymentId, PayEnums.BACK_SCAN); - } else { - upOrderPayInfo(orderInfo.getId(), PayEnums.BACK_SCAN, paymentId, - payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark()); - } - return mapCzgResult; - } - - @Override - @Transactional - public CzgResult cashPayVip(VipPayParamDTO payParam) { - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); - AssertUtil.isNull(shopInfo, "店铺不存在"); - if (shopInfo.getIsMemberInPwd().equals(1)) { - AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用"); - AssertUtil.isBlank(payParam.getPwd(), "请输入操作密码后充值"); - if (!shopInfo.getOperationPwd().equals(SecureUtil.md5(payParam.getPwd()))) { - return CzgResult.failure("支付密码错误"); - } - } -// if (shopUser.getIsVip().equals(0)) { -// //更新会员 -// ShopUser updateInfo = new ShopUser(); -// updateInfo.setIsVip(1); -// updateInfo.setJoinTime(LocalDateTime.now()); -// updateInfo.setId(payParam.getShopUserId()); -// shopUserService.updateById(updateInfo); -// } - shopRechargeService.recharge(shopUser.getMainShopId(), shopUser.getId(), payParam.getRechargeDetailId(), - payParam.getAmount(), null, "cash", ShopUserFlowBizEnum.CASH_IN, true); - return CzgResult.success(); - } - - @Override - @Transactional - public CzgResult> jsPayVip(String clintIp, VipPayParamDTO payParam) { - boolean isFree = checkPayVip(payParam); - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); - String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; - initOrderPayment(new OrderPayment(payParam.getShopId(), shopUser.getId(), payType, PayTypeConstants.PayType.PAY, payOrderNo, - "", payParam.getAmount(), isFree ? payParam.getOrderId() : payParam.getActivateId())); - return jsPay(payParam.getShopId(), payParam.getPayType(), new CzgJsPayReq(payOrderNo, payParam.getAmount().multiply(MONEY_RATE).longValue(), - "会员充值", payParam.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public CzgResult> ltPayMember(String clientIP, VipMemberPayParamDTO payParam) { - ShopUser shopUser = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getMainShopId, shopInfoService.getMainIdByShopId(payParam.getShopId())).eq(ShopUser::getId, payParam.getShopUserId())); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); - MemberOrder memberOrder = memberOrderService.createMemberOrder(new MemberOrderDTO().setName(payParam.getName()) - .setNum(1).setNickName(payParam.getNickName()) - .setOrderType(payParam.getOrderType()) - .setPlatformType(payParam.getPlatformType()).setSex(payParam.getSex()).setUserId(shopUser.getUserId()).setShopId(payParam.getShopId()).setBirthDay(payParam.getBirthDay())); - - AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); - initOrderPayment(new OrderPayment(payParam.getShopId(), shopUser.getId(), PayTypeConstants.SourceType.MEMBER_PAY, PayTypeConstants.PayType.PAY, payOrderNo, - "", memberOrder.getAmount(), memberOrder.getId())); - return ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(payOrderNo, memberOrder.getAmount().multiply(MONEY_RATE).longValue(), - payParam.getPayType(), "会员充值", "wechatPay".equals(payParam.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId(), clientIP, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional - public CzgResult> ltPayVip(String clintIp, VipPayParamDTO payParam) { - // 霸王餐校验 - boolean isFree = checkPayVip(payParam); - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); - AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); - - MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId()); - if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { - throw new CzgException("未开启自定义充值金额"); - } - Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); - if (isFree) { - BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount()); - payParam.setAmount(amount); - } - - String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); - String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; - initOrderPayment(new OrderPayment(payParam.getShopId(), shopUser.getId(), payType, PayTypeConstants.PayType.PAY, payOrderNo, - "", payParam.getAmount(), isFree ? payParam.getOrderId() : payParam.getActivateId())); - return ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(payOrderNo, payParam.getAmount().multiply(MONEY_RATE).longValue(), - payParam.getPayType(), "会员充值", payParam.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - public CzgResult> recharge(String clientIP, VipPayParamDTO rechargeDTO, Long shopUserId) { - boolean isFree = checkPayVip(rechargeDTO); - Long mainShopId = shopInfoService.getMainIdByShopId(rechargeDTO.getShopId()); - - ShopUser shopUser = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getMainShopId, mainShopId) - .eq(ShopUser::getId, shopUserId)); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - MkShopRechargeVO rechargeVO = shopRechargeService.detail(rechargeDTO.getShopId()); - if (rechargeDTO.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { - throw new CzgException("未开启自定义充值金额"); - } - UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); - - BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, rechargeDTO.getShopId(), shopUser.getUserId(), rechargeDTO.getRechargeDetailId(), rechargeDTO.getAmount()); - String payOrderNo = rechargeDTO.getPlatformType() + CzgRandomUtils.snowflake(); - String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; - initOrderPayment(new OrderPayment(rechargeDTO.getShopId(), shopUser.getId(), payType, PayTypeConstants.PayType.PAY, payOrderNo, - "", amount, isFree ? rechargeDTO.getOrderId() : rechargeDTO.getRechargeDetailId())); - return ltPay(rechargeDTO.getShopId(), rechargeDTO.getPayType(), new CzgLtPayReq(payOrderNo, amount.multiply(MONEY_RATE).longValue(), - rechargeDTO.getPayType(), "会员充值", "wechatPay".equals(rechargeDTO.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId(), - clientIP, rechargeDTO.getReturnUrl(), rechargeDTO.getBuyerRemark(), "")); - } - - @Override - @Transactional - public CzgResult> scanPayVip(String clintIp, VipPayParamDTO payParam) { - boolean isFree = checkPayVip(payParam); - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); - String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; - initOrderPayment(new OrderPayment(payParam.getShopId(), shopUser.getId(), payType, PayTypeConstants.PayType.PAY, payOrderNo, - "", payParam.getAmount(), isFree ? payParam.getOrderId() : payParam.getActivateId())); - return scanPay(payParam.getShopId(), new CzgScanPayReq(payOrderNo, payParam.getAmount().multiply(MONEY_RATE).longValue(), - "会员充值", clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); - } - - @Override - @Transactional - public CzgResult> microPayVip(VipPayParamDTO payParam) { - boolean isFree = checkPayVip(payParam); - AssertUtil.isBlank(payParam.getAuthCode(), "扫描码不能为空"); - ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); - AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); - String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); - String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; - initOrderPayment(new OrderPayment(payParam.getShopId(), shopUser.getId(), payType, PayTypeConstants.PayType.PAY, payOrderNo, - payParam.getAuthCode(), payParam.getAmount(), isFree ? payParam.getOrderId() : payParam.getActivateId())); - CzgResult> mapCzgResult = microPay(payParam.getShopId(), new CzgMicroPayReq(payOrderNo, payParam.getAmount().multiply(MONEY_RATE).longValue(), - "会员充值", payParam.getAuthCode(), payParam.getBuyerRemark(), "")); - mapCzgResult.setData(Map.of("payOrderNo", payOrderNo)); - return mapCzgResult; - } - - @Override @Transactional public CzgResult> ltPayOther(LtPayOtherDTO param, String payType, String detail) { AssertUtil.isBlank(param.getOpenId(), "用户小程序ID不能为空"); AssertUtil.isBlank(param.getPayType(), "支付方式不能为空"); - String payOrderNo = "DH" + IdUtil.getSnowflakeNextId(); - initOrderPayment(new OrderPayment(param.getShopId(), param.getRecordId(), payType, PayTypeConstants.PayType.PAY, payOrderNo, - "", param.getPrice(), null)); - return ltPay(param.getShopId(), param.getPayType(), new CzgLtPayReq(payOrderNo, param.getPrice().multiply(MONEY_RATE).longValue(), - param.getPayType(), detail, param.getOpenId(), param.getIp(), "", "", "")); + String payOrderNo = "LT" + IdUtil.getSnowflakeNextId(); + initPayment(OrderPayment.pay(param.getShopId(), param.getRecordId(), payType, payOrderNo, + param.getPrice(), "", null)); + return pay(param.getShopId(), CzgPayEnum.LT_PAY, + CzgPayBaseReq.ltPayReq(payOrderNo, detail, param.getPrice().multiply(MONEY_RATE).longValue(), + param.getPayType(), param.getOpenId(), param.getIp())); } @Override - public CzgResult> refundVipBefore(VipRefundDTO payParam) { - Map resultMap = new HashMap<>(5); - ShopUser shopUser = shopUserService.getShopUserInfo(payParam.getShopId(), payParam.getUserId()); - AssertUtil.isNull(shopUser, "该店铺用户不存在"); - ShopUserFlow inFlow = userFlowService.getById(payParam.getFlowId()); - AssertUtil.isNull(inFlow, "充值记录不存在"); - QueryWrapper queryWrapper = new QueryWrapper(); - queryWrapper.eq(ShopUserFlow::getRelationId, payParam.getFlowId()); - queryWrapper.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode()); - ShopUserFlow giftFlow = userFlowService.getOne(queryWrapper); - resultMap.put("amount", shopUser.getAmount()); - resultMap.put("inAmount", inFlow.getAmount()); - resultMap.put("inRefundAmount", inFlow.getRefundAmount()); - resultMap.put("giftAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getAmount()); - resultMap.put("giftRefundAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getRefundAmount()); - return CzgResult.success(resultMap); - } - - @Override - @Transactional - public CzgResult refundVip(VipRefundDTO refPayParam) { - ShopInfo shopInfo = shopInfoService.getById(refPayParam.getShopId()); - if (shopInfo.getIsReturnPwd().equals(1)) { - AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用"); - if (!SecureUtil.md5(refPayParam.getPwd()).equals(shopInfo.getOperationPwd())) { - throw new CzgException("操作密码错误"); - } - } - ShopUser shopUser = shopUserService.getShopUserInfo(refPayParam.getShopId(), refPayParam.getUserId()); - ShopUserFlow inFlow = userFlowService.getById(refPayParam.getFlowId()); - AssertUtil.isNull(inFlow, "充值记录不存在"); - if ("cashIn".equals(inFlow.getBizCode()) || "adminIn".equals(inFlow.getBizCode())) { - refPayParam.setCashRefund(true); - } - QueryWrapper queryWrapper = new QueryWrapper(); - queryWrapper.eq(ShopUserFlow::getRelationId, refPayParam.getFlowId()); - queryWrapper.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode()); - ShopUserFlow giftFlow = userFlowService.getOne(queryWrapper); - if ((inFlow.getAmount().subtract(inFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0) { - return CzgResult.failure("退款失败,退款金额不可大于可退金额"); - } - //用户余额减去赠送金额 小于 退款金额 则需要勾选 超额退款 - if (giftFlow != null) { - if (shopUser.getAmount().subtract(giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) { - return CzgResult.failure("超额退款,请勾选 超额退款后重试"); - } - } else { - if (shopUser.getAmount().compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) { - return CzgResult.failure("超额退款,请勾选 超额退款后重试"); - } - } - Long refPaymentId = null; - if (!refPayParam.isCashRefund()) { - OrderPayment payment; - if (inFlow.getRelationId() != null) { - payment = paymentService.getById(inFlow.getRelationId()); - } else { - return CzgResult.failure("退款失败,该充值记录不存在"); - } - String refPayOrderNo = "REFVIP" + IdUtil.getSnowflakeNextId(); - refPaymentId = initOrderPayment(new OrderPayment(refPayParam.getShopId(), shopUser.getId(), - PayTypeConstants.SourceType.MEMBER_IN, PayTypeConstants.PayType.REFUND, refPayOrderNo, null, refPayParam.getRefAmount())); - CzgResult refund = refund(refPayParam.getShopId(), new CzgRefundReq(refPayOrderNo, refPayParam.getRemark(), - refPayParam.getRefAmount().multiply(MONEY_RATE).longValue(), payment.getOrderNo(), "")); - if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getState())) { - throw new CzgException(refund.getMsg()); - } else { - paymentService.updateChain() - .eq(OrderPayment::getId, refPaymentId) - .set(OrderPayment::getPayTime, refund.getData().getRefundTime()) - .set(OrderPayment::getTradeNumber, refund.getData().getRefundOrderId()) - .set(OrderPayment::getPayStatus, PayTypeConstants.PayStatus.SUCCESS) - .set(OrderPayment::getRespJson, JSONObject.toJSONString(refund.getData())) - .update(); - } - } - ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO() - .setId(shopUser.getId()) - .setMoney(refPayParam.getRefAmount()) - .setType(0) - .setRemark("退款") - .setBizEnum(refPayParam.isCashRefund() ? ShopUserFlowBizEnum.RECHARGE_CASH_REFUND : ShopUserFlowBizEnum.RECHARGE_REFUND) - .setRelationId(refPaymentId) - .setRechargeId(inFlow.getId()); - //更新会员余额 并生成流水 - shopUserService.updateMoney(shopUserMoneyEditDTO); - userFlowService.updateRefund(inFlow.getId(), refPayParam.getRefAmount()); - if (giftFlow != null && (giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(BigDecimal.ZERO) > 0) { - ShopUserMoneyEditDTO giftFlowEdit = new ShopUserMoneyEditDTO() - .setId(shopUser.getId()) - .setMoney(giftFlow.getAmount()) - .setType(0) - .setRemark("退款") - .setBizEnum(ShopUserFlowBizEnum.RECHARGE_REFUND) - .setRelationId(refPaymentId) - .setRechargeId(giftFlow.getId()); - //更新会员余额 并生成流水 - shopUserService.updateMoney(giftFlowEdit); - userFlowService.updateRefund(giftFlow.getId(), giftFlow.getAmount()); - } - //移除优惠券 - recordService.remove(QueryWrapper.create() - .eq(MkShopCouponRecord::getSourceFlowId, inFlow.getId()) - .eq(MkShopCouponRecord::getSource, "activate") - .eq(MkShopCouponRecord::getStatus, 0)); - return CzgResult.success(); - } - - @Override - @Transactional - public CzgResult refundOrderBefore(OrderInfoRefundDTO param) { - OrderInfo orderInfo = orderInfoService.getById(param.getOrderId()); - if (orderInfo.getStatus().equals(OrderStatusEnums.CANCELLED.getCode())) { - throw new CzgException("订单已过期不可退单"); - } - boolean isFirstRefund = orderInfo.getRefundAmount().compareTo(BigDecimal.ZERO) == 0; - ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId()); - Map returnProMap = new HashMap<>(); - boolean isPay = true; - String refPayOrderNo = "REFO" + IdUtil.getSnowflakeNextId(); - if (orderInfo.getStatus().equals(OrderStatusEnums.UNPAID.getCode())) { - isPay = false; - refPayOrderNo = ""; - } - if (isPay) { - if (shopInfo.getIsReturnPwd().equals(1)) { - AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用"); - if (!SecureUtil.md5(param.getPwd()).equals(shopInfo.getOperationPwd())) { - throw new CzgException("操作密码错误"); - } - } - orderInfo.setRefundAmount(orderInfo.getRefundAmount().add(param.getRefundAmount())); - if (orderInfo.getRefundAmount().compareTo(orderInfo.getPayAmount()) > 0) { - throw new CzgException("退单失败,可退金额不足"); - } - } - if (CollUtil.isNotEmpty(param.getRefundDetails())) { - for (OrderDetail refundDetail : param.getRefundDetails()) { - AssertUtil.isNull(refundDetail.getNum(), "退单数量不能为空"); - //退款数量 - BigDecimal refNum = refundDetail.getNum(); - OrderDetail orderDetail = orderDetailService.getById(refundDetail.getId()); - //可退数量=订单数量-退单数量-退菜数量 - BigDecimal returnNum = orderDetail.getNum().subtract(orderDetail.getRefundNum()).subtract(orderDetail.getReturnNum()); - if (returnNum.compareTo(BigDecimal.ZERO) <= 0 || returnNum.compareTo(refundDetail.getNum()) < 0) { - throw new CzgException("退单失败," + orderDetail.getProductName() + "可退数量不足"); - } - refundDetail.setReturnAmount(refundDetail.getNum().multiply(orderDetail.getUnitPrice()).setScale(2, RoundingMode.UP)); - if (isPay) { - orderDetail.setRefundNum(orderDetail.getRefundNum().add(refNum)); - if (orderDetail.getNum().compareTo(orderDetail.getRefundNum().add(orderDetail.getReturnNum())) == 0) { - orderDetail.setStatus(OrderStatusEnums.REFUND.getCode()); - } else { - orderDetail.setStatus(OrderStatusEnums.PART_REFUND.getCode()); - } - } else { - orderDetail.setReturnNum(orderDetail.getReturnNum().add(refNum)); - if (orderDetail.getPackNumber().compareTo(BigDecimal.ZERO) > 0 && orderDetail.getPackNumber().compareTo(orderDetail.getNum().subtract(orderDetail.getReturnNum())) > 0) { - orderDetail.setPackNumber(orderDetail.getNum().subtract(orderDetail.getReturnNum())); - } - } - orderDetail.setRefundNo(refPayOrderNo); - orderDetail.setRefundRemark(orderDetail.getRefundRemark() + param.getRefundReason()); -// if (isPay) { - orderDetail.setReturnAmount(orderDetail.getReturnAmount().add(refundDetail.getReturnAmount())); - if (orderDetail.getReturnAmount().compareTo(orderDetail.getPayAmount()) > 0) { - orderDetail.setReturnAmount(orderDetail.getPayAmount()); - } -// } - orderDetailService.updateById(orderDetail); - if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) { - returnProMap.put(Convert.toStr(orderDetail.getProductId()), refundDetail.getNum()); - } - } - long count = orderDetailService.queryChain() - .eq(OrderDetail::getOrderId, orderInfo.getId()) - .ne(OrderDetail::getStatus, OrderStatusEnums.REFUND.getCode()).count(); - if (count > 0 && isPay) { - orderInfo.setStatus(OrderStatusEnums.PART_REFUND.getCode()); - } else if (isPay) { - orderInfo.setStatus(OrderStatusEnums.REFUND.getCode()); - } - } else { - orderInfo.setStatus(OrderStatusEnums.REFUND.getCode()); - List orderDetails = orderDetailService.queryChain() - .select(OrderDetail::getId, OrderDetail::getProductId, OrderDetail::getNum, OrderDetail::getReturnNum, OrderDetail::getPackAmount, OrderDetail::getReturnNum) - .eq(OrderDetail::getOrderId, orderInfo.getId()) - .list(); - for (OrderDetail orderDetail : orderDetails) { - if (isPay) { - if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) { - returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum()).subtract(orderDetail.getRefundNum())); - } - orderDetail.setReturnAmount(orderDetail.getPayAmount()); - orderDetail.setRefundNum(orderDetail.getNum().subtract(orderDetail.getReturnNum())); - orderDetail.setStatus(OrderStatusEnums.REFUND.getCode()); - } else { - if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) { - returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum())); - } - orderDetail.setReturnNum(orderDetail.getNum()); - orderDetail.setStatus(OrderStatusEnums.CANCELLED.getCode()); - } - } - orderDetailService.updateBatch(orderDetails); - } - //总退款金额 - //TODO 退款 券 未处理 - if (isPay) { - orderInfo.setRefundType("cash"); - //非现金退款 - if (!param.isCash()) { - if (orderInfo.getPayType().equals(PayEnums.VIP_PAY.getValue())) { - ShopUser shopUser = shopUserService.getShopUserInfo(orderInfo.getShopId(), orderInfo.getUserId()); - //会员支付 退钱 - ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO() - .setId(shopUser.getId()) - .setMoney(param.getRefundAmount()) - .setType(1) - .setRelationId(orderInfo.getId()) - .setBizEnum(ShopUserFlowBizEnum.ORDER_REFUND); - shopUserService.updateMoney(shopUserMoneyEditDTO); - } else if (orderInfo.getPayType().equals(PayEnums.CREDIT_PAY.getValue())) { - AssertUtil.isNull(orderInfo.getCreditBuyerId(), "挂账单退款失败,未查询到挂账人"); - buyerOrderService.partRefund(orderInfo.getCreditBuyerId().toString(), orderInfo.getId(), param.getRefundAmount()); - } else if (!orderInfo.getPayType().equals(PayEnums.CASH_PAY.getValue())) { - //退款 param.getRefundAmount() - if (orderInfo.getPayOrderId() == null) { - throw new CzgException("退款失败,支付记录不存在"); - } - refundOrder(orderInfo.getShopId(), orderInfo.getId(), orderInfo.getPayOrderId(), - refPayOrderNo, StrUtil.isBlank(param.getRefundReason()) ? "退款" : param.getRefundReason(), param.getRefundAmount()); - } - orderInfo.setRefundType("payBack"); - } - } else { - orderInfo.setOrderAmount(orderInfo.getOrderAmount().subtract(param.getRefundAmount())); - } - orderInfo.setRefundRemark(orderInfo.getRefundRemark() + param.getRefundReason()); - orderInfoService.updateById(orderInfo); - //退款后续 - //退款返还库存 - if (!returnProMap.isEmpty()) { - rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderInfo.getId(), "returnProMap", returnProMap))); - } - FunUtils.asyncSafeRunVoid(() -> refundOrderAfter(orderInfo.getId(), orderInfo.getShopId(), orderInfo.getUserId(), orderInfo.getOrderNo(), - orderInfo.getPointsNum(), isFirstRefund, orderInfo.getStatus().equals(OrderStatusEnums.REFUND.getCode()))); - return CzgResult.success(); - } - - //触发订单退款时 后续处理 - @Async - public void refundOrderAfter(@NonNull Long orderId, @NonNull Long shopId, Long userId, String orderNo, - Integer pointsNum, boolean isFirstRefund, boolean isAllRefund) { - if (isFirstRefund) { - // 退款分销还原 - FunUtils.safeRunVoid(() -> distributionUserService.refund(orderId, orderNo), - "订单id:{} 退款,分销处理失败", orderId); - if (userId == null) { - return; - } - FunUtils.safeRunVoid(() -> consumerCouponService.removeConsumerCoupon(shopId, userId, orderId), - "订单id:{} 退款,消费赠券回撤处理失败", orderId); - FunUtils.safeRunVoid(() -> consumeCashbackService.removeCashback(shopId, userId, orderId, orderNo), - "订单id:{} 退款,消费返现扣除处理失败", orderId); - FunUtils.safeRunVoid(() -> pointsConfigService.removeConsumeAwardPoints(shopId, userId, orderId, orderNo), - "订单id:{} 退款,赠送积分扣除失败", orderId); - } - if (isAllRefund && userId != null && pointsNum != null && pointsNum > 0) { - FunUtils.safeRunVoid(() -> mkPointsUserService.alterPoints(userId, null, shopId, PointsConstant.ADD, - pointsNum, orderId, StrUtil.format("订单退款返还{}积分", pointsNum)), - "订单id:{} 退款,赠送积分扣除失败", orderId); - - } - } - - @Override - @Transactional - public void refundOrder(@NonNull Long shopId, @NonNull Long orderId, @NonNull Long payOrderId, @NonNull String refPayOrderNo, - @NonNull String refundReason, @NonNull BigDecimal refundAmount) { - OrderPayment payment = paymentService.getById(payOrderId); - AssertUtil.isNull(payment, "退款失败支付记录不存在"); - Long refundId = initOrderPayment(new OrderPayment(shopId, orderId, PayTypeConstants.SourceType.ORDER, PayTypeConstants.PayType.REFUND, refPayOrderNo, null, refundAmount, payment.getId())); - CzgResult refund = refund(shopId, new CzgRefundReq(refPayOrderNo, refundReason, refundAmount.multiply(MONEY_RATE).longValue(), - payment.getOrderNo(), "")); - if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getState())) { - if (refund.getData() != null && refund.getData().getNote() != null) { - throw new CzgException(refund.getData().getNote()); - } - throw new CzgException(refund.getMsg()); - } else { - OrderPayment uOrderPayment = new OrderPayment(); - uOrderPayment.setPayTime(LocalDateTime.now()); - uOrderPayment.setTradeNumber(refund.getData().getRefundOrderId()); - uOrderPayment.setRespJson(JSONObject.toJSONString(refund.getData())); - paymentService.update(uOrderPayment, QueryWrapper.create().eq(OrderPayment::getId, refundId)); + public CzgResult refund(@NonNull Long shopId, CzgRefundReq bizData) { + ShopMerchant shopMerchant = getMerchant(shopId); + PayAdapter adapter = PayAdapterFactory.getAdapter(shopMerchant.getChannel()); + String payData = null; + if (shopMerchant.getChannel().equals(PayChannelCst.NATIVE)) { + payData = shopMerchant.getNativePayJson(); + bizData.setNotifyUrl(sysParamsService.getSysParamValue(ParamCodeCst.System.NATIVE_REFUND_NOTIFY_URL)); + } else if (shopMerchant.getChannel().equals(PayChannelCst.POLY)) { + payData = shopMerchant.getPolyPayJson(); + bizData.setNotifyUrl(sysParamsService.getSysParamValue(ParamCodeCst.System.POLY_REFUND_NOTIFY_URL)); } + return adapter.refund(getDomain(), payData, bizData); } @Override @@ -878,17 +105,18 @@ public class PayServiceImpl implements PayService { @NonNull String refundReason, @NonNull BigDecimal refundAmount) { OrderPayment payment = paymentService.getById(payOrderId); AssertUtil.isNull(payment, "退款失败,支付记录不存在"); - Long refundId = initOrderPayment(new OrderPayment(shopId, sourceId, payment.getSourceType(), PayTypeConstants.PayType.REFUND, refPayOrderNo, null, refundAmount, payment.getId())); - CzgResult refund = refund(shopId, new CzgRefundReq(refPayOrderNo, refundReason, refundAmount.multiply(MONEY_RATE).longValue(), - payment.getOrderNo(), "")); + Long refundId = initPayment(OrderPayment.refund(shopId, sourceId, payment.getSourceType(), refPayOrderNo, refundAmount, payment.getId(), payment.getPlatformType())); + CzgResult refund = refund(shopId, new CzgRefundReq(refPayOrderNo, refundReason, refundAmount.multiply(MONEY_RATE).longValue(), + payment.getAmount().multiply(PayService.MONEY_RATE).longValue(), payment.getOrderNo(), "", payment.getPlatformType())); OrderPayment uOrderPayment = new OrderPayment(); uOrderPayment.setRespJson(JSONObject.toJSONString(refund.getData())); - if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getState())) { + if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getStatus())) { uOrderPayment.setPayStatus(PayTypeConstants.PayStatus.FAIL); } else { - uOrderPayment.setTradeNumber(refund.getData().getRefundOrderId()); + uOrderPayment.setTradeNumber(refund.getData().getThirdRefundNo()); uOrderPayment.setPayStatus(PayTypeConstants.PayStatus.SUCCESS); uOrderPayment.setPayTime(LocalDateTimeUtil.parse(refund.getData().getRefundTime(), "yyyy-MM-dd HH:mm:ss")); + uOrderPayment.setRespJson(refund.getData().getOriginalData()); } paymentService.update(uOrderPayment, QueryWrapper.create().eq(OrderPayment::getId, refundId)); } @@ -899,18 +127,19 @@ public class PayServiceImpl implements PayService { //支付的 订单 OrderPayment payment = paymentService.getById(refundPayment.getRelatedId()); AssertUtil.isNull(payment, "退款失败,支付记录不存在"); - Long refundCompensate = initOrderPayment(new OrderPayment(refundPayment.getShopId(), refundPayment.getSourceId(), payment.getSourceType(), PayTypeConstants.PayType.REFUND_COMPENSATE, - refPayOrderNo, null, refundPayment.getAmount(), refundPayment.getId())); - CzgResult refund = refund(payment.getShopId(), new CzgRefundReq(refPayOrderNo, "退款补偿", refundPayment.getAmount().multiply(MONEY_RATE).longValue(), - payment.getOrderNo(), "")); + Long refundCompensate = initPayment(OrderPayment.refundCompensate(refundPayment.getShopId(), refundPayment.getSourceId(), + payment.getSourceType(), refPayOrderNo, refundPayment.getAmount(), refundPayment.getId())); + CzgResult refund = refund(payment.getShopId(), new CzgRefundReq(refPayOrderNo, "退款补偿", refundPayment.getAmount().multiply(MONEY_RATE).longValue(), + payment.getAmount().multiply(PayService.MONEY_RATE).longValue(), payment.getOrderNo(), "", payment.getPlatformType())); OrderPayment uOrderPayment = new OrderPayment(); - uOrderPayment.setTradeNumber(refund.getData().getRefundOrderId()); + uOrderPayment.setTradeNumber(refund.getData().getThirdRefundNo()); uOrderPayment.setRespJson(JSONObject.toJSONString(refund.getData())); - if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getState())) { + if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getStatus())) { uOrderPayment.setPayStatus(PayTypeConstants.PayStatus.FAIL); } else { uOrderPayment.setPayStatus(PayTypeConstants.PayStatus.SUCCESS); uOrderPayment.setPayTime(LocalDateTimeUtil.parse(refund.getData().getRefundTime(), "yyyy-MM-dd HH:mm:ss")); + uOrderPayment.setRespJson(refund.getData().getOriginalData()); } paymentService.update(uOrderPayment, QueryWrapper.create().eq(OrderPayment::getId, refundPayment.getId())); paymentService.update(uOrderPayment, QueryWrapper.create().eq(OrderPayment::getId, refundCompensate)); @@ -918,144 +147,95 @@ public class PayServiceImpl implements PayService { @Override @Transactional - public CzgResult queryPayOrder(@NonNull Long shopId, String payOrderId, String mchOrderNo) { + public CzgResult queryPayOrder(@NonNull Long shopId, String payOrderId, String mchOrderNo, String platform) { ShopMerchant shopMerchant = getMerchant(shopId); - return czgPayService.queryPayOrder(shopMerchant.getAppId(), shopMerchant.getAppSecret(), payOrderId, mchOrderNo); + PayAdapter adapter = PayAdapterFactory.getAdapter(shopMerchant.getChannel()); + String payData = null; + if (shopMerchant.getChannel().equals(PayChannelCst.NATIVE)) { + payData = shopMerchant.getNativePayJson(); + } else if (shopMerchant.getChannel().equals(PayChannelCst.POLY)) { + payData = shopMerchant.getPolyPayJson(); + } + return adapter.queryPayOrder(getDomain(), payData, payOrderId, mchOrderNo, platform); } @Override @Transactional - public CzgResult queryRefund(@NonNull Long shopId, String mchRefundNo, String refundOrderId) { + public CzgResult queryRefund(@NonNull Long shopId, String mchRefundNo, String refundOrderId) { ShopMerchant shopMerchant = getMerchant(shopId); - return czgPayService.queryRefundOrder(shopMerchant.getAppId(), shopMerchant.getAppSecret(), mchRefundNo, refundOrderId); + PayAdapter adapter = PayAdapterFactory.getAdapter(shopMerchant.getChannel()); + String payData = null; + if (shopMerchant.getChannel().equals(PayChannelCst.NATIVE)) { + payData = shopMerchant.getNativePayJson(); + } else if (shopMerchant.getChannel().equals(PayChannelCst.POLY)) { + payData = shopMerchant.getPolyPayJson(); + } + return adapter.queryRefund(getDomain(), payData, mchRefundNo, refundOrderId); } - private Long initOrderPayment(OrderPayment payment) { + @Override + public Long initPayment(OrderPayment payment) { paymentMapper.insert(payment); return payment.getId(); } - private void upOrderPayInfo(@NonNull Long orderId, @NonNull PayEnums payType, @NotBlank Long paymentId, String remark) { - if (paymentId == null) { - throw new CzgException("未获取到支付记录"); + private ShopMerchant getMerchant(Long shopId) { + ShopMerchant shopMerchant = shopMerchantService.getByShopId(shopId); + if (shopMerchant == null || StrUtil.isBlank(shopMerchant.getChannel())) { + throw new CzgException("暂未开通支付!"); } - orderInfoCustomService.updatePayOrderId(orderId, paymentId, payType.getValue(), remark); + if (shopMerchant.getChannel().equals(PayChannelCst.NATIVE)) { + if (StrUtil.isBlank(shopMerchant.getNativePayJson())) { + throw new CzgException("原生支付未开通"); + } + } else if (shopMerchant.getChannel().equals(PayChannelCst.POLY)) { + if (StrUtil.isBlank(shopMerchant.getPolyPayJson())) { + throw new CzgException("聚合支付未开通"); + } + } else { + throw new CzgException("暂不支持的支付渠道"); + } + return shopMerchant; } - - private CzgResult> h5Pay(@NonNull Long shopId, CzgH5PayReq bizData) { - ShopMerchant shopMerchant = getMerchant(shopId); - bizData.assignMerchant(shopMerchant.getStoreId(), shopMerchant.getMerchantName(), - sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_NOTIFY_URL)); - CzgResult h5PayRespCzgResult = czgPayService.h5Pay(shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); - return execPayResult(h5PayRespCzgResult); + private String getDomain() { + return sysParamsService.getSysParamValue(ParamCodeCst.System.POLY_DOMAIN); } - private CzgResult> jsPay(@NonNull Long shopId, @NonNull String payType, CzgJsPayReq bizData) { - ShopMerchant shopMerchant = getMerchant(shopId); - bizData.setSubAppid("aliPay".equals(payType) ? shopMerchant.getAlipaySmallAppid() : shopMerchant.getWechatSmallAppid()); - AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + ("aliPay".equals(payType) ? "支付宝" : "微信") + "小程序"); - bizData.assignMerchant(shopMerchant.getStoreId(), shopMerchant.getMerchantName(), - sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_NOTIFY_URL)); - bizData.setPayType("aliPay".equals(payType) ? "ALIPAY" : "WECHAT"); - CzgResult jsPayRespCzgResult = czgPayService.jsPay(shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); - return execPayResult(jsPayRespCzgResult); + private String getNotifyUrl(String channel) { + String notifyUrl = ""; + if (channel.equals(PayChannelCst.NATIVE)) { + notifyUrl = sysParamsService.getSysParamValue(ParamCodeCst.System.NATIVE_PAY_NOTIFY_URL); + } else if (channel.equals(PayChannelCst.POLY)) { + notifyUrl = sysParamsService.getSysParamValue(ParamCodeCst.System.POLY_PAY_NOTIFY_URL); + } + if (StrUtil.isBlank(notifyUrl)) { + throw new CzgException(channel.equals(PayChannelCst.NATIVE) ? "原生支付回调地址未配置" : "聚合支付回调地址未配置"); + } + return notifyUrl; } - public CzgResult> ltPay(@NonNull Long shopId, String payType, CzgLtPayReq bizData) { - ShopMerchant shopMerchant = getMerchant(shopId); - bizData.setSubAppid("aliPay".equals(payType) ? shopMerchant.getAlipaySmallAppid() : shopMerchant.getWechatSmallAppid()); - AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + ("aliPay".equals(payType) ? "支付宝" : "微信") + "小程序"); - bizData.assignMerchant(shopMerchant.getStoreId(), shopMerchant.getMerchantName(), - sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_NOTIFY_URL)); - CzgResult ltPayRespCzgResult = czgPayService.ltPay(shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); - return execPayResult(ltPayRespCzgResult); - } - - private CzgResult> scanPay(@NonNull Long shopId, CzgScanPayReq bizData) { - ShopMerchant shopMerchant = getMerchant(shopId); - bizData.assignMerchant(shopMerchant.getStoreId(), shopMerchant.getMerchantName(), - sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_NOTIFY_URL)); - CzgResult scanPayRespCzgResult = czgPayService.scanPay(shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); - return execPayResult(scanPayRespCzgResult); - } - - private CzgResult> microPay(@NonNull Long shopId, CzgMicroPayReq bizData) { - AssertUtil.isBlank(bizData.getAuthCode(), "扫码失败,请重试"); - if (bizData.getAuthCode().length() > 26) { + private void checkMicroPay(CzgPayBaseReq bizData, ShopMerchant shopMerchant) { + String data = bizData.getAuthCode(); + AssertUtil.isBlank(data, "扫码失败,请重试"); + if (data.length() > 26) { throw new CzgException("支付失败,不支持的条码"); } - ShopMerchant shopMerchant = getMerchant(shopId); - bizData.assignMerchant(shopMerchant.getStoreId(), shopMerchant.getMerchantName(), - sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_NOTIFY_URL)); - String firstTwoDigitsStr = bizData.getAuthCode().substring(0, 2); + String firstTwoDigitsStr = data.substring(0, 2); // 将截取的字符串转换为整数 int firstTwoDigits = Integer.parseInt(firstTwoDigitsStr); // 判断范围 if (firstTwoDigits >= 10 && firstTwoDigits <= 15) { //微信支付 - bizData.setSubAppid(shopMerchant.getWechatSmallAppid()); + bizData.setSubAppid(shopMerchant.getWechatAppId()); + bizData.setPayType(SystemConstants.PayType.WECHAT); } else if (firstTwoDigits >= 25 && firstTwoDigits <= 30) { //支付宝支付 - bizData.setSubAppid(shopMerchant.getAlipaySmallAppid()); + bizData.setSubAppid(shopMerchant.getAlipayAppId()); + bizData.setPayType(SystemConstants.PayType.ALIPAY); } else { throw new CzgException("扫描码非法或暂不支持"); } - CzgResult czgScanPayResult = czgPayService.microPay(shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); - - return execPayResult(czgScanPayResult); - } - - private CzgResult refund(@NonNull Long shopId, CzgRefundReq bizData) { - ShopMerchant shopMerchant = getMerchant(shopId); - bizData.setNotifyUrl(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_REFUND_NOTIFY_URL)); - return czgPayService.refundOrder(shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); - } - - private ShopMerchant getMerchant(Long shopId) { - try { - return shopMerchantService.getById(shopId); - } catch (Exception e) { - throw new CzgException("暂未开通支付"); - } - } - - private CzgResult> execPayResult(CzgResult res) { - CzgResult> result = CzgResult.success(); - if (res.getCode() != 200 || res.getData() == null) { - result.setCode(500); - result.setMsg(res.getMsg()); - return result; - } - - CzgBaseResp data = res.getData(); - - Map map = new HashMap<>(); - switch (data) { - case CzgMicroPayResp ignored -> { - if ("TRADE_SUCCESS".equals(data.getState())) { - return result; - } else if ("TRADE_AWAIT".equals(data.getState())) { - result.setCode(CzgRespCode.WAIT_PAY.getCode()); - result.setMsg("等待用户付款"); - } else { - result.setCode(CzgRespCode.FAILURE.getCode()); - } - } - case CzgH5PayResp h5PayResp -> - map = JSONObject.parseObject(JSONObject.toJSONString(h5PayResp.getPayInfo())); - case CzgJsPayResp jsPayResp -> - map = JSONObject.parseObject(JSONObject.toJSONString(jsPayResp.getPayInfo())); - case CzgLtPayResp ltPayResp -> - map = JSONObject.parseObject(JSONObject.toJSONString(ltPayResp.getPayInfo())); - case CzgScanPayResp scanPayResp -> - map = JSONObject.parseObject(JSONObject.toJSONString(scanPayResp.getPayInfo())); - default -> throw new IllegalStateException("Unexpected value: " + data); - } - - result.setData(map); - - return result; } } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PrintMachineLogServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PrintMachineLogServiceImpl.java index 04025d977..c13a5395c 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PrintMachineLogServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PrintMachineLogServiceImpl.java @@ -2,32 +2,36 @@ package com.czg.service.order.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.map.MapProxy; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.text.UnicodeUtil; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson2.JSONObject; import com.czg.account.entity.PrintMachine; -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; -import com.mybatisflex.core.query.QueryWrapper; -import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.czg.config.RedisCst; +import com.czg.market.service.OrderInfoService; +import com.czg.order.entity.OrderInfo; import com.czg.order.entity.PrintMachineLog; import com.czg.order.service.PrintMachineLogService; +import com.czg.service.RedisService; import com.czg.service.order.mapper.PrintMachineLogMapper; +import com.czg.service.order.print.FeiPrinter; +import com.czg.service.order.print.YxyPrinter; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.digest.DigestUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import java.util.*; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * 店铺小票打印记录ServiceImpl @@ -38,139 +42,47 @@ import java.util.*; @Slf4j @Service @RequiredArgsConstructor -public class PrintMachineLogServiceImpl extends ServiceImpl implements PrintMachineLogService{ - //请求地址 - private static final String URL_STR = "https://ioe.car900.com/v1/openApi/dev/customPrint.json"; - //APPID - private static final String APP_ID = "ZF544"; - //USERCODE - private static final String USER_CODE = "ZF544"; - //APPSECRET - private static final String APP_SECRET = "2022bsjZF544GAH"; +public class PrintMachineLogServiceImpl extends ServiceImpl implements PrintMachineLogService { - public static final String URL = "http://api.feieyun.cn/Api/Open/";//不需要修改 + @Resource + private OrderInfoService orderInfoService; + @Lazy + @Resource + private YxyPrinter yxyPrinter; + @Lazy + @Resource + private FeiPrinter feiPrinter; + @Resource + private RedisService redisService; - public static final String USER = "chaozhanggui2022@163.com";//*必填*:账号名 - public static final String UKEY = "UfWkhXxSkeSSscsU";//*必填*: 飞鹅云后台注册账号后生成的UKEY 【备注:这不是填打印机的KEY】 - public static final String SN = "960238952";//*必填*:打印机编号,必须要在管理后台里添加打印机或调用API接口添加之后,才能调用API - /** - * 获取TOKEN值 - * - * @param timestamp 时间戳,13位 - * @param requestId 请求ID,自定义 - * @return - */ - private static Map getToken(String timestamp, String requestId) { - StringBuilder token = new StringBuilder(); - StringBuilder encode = new StringBuilder(); - SortedMap map = new TreeMap<>(); - map.put("appId", APP_ID); - map.put("timestamp", timestamp); - map.put("requestId", requestId); - map.put("userCode", USER_CODE); - for (Map.Entry next : map.entrySet()) { - String key = next.getKey(); - Object value = next.getValue(); - token.append(key).append(value); - encode.append(key).append("=").append(value).append("&"); + Map yxxStatusMap = Map.of( + 0, "离线(设备上线后自动补打)", + 1, "在线", + 2, "获取失败", + 3, "未激活", + 4, "设备已禁用"); + + @Async + @Override + public void save(PrintMachine config, String bizType, String printContent, String respJson) { + if (config == null) { + return; } - System.out.println("token" + token); - Map finalMap = new HashMap<>(); - finalMap.put("ENCODE", encode.toString()); - System.out.println("+++++++++++++++" + token + APP_SECRET); - finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase()); - return finalMap; + save(null, config, bizType, printContent, respJson); } - /** - * 检查打印状态 - * - * @param devName 设备名称,(唯一) 对应配置表中的address字段即(IP地址/打印机编号) - * @param taskId 打印任务id,用于复查打印状态,云想印=orderId - * @return - */ - public static String checkPrintStatus(String devName, String taskId) { - String time = String.valueOf(System.currentTimeMillis()); - String uuid = UUID.randomUUID().toString(); - Map param = getToken(time, uuid); - String token = param.get("TOKEN"); - Map paramMap = new HashMap<>(); - - paramMap.put("devName", devName); - paramMap.put("orderId", taskId); - paramMap.put("token", token); - paramMap.put("appId", APP_ID); - paramMap.put("timestamp", time); - paramMap.put("requestId", uuid); - paramMap.put("userCode", USER_CODE); - - return HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5); - } - private static String signature(String USER, String UKEY, String STIME) { - return DigestUtils.sha1Hex(USER + UKEY + STIME); - } - /** - * 检查飞鹅打印机打印任务是否已打印 - * - * @param printOrderId 打印订单编号 - * @return null-未知错误,true-已打印,false-未打印 - */ - public static Boolean checkFPrintStatus(String printOrderId) { - String STIME = String.valueOf(System.currentTimeMillis() / 1000); - Map paramMap = new HashMap<>(); - paramMap.put("user", USER); - paramMap.put("stime", STIME); - paramMap.put("sig", signature(USER, UKEY, STIME)); - paramMap.put("apiname", "Open_queryOrderState"); - paramMap.put("orderid", printOrderId); - Boolean ret; - try { - String resp = HttpUtil.post(URL, paramMap, 1000 * 5); - //成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4} - //失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4} - JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp)); - ret = json.getBool("data"); - } catch (Exception e) { - ret = null; - } - return ret; - } - /** - * 检查飞鹅打印机是否在线 - * - * @param sn 设备编号 - * @return 在线,工作状态正常。/离线。/未知错误 - */ - public static String checkOnline(String sn) { - String STIME = String.valueOf(System.currentTimeMillis() / 1000); - Map paramMap = new HashMap<>(); - paramMap.put("user", USER); - paramMap.put("stime", STIME); - paramMap.put("sig", signature(USER, UKEY, STIME)); - paramMap.put("apiname", "Open_queryPrinterStatus"); - paramMap.put("sn", sn); - String msg; - try { - String resp = HttpUtil.post(URL, paramMap, 1000 * 5); - //成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4} - //成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7} - JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp)); - msg = json.getStr("data"); - } catch (Exception e) { - msg = "未知错误"; - } - return msg; - } /** * 保存打印记录 * + * @param orderId 订单Id * @param config 打印机配置 * @param bizType 业务类型 * @param printContent 打印内容 * @param respJson 打印机响应结果 */ @Async - public void save(PrintMachine config, String bizType, String printContent, Object respJson) { + @Override + public void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson) { if (config == null) { return; } @@ -179,51 +91,41 @@ public class PrintMachineLogServiceImpl extends ServiceImpl yxxStatusMap = MapUtil.builder(0, "离线(设备上线后自动补打)").put(1, "在线").put(2, "获取失败").put(3, "未激活").put(4, "设备已禁用").build(); + JSONObject resp = JSONObject.parseObject(respJson); // 云想印 - if ("云享印".equals(config.getContentType())) { - cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson); - int code = resp.getInt("code"); - cn.hutool.json.JSONObject data = resp.getJSONObject("data").getJSONObject("data"); + if ("云想印".equals(config.getContentType())) { + int code = resp.getIntValue("code"); + JSONObject respData = resp.getJSONObject("data"); + JSONObject data = respData.getJSONObject("data"); //设备状态,0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用 - int status = data.getInt("status"); + int status = data.getIntValue("status"); if (code != 0) { failFlag = 1; respCode = code + ""; - respMsg = resp.getStr("msg"); + respMsg = resp.getString("msg"); } else if (status != 1) { failFlag = 1; respCode = code + ""; - respMsg = status + "_" + yxxStatusMap.get(status); } if (code == 0) { - String taskId = resp.getJSONObject("data").getStr("orderId"); - entity.setTaskId(taskId); + entity.setTaskId(respData.getString("orderId")); } // 飞鹅云打印机暂时没有适配,先return不做打印记录 } else if ("飞鹅".equals(config.getContentType())) { - cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson); - int ret = resp.getInt("ret"); + int ret = resp.getIntValue("ret"); if (ret != 0) { failFlag = 1; respCode = ret + ""; - respMsg = resp.getStr("msg"); + respMsg = resp.getString("msg"); } else { - String printOrderId = resp.getStr("data"); - entity.setTaskId(printOrderId); + entity.setTaskId(resp.getString("data")); } } else { // 其他打印机暂时没有适配,先return不做打印记录 return; } entity.setBizType(bizType); -// entity.setCreateUserId(config.getCurrentUserId()); -// entity.setCreateUserName(config.getCurrentUserName()); -// if (StrUtil.isNotBlank(config.getCurrentUserNickName())) { -// entity.setCreateUserName(StrUtil.concat(true, config.getCurrentUserNickName(), " | ", config.getCurrentUserName())); -// } entity.setPrintContent(printContent); entity.setCreateTime(DateUtil.date().toLocalDateTime()); if (failFlag == 0) { @@ -233,66 +135,213 @@ public class PrintMachineLogServiceImpl extends ServiceImpl checkPrintStatus(orderId, config, entity)); + } - // 云想印 - if ("云享印".equals(config.getContentType())) { - // 延迟3ms,复查打印状态 (用户可以根据设备信息查询到当前设备的在线情况(注:该接口只能提供参考,设备的离线状态是在设备离线3分钟后才会生效)) - ThreadUtil.safeSleep(1000 * 5); - String jsonStr = checkPrintStatus(config.getAddress(), entity.getTaskId()); - cn.hutool.json.JSONObject resp = JSONUtil.parseObj(jsonStr); - int code = resp.getInt("code"); - if (code == 0) { - cn.hutool.json.JSONObject data = resp.getJSONObject("data"); - boolean status = data.containsKey("status"); - if (!status) { + /** + * 类级别成员变量:基于虚拟线程的固定大小(5)定时线程池 + * // Java 21+ 虚拟线程工厂,支持命名 + */ + private final ScheduledExecutorService virtualThreadScheduler = Executors.newScheduledThreadPool( + 5, + Thread.ofVirtual().name("print-query-vt-", 0).factory() + ); + + /** + * 打印机状态查询(解决 retryFuture 爆红问题 + 虚拟线程 + 轮询重试) + * + * @param orderId 订单Id + * @param config 打印机配置 + * @param entity 打印日志实体 + */ + public void checkPrintStatus(Long orderId, PrintMachine config, PrintMachineLog entity) { + // 最大重试次数 + int maxRetryTimes = 5; + AtomicInteger executedTimes = new AtomicInteger(0); + + // 原子引用包装ScheduledFuture,用于后续取消轮询 + AtomicReference> retryFutureRef = new AtomicReference<>(); + + // 核心查询任务(修正后,逻辑内聚) + Runnable printQueryTask = () -> { + int currentTimes = executedTimes.incrementAndGet(); + boolean isPrintSuccess = false; + boolean isLastTask = false; + + try { + // 1. 云想印打印机状态查询 + if ("云想印".equals(config.getContentType())) { + String jsonStr = yxyPrinter.checkPrintStatus(config.getAddress(), entity.getTaskId()); + log.info("云想印打印状态查询结果(第{}次,虚拟线程:{}):{}", + currentTimes, Thread.currentThread().getName(), jsonStr); + JSONObject resp = JSONObject.parseObject(jsonStr); + int code = resp.getIntValue("code"); + if (code == 0) { + JSONObject data = resp.getJSONObject("data"); + if (data.containsKey("status")) { + isPrintSuccess = data.getBooleanValue("status", false); + updatePrintLogEntity(entity, isPrintSuccess); + } + } + } + // 2. 飞鹅云打印机状态查询 + else if ("飞鹅".equals(config.getContentType())) { + Boolean success = feiPrinter.checkFPrintStatus(entity.getTaskId()); + if (success == null) { + entity.setFailFlag(1); + entity.setRespMsg("打印失败,未知错误"); + entity.setPrintTime(null); + } else if (success) { + isPrintSuccess = true; + updatePrintLogEntity(entity, true); + } else { + String msg = feiPrinter.checkOnline(entity.getAddress()); + if (msg.indexOf("在线,工作状态正常") > 0) { + isPrintSuccess = true; + updatePrintLogEntity(entity, true); + } else { + isPrintSuccess = false; + entity.setFailFlag(1); + entity.setPrintTime(null); + entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg)); + } + } + } else { + log.info("打印类型为其他类型,终止打印状态查询轮询任务"); + ScheduledFuture future = retryFutureRef.get(); + if (future != null && !future.isCancelled()) { + boolean cancelSuccess = future.cancel(false); // 取消后续轮询(不中断当前任务) + log.info("其他打印类型,取消轮询任务:{}", cancelSuccess ? "成功" : "失败"); + } + } + + // 3. 打印成功:取消后续轮询任务 + if (isPrintSuccess) { + isLastTask = true; + ScheduledFuture future = retryFutureRef.get(); + if (future != null && !future.isCancelled()) { + future.cancel(false); // 不中断当前任务,仅取消后续任务 + } return; } - boolean success = data.getBool("status", false); - if (entity.getFailFlag() == 0 && success) { - entity.setFailFlag(0); - entity.setRespMsg("打印成功"); - entity.setPrintTime(entity.getCreateTime()); - } else if (entity.getFailFlag() == 1 && success) { - entity.setFailFlag(0); - entity.setPrintTime(DateUtil.date().toLocalDateTime()); - entity.setRespMsg("打印成功"); - // 如果设备在线 and 休眠5秒后查询结果是未打印,即视为设备已离线,云端3分钟后才会同步到离线信息 - } else if (entity.getFailFlag() == 0 && !success) { - entity.setFailFlag(1); - entity.setPrintTime(null); - entity.setRespMsg("0_离线(设备上线后自动补打)"); - } else { - entity.setFailFlag(1); - entity.setPrintTime(null); - entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", entity.getRespMsg())); + + // 4. 达到最大重试次数:取消后续轮询任务 + if (currentTimes >= maxRetryTimes) { + isLastTask = true; + ScheduledFuture future = retryFutureRef.get(); + if (future != null && !future.isCancelled()) { + future.cancel(false); + } + } + + } catch (Exception e) { + log.error("第{}次打印机状态查询异常(虚拟线程:{})", + currentTimes, Thread.currentThread().getName(), e); + // 异常时达到最大重试次数,同样取消任务 + if (currentTimes >= maxRetryTimes) { + ScheduledFuture future = retryFutureRef.get(); + if (future != null && !future.isCancelled()) { + boolean cancelSuccess = future.cancel(false); + log.info("查询异常且达到最大重试次数,取消轮询任务:{}", cancelSuccess ? "成功" : "失败"); + } + } + } finally { + //仅当是最后一次任务时,才执行更新操作 + if (isLastTask) { + super.updateById(entity); + updateOrderEntity(orderId, config, isPrintSuccess); } - super.updateById(entity); } - // 飞鹅云打印机 - } else if ("飞鹅".equals(config.getContentType())) { - ThreadUtil.safeSleep(1000 * 5); - Boolean success = checkFPrintStatus(entity.getTaskId()); - if (success == null) { - entity.setFailFlag(1); - entity.setRespMsg("打印失败,未知错误"); - } else if (success) { - entity.setFailFlag(0); - entity.setPrintTime(DateUtil.date().toLocalDateTime()); - entity.setRespMsg("打印成功"); + }; + + // 修正:统一使用scheduleAtFixedRate,避免任务重复执行 + // 首次延迟10秒执行,后续每隔30秒执行一次(符合原逻辑的首次查询延迟,后续轮询间隔) + ScheduledFuture retryFuture = virtualThreadScheduler.scheduleAtFixedRate( + printQueryTask, + 10, + 30, + TimeUnit.SECONDS + ); + + // 修正:先赋值AtomicReference,再让任务可能执行,避免线程安全隐患 + retryFutureRef.set(retryFuture); + // 修正:关闭钩子仅注册一次(通过静态代码块或类初始化时注册,避免重复注册) + registerShutdownHookOnce(); + } + + /** + * 更新状态 + */ + private void updateOrderEntity(Long orderId, PrintMachine config, boolean isPrintSuccess) { + redisService.runFunAndCheckKey(() -> { + OrderInfo orderInfo = orderInfoService.getOne(query().select(OrderInfo::getPrintStatus).eq(OrderInfo::getId, orderId)); + if (orderInfo == null) { + orderInfo = new OrderInfo(); + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", config.getId()); + jsonObject.put("name", config.getName()); + jsonObject.put("time", LocalDateTimeUtil.formatNormal(LocalDateTime.now())); + orderInfo.upPrintStatus(jsonObject, isPrintSuccess); + orderInfoService.update(orderInfo, query().eq(OrderInfo::getId, orderId)); + return orderInfo; + }, RedisCst.getLockKey("UP_ORDER_PRINT", orderId)); + redisService.del("order:print:" + orderId); + } + + /** + * 统一更新打印日志实体 + * + * @param entity 打印日志实体 + * @param isPrintSuccess 是否打印成功 + */ + private void updatePrintLogEntity(PrintMachineLog entity, boolean isPrintSuccess) { + if (isPrintSuccess) { + entity.setFailFlag(0); + entity.setRespMsg("打印成功"); + entity.setPrintTime(entity.getFailFlag() == 0 ? entity.getCreateTime() : DateUtil.date().toLocalDateTime()); + } else { + entity.setFailFlag(1); + entity.setPrintTime(null); + if (entity.getFailFlag() == 0) { + entity.setRespMsg("0_离线(设备上线后自动补打)"); } else { - String msg = checkOnline(entity.getAddress()); - if (msg.indexOf("在线,工作状态正常") > 0) { - entity.setFailFlag(0); - entity.setPrintTime(DateUtil.date().toLocalDateTime()); - entity.setRespMsg("打印成功"); - } else { - entity.setFailFlag(1); - entity.setPrintTime(null); - entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg)); - } + entity.setRespMsg(entity.getRespMsg()); } - super.updateById(entity); } } + // 静态标识,确保关闭钩子仅注册一次 + private static volatile boolean shutdownHookRegistered = false; + // 锁对象,保证线程安全 + private static final Object HOOK_LOCK = new Object(); + + /** + * 统一注册JVM关闭钩子(仅执行一次) + */ + private void registerShutdownHookOnce() { + if (!shutdownHookRegistered) { + synchronized (HOOK_LOCK) { + // 双重校验锁,避免多线程下重复注册 + if (!shutdownHookRegistered) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (virtualThreadScheduler != null && !virtualThreadScheduler.isShutdown()) { + virtualThreadScheduler.shutdown(); + try { + if (!virtualThreadScheduler.awaitTermination(10, TimeUnit.SECONDS)) { + log.warn("虚拟线程调度器10秒内未关闭,强制关闭..."); + virtualThreadScheduler.shutdownNow(); + } + } catch (InterruptedException e) { + log.error("等待虚拟线程调度器终止时被中断,强制关闭", e); + virtualThreadScheduler.shutdownNow(); + Thread.currentThread().interrupt(); // 保留中断状态 + } + } + }, "PrinterScheduler-ShutdownHook")); + shutdownHookRegistered = true; + } + } + } + } } diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopDirectMerchantServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopDirectMerchantServiceImpl.java new file mode 100644 index 000000000..fec79bec8 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopDirectMerchantServiceImpl.java @@ -0,0 +1,176 @@ +package com.czg.service.order.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.EntryManager; +import com.czg.OcrUtils; +import com.czg.PayCst; +import com.czg.config.RabbitPublisher; +import com.czg.constants.ParamCodeCst; +import com.czg.dto.req.*; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.czg.service.order.dto.MerchantQueryDTO; +import com.czg.service.order.mapper.ShopDirectMerchantMapper; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.czg.system.service.SysParamsService; +import com.czg.utils.FunUtils; +import com.czg.utils.PageUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 商户进件 服务层实现。 + * + * @author ww + * @since 2026-01-07 + */ +@Service +public class ShopDirectMerchantServiceImpl extends ServiceImpl implements ShopDirectMerchantService { + + @Resource + private RabbitPublisher rabbitPublisher; + @DubboReference + private SysParamsService sysParamsService; + + + // 全局原子计数器,按天重置(避免数值过大) + private static final AtomicLong COUNTER = new AtomicLong(0); + // 记录上一次的日期,用于重置计数器 + private static String LAST_DATE = DateUtil.format(new Date(), "yyyyMMdd"); + + @Override + public JSONObject getInfoByImg(String url, String type) throws Exception { + Map aliOssKeys = sysParamsService.getParamsByMap("ali_oss_key_set", ParamCodeCst.ALI_OSS_KEY_SET); + return OcrUtils.getInfoByImg(aliOssKeys.get(ParamCodeCst.AliYun.ALI_SMS_KEY), aliOssKeys.get(ParamCodeCst.AliYun.ALI_SMS_SECRET), url, type); + } + + @Override + public Page getEntryList(MerchantQueryDTO queryParam) { + PageHelper.startPage(queryParam.getPage(), queryParam.getSize()); + List entryList = mapper.getEntryList(queryParam); + return PageUtil.convert(new PageInfo<>(entryList)); + } + + @Override + public AggregateMerchantVO getEntry(Long shopId) { + ShopDirectMerchant merchant = getOne(query() + .eq(ShopDirectMerchant::getShopId, shopId)); + if (merchant == null) { + return null; + } + return convertVO(merchant); + } + + @Override + public boolean entryManager(AggregateMerchantDto reqDto) { + boolean isSave = false; + boolean result; + if (StrUtil.isBlank(reqDto.getMerchantCode())) { + reqDto.setMerchantCode(getMerchantCode()); + isSave = true; + } + EntryManager.verifyEntryParam(reqDto); + + + ShopDirectMerchant merchant = new ShopDirectMerchant(); + merchant.setShopId(reqDto.getShopId()); + merchant.setMerchantCode(reqDto.getMerchantCode()); + merchant.setLicenceNo(reqDto.getBusinessLicenceInfo().getLicenceNo()); + + MerchantBaseInfoDto merchantBaseInfo = reqDto.getMerchantBaseInfo(); + merchant.setAlipayAccount(merchantBaseInfo.getAlipayAccount()); + merchant.setUserType(merchantBaseInfo.getUserType()); + merchant.setShortName(merchantBaseInfo.getShortName()); + + merchant.setMerchantBaseInfo(JSONObject.toJSONString(reqDto.getMerchantBaseInfo())); + merchant.setLegalPersonInfo(JSONObject.toJSONString(reqDto.getLegalPersonInfo())); + merchant.setBusinessLicenceInfo(JSONObject.toJSONString(reqDto.getBusinessLicenceInfo())); + merchant.setStoreInfo(JSONObject.toJSONString(reqDto.getStoreInfo())); + merchant.setSettlementInfo(JSONObject.toJSONString(reqDto.getSettlementInfo())); + + if (isSave) { + merchant.setAlipayStatus(PayCst.EntryStatus.WAIT); + merchant.setWechatStatus(PayCst.EntryStatus.WAIT); + result = save(merchant); + } else { + ShopDirectMerchant directMerchant = getById(reqDto.getShopId()); + if (directMerchant.getAlipayStatus().equals(PayCst.EntryStatus.INIT) || directMerchant.getAlipayStatus().equals(PayCst.EntryStatus.REJECTED)) { + merchant.setAlipayStatus(PayCst.EntryStatus.WAIT); + } + if (directMerchant.getWechatStatus().equals(PayCst.EntryStatus.INIT) || directMerchant.getWechatStatus().equals(PayCst.EntryStatus.REJECTED)) { + merchant.setWechatStatus(PayCst.EntryStatus.WAIT); + } + result = updateById(merchant); + } + //发送进件队列消息 + FunUtils.transactionSafeRun(() -> rabbitPublisher.sendEntryManagerMsg(reqDto.getShopId() + ":" + merchant.getLicenceNo())); + return result; + } + + private static String getMerchantCode() { + Date now = new Date(); + // 1. 获取当前日期(yyyyMMdd) + String currentDate = DateUtil.format(now, "yyyyMMdd"); + // 2. 每天重置计数器,避免数值溢出 + synchronized (COUNTER) { + if (!currentDate.equals(LAST_DATE)) { + COUNTER.set(0); + LAST_DATE = currentDate; + } + } + // 3. 原子递增,获取唯一序号(同一毫秒内不会重复) + long seq = COUNTER.incrementAndGet(); + // 4. 时间戳(毫秒级)+ 6位序号(补零) + String timeStr = DateUtil.format(now, "yyyyMMddHHmmss"); + String seqStr = String.format("%03d", seq); + return "CZG" + timeStr + seqStr; + } + + + public AggregateMerchantVO convertVO(ShopDirectMerchant entity) { + if (entity == null) { + return null; + } + + AggregateMerchantVO vo = new AggregateMerchantVO(); + vo.setShopId(entity.getShopId()); + vo.setMerchantCode(entity.getMerchantCode()); + vo.setShopName(entity.getShopName()); + + // 解析JSON字段 + vo.setMerchantBaseInfo(JSONObject.parseObject(entity.getMerchantBaseInfo(), MerchantBaseInfoDto.class)); + vo.setLegalPersonInfo(JSONObject.parseObject(entity.getLegalPersonInfo(), LegalPersonInfoDto.class)); + vo.setBusinessLicenceInfo(JSONObject.parseObject(entity.getBusinessLicenceInfo(), BusinessLicenceInfoDto.class)); + vo.setStoreInfo(JSONObject.parseObject(entity.getStoreInfo(), StoreInfoDto.class)); + vo.setSettlementInfo(JSONObject.parseObject(entity.getSettlementInfo(), SettlementInfoDto.class)); + + // 设置其他字段 + vo.setUserType(entity.getUserType()); + vo.setShortName(entity.getShortName()); + vo.setAlipayAccount(entity.getAlipayAccount()); + vo.setCreateTime(entity.getCreateTime()); + vo.setUpdateTime(entity.getUpdateTime()); + vo.setWechatApplyId(entity.getWechatApplyId()); + vo.setWechatStatus(entity.getWechatStatus()); + vo.setWechatErrorMsg(entity.getWechatErrorMsg()); + vo.setWechatSignUrl(entity.getWechatSignUrl()); + vo.setAlipayOrderId(entity.getAlipayOrderId()); + vo.setAlipayStatus(entity.getAlipayStatus()); + vo.setAlipayErrorMsg(entity.getAlipayErrorMsg()); + vo.setAlipaySignUrl(entity.getAlipaySignUrl()); + + return vo; + } +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopMerchantServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopMerchantServiceImpl.java new file mode 100644 index 000000000..6feb1f464 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopMerchantServiceImpl.java @@ -0,0 +1,148 @@ +package com.czg.service.order.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.account.service.ShopInfoService; +import com.czg.constants.SystemConstants; +import com.czg.order.dto.ShopMerchantDTO; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.order.entity.ShopMerchant; +import com.czg.order.service.ShopMerchantService; +import com.czg.pay.AlipayAuthInfoDto; +import com.czg.pay.NativeMerchantDTO; +import com.czg.pay.PolyMerchantDTO; +import com.czg.service.order.mapper.ShopMerchantMapper; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.czg.utils.AssertUtil; +import com.czg.utils.CzgStrUtils; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +/** + * 第三方商户进件 服务层实现。 + * + * @author Administrator + * @since 2025-02-11 + */ +@CacheConfig(cacheNames = "shop:merchant") +@Service +public class ShopMerchantServiceImpl extends ServiceImpl implements ShopMerchantService { + + @Resource + private ShopDirectMerchantService shopDirectMerchantService; + @DubboReference + private ShopInfoService shopInfoService; + + @Override + public ShopMerchantDTO detail(Long shopId) { + ShopMerchantDTO shopMerchantVO = new ShopMerchantDTO(); + ShopMerchant one = getOne(query().eq(ShopMerchant::getShopId, shopId)); + if (one == null) { + return null; + } + shopMerchantVO.setShopId(shopId); + shopMerchantVO.setChannel(one.getChannel()); + shopMerchantVO.setRelatedId(one.getRelatedId()); + if (StrUtil.isNotBlank(one.getNativePayJson())) { + shopMerchantVO.setNativeMerchantDTO(JSONObject.parseObject(one.getNativePayJson(), NativeMerchantDTO.class)); + } + if (StrUtil.isNotBlank(one.getPolyPayJson())) { + shopMerchantVO.setPolyMerchantDTO(JSONObject.parseObject(one.getPolyPayJson(), PolyMerchantDTO.class)); + } + if (one.getRelatedId() != null) { + shopMerchantVO.setShopDirectMerchant(shopDirectMerchantService.getOne(query().eq(ShopDirectMerchant::getShopId, one.getRelatedId()))); + } + return shopMerchantVO; + } + + /** + * * shopId 修改的店铺Id + * * relatedLicenceNo 关联值 原生native必填 对应 tb_shop_direct_merchant 的 licence_no营业执照 + * * channel {@link com.czg.constant.PayChannelCst} + * * jsonObject 支付参数 详情 + * * isUp 是否要修改 传否时 如果存在支付数据 且与当前渠道不一致 则不修改 + */ + @Override + @CacheEvict(key = "#shopMerchantParam.shopId") + public Boolean editEntry(ShopMerchantDTO shopMerchantParam, boolean isUp) { + ShopMerchant shopMerchant = getOne(query().eq(ShopMerchant::getShopId, shopMerchantParam.getShopId())); + if (shopMerchant == null) { + shopMerchant = new ShopMerchant(); + shopMerchant.setAlipayAppId(SystemConstants.PayType.ALIPAY_APP_ID); + shopMerchant.setWechatAppId(SystemConstants.PayType.WECHAT_APP_ID); + shopMerchant.setRelatedId(shopMerchantParam.getShopId()); + } + shopMerchant.setShopId(shopMerchantParam.getShopId()); + if (isUp) { + shopMerchant.setChannel(CzgStrUtils.getStrOrNull(shopMerchantParam.getChannel())); + shopMerchant.setRelatedId(shopMerchantParam.getRelatedId()); + if (shopMerchantParam.getRelatedId() == null || !shopMerchantParam.getRelatedId().equals(shopMerchant.getRelatedId())) { + ShopDirectMerchant shopDirectMerchant = shopDirectMerchantService.getById(shopMerchantParam.getRelatedId()); + if (shopDirectMerchant != null) { + NativeMerchantDTO nativeMerchantDTO = new NativeMerchantDTO(); + nativeMerchantDTO.setWechatMerchantId(shopDirectMerchant.getWechatMerchantId()); + nativeMerchantDTO.setAlipayMerchantId(shopDirectMerchant.getAlipayMerchantId()); + if (StrUtil.isNotBlank(shopDirectMerchant.getAlipayAuthInfo())) { + AlipayAuthInfoDto alipayAuthInfoDto = JSONObject.parseObject(shopDirectMerchant.getAlipayAuthInfo(), AlipayAuthInfoDto.class); + nativeMerchantDTO.setAlipayAuthInfo(alipayAuthInfoDto); + } + shopMerchant.setNativePayJson(JSONObject.toJSONString(nativeMerchantDTO)); + } else { + shopMerchant.setNativePayJson(null); + shopMerchant.setWechatAppId(null); + shopMerchant.setAlipayAppId(null); + } + } + if (shopMerchantParam.getPolyMerchantDTO() != null) { + shopMerchant.setPolyPayJson(JSONObject.toJSONString(shopMerchantParam.getPolyMerchantDTO())); + } + } else { + //只有进件会进入这里 + if (shopMerchant.getRelatedId() == null) { + shopMerchant.setRelatedId(shopMerchantParam.getShopId()); + } + if (StrUtil.isBlank(shopMerchant.getNativePayJson()) && shopMerchantParam.getNativeMerchantDTO() != null) { + shopMerchant.setNativePayJson(JSONObject.toJSONString(shopMerchantParam.getNativeMerchantDTO())); + } + } + if (shopMerchant.getId() == null) { + return save(shopMerchant); + } else { + return updateById(shopMerchant, false); + } + } + + @Override + @CacheEvict(allEntries = true) + public void upMerchant(@NotBlank Long relatedId, @NotNull NativeMerchantDTO nativeMerchantDTO) { + ShopMerchant upShopMerchant = new ShopMerchant(); + upShopMerchant.setAlipayAppId(nativeMerchantDTO.getAlipayMerchantId()); + upShopMerchant.setWechatAppId(nativeMerchantDTO.getWechatMerchantId()); + upShopMerchant.setNativePayJson(JSONObject.toJSONString(nativeMerchantDTO)); + update(upShopMerchant, query().eq(ShopMerchant::getRelatedId, relatedId)); + } + + @Cacheable(key = "#shopId") + @Override + public ShopMerchant getByShopId(Long shopId) { + ShopMerchant shopMerchant = getOne(query().eq(ShopMerchant::getShopId, shopId)); + AssertUtil.isNull(shopMerchant, "暂未开通支付."); + return shopMerchant; + } + + @Override + public ShopDirectMerchant getMainMerchant(Long shopId) { + Long mainIdByShopId = shopInfoService.getMainIdByShopId(shopId); + if (mainIdByShopId != null) { + return shopDirectMerchantService.getOne(query().eq(ShopDirectMerchant::getShopId, mainIdByShopId)); + } + return null; + } +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopUserServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopUserServiceImpl.java new file mode 100644 index 000000000..871212335 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopUserServiceImpl.java @@ -0,0 +1,362 @@ +package com.czg.service.order.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.SecureUtil; +import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO; +import com.czg.account.entity.*; +import com.czg.account.service.*; +import com.czg.constants.PayTypeConstants; +import com.czg.enums.CzgPayEnum; +import com.czg.enums.ShopUserFlowBizEnum; +import com.czg.exception.CzgException; +import com.czg.market.dto.MemberOrderDTO; +import com.czg.market.entity.MemberOrder; +import com.czg.market.entity.MkShopCouponRecord; +import com.czg.market.service.MemberOrderService; +import com.czg.market.service.MkShopCouponRecordService; +import com.czg.market.service.MkShopRechargeService; +import com.czg.market.vo.MkShopRechargeVO; +import com.czg.order.dto.CheckOrderPay; +import com.czg.order.dto.LtPayOtherDTO; +import com.czg.order.entity.OrderInfo; +import com.czg.order.entity.OrderPayment; +import com.czg.order.service.OrderInfoCustomService; +import com.czg.order.service.OrderPaymentService; +import com.czg.pay.CzgPayBaseReq; +import com.czg.pay.CzgRefundReq; +import com.czg.pay.RefundRespDTO; +import com.czg.resp.CzgResult; +import com.czg.service.order.dto.VipMemberPayParamDTO; +import com.czg.service.order.dto.VipPayParamDTO; +import com.czg.service.order.dto.VipRefundDTO; +import com.czg.service.order.service.PayService; +import com.czg.service.order.service.ShopUserPayService; +import com.czg.utils.AssertUtil; +import com.czg.utils.CzgRandomUtils; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.annotation.Resource; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ww + */ +@Service +public class ShopUserServiceImpl implements ShopUserPayService { + @Resource + private PayService payService; + + @DubboReference + private ShopUserService shopUserService; + @DubboReference + private UserInfoService userInfoService; + @DubboReference + private ShopInfoService shopInfoService; + @DubboReference + private FreeDineConfigService freeConfigService; + @Resource + private OrderInfoCustomService orderInfoCustomService; + @DubboReference + private MemberOrderService memberOrderService; + @Resource + private MkShopRechargeService shopRechargeService; + @DubboReference + private ShopUserFlowService userFlowService; + @Resource + private MkShopCouponRecordService recordService; + @Resource + private OrderPaymentService paymentService; + + /** + * 会员充值 校验 + * + * @return 是否是霸王餐充值 + */ + private boolean checkPayVip(VipPayParamDTO payParam) { + if (payParam.getAmount().compareTo(BigDecimal.ZERO) <= 0) { + throw new CzgException("充值金额不正确"); + } + if (payParam.getOrderId() != null) { + FreeDineConfig freeConfig = freeConfigService.getById(shopInfoService.getMainIdByShopId(payParam.getShopId())); + AssertUtil.isNull(freeConfig, "该店铺未启用霸王餐"); + if (!freeConfig.getEnable()) { + throw new CzgException("该店铺未启用霸王餐"); + } + CheckOrderPay checkOrderPay = payParam.getCheckOrderPay(); + OrderInfo orderInfo = orderInfoCustomService.checkOrderPay(checkOrderPay.setFreeDine(true).setWithCoupon(freeConfig.getWithCoupon()).setWithPoints(freeConfig.getWithPoints())); + payParam.setAmount(orderInfo.getOrderAmount().multiply(BigDecimal.valueOf(freeConfig.getRechargeTimes()))); + return true; + } + return false; + } + + @Override + @Transactional + public CzgResult cashPayVip(VipPayParamDTO payParam) { + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); + AssertUtil.isNull(shopInfo, "店铺不存在"); + if (shopInfo.getIsMemberInPwd().equals(1)) { + AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用"); + AssertUtil.isBlank(payParam.getPwd(), "请输入操作密码后充值"); + if (!shopInfo.getOperationPwd().equals(SecureUtil.md5(payParam.getPwd()))) { + return CzgResult.failure("支付密码错误"); + } + } +// if (shopUser.getIsVip().equals(0)) { +// //更新会员 +// ShopUser updateInfo = new ShopUser(); +// updateInfo.setIsVip(1); +// updateInfo.setJoinTime(LocalDateTime.now()); +// updateInfo.setId(payParam.getShopUserId()); +// shopUserService.updateById(updateInfo); +// } + shopRechargeService.recharge(shopUser.getMainShopId(), shopUser.getId(), payParam.getRechargeDetailId(), + payParam.getAmount(), null, "cash", ShopUserFlowBizEnum.CASH_IN, true); + return CzgResult.success(); + } + + @Override + @Transactional + public CzgResult> jsPayVip(String clintIp, VipPayParamDTO payParam) { + boolean isFree = checkPayVip(payParam); + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); + String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; + payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), payType, payOrderNo, + payParam.getAmount(), "", isFree ? payParam.getOrderId() : payParam.getActivateId())); + return payService.pay(payParam.getShopId(), CzgPayEnum.JS_PAY, + CzgPayBaseReq.jsPayReq(payOrderNo, "会员充值", payParam.getAmount().multiply(PayService.MONEY_RATE).longValue(), + payParam.getPayType(), payParam.getOpenId(), clintIp)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public CzgResult> ltPayMember(String clientIp, VipMemberPayParamDTO payParam) { + ShopUser shopUser = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getMainShopId, shopInfoService.getMainIdByShopId(payParam.getShopId())).eq(ShopUser::getId, payParam.getShopUserId())); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + MemberOrder memberOrder = memberOrderService.createMemberOrder(new MemberOrderDTO().setName(payParam.getName()) + .setNum(1).setNickName(payParam.getNickName()).setOrderType(payParam.getOrderType()) + .setPlatformType(payParam.getPlatformType()).setSex(payParam.getSex()).setUserId(shopUser.getUserId()) + .setShopId(payParam.getShopId()).setBirthDay(payParam.getBirthDay())); + AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + + LtPayOtherDTO payParam1 = new LtPayOtherDTO() + .setOpenId(payParam.getOpenId()) + .setPayType(payParam.getPayType()) + .setShopId(payParam.getShopId()) + .setRecordId(shopUser.getId()) + .setPrice(memberOrder.getAmount()) + .setIp(clientIp); + return payService.ltPayOther(payParam1, PayTypeConstants.SourceType.MEMBER_PAY, "会员充值"); + } + + @Override + @Transactional + public CzgResult> ltPayVip(String clintIp, VipPayParamDTO payParam) { + // 霸王餐校验 + boolean isFree = checkPayVip(payParam); + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空"); + AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空"); + + MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId()); + if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { + throw new CzgException("未开启自定义充值金额"); + } + Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); + if (isFree) { + BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount()); + payParam.setAmount(amount); + } + + String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; + LtPayOtherDTO payParam1 = new LtPayOtherDTO() + .setOpenId(payParam.getOpenId()) + .setPayType(payParam.getPayType()) + .setShopId(payParam.getShopId()) + .setRecordId(shopUser.getId()) + .setPrice(payParam.getAmount()) + .setIp(clintIp); + return payService.ltPayOther(payParam1, payType, "会员充值"); + } + + @Override + public CzgResult> recharge(String clientIp, VipPayParamDTO rechargeDTO, Long shopUserId) { + boolean isFree = checkPayVip(rechargeDTO); + Long mainShopId = shopInfoService.getMainIdByShopId(rechargeDTO.getShopId()); + + ShopUser shopUser = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getMainShopId, mainShopId) + .eq(ShopUser::getId, shopUserId)); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + MkShopRechargeVO rechargeVO = shopRechargeService.detail(rechargeDTO.getShopId()); + if (rechargeDTO.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) { + throw new CzgException("未开启自定义充值金额"); + } + UserInfo userInfo = userInfoService.getById(shopUser.getUserId()); + BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, rechargeDTO.getShopId(), shopUser.getUserId(), rechargeDTO.getRechargeDetailId(), rechargeDTO.getAmount()); + String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; + + LtPayOtherDTO payParam = new LtPayOtherDTO() + .setOpenId("wechatPay".equals(rechargeDTO.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId()) + .setPayType(rechargeDTO.getPayType()) + .setShopId(rechargeDTO.getShopId()) + .setRecordId(shopUser.getId()) + .setPrice(amount) + .setIp(clientIp); + return payService.ltPayOther(payParam, payType, "会员充值"); + } + + @Override + @Transactional + public CzgResult> scanPayVip(String clintIp, VipPayParamDTO payParam) { + boolean isFree = checkPayVip(payParam); + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); + String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; + payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), payType, payOrderNo, + payParam.getAmount(), "", isFree ? payParam.getOrderId() : payParam.getActivateId())); + return payService.pay(payParam.getShopId(), CzgPayEnum.SCAN_PAY, + CzgPayBaseReq.scanPayReq(payOrderNo, "会员充值", payParam.getAmount().multiply(PayService.MONEY_RATE).longValue(), clintIp)); + } + + @Override + @Transactional + public CzgResult> microPayVip(VipPayParamDTO payParam) { + boolean isFree = checkPayVip(payParam); + AssertUtil.isBlank(payParam.getAuthCode(), "扫描码不能为空"); + ShopUser shopUser = shopUserService.getById(payParam.getShopUserId()); + AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在"); + String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake(); + String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN; + payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), payType, payOrderNo, payParam.getAmount(), "", isFree ? payParam.getOrderId() : payParam.getActivateId())); + CzgResult> mapCzgResult = payService.pay(payParam.getShopId(), CzgPayEnum.MICRO_PAY, + CzgPayBaseReq.microPay(payOrderNo, "会员充值", payParam.getAmount().multiply(PayService.MONEY_RATE).longValue(), payParam.getAuthCode())); + mapCzgResult.setData(Map.of("payOrderNo", payOrderNo)); + return mapCzgResult; + } + + + @Override + public CzgResult> refundVipBefore(VipRefundDTO payParam) { + Map resultMap = new HashMap<>(5); + ShopUser shopUser = shopUserService.getShopUserInfo(payParam.getShopId(), payParam.getUserId()); + AssertUtil.isNull(shopUser, "该店铺用户不存在"); + ShopUserFlow inFlow = userFlowService.getById(payParam.getFlowId()); + AssertUtil.isNull(inFlow, "充值记录不存在"); + QueryWrapper queryWrapper = new QueryWrapper(); + queryWrapper.eq(ShopUserFlow::getRelationId, payParam.getFlowId()); + queryWrapper.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode()); + ShopUserFlow giftFlow = userFlowService.getOne(queryWrapper); + resultMap.put("amount", shopUser.getAmount()); + resultMap.put("inAmount", inFlow.getAmount()); + resultMap.put("inRefundAmount", inFlow.getRefundAmount()); + resultMap.put("giftAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getAmount()); + resultMap.put("giftRefundAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getRefundAmount()); + return CzgResult.success(resultMap); + } + + @Override + @Transactional + public CzgResult refundVip(VipRefundDTO refPayParam) { + ShopInfo shopInfo = shopInfoService.getById(refPayParam.getShopId()); + if (shopInfo.getIsReturnPwd().equals(1)) { + AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用"); + if (!SecureUtil.md5(refPayParam.getPwd()).equals(shopInfo.getOperationPwd())) { + throw new CzgException("操作密码错误"); + } + } + ShopUser shopUser = shopUserService.getShopUserInfo(refPayParam.getShopId(), refPayParam.getUserId()); + ShopUserFlow inFlow = userFlowService.getById(refPayParam.getFlowId()); + AssertUtil.isNull(inFlow, "充值记录不存在"); + if ("cashIn".equals(inFlow.getBizCode()) || "adminIn".equals(inFlow.getBizCode())) { + refPayParam.setCashRefund(true); + } + QueryWrapper queryWrapper = new QueryWrapper(); + queryWrapper.eq(ShopUserFlow::getRelationId, refPayParam.getFlowId()); + queryWrapper.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode()); + ShopUserFlow giftFlow = userFlowService.getOne(queryWrapper); + if ((inFlow.getAmount().subtract(inFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0) { + return CzgResult.failure("退款失败,退款金额不可大于可退金额"); + } + //用户余额减去赠送金额 小于 退款金额 则需要勾选 超额退款 + if (giftFlow != null) { + if (shopUser.getAmount().subtract(giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) { + return CzgResult.failure("超额退款,请勾选 超额退款后重试"); + } + } else { + if (shopUser.getAmount().compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) { + return CzgResult.failure("超额退款,请勾选 超额退款后重试"); + } + } + Long refPaymentId = null; + if (!refPayParam.isCashRefund()) { + OrderPayment payment; + if (inFlow.getRelationId() != null) { + payment = paymentService.getById(inFlow.getRelationId()); + } else { + return CzgResult.failure("退款失败,该充值记录不存在"); + } + String refPayOrderNo = "REFVIP" + IdUtil.getSnowflakeNextId(); + refPaymentId = payService.initPayment(OrderPayment.refund(refPayParam.getShopId(), shopUser.getId(), PayTypeConstants.SourceType.MEMBER_IN, + refPayOrderNo, refPayParam.getRefAmount(), inFlow.getId(), payment.getPlatformType())); + CzgResult refund = payService.refund(refPayParam.getShopId(), new CzgRefundReq(refPayOrderNo, refPayParam.getRemark(), + refPayParam.getRefAmount().multiply(PayService.MONEY_RATE).longValue(), payment.getAmount().multiply(PayService.MONEY_RATE).longValue(), + payment.getOrderNo(), "", payment.getPlatformType())); + if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getStatus())) { + throw new CzgException(refund.getMsg()); + } else { + paymentService.updateChain() + .eq(OrderPayment::getId, refPaymentId) + .set(OrderPayment::getPayTime, refund.getData().getRefundTime()) + .set(OrderPayment::getTradeNumber, refund.getData().getThirdRefundNo()) + .set(OrderPayment::getPayStatus, PayTypeConstants.PayStatus.SUCCESS) + .set(OrderPayment::getRespJson, refund.getData().getOriginalData()) + .update(); + } + } + ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO() + .setId(shopUser.getId()) + .setMoney(refPayParam.getRefAmount()) + .setType(0) + .setRemark("退款") + .setBizEnum(refPayParam.isCashRefund() ? ShopUserFlowBizEnum.RECHARGE_CASH_REFUND : ShopUserFlowBizEnum.RECHARGE_REFUND) + .setRelationId(refPaymentId) + .setRechargeId(inFlow.getId()); + //更新会员余额 并生成流水 + shopUserService.updateMoney(shopUserMoneyEditDTO); + userFlowService.updateRefund(inFlow.getId(), refPayParam.getRefAmount()); + if (giftFlow != null && (giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(BigDecimal.ZERO) > 0) { + ShopUserMoneyEditDTO giftFlowEdit = new ShopUserMoneyEditDTO() + .setId(shopUser.getId()) + .setMoney(giftFlow.getAmount()) + .setType(0) + .setRemark("退款") + .setBizEnum(ShopUserFlowBizEnum.RECHARGE_REFUND) + .setRelationId(refPaymentId) + .setRechargeId(giftFlow.getId()); + //更新会员余额 并生成流水 + shopUserService.updateMoney(giftFlowEdit); + userFlowService.updateRefund(giftFlow.getId(), giftFlow.getAmount()); + } + //移除优惠券 + recordService.remove(QueryWrapper.create() + .eq(MkShopCouponRecord::getSourceFlowId, inFlow.getId()) + .eq(MkShopCouponRecord::getSource, "activate") + .eq(MkShopCouponRecord::getStatus, 0)); + return CzgResult.success(); + } + +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/utils/FunUtil.java b/cash-service/order-service/src/main/java/com/czg/service/order/utils/FunUtil.java deleted file mode 100644 index b1dfa4b5d..000000000 --- a/cash-service/order-service/src/main/java/com/czg/service/order/utils/FunUtil.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.czg.service.order.utils; - -import cn.hutool.core.lang.func.Func0; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.script.DefaultRedisScript; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * @author Administrator - */ -@Slf4j -@Component -public class FunUtil { - @Resource - private RedisTemplate redisTemplate; - public static int retryCount = 5; - - /** - * 执行任务并保证锁唯一 - * @param supplier 业务逻辑 - * @param lockKey Redis锁的Key - * @return 业务逻辑返回值 - */ - public T runFunAndCheckKey(Supplier supplier, String lockKey) { - String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId()); - try { - // 尝试获取锁,超时时间 5 秒,防止死锁 - boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)); - int count = 0; - // 初始等待 10ms - int retryDelay = 10; - - while (!lock) { - // 最多重试 10 次,大约 10 秒 - if (count++ > 50) { - throw new RuntimeException("系统繁忙, 稍后再试"); - } - Thread.sleep(retryDelay); - // 指数退避,最大等待 200ms - retryDelay = Math.min(retryDelay * 2, 200); - lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)); - } - - // 执行任务 - return supplier.get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("线程被中断", e); - } catch (Exception e) { - log.error("执行出错:{}", e.getMessage(), e); - throw e; - } finally { - // 释放锁(使用 Lua 脚本确保原子性) - unlock(lockKey, lockValue); - } - } - - /** - * 使用 Lua 脚本确保释放锁的原子性 - * @param lockKey 锁的 Key - * @param lockValue 当前线程的锁值 - */ - private void unlock(String lockKey, String lockValue) { - String luaScript = - "if redis.call('get', KEYS[1]) == ARGV[1] then " + - "return redis.call('del', KEYS[1]) " + - "else return 0 end"; - redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), - Collections.singletonList(lockKey), lockValue); - } - - public static R runFunAndRetry( - Supplier function, - Function check, Consumer errFun) { - log.info("工具类开始执行函数"); - R result = function.get(); - boolean flag = check.apply(result); - - log.info("执行结果: {}", result); - - while (flag && retryCount-- > 0) { - log.info("执行函数失败, 剩余尝试次数{}", retryCount); - result = function.get(); - log.info("执行结果: {}", result); - flag = check.apply(result); - } - - if (flag) { - errFun.accept(result); - } - return result; - } - - /** - * 防抖函数:在指定秒数内相同 Key 的任务只会执行一次 - * @param key 防抖使用的 Redis Key - * @param seconds 防抖时间(秒) - * @param task 要执行的业务逻辑 - * @return true 执行了任务;false 在防抖期内被拦截 - */ - public boolean debounce(String key, long seconds, Runnable task) { - try { - Boolean success = redisTemplate.opsForValue().setIfAbsent( - key, "1", seconds, TimeUnit.SECONDS - ); - - if (Boolean.TRUE.equals(success)) { - task.run(); - return true; - } - return false; - } catch (Exception e) { - log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e); - return false; - } - } -} diff --git a/cash-service/order-service/src/main/resources/mapper/OrderDetailMapper.xml b/cash-service/order-service/src/main/resources/mapper/OrderDetailMapper.xml index 055fdb31b..c1dee96ba 100644 --- a/cash-service/order-service/src/main/resources/mapper/OrderDetailMapper.xml +++ b/cash-service/order-service/src/main/resources/mapper/OrderDetailMapper.xml @@ -6,20 +6,23 @@ INSERT INTO tb_order_detail(order_id, shop_id, product_id, product_img, product_name, product_type, sku_id, - sku_name, price,member_price,discount_sale_amount,unit_price, discount_amount, pack_amount, pay_amount, + sku_name, price,member_price,discount_sale_amount,unit_price, discount_amount,is_gift, pack_amount, pay_amount, return_amount, num, pack_number, coupon_num, is_time_discount, return_num, refund_num, refund_no, discount_sale_note, status, place_num, is_temporary, is_print, is_wait_call, - pro_group_info, remark, refund_remark, create_time, update_time, is_urgent, sub_status, start_order_time, dish_out_time, food_serve_time, order_time) + pro_group_info, remark, refund_remark, create_time, update_time, is_urgent, sub_status, start_order_time, + dish_out_time, food_serve_time, order_time) VALUES (#{orderId}, #{entity.shopId}, #{entity.productId}, #{entity.productImg}, #{entity.productName}, #{entity.productType}, #{entity.skuId}, #{entity.skuName}, #{entity.price},#{entity.memberPrice}, - #{entity.discountSaleAmount}, #{entity.unitPrice}, #{entity.discountAmount}, + #{entity.discountSaleAmount}, #{entity.unitPrice}, #{entity.discountAmount},#{entity.isGift}, #{entity.packAmount}, #{entity.payAmount}, #{entity.returnAmount}, #{entity.num}, #{entity.packNumber}, #{entity.couponNum}, #{entity.isTimeDiscount}, #{entity.returnNum}, #{entity.refundNum}, #{entity.refundNo}, #{entity.discountSaleNote}, #{entity.status}, #{entity.placeNum}, #{entity.isTemporary}, #{entity.isPrint}, #{entity.isWaitCall}, #{entity.proGroupInfo}, #{entity.remark}, #{entity.refundRemark}, - now(), now(), #{entity.isUrgent}, #{entity.subStatus},#{entity.startOrderTime},#{entity.dishOutTime},#{entity.foodServeTime}, #{entity.orderTime}) + now(), now(), #{entity.isUrgent}, + #{entity.subStatus},#{entity.startOrderTime},#{entity.dishOutTime},#{entity.foodServeTime}, + #{entity.orderTime}) + select merchant.*,shop.shop_name as shopName + from tb_shop_direct_merchant merchant + left join tb_shop_info shop on merchant.shop_id = shop.id + + + and merchant.user_type = #{queryParam.userType} + + + and shop.shop_name like concat('%',#{queryParam.shopName},'%') + + + and (merchant.wechat_status = #{queryParam.status} or merchant.alipay_status = #{queryParam.status}) + + + and merchant.alipay_account like concat('%',#{queryParam.alipayAccount},'%') + + + + diff --git a/cash-service/account-service/src/main/resources/mapper/ShopMerchantMapper.xml b/cash-service/order-service/src/main/resources/mapper/ShopMerchantMapper.xml similarity index 71% rename from cash-service/account-service/src/main/resources/mapper/ShopMerchantMapper.xml rename to cash-service/order-service/src/main/resources/mapper/ShopMerchantMapper.xml index cde3e8e17..256e3d1c7 100644 --- a/cash-service/account-service/src/main/resources/mapper/ShopMerchantMapper.xml +++ b/cash-service/order-service/src/main/resources/mapper/ShopMerchantMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/cash-service/pay-service/pom.xml b/cash-service/pay-service/pom.xml index 92db97998..c4302235b 100644 --- a/cash-service/pay-service/pom.xml +++ b/cash-service/pay-service/pom.xml @@ -5,8 +5,9 @@ 4.0.0 com.czg - cash-service + cash 1.0.0 + ../../pom.xml pay-service @@ -18,14 +19,19 @@ - com.github.javen205 - IJPay-All + com.czg + cash-common-tools com.czg - czg-pay + poly-pay + ${project.version} + + + com.czg + aggregation-pay ${project.version} - + \ No newline at end of file diff --git a/cash-service/pay-service/src/main/java/com/czg/PayAdapter.java b/cash-service/pay-service/src/main/java/com/czg/PayAdapter.java new file mode 100644 index 000000000..7761d552f --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/PayAdapter.java @@ -0,0 +1,45 @@ +package com.czg; + +import com.czg.enums.CzgPayEnum; +import com.czg.pay.*; +import com.czg.resp.CzgResult; +import jakarta.validation.constraints.NotBlank; +import lombok.NonNull; + +import java.util.Map; + +/** + * 支付适配器接口 + * + * @author ww + * + */ +public interface PayAdapter { + + /** + * 支付渠道 + * {@link com.czg.constant.PayChannelCst} + */ + String getChannel(); + + /** + * 统一支付接口 + * + * @param payType 支付类型 + * @param payData 支付数据 Json类型 + * 对应 聚合支付参数 {@link PolyMerchantDTO} + * 对应 原生支付参数 {@link NativeMerchantDTO} + * @param domain 域名 请求地址 + * @param notifyUrl 通知地址 + * @param bizData 业务数据 + */ + CzgResult> pay(@NonNull CzgPayEnum payType, @NotBlank String payData, @NotBlank String domain, + @NotBlank String notifyUrl, CzgPayBaseReq bizData); + + CzgResult refund(@NotBlank String domain, @NotBlank String payData, CzgRefundReq bizData); + + CzgResult queryPayOrder(@NotBlank String domain, @NotBlank String payData, String payOrderId, String mchOrderNo, String platform); + + CzgResult queryRefund(@NotBlank String domain, @NotBlank String payData, String mchRefundNo, String refundOrderId); + +} diff --git a/cash-service/pay-service/src/main/java/com/czg/PayAdapterFactory.java b/cash-service/pay-service/src/main/java/com/czg/PayAdapterFactory.java new file mode 100644 index 000000000..489f5fc94 --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/PayAdapterFactory.java @@ -0,0 +1,39 @@ +package com.czg; + +import com.czg.constant.PayChannelCst; +import com.czg.impl.NativePayAdapter; +import com.czg.impl.PolyPayAdapter; + +import java.util.HashMap; +import java.util.Map; + +/** + * 支付适配器工厂 + * + * @author ww + */ +public class PayAdapterFactory { + + private static final Map ADAPTER_MAP = new HashMap<>(); + + static { + ADAPTER_MAP.put(PayChannelCst.POLY, new PolyPayAdapter()); + ADAPTER_MAP.put(PayChannelCst.NATIVE, new NativePayAdapter()); + } + + + /** + * 获取支付适配器 + * + * @param channel 支付渠道,仅支持 {@link PayChannelCst} + */ + public static PayAdapter getAdapter(String channel) { + PayAdapter adapter = ADAPTER_MAP.get(channel); + if (adapter == null) { + throw new IllegalStateException("支付渠道未注册适配器: " + channel); + } + return adapter; + } + + +} diff --git a/cash-service/pay-service/src/main/java/com/czg/impl/NativePayAdapter.java b/cash-service/pay-service/src/main/java/com/czg/impl/NativePayAdapter.java new file mode 100644 index 000000000..fbb7fe185 --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/impl/NativePayAdapter.java @@ -0,0 +1,84 @@ +package com.czg.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.czg.PayAdapter; +import com.czg.PayManager; +import com.czg.constant.PayChannelCst; +import com.czg.enums.CzgPayEnum; +import com.czg.exception.CzgException; +import com.czg.pay.*; +import com.czg.resp.CzgResult; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +/** + * 原生支付适配器 + * + * @author ww + * @date 2023/10/20 14:20 + */ +@Slf4j +public class NativePayAdapter implements PayAdapter { + + + @Override + public String getChannel() { + return PayChannelCst.NATIVE; + } + + @Override + public CzgResult> pay(@NonNull CzgPayEnum payType, String payData, String domain, String notifyUrl, CzgPayBaseReq bizData) { + try { + NativeMerchantDTO merchantDTO = getMerchantDTO(payData); + return switch (payType) { +// case H5_PAY: +// return h5Pay(merchantDTO, domain, notifyUrl, bizData); + case JS_PAY -> jsPay(merchantDTO, notifyUrl, bizData); + case LT_PAY -> jsPay(merchantDTO, notifyUrl, bizData); +// case SCAN_PAY: +// return scanPay(merchantDTO, domain, notifyUrl, bizData); + case MICRO_PAY -> + //扫码支付 扫描码 + barPay(merchantDTO, notifyUrl, bizData); + default -> throw new CzgException("原生支付不支持该支付方式: " + bizData.getPayType()); + }; + } catch (Exception e) { + log.error("聚合支付处理失败: {}", e.getMessage(), e); + return CzgResult.failure("聚合支付处理失败: " + e.getMessage()); + } + } + + @Override + public CzgResult refund(String domain, String payData, CzgRefundReq bizData) { + NativeMerchantDTO merchantDTO = getMerchantDTO(payData); + return CzgResult.success(PayManager.refund(bizData, bizData.getNotifyUrl(), merchantDTO)); + } + + @Override + public CzgResult queryPayOrder(String domain, String payData, String payOrderId, String mchOrderNo, String platform) { + NativeMerchantDTO merchantDTO = getMerchantDTO(payData); + QueryOrderRespDTO respDTO = PayManager.queryOrderStatus(platform, mchOrderNo, merchantDTO); + return CzgResult.success(respDTO); + } + + @Override + public CzgResult queryRefund(String domain, String payData, String mchRefundNo, String refundOrderId) { + return null; + } + + private CzgResult> jsPay(NativeMerchantDTO merchantDTO, String notifyUrl, CzgPayBaseReq bizData) { + bizData.setNotifyUrl(notifyUrl); + return CzgResult.success(PayManager.jsapiPay(bizData, merchantDTO)); + } + + private CzgResult> barPay(NativeMerchantDTO merchantDTO, String notifyUrl, CzgPayBaseReq bizData) { + bizData.setNotifyUrl(notifyUrl); + return CzgResult.success(PayManager.barPay(bizData, merchantDTO)); + } + + private NativeMerchantDTO getMerchantDTO(String payData) { + return JSONObject.parseObject(payData, NativeMerchantDTO.class); + } +} diff --git a/cash-service/pay-service/src/main/java/com/czg/impl/PolyPayAdapter.java b/cash-service/pay-service/src/main/java/com/czg/impl/PolyPayAdapter.java new file mode 100644 index 000000000..8a7a16bfe --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/impl/PolyPayAdapter.java @@ -0,0 +1,125 @@ +package com.czg.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.czg.PayAdapter; +import com.czg.PolyPayUtils; +import com.czg.constant.PayChannelCst; +import com.czg.constants.SystemConstants; +import com.czg.entity.resp.CzgBaseResp; +import com.czg.entity.resp.CzgRefundResp; +import com.czg.enums.CzgPayEnum; +import com.czg.exception.CzgException; +import com.czg.pay.*; +import com.czg.resp.CzgResult; +import com.czg.utils.AssertUtil; +import jakarta.validation.constraints.NotBlank; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +/** + * @author ww + */ +@Slf4j +public class PolyPayAdapter implements PayAdapter { + @Override + public String getChannel() { + return PayChannelCst.POLY; + } + + @Override + public CzgResult> pay(@NonNull CzgPayEnum payType, @NotBlank String payData, @NotBlank String domain, + @NotBlank String notifyUrl, CzgPayBaseReq bizData) { + try { + PolyMerchantDTO polyMerchantDTO = JSONObject.parseObject(payData, PolyMerchantDTO.class); + return switch (payType) { + case H5_PAY -> h5Pay(polyMerchantDTO, domain, notifyUrl, bizData); + case JS_PAY -> jsPay(polyMerchantDTO, domain, notifyUrl, bizData); + case LT_PAY -> ltPay(polyMerchantDTO, domain, notifyUrl, bizData); + case SCAN_PAY -> scanPay(polyMerchantDTO, domain, notifyUrl, bizData); + case MICRO_PAY -> microPay(polyMerchantDTO, domain, notifyUrl, bizData); + default -> throw new CzgException("聚合支付不支持该支付方式: " + payType); + }; + } catch (Exception e) { + log.error("聚合支付处理失败: {}", e.getMessage(), e); + return CzgResult.failure("聚合支付处理失败: " + e.getMessage()); + } + } + + + @Override + public CzgResult refund(@NotBlank String domain, @NotBlank String payData, CzgRefundReq bizData) { + PolyMerchantDTO shopMerchant = JSONObject.parseObject(payData, PolyMerchantDTO.class); + CzgResult result = PolyPayUtils.refundOrder(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); + return convertRefundResp(result); + } + + @Override + public CzgResult queryPayOrder(@NotBlank String payData, @NotBlank String domain, String payOrderId, String mchOrderNo, String platform) { + PolyMerchantDTO shopMerchant = JSONObject.parseObject(payData, PolyMerchantDTO.class); + CzgResult result = PolyPayUtils.queryPayOrder(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), payOrderId, mchOrderNo); + if (result.isSuccess()) { + QueryOrderRespDTO respDTO = new QueryOrderRespDTO() + .setStatus(result.getData().getState()) + .setOrderNo(result.getData().getMchOrderNo()) + .setAmount(result.getData().getAmount()) + .setErrorMsg("") + .setOriginResp(JSONObject.toJSONString(result.getData())); + return CzgResult.success(respDTO); + } + return CzgResult.failure(result.getMsg()); + } + + @Override + public CzgResult queryRefund(@NotBlank String payData, @NotBlank String domain, String mchRefundNo, String refundOrderId) { + PolyMerchantDTO shopMerchant = JSONObject.parseObject(payData, PolyMerchantDTO.class); + CzgResult result = PolyPayUtils.queryRefundOrder(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), mchRefundNo, refundOrderId); + return convertRefundResp(result); + } + + private CzgResult> h5Pay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) { + bizData.polyBase(shopMerchant.getStoreId(), notifyUrl); + return PolyPayUtils.h5Pay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); + } + + private CzgResult> jsPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) { + AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + (SystemConstants.PayType.ALIPAY.equals(bizData.getPayType()) ? "支付宝" : "微信") + "小程序"); + bizData.setPayType(SystemConstants.PayType.ALIPAY.equals(bizData.getPayType()) ? "ALIPAY" : "WECHAT"); + bizData.polyBase(shopMerchant.getStoreId(), notifyUrl); + return PolyPayUtils.jsPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); + + } + + private CzgResult> ltPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) { + AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + ("aliPay".equals(bizData.getPayType()) ? "支付宝" : "微信") + "小程序"); + bizData.polyBase(shopMerchant.getStoreId(), notifyUrl); + return PolyPayUtils.ltPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); + } + + private CzgResult> scanPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) { + bizData.polyBase(shopMerchant.getStoreId(), notifyUrl); + return PolyPayUtils.scanPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); + } + + private CzgResult> microPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) { + bizData.polyBase(shopMerchant.getStoreId(), notifyUrl); + return PolyPayUtils.microPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData); + } + + private CzgResult convertRefundResp(CzgResult result) { + if (result.isSuccess()) { + RefundRespDTO respDTO = new RefundRespDTO() + .setStatus(result.getData().getState()) + .setMerchantRefundNo(result.getData().getMchRefundNo()) + .setThirdRefundNo(result.getData().getRefundOrderId()) + .setPlatform(result.getData().getPayType()) + .setRefundAmount(result.getData().getRefundAmt()) + .setRefundTime(result.getData().getRefundTime()) + .setOriginalData(result.getData().getExtParam()); + return CzgResult.success(respDTO); + } + + return CzgResult.failure(result.getMsg()); + } +} diff --git a/cash-service/pay-service/src/main/java/com/czg/service/CzgPayService.java b/cash-service/pay-service/src/main/java/com/czg/service/CzgPayService.java deleted file mode 100644 index 551042d98..000000000 --- a/cash-service/pay-service/src/main/java/com/czg/service/CzgPayService.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.czg.service; - -import com.czg.entity.notify.CzgPayNotifyDTO; -import com.czg.entity.notify.CzgRefundNotifyDTO; -import com.czg.entity.req.*; -import com.czg.entity.resp.*; -import com.czg.resp.CzgResult; -import lombok.NonNull; - -/** - * @author ww - * @description - */ -public interface CzgPayService { - /** - * h5支付 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - */ - CzgResult h5Pay(@NonNull String appId, @NonNull String appSecret, CzgH5PayReq bizData); - - /** - * js支付 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - */ - CzgResult jsPay(@NonNull String appId, @NonNull String appSecret, CzgJsPayReq bizData); - - /** - * 小程序支付 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - */ - CzgResult ltPay(@NonNull String appId, @NonNull String appSecret, CzgLtPayReq bizData); - - /** - * PC扫码支付 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - */ - CzgResult scanPay(@NonNull String appId, @NonNull String appSecret, CzgScanPayReq bizData); - - /** - * 聚合反扫 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - */ - CzgResult microPay(@NonNull String appId, @NonNull String appSecret, CzgMicroPayReq bizData); - - /** - * 订单查询 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - * @param payOrderId 平台订单号 - * @param mchOrderNo 商户订单号 - */ - CzgResult queryPayOrder(@NonNull String appId, @NonNull String appSecret, - String payOrderId, String mchOrderNo); - - - /** - * 订单退款 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - */ - CzgResult refundOrder(@NonNull String appId, @NonNull String appSecret, CzgRefundReq bizData); - - /** - * 退款订单查询 - * - * @param appId 应用id tb_shop_merchant 表中的 app_id - * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret - * @param mchRefundNo 商户退款订单号 二选一 - * @param refundOrderId 平台退款订单号 二选一 - */ - CzgResult queryRefundOrder(@NonNull String appId, @NonNull String appSecret, - String mchRefundNo, String refundOrderId); - - -} diff --git a/cash-service/pay-service/src/main/java/com/czg/service/Impl/CzgPayServiceImpl.java b/cash-service/pay-service/src/main/java/com/czg/service/Impl/CzgPayServiceImpl.java deleted file mode 100644 index 55b0e0672..000000000 --- a/cash-service/pay-service/src/main/java/com/czg/service/Impl/CzgPayServiceImpl.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.czg.service.Impl; - -import com.czg.CzgPayUtils; -import com.czg.constants.ParamCodeCst; -import com.czg.entity.req.*; -import com.czg.entity.resp.*; -import com.czg.resp.CzgResult; -import com.czg.service.CzgPayService; -import com.czg.system.service.SysParamsService; -import lombok.NonNull; -import org.apache.dubbo.config.annotation.DubboReference; -import org.springframework.stereotype.Service; - -/** - * @author ww - * @description 1 - */ -@Service -public class CzgPayServiceImpl implements CzgPayService { - - @DubboReference - private SysParamsService sysParamsService; - - @Override - public CzgResult h5Pay(@NonNull String appId, @NonNull String appSecret, CzgH5PayReq bizData) { - return CzgPayUtils.h5Pay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData); - } - - @Override - public CzgResult jsPay(@NonNull String appId, @NonNull String appSecret, CzgJsPayReq bizData) { - return CzgPayUtils.jsPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData); - } - - @Override - public CzgResult ltPay(@NonNull String appId, @NonNull String appSecret, CzgLtPayReq bizData) { - return CzgPayUtils.ltPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData); - } - - @Override - public CzgResult scanPay(@NonNull String appId, @NonNull String appSecret, CzgScanPayReq bizData) { - return CzgPayUtils.scanPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData); - } - - @Override - public CzgResult microPay(@NonNull String appId, @NonNull String appSecret, CzgMicroPayReq bizData) { - return CzgPayUtils.microPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData); - } - - @Override - public CzgResult queryPayOrder(@NonNull String appId, @NonNull String appSecret, String payOrderId, String mchOrderNo) { - return CzgPayUtils.queryPayOrder(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, payOrderId, mchOrderNo); - } - - @Override - public CzgResult refundOrder(@NonNull String appId, @NonNull String appSecret, CzgRefundReq bizData) { - return CzgPayUtils.refundOrder(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData); - } - - @Override - public CzgResult queryRefundOrder(@NonNull String appId, @NonNull String appSecret, String mchRefundNo, String refundOrderId) { - return CzgPayUtils.queryRefundOrder(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, mchRefundNo, refundOrderId); - } - -} diff --git a/cash-service/pom.xml b/cash-service/pom.xml index a74a3949c..822eb7b58 100644 --- a/cash-service/pom.xml +++ b/cash-service/pom.xml @@ -18,7 +18,6 @@ product-service system-service code-generator - pay-service market-service diff --git a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductStockFlowServiceImpl.java b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductStockFlowServiceImpl.java index 171084153..b2960d2bf 100644 --- a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductStockFlowServiceImpl.java +++ b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductStockFlowServiceImpl.java @@ -8,7 +8,6 @@ import cn.hutool.core.util.StrUtil; import com.czg.product.entity.Product; import com.czg.product.entity.ProductStockFlow; import com.czg.product.service.ProductStockFlowService; -import com.czg.sa.StpKit; import com.czg.service.product.mapper.ConsInfoMapper; import com.czg.service.product.mapper.ProductMapper; import com.czg.service.product.mapper.ProductStockFlowMapper; @@ -44,15 +43,7 @@ public class ProductStockFlowServiceImpl extends ServiceImpl #{item} + or t1.sync_id in + + #{item} + order by t1.sort desc,t1.id desc diff --git a/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysBankInfoMapper.java b/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysBankInfoMapper.java new file mode 100644 index 000000000..92c6db03a --- /dev/null +++ b/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysBankInfoMapper.java @@ -0,0 +1,14 @@ +package com.czg.service.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.czg.system.entity.SysBankInfo; + +/** + * 银行账户信息表 映射层。 + * + * @author ww + * @since 2026-01-06 + */ +public interface SysBankInfoMapper extends BaseMapper { + +} diff --git a/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysCategoryInfoMapper.java b/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysCategoryInfoMapper.java new file mode 100644 index 000000000..84d0d82a8 --- /dev/null +++ b/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysCategoryInfoMapper.java @@ -0,0 +1,14 @@ +package com.czg.service.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.czg.system.entity.SysCategoryInfo; + +/** + * 类目信息表 映射层。 + * + * @author ww + * @since 2026-01-06 + */ +public interface SysCategoryInfoMapper extends BaseMapper { + +} diff --git a/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysRegionMapper.java b/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysRegionMapper.java new file mode 100644 index 000000000..b438c3d3d --- /dev/null +++ b/cash-service/system-service/src/main/java/com/czg/service/system/mapper/SysRegionMapper.java @@ -0,0 +1,14 @@ +package com.czg.service.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.czg.system.entity.SysRegion; + +/** + * 行政区表 映射层。 + * + * @author ww + * @since 2026-01-06 + */ +public interface SysRegionMapper extends BaseMapper { + +} diff --git a/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysBankInfoServiceImpl.java b/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysBankInfoServiceImpl.java new file mode 100644 index 000000000..9678422fb --- /dev/null +++ b/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysBankInfoServiceImpl.java @@ -0,0 +1,26 @@ +package com.czg.service.system.service.impl; + +import com.czg.BaseQueryParam; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.czg.system.entity.SysBankInfo; +import com.czg.system.service.SysBankInfoService; +import com.czg.service.system.mapper.SysBankInfoMapper; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 银行账户信息表 服务层实现。 + * + * @author ww + * @since 2026-01-06 + */ +@Service +public class SysBankInfoServiceImpl extends ServiceImpl implements SysBankInfoService { + + @Override + public Page bankInfoList(BaseQueryParam param, String bankName) { + return page(Page.of(param.getPage(), param.getSize()), query().like(SysBankInfo::getBankAlias, bankName)); + } +} diff --git a/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysCategoryInfoServiceImpl.java b/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysCategoryInfoServiceImpl.java new file mode 100644 index 000000000..f8ea19e9e --- /dev/null +++ b/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysCategoryInfoServiceImpl.java @@ -0,0 +1,43 @@ +package com.czg.service.system.service.impl; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import com.czg.system.vo.SysCategoryInfoVO; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.czg.system.entity.SysCategoryInfo; +import com.czg.system.service.SysCategoryInfoService; +import com.czg.service.system.mapper.SysCategoryInfoMapper; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 类目信息表 服务层实现。 + * + * @author ww + * @since 2026-01-06 + */ +@Service +public class SysCategoryInfoServiceImpl extends ServiceImpl implements SysCategoryInfoService { + + @Cacheable(value = "common:category", key = "'all'") + @Override + public List categoryList() { + List list = list(); + if (CollUtil.isEmpty(list)) { + return List.of(); + } + List result = new ArrayList<>(); + Map> stringListMap = CollStreamUtil.groupByKey(list, SysCategoryInfo::getFirstCategory); + stringListMap.forEach((k, v) -> { + SysCategoryInfoVO vo = new SysCategoryInfoVO(); + vo.setFirstCategory(k); + vo.setChild(v); + result.add(vo); + }); + return result; + } +} diff --git a/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysRegionServiceImpl.java b/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysRegionServiceImpl.java new file mode 100644 index 000000000..4820a7190 --- /dev/null +++ b/cash-service/system-service/src/main/java/com/czg/service/system/service/impl/SysRegionServiceImpl.java @@ -0,0 +1,56 @@ +package com.czg.service.system.service.impl; + +import com.czg.service.system.mapper.SysRegionMapper; +import com.czg.system.entity.SysRegion; +import com.czg.system.service.SysRegionService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 行政区表 服务层实现。 + * + * @author ww + * @since 2026-01-06 + */ +@Service +public class SysRegionServiceImpl extends ServiceImpl implements SysRegionService { + + + @Cacheable(value = "common:region", key = "'all'") + @Override + public List regionList() { + // 1. 单次查询获取所有数据 + List allRegions = list(query().ne(SysRegion::getRegionLevel, 1)); + // 2. 一次性按层级分组,减少流遍历次数 + Map> regionByLevel = allRegions.stream() + .collect(Collectors.groupingBy(SysRegion::getRegionLevel)); + + // 3. 获取各层级数据,默认空列表避免空指针 + List parents = regionByLevel.getOrDefault(2, List.of()); + List level3Regions = regionByLevel.getOrDefault(3, List.of()); + List level4Regions = regionByLevel.getOrDefault(4, List.of()); + + // 4. 构建3级地区的子节点映射(4级),使用HashMap保证性能 + Map> level4ByParentId = level4Regions.stream() + .collect(Collectors.groupingBy(SysRegion::getParentRegionId, Collectors.toList())); + + level3Regions.forEach(level3 -> { + List children = level4ByParentId.getOrDefault(level3.getRegionId(), List.of()); + level3.setChildren(children); + }); + + Map> level3ByParentId = level3Regions.stream() + .collect(Collectors.groupingBy(SysRegion::getParentRegionId)); + + parents.forEach(parent -> { + List children = level3ByParentId.getOrDefault(parent.getRegionId(), List.of()); + parent.setChildren(children); + }); + return parents; + } +} diff --git a/cash-service/system-service/src/main/resources/mapper/SysBankInfoMapper.xml b/cash-service/system-service/src/main/resources/mapper/SysBankInfoMapper.xml new file mode 100644 index 000000000..0edfabab3 --- /dev/null +++ b/cash-service/system-service/src/main/resources/mapper/SysBankInfoMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/cash-service/system-service/src/main/resources/mapper/SysCategoryInfoMapper.xml b/cash-service/system-service/src/main/resources/mapper/SysCategoryInfoMapper.xml new file mode 100644 index 000000000..b3a1df485 --- /dev/null +++ b/cash-service/system-service/src/main/resources/mapper/SysCategoryInfoMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/cash-service/system-service/src/main/resources/mapper/SysRegionMapper.xml b/cash-service/system-service/src/main/resources/mapper/SysRegionMapper.xml new file mode 100644 index 000000000..bb643d005 --- /dev/null +++ b/cash-service/system-service/src/main/resources/mapper/SysRegionMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/pom.xml b/pom.xml index fc763ccfa..0931e1d0d 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ cash-sdk cash-dependencies cash-api/market-server + cash-service/pay-service