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..bd70c794c --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/controller/admin/EntryManagerController.java @@ -0,0 +1,75 @@ +package com.czg.controller.admin; + +import com.czg.EntryManager; +import com.czg.annotation.Debounce; +import com.czg.dto.req.AggregateMerchantDto; +import com.czg.dto.resp.BankBranchDto; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.czg.resp.CzgResult; +import com.czg.sa.StpKit; +import com.czg.task.EntryManagerTask; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 进件管理 + * + * @author ww + */ +@AllArgsConstructor +@RestController +@RequestMapping("/admin/data/entryManager") +public class EntryManagerController { + + @Resource + private ShopDirectMerchantService shopDirectMerchantService; + @Resource + private EntryManagerTask entryManagerTask; + + /** + * 查询银行支行列表 + * + * @param province 省份 陕西省 从 /system/admin/common/region获取 + * @param city 城市 西安市 从 /system/admin/common/region获取 + * @param instId 顶级机构ID CMB 从 /system/admin/common/bankInfo 获取 + */ + @GetMapping("bankBranchList") + public CzgResult> queryBankBranchList(String province, String city, String instId) { + return CzgResult.success(EntryManager.queryBankBranchList(province, city, instId)); + } + + + /** + * 获取进件信息 + */ + @GetMapping + public CzgResult getEntry(Long shopId) { + return CzgResult.success(shopDirectMerchantService.getEntry(shopId)); + } + + /** + * 主动查询进件信息状态 + * 进件状态是INIT 待处理 AUDIT 审核中 SIGN 待签约 + * 3分钟内只能查一次 + */ + @GetMapping + @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/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..540c3ecdd --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/mq/EntryManagerMqListener.java @@ -0,0 +1,124 @@ +package com.czg.mq; + +import cn.hutool.core.util.StrUtil; +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.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 { + String messageId = message.getMessageProperties().getMessageId(); + if (hasMessageId(messageId)) { + return; + } + try { + Long shopId = Long.valueOf(msg); + // 将唯一标识添加到日志上下文 + ThreadContext.put("traceId", messageId); + // 安全转换shopId + if (shopId == null) { + channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); + return; + } + AggregateMerchantVO entry = shopDirectMerchantService.getEntry(Long.valueOf(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.setShopId(entry.getShopId()); + + merchant.setWechatStatus(resp.getWechatStatus()); + merchant.setWechatErrorMsg(resp.getWechatErrorMsg()); + + merchant.setAlipayStatus(resp.getAlipayStatus()); + merchant.setAlipayErrorMsg(resp.getAlipayErrorMsg()); + shopDirectMerchantService.updateById(merchant); + } + channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); + } catch (Exception e) { + log.error("进件MQ对接业务异常shopId:{}", msg, e); + ShopDirectMerchant merchant = new ShopDirectMerchant(); + merchant.setShopId(Long.valueOf(msg)); + merchant.setWechatStatus(PayCst.EntryStatus.REJECTED); + merchant.setAlipayStatus(PayCst.EntryStatus.REJECTED); + merchant.setErrorMsg("系统错误,请联系管理员后重试。"); + shopDirectMerchantService.updateById(merchant); + + channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); + } finally { + delMessageId(messageId); + // 清除日志上下文信息 + 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 c220d1f3f..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 @@ -48,9 +48,6 @@ public class OrderMqListener { 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 31781b987..c098099e7 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,12 +6,13 @@ 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; @@ -27,16 +28,15 @@ public class PrintMqListener { @Resource private MqLogService mqLogService; @Resource - private RedisService redisService; - + private FunUtil funUtil; @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); @@ -52,14 +52,14 @@ 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"); - redisService.runFunAndCheckKey(() -> { + funUtil.runFunAndCheckKey(() -> { printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER); return null; }, RedisCst.getLockKey("orderPrint", orderId)); @@ -71,14 +71,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..e8662c092 --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/task/EntryManagerTask.java @@ -0,0 +1,87 @@ +package com.czg.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.czg.EntryManager; +import com.czg.PayCst; +import com.czg.account.entity.ShopInfo; +import com.czg.account.service.ShopInfoService; +import com.czg.dto.resp.QueryStatusResp; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.order.service.ShopOrderStatisticService; +import com.czg.order.service.ShopProdStatisticService; +import com.czg.order.service.ShopTableOrderStatisticService; +import com.czg.service.RedisService; +import com.czg.service.order.service.ShopDirectMerchantService; +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.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 进件查询 + * + * @author ww + */ +@Component +@Slf4j +public class EntryManagerTask { + @Resource + private ShopDirectMerchantService shopDirectMerchantService; + @DubboReference + private ShopInfoService shopInfoService; + + //每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())) { + QueryStatusResp wechatStatus = EntryManager.queryWechatEntryStatus(shopDirectMerchant.getMerchantCode()); + shopDirectMerchant.setWechatStatus(wechatStatus.getStatus()); + shopDirectMerchant.setWechatErrorMsg(wechatStatus.getFailReason()); + shopDirectMerchant.setWechatSignUrl(wechatStatus.getSignUrl()); + if (PayCst.EntryStatus.FINISH.equals(wechatStatus.getStatus())) { + wechatMerchantId = wechatStatus.getThirdMerchantId(); + } + } + if (PayCst.EntryStatus.NEED_QUERY_LIST.contains(shopDirectMerchant.getAlipayStatus())) { + QueryStatusResp alipayStatus = EntryManager.queryAlipayEntryStatus(shopDirectMerchant.getMerchantCode()); + shopDirectMerchant.setAlipayStatus(alipayStatus.getStatus()); + shopDirectMerchant.setAlipayErrorMsg(alipayStatus.getFailReason()); + shopDirectMerchant.setAlipaySignUrl(alipayStatus.getSignUrl()); + if (PayCst.EntryStatus.FINISH.equals(alipayStatus.getStatus())) { + alipayMerchantId = alipayStatus.getThirdMerchantId(); + } + } + shopDirectMerchantService.updateById(shopDirectMerchant); + if (StrUtil.isNotBlank(wechatMerchantId) || StrUtil.isNotBlank(alipayMerchantId)) { + shopInfoService.editEntry(shopDirectMerchant.getShopId(), wechatMerchantId, alipayMerchantId); + } + } + } +} \ 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/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..2585c6995 --- /dev/null +++ b/cash-api/system-server/src/main/java/com/czg/controller/admin/SysCommonController.java @@ -0,0 +1,61 @@ +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.SysCategoryInfo; +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; +import java.util.Map; + +/** + * 通用 + * + * @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-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..8903cad34 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-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 1e115c903..1bf743587 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 @@ -142,6 +142,14 @@ public class ShopInfo implements Serializable { * -1 平台禁用 0-过期,1正式营业, */ private Integer status; + /** + * 微信商户id + */ + private String wechatMerchantId; + /** + * 支付宝商户id + */ + private String alipayMerchantId; /** * 到期时间 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..dcb5e7af2 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 @@ -37,6 +37,11 @@ public interface ShopInfoService extends IService { Boolean edit(ShopInfoEditDTO shopInfoEditDTO); + /** + * 进件结果保存 + */ + Boolean editEntry(Long shopId, String wechatMerchantId, String alipayMerchantId); + ShopDetailDTO detail(Long id) throws CzgException; ShopInfoByCodeDTO getByCode(String tableCode, String lat, String lng, boolean checkState); 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..5fbea4472 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/entity/ShopDirectMerchant.java @@ -0,0 +1,111 @@ +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; + + /** + * 营业执照编号 + */ + private String licenceNo; + + /** + * 商户编号(在当前系统唯一) + */ + private String merchantCode; + + /** + * 商户基础信息 + */ + 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 wechatStatus; + + /** + * 微信进件错误信息 + */ + private String wechatErrorMsg; + /** + * 微信进件签名地址 + */ + private String wechatSignUrl; + + /** + * 支付宝状态 + */ + private String alipayStatus; + + /** + * 支付宝进件错误信息 + */ + private String alipayErrorMsg; + /** + * 支付宝进件签名地址 + */ + private String alipaySignUrl; + +} 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..bc298d759 --- /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..1fe2a78c7 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysCategoryInfo.java @@ -0,0 +1,77 @@ +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; + + /** + * 一级类目 + */ + 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..1b50b9e9a --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/entity/SysRegion.java @@ -0,0 +1,70 @@ +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; + + /** + * 子级区域 + */ + @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-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..1955b7850 --- /dev/null +++ b/cash-sdk/aggregation-pay/pom.xml @@ -0,0 +1,35 @@ + + + 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 + + + + \ 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..125864422 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java @@ -0,0 +1,458 @@ +package com.czg; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import com.czg.dto.req.*; +import com.czg.dto.resp.BankBranchDto; +import com.czg.dto.resp.EntryRespDto; +import com.czg.dto.resp.EntryThirdRespDto; +import com.czg.dto.resp.QueryStatusResp; +import com.czg.exception.CzgException; +import com.czg.third.alipay.AlipayEntryManager; +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 merchantCode 商户编号 + * @return 进件状态 + */ + public static QueryStatusResp queryWechatEntryStatus(String merchantCode) { + return WechatEntryManager.queryMerchantEntryStatus(null, merchantCode); + } + + /** + * 查询支付宝进件状态 + * + * @param merchantCode 商户编号 + * @return 进件状态 + */ + public static QueryStatusResp queryAlipayEntryStatus(String merchantCode) { + return AlipayEntryManager.queryMerchantEntryStatus(null, merchantCode); + } + + /** + * 进件 + * 请先执行: + * 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(() -> AlipayEntryManager.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()); + } + } + 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.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) { + + + AggregateMerchantDto merchantDto = getTestMerchantEntryData(); + +// verifyEntryParam(merchantDto); +// uploadParamImage(merchantDto); + + verifyEntryParam(merchantDto); + uploadParamImage(merchantDto); + System.out.println(merchantDto); +// entryMerchant(merchantDto, PayCst.Platform.WECHAT); +// entryMerchant(merchantDto, PayCst.Platform.ALIPAY); + entryMerchant(merchantDto, PayCst.Platform.WECHAT, PayCst.Platform.ALIPAY); + } + + private static AggregateMerchantDto getTestMerchantEntryData() { + AggregateMerchantDto merchantDto = new AggregateMerchantDto(); + merchantDto.setMerchantCode("20220106000000000001"); + + MerchantBaseInfoDto baseInfoDto = new MerchantBaseInfoDto(); + baseInfoDto.setUserType("1"); + baseInfoDto.setCompanyChildType("1"); + baseInfoDto.setShortName("测试商户"); + baseInfoDto.setMccCode("0741"); + 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/PayCst.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayCst.java new file mode 100644 index 000000000..8bce68aae --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayCst.java @@ -0,0 +1,82 @@ +package com.czg; + +import java.util.List; + +/** + * 支付相关常量 + * + * @author yjjie + * @date 2026/1/7 09:20 + */ +public interface PayCst { + + /** + * 平台 + */ + 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/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..3bebf4567 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/MerchantBaseInfoDto.java @@ -0,0 +1,97 @@ +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; + + /** + * 【必填】 + * 行业编码 + */ + private String mccCode; + + /** + * 联系人类型 + * 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/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/resp/BankBranchDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/BankBranchDto.java new file mode 100644 index 000000000..f3c473090 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/BankBranchDto.java @@ -0,0 +1,58 @@ +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; +} 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..f6abe75e4 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryRespDto.java @@ -0,0 +1,46 @@ +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; + + /** + * 支付宝状态 + * {@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..449b56f46 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/EntryThirdRespDto.java @@ -0,0 +1,34 @@ +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 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..b6edf25f2 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/resp/QueryStatusResp.java @@ -0,0 +1,49 @@ +package com.czg.dto.resp; + +import lombok.Data; + +/** + * 查询进件状态 + * @author yjjie + * @date 2026/1/7 17:12 + */ +@Data +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/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..105476b38 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayEntryManager.java @@ -0,0 +1,382 @@ +package com.czg.third.alipay; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +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) { + +// queryBankBranchList("CMB", "陕西省", "西安市"); + uploadImage(null, "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240312/31476c871c224389aea0ac4e17c964a3.jpg"); + + } + + // 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); + 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/AlipayReqUtils.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayReqUtils.java new file mode 100644 index 000000000..c81f50ed6 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayReqUtils.java @@ -0,0 +1,224 @@ +package com.czg.third.alipay; + + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayConfig; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.*; +import com.alipay.api.request.AntMerchantExpandIndirectZftSimplecreateRequest; +import com.alipay.api.response.AntMerchantExpandIndirectZftSimplecreateResponse; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author yjjie + * @date 2026/1/5 13:46 + */ +public class AlipayReqUtils { + public static void main(String[] args) throws AlipayApiException { +// DefaultAlipayClient defaultAlipayClient = AlipayClient.getDefaultClient(null); +// AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); + DefaultAlipayClient defaultAlipayClient = new DefaultAlipayClient(getAlipayConfig()); + + // 构造请求参数以调用接口 + AntMerchantExpandIndirectZftSimplecreateRequest request = new AntMerchantExpandIndirectZftSimplecreateRequest(); + AntMerchantExpandIndirectZftSimplecreateModel model = new AntMerchantExpandIndirectZftSimplecreateModel(); + + // 设置商户编号 + model.setExternalId("1052900599901941"); + + // 设置签约支付宝账户 + model.setBindingAlipayLogonId("18434286340"); + + // 设置商户别名 + model.setAliasName("一点点"); + + // 设置商户客服电话 +// model.setServicePhone("0571-85022088"); + + // 设置商户联系人信息 + ContactInfo contactInfos = new ContactInfo(); + contactInfos.setIdCardNo("142725199902176419"); +// contactInfos.setPhone("0571-85022088"); + contactInfos.setName("王伟"); + contactInfos.setMobile("18434286340"); + contactInfos.setEmail("18434286340"); + model.setContactInfos(contactInfos); + + // 设置进件的二级商户名称 + model.setName("王伟"); + + // 设置默认结算规则 + DefaultSettleRule defaultSettleRule = new DefaultSettleRule(); + defaultSettleRule.setDefaultSettleType("alipayAccount"); + defaultSettleRule.setDefaultSettleTarget("18434286340"); + model.setDefaultSettleRule(defaultSettleRule); + + // 设置结算支付宝账号 + model.setAlipayLogonId("18434286340"); + + // 设置结算银行卡信息 + SettleCardInfo bizCards = new SettleCardInfo(); + bizCards.setAccountInstName("招商银行"); + bizCards.setBankCode("103290003044"); + bizCards.setAccountType("DC"); + bizCards.setUsageType("01"); + bizCards.setAccountHolderName("王伟"); + bizCards.setAccountInstCity("杭州市"); + bizCards.setAccountInstId("CMB"); + bizCards.setAccountNo("6214831259609102"); + bizCards.setAccountBranchName("招商银行杭州高新支行"); + bizCards.setAccountInstProvince("浙江省"); + model.setBizCards(bizCards); + + // 设置授权函 + model.setLicenseAuthLetterImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); + + // 设置开票资料信息 +// MerchantInvoiceInfo invoiceInfo = new MerchantInvoiceInfo(); +// invoiceInfo.setMailTelephone("057162288888"); +// invoiceInfo.setTaxPayerQualification("01"); +// invoiceInfo.setAddress("浙江省杭州市西湖区西溪路蚂蚁金服"); +// invoiceInfo.setAcceptElectronic(false); +// invoiceInfo.setTelephone("057162288888"); +// invoiceInfo.setTitle("蚂蚁金服(杭州)信息技术有限公司"); +// invoiceInfo.setMailName("张三"); +// invoiceInfo.setAutoInvoice(true); +// invoiceInfo.setTaxPayerValid("19981011"); +// invoiceInfo.setTaxNo("51010482542598631219"); +// invoiceInfo.setBankName("中国银行"); +// AddressInfo mailAddress = new AddressInfo(); +// mailAddress.setAddress("万塘路18号黄龙时代广场B座"); +// mailAddress.setDistrictCode("371002"); +// mailAddress.setLatitude("60.270001"); +// mailAddress.setCityCode("371000"); +// mailAddress.setPoiid("B0FFIVU189"); +// mailAddress.setProvinceCode("370000"); +// mailAddress.setLongitude("120.760001"); +// invoiceInfo.setMailAddress(mailAddress); +// invoiceInfo.setBankAccount("1234567812345678123"); +// model.setInvoiceInfo(invoiceInfo); + + // 设置商户使用服务 + List service = new ArrayList(); + service.add("当面付"); + model.setService(service); + + // 设置经营地址 + AddressInfo businessAddress = new AddressInfo(); + businessAddress.setAddress("万塘路18号黄龙时代广场B座"); + businessAddress.setDistrictCode("371002"); + businessAddress.setLatitude("60.270001"); + businessAddress.setCityCode("371000"); + businessAddress.setPoiid("B0FFIVU189"); + businessAddress.setProvinceCode("370000"); + businessAddress.setType("BUSINESS_ADDRESS"); + businessAddress.setLongitude("120.760001"); + model.setBusinessAddress(businessAddress); + + // 设置门头照 + model.setOutDoorImages("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); + + // 设置内景照 + model.setInDoorImages("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); + + // 设置商户站点信息 +// SiteInfo sites = new SiteInfo(); +// sites.setIcpOrgName("支付宝(中国)网络技术有限公司"); +// sites.setSiteType("01"); +// sites.setSiteDomain("www.alipay.com"); +// sites.setScreenshotImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); +// sites.setRemark("备注说明"); +// sites.setAuthLetterImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); +// sites.setSiteName("XXX网站"); +// sites.setMarket("豌豆荚"); +// sites.setPassword("测试密码"); +// sites.setDownload("https://itunes.apple.com/cn/app/id333206289?mt=8"); +// sites.setTinyAppId("2021004105652035"); +// sites.setSiteUrl("https://open.alipay.com"); +// sites.setIcpServiceName("支付宝"); +// sites.setRemarkImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); +// sites.setAccount("测试账号"); +// sites.setIcpNo("沪ICP备15027489号-2"); +// sites.setStatus("ONLINE"); +// model.setSites(sites); + + // 设置商户类别码 mcc + model.setMcc("B0007"); + + // 设置商户行业资质图片 + List qualifications = new ArrayList(); + IndustryQualificationInfo qualifications0 = new IndustryQualificationInfo(); + qualifications0.setIndustryQualificationImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); + qualifications0.setIndustryQualificationType("323"); + qualifications.add(qualifications0); + model.setQualifications(qualifications); + + // 设置补充证件号 + model.setAdditionalCertNo("9133010608210550XR"); + + // 设置补充证件类型 + model.setAdditionalCertType("201"); + + // 设置补充证件图片 + model.setAdditionalCertImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg"); + + // uid参数未来计划废弃,存量商户可继续使用,新商户请使用openid。请根据应用-开发配置-openid配置选择支持的字段。 + // model.setInfoSourceUid("2088111122223333"); + + // 设置(平替原来的info_source_uid字段 +// model.setInfoSourceOpenId("074a1CcTG1LelxKe4xQC0zgNdId0nxi95b5lsNpazWYoCo5"); + + // uid参数未来计划废弃,存量商户可继续使用,新商户请使用openid。请根据应用-开发配置-openid配置选择支持的字段。 + // model.setOverseaSettleAccount("2088111122223333"); + + // 设置(平替原来的oversea_settle_open_id字段 +// model.setOverseaSettleOpenId("074a1CcTG1LelxKe4xQC0zgNdId0nxi95b5lsNpazWYoCo5"); + + // 设置二级商户与服务商的签约时间 + model.setSignTimeWithIsv("2015-04-15"); + + // 设置代扣产品信息 + ZFTWithholdingInfo zftWithholdingInfo = new ZFTWithholdingInfo(); + zftWithholdingInfo.setWithholdingServiceFeatureName("GENERAL_WITHHOLDING_P"); + zftWithholdingInfo.setSignScene("DEFAULT"); + model.setZftWithholdingInfo(zftWithholdingInfo); + + // 设置交易场景 + List tradeScene = new ArrayList(); + tradeScene.add("TINY_APP"); + model.setTradeScene(tradeScene); + + request.setBizModel(model); + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + + AntMerchantExpandIndirectZftSimplecreateResponse response = defaultAlipayClient.execute(request); + System.out.println(response.getBody()); + + if (response.isSuccess()) { + System.out.println("调用成功"); + } else { + System.out.println("调用失败"); + // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接 + // String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response); + // System.out.println(diagnosisUrl); + } + } + + private static AlipayConfig getAlipayConfig() { + String privateKey = "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=="; + String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB"; + AlipayConfig alipayConfig = new AlipayConfig(); + alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do"); + alipayConfig.setAppId("2021004174605036"); + alipayConfig.setPrivateKey(privateKey); + alipayConfig.setFormat("json"); + alipayConfig.setAlipayPublicKey(alipayPublicKey); + alipayConfig.setCharset("UTF-8"); + alipayConfig.setSignType("RSA2"); + return alipayConfig; + } + +} 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..d1ce39399 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/config/AlipayConfigDto.java @@ -0,0 +1,47 @@ +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 getDefaultConfig() { + 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/alipay/dto/entry/AlipayAddressReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayAddressReqDto.java new file mode 100644 index 000000000..71bb9e137 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayAddressReqDto.java @@ -0,0 +1,43 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 经营地址 + * @author yjjie + * @date 2026/1/4 11:20 + */ +@Data +public class AlipayAddressReqDto { + + /** + * 【必填】 + * 城市编码 + * 蚂蚁店铺请按照蚂蚁店铺地区码 表格中填写。 + * 直付通商户请按照直付通商户地区码 表格中内容填写。 + */ + @JSONField(name = "city_code") + private String cityCode; + + /** + * 【必填】 + * 区县编码 + */ + @JSONField(name = "district_code") + private String districtCode; + + /** + * 【必填】 + * 详细地址 + */ + @JSONField(name = "address") + private String address; + + /** + * 【必填】 + * 省份编码 + */ + @JSONField(name = "province_code") + private String provinceCode; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayBizCardsReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayBizCardsReqDto.java new file mode 100644 index 000000000..06cb1fe0a --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayBizCardsReqDto.java @@ -0,0 +1,89 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 结算银行卡信息 + * @author yjjie + * @date 2026/1/4 11:14 + */ +@Data +public class AlipayBizCardsReqDto { + + /** + * 【必填】 + * 开户支行名 + */ + @JSONField(name = "account_branch_name") + private String accountBranchName; + + /** + * 【必填】 + * 卡户名 + */ + @JSONField(name = "account_holder_name") + private String accountHolderName; + + /** + * 【必填】 + * 开户行所在地-省 + */ + @JSONField(name = "account_inst_province") + private String accountInstProvince; + + /** + * 【必填】 + * 开户行所在地-市 + */ + @JSONField(name = "account_inst_city") + private String accountInstCity; + + /** + * 【必填】 + * 开户行简称缩写 + */ + @JSONField(name = "account_inst_id") + private String accountInstId; + + /** + * 【必填】 + * 银行名称 + */ + @JSONField(name = "account_inst_name") + private String accountInstName; + + /** + * 【必填】 + * 银行卡号 + */ + @JSONField(name = "account_no") + private String accountNo; + + /** + * 【必填】 + * 银行卡类型 + * 【枚举值】 + * 借记卡: DC + * 信用卡: CC + */ + @JSONField(name = "account_type") + private String accountType; + + /** + * 【必填】 + * 账号使用类型 + * 【枚举值】 + * 对公: 01 + * 对私: 02 + */ + @JSONField(name = "usage_type") + private String usageType; + + /** + * 【选填】 + * 联行号 + */ + @JSONField(name = "bank_code") + private String bankCode; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayContactInfoReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayContactInfoReqDto.java new file mode 100644 index 000000000..c243994ef --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayContactInfoReqDto.java @@ -0,0 +1,50 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 商户联系人信息 + * @author yjjie + * @date 2026/1/4 11:02 + */ +@Data +public class AlipayContactInfoReqDto { + /** + * 【必填】 + * 联系人名字 + */ + @JSONField(name = "name") + private String name; + + /** + * 【必填】 + * email | mobile | phone 三选一 + * 电子邮箱 + */ + @JSONField(name = "email") + private String email; + + /** + * 【必填】 + * email | mobile | phone 三选一 + * 手机号码 + */ + @JSONField(name = "mobile") + private String mobile; + + /** + * 【必填】 + * email | mobile | phone 三选一 + * 电话 + */ + @JSONField(name = "phone") + private String phone; + + /** + * 【必填】 + * 身份证号 + */ + @JSONField(name = "id_card_no") + private String idCardNo; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayEntryReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayEntryReqDto.java new file mode 100644 index 000000000..cbc0a52f5 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayEntryReqDto.java @@ -0,0 +1,249 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; + +/** + * 平台商提交二级商户资料进行进件,完成二级商户入驻 + * ... + * @author yjjie + * @date 2025/12/29 14:19 + */ +@Data +public class AlipayEntryReqDto { + + /** + * 【必填】 + * 商户编号,由一级商户定义,保证在一级商户下唯一即可 + */ + @JSONField(name = "external_id") + private String externalId; + + /** + * 【必填】 + * 商户别名。支付宝收银台及账单中的商户名称会展示此处设置的别名。如果涉及支付宝APP内的支付,支付结果页也会展示该别名;如果涉及线下当面付场景,请填写线下店铺名称 + */ + @JSONField(name = "alias_name") + private String aliasName; + + /** + * 【必填】 + * 商户联系人信息。在本业务中,ContactInfo对象中联系人姓名、手机号必填,其他选填 + */ + @JSONField(name = "contact_infos") + private AlipayContactInfoReqDto contactInfo; + + /** + * 【必填】 + * 默认结算规则。当调用收单接口,settle_info中设置默认结算规则(defaultSettle)时,交易资金将结算至此处设置的默认结算目标账户中。其详细描述及收单接口传参示例参考功能包文档 + */ + @JSONField(name = "default_settle_rule") + private AlipaySettleRuleReqDto defaultSettleRule; + + /** + * 【必填】 + * 商户使用服务 + * 可选值有:当面付、jsapi支付、app支付、wap支付、电脑支付、预授权支付、商户代扣、小程序支付、订单码支付。其值会影响其他字段必填性,详见其他字段描述 + * 当面付: 当面付 + * jsapi支付: jsapi支付 + * app支付: app支付 + * wap支付: wap支付 + * 电脑支付: 电脑支付 + * 预授权支付: 预授权支付 + * 商户代扣: 商户代扣 + * 小程序支付: 小程序支付 + * 订单码支付: 订单码支付 + */ + @JSONField(name = "service") + private List service; + + /** + * 【必填】 + * 商户证件编号 + * 按商户类型merchant_type的说明提供对应的证件编号 + */ + @JSONField(name = "cert_no") + private String certNo; + + /** + * 【必填】 + * 商户类别码 mcc + * ... + * 可查看 进件MCC与资质要求 202212.xlsx,特殊行业要按照MCC说明中的资质一栏上传辅助资质,辅助资质要在 qualifications 中上传,会有人工审核。 + * 【示例值】B0007 + */ + @JSONField(name = "mcc") + private String mcc; + + /** + * 【选填】 + * 结算支付宝账号 + * 结算账号使用支付宝账号时必填,本字段指定交易资金结算的具体支付宝账号,与binding_alipay_logon_id同主体的支付宝账号即可 + */ + @JSONField(name = "alipay_logon_id") + private String alipayLogonId; + + /** + * 【选填】 + * 法人名称 + * 非个人商户类型必填 + */ + @JSONField(name = "legal_name") + private String legalName; + + /** + * 【选填】 + * 法人证件编号 + * 法人证件编号,非个人商户类型必填 + */ + @JSONField(name = "legal_cert_no") + private String legalCertNo; + + /** + * 【选填】 + * 商户证件图 + * 目前只有当商户类型是个人商户且使用当面付服务时才需填写 + */ + @JSONField(name = "cert_image_back") + private String certImageBack; + + /** + * 【选填】 + * 商户证件图片 + * 本业务接口中,如果是特殊行业必填;使用当面付服务时,非个人必填,个人结算到卡时必填。其值为使用ant.merchant.expand.indirect.image.upload上传图片得到的一串oss key。 + */ + @JSONField(name = "cert_image") + private String certImage; + + /** + * 【选填】 + * 进件的二级商户名称 + * 一般情况下要与证件的名称相同。个体工商户类型可以放宽到法人名称 + */ + @JSONField(name = "name") + private String name; + + /** + * 【选填】 + * 法人证件类型 + * 默认可不填,认为legal_cert_no是大陆身份证。类型包括:100 大陆身份证;105 港澳居民往来内地通行证;106 台湾同胞往来大陆通行证;108 外国人居留证 + * 【枚举值】 + * 大陆身份证: 100 + * 港澳居民往来内地通行证: 105 + * 台湾同胞往来大陆通行证: 106 + * 外国人居留证: 108 + */ + @JSONField(name = "legal_cert_type") + private String legalCertType; + + /** + * 【选填】 + * 商户类型 + * 01:企业;cert_type填写201(营业执照);cert_no填写营业执照号; + * 02:事业单位:cert_type填写218(事业单位法人证书);cert_no填写事业单位法人证书编号; + * 03:民办非企业组织:cert_type填写204(民办非企业登记证书);cert_no填写民办非企业登记证书编号; + * 04:社会团体:cert_type填写206(社会团体法人登记证书);cert_no填写社会团体法人登记证书编号; + * 05:党政及国家机关:cert_type填写219(党政机关批准设立文件/行政执法主体资格证);cert_no填写党政机关批准设立文件/行政执法主体资格证编号; + * 06:个人商户:cert_type填写100(个人身份证);cert_no填写个人身份证号码; + * 07:个体工商户:cert_type填写201(营业执照);cert_no填写营业执照编号; + */ + @JSONField(name = "merchant_type") + private String merchantType; + + /** + * 【选填】 + * 商户证件类型 + * 按商户类型merchant_type的说明提供对应的证件类型。 + * 营业执照: 201 + * 事业单位法人证书: 218 + * 民办非企业登记证书: 204 + * 社会团体法人登记证书: 206 + * 党政机关批准设立文件/行政执法主体资格证: 219 + * 个人身份证: 100 + */ + @JSONField(name = "cert_type") + private String certType; + + /** + * 【选填】 + * 证件名称 + * 目前只有个体工商户商户类型要求填入本字段,填写值为个体工商户营业执照上的名称 + */ + @JSONField(name = "cert_name") + private String certName; + + /** + * 【选填】 + * 结算银行卡信息 + * 结算银行卡信息,结算账号使用银行卡时必填。本业务当前只允许传入一张结算卡。个人类型商户不允许结算到银行卡 + * 【必选条件】结算银行卡信息,结算账号使用银行卡时必填。本业务当前只允许传入一张结算卡。个人类型商户不允许结算到银行卡 + */ + @JSONField(name = "biz_cards") + private AlipayBizCardsReqDto bizCards; + + /** + * 【选填】 + * 经营地址 + * 使用当面付服务时必填。地址对象中省、市、区、地址必填,其余选填 + */ + @JSONField(name = "business_address") + private AlipayAddressReqDto businessAddress; + + /** + * 【选填】 + * 门头照 + * 使用当面付服务时必填。其值为使用ant.merchant.expand.indirect.image.upload上传图片得到的一串oss key + */ + @JSONField(name = "out_door_images") + private String outDoorImages; + + /** + * 【选填】 + * 内景照 + * 使用当面付服务时必填。其值为使用ant.merchant.expand.indirect.image.upload上传图片得到的一串oss key + */ + @JSONField(name = "in_door_images") + private String inDoorImages; + + /** + * 【选填】 + * 授权函 + * 《说明函》模板参考 + * 当商户名与结算卡户名不一致。《说明函》模板参考。涉及外籍法人(这种情况上传任意能证明身份的图片)时必填, + * 其值为使用ant.merchant.expand.indirect.image.upload上传图片得到的一串oss key。(商户类型为个体工商户时,本字段仅需上传营业执照非授权函) + */ + @JSONField(name = "license_auth_letter_image") + private String licenseAuthLetterImage; + + /** + * 【选填】 + * 商户站点信息 + * 包括网站、app、小程序。商户使用服务包含电脑支付、wap支付时,必须填充一个类型为01(网站)的SiteInfo对象,site_type/site_url/site_name必填; + * 当包含app支付时,必须至少填充类型为02(APP)或06(支付宝小程序)中一种类型的SiteInfo对象,site_type/site_name必填;当包含jsapi支付时,必须填充一个类型为06(支付宝小程序)的SiteInfo对象; + */ + @JSONField(name = "sites") + private AlipaySitesReqDto sites; + + /** + * 【选填】 + * 商户行业资质图片 + * 当商户的经营类目选择了特殊行业时该字段必填,需要特殊行业资质文件。每项行业资质信息中,industry_qualification_type和industry_qualification_image均必填。 + */ + @JSONField(name = "qualifications") + private List qualifications; + + /** + * 【选填】 + * 交易场景 + * 【枚举值】 + * 小程序支付场景: TINY_APP + * H5场景: WAP + * 线下当面付场景: OFFLINE + * APP支付场景: APP + * 网站支付场景: PC + */ + @JSONField(name = "trade_scene") + private List tradeScene; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayImageUploadReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayImageUploadReqDto.java new file mode 100644 index 000000000..666759afe --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayImageUploadReqDto.java @@ -0,0 +1,31 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 图片上传 + * @author yjjie + * @date 2026/1/4 13:46 + */ +@Data +@Accessors(chain = true) +public class AlipayImageUploadReqDto { + + /** + * 【必填】 + * 图片格式 + * 支持格式:bmp、jpg、jpeg、png、gif. + */ + @JSONField(name = "image_type") + private String imageType; + + /** + * 【必填】 + * 图片二进制字节流 + * 最大为10M + */ + @JSONField(name = "image_content") + private byte[] imageContent; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayQualificationsReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayQualificationsReqDto.java new file mode 100644 index 000000000..81e66a598 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipayQualificationsReqDto.java @@ -0,0 +1,30 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 商户行业资质 + * @author yjjie + * @date 2026/1/4 11:40 + */ +@Data +public class AlipayQualificationsReqDto { + + /** + * 【必填】 + * 商户行业资质类型 具体选值参见 文档 + * 【枚举值】 + * 金融许可证: 323 + * 【示例值】323 + */ + @JSONField(name = "industry_qualification_type") + private String industryQualificationType; + + /** + * 【必填】 + * 商户行业资质图片 + */ + @JSONField(name = "industry_qualification_image") + private String industryQualificationImage; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipaySettleRuleReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipaySettleRuleReqDto.java new file mode 100644 index 000000000..307a0425c --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipaySettleRuleReqDto.java @@ -0,0 +1,29 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 结算规则 + * @author yjjie + * @date 2026/1/4 11:06 + */ +@Data +public class AlipaySettleRuleReqDto { + + /** + * 【必填】 + * 默认结算类型 + * 可选值有bankCard/alipayAccount。bankCard表示结算到银行卡;alipayAccount表示结算到支付宝账号 + */ + @JSONField(name = "default_settle_type") + private String defaultSettleType; + + /** + * 【必填】 + * 默认结算目标 + * 当默认结算类型为bankCard时填写银行卡卡号,其值需在进件填写的结算银行卡范围内;当默认结算类型为alipayAccount时填写支付宝账号登录号,其值需在进件填写的结算支付宝账号范围内。 + */ + @JSONField(name = "default_settle_target") + private String defaultSettleTarget; +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipaySitesReqDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipaySitesReqDto.java new file mode 100644 index 000000000..4ce85c3c7 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/dto/entry/AlipaySitesReqDto.java @@ -0,0 +1,148 @@ +package com.czg.third.alipay.dto.entry; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @author yjjie + * @date 2026/1/4 11:31 + */ +@Data +public class AlipaySitesReqDto { + + /** + * 【必填】 + * 网站类型 + * 【枚举值】 + * 网站: 01 + * APP: 02 + * 服务窗: 03 + * 公众号: 04 + * 其他: 05 + * 支付宝小程序: 06 + * 手机网站/H5: 07 + */ + @JSONField(name = "site_type") + private String siteType; + + /** + * 【选填】 + * 站点地址 + * 当传入service,且包含jsapi支付时,sites的site_type=06, site_url必填 + */ + @JSONField(name = "site_url") + private String siteUrl; + + /** + * 【选填】 + * 站点名称 + * 当传入service,且包含jsapi支付、小程序支付时,sites的site_type=06, site_name必填 + */ + @JSONField(name = "site_name") + private String siteName; + + /** + * 【选填】 + * 截图照片 + * 当传入交易场景trade_scene,且当传入trade_scene=WAP、trade_scene=APP、trade_scene=PC时该参数必传 + */ + @JSONField(name = "screenshot_image") + private String screenshotImage; + + /** + * 【选填】 + * 小程序appId + * 当传入service,且包含jsapi支付时,sites的site_type=06, tiny_app_id必填。 + */ + @JSONField(name = "tiny_app_id") + private String tinyAppId; + + /** + * 【选填】 + * 测试账号 + */ + @JSONField(name = "account") + private String account; + + /** + * 【选填】 + * 测试账号密码 + */ + @JSONField(name = "password") + private String password; + + /** + * 【选填】 + * 上架状态 + * 【枚举值】 + * 已上线: ONLINE + * 已上线-内部: ONLINE_INNER + * 未上线: OFFLINE + */ + @JSONField(name = "status") + private String status; + + /** + * 【选填】 + * 授权函照片 + */ + @JSONField(name = "auth_letter_image") + private String authLetterImage; + + /** + * 【选填】 + * 备注说明 + */ + @JSONField(name = "remark") + private String remark; + + /** + * 【选填】 + * 备注说明图片 + */ + @JSONField(name = "remark_image") + private String remarkImage; + + /** + * 【选填】 + * 网站域名 + */ + @JSONField(name = "site_domain") + private String siteDomain; + + /** + * 【选填】 + * ICP备案主体信息服务名称 + */ + @JSONField(name = "icp_service_name") + private String icpServiceName; + + /** + * 【选填】 + * ICP备案/许可证号 + */ + @JSONField(name = "icp_no") + private String icpNo; + + /** + * 【选填】 + * ICP备案主体主办单位名称 + */ + @JSONField(name = "icp_org_name") + private String icpOrgName; + + /** + * 【选填】 + * 下载地址 + */ + @JSONField(name = "download") + private String download; + + /** + * 【选填】 + * 应用市场 + * 【示例值】豌豆荚 + */ + @JSONField(name = "market") + private String market; +} 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..0b320481e --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatEntryManager.java @@ -0,0 +1,418 @@ +package com.czg.third.wechat; + +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.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.WechatEntryMiniProgramReqDto; +import com.czg.third.wechat.dto.req.entry.business.sales.WechatEntrySalesInfoReqDto; +import com.czg.third.wechat.dto.req.entry.business.sales.WechatEntryStoreInfoReqDto; +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.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 merchantCode 自系统的商户编号 + * @return 进件状态 + */ + public static QueryStatusResp queryMerchantEntryStatus(WechatPayConfigDto configDto, String merchantCode) { + QueryStatusResp queryStatusResp = new QueryStatusResp(); + queryStatusResp.setPlatform(PayCst.Platform.WECHAT); + queryStatusResp.setMerchantCode(merchantCode); + + String resp = WechatReqUtils.getReq(configDto, "/v3/applyment4sub/applyment/business_code/" + merchantCode, Map.of()); + JSONObject object = JSONObject.parseObject(resp); + JSONObject data = object.getJSONObject("data"); + if (data == null) { + log.error("微信查询进件状态失败:{}", resp); + queryStatusResp.setFailReason(object.getString("message")); + queryStatusResp.setStatus(PayCst.EntryStatus.REJECTED); + return queryStatusResp; + } + log.info("微信查询进件状态:{}", resp); + WechatQueryStateResp stateResp = JSONObject.parseObject(data.toJSONString(), 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(""); + } + 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) { + WechatEntryReqDto entryReqDto = buildEntryParams(configDto, reqDto); + EntryThirdRespDto respDto = new EntryThirdRespDto() + .setPlatform(PayCst.Platform.WECHAT); + + log.info("微信进件参数:{}", JSONObject.toJSONString(entryReqDto)); + try { + String params = JSONObject.toJSONString(entryReqDto, JSONWriter.Feature.IgnoreEmpty); + String respBody = WechatReqUtils.postReq(configDto, "/v3/applyment4sub/applyment/", params); + JSONObject object = JSONObject.parseObject(respBody); + JSONObject data = object.getJSONObject("data"); + log.info("微信进件结果:{}", respBody); + if (data != null) { + respDto.setStatus(PayCst.EntryStatus.INIT); + respDto.setEntryId(data.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(), e); + 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 JSONObject 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); + } + + 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 (Exception e) { + log.error("微信上传图片报错,URL:{},错误信息:{}", url, e.getMessage(), e); + } + 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(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(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(businessLicenceInfo.getLicenceEndDate()); + subjectInfo.setBusinessLicenseInfo(licenseReqDto); + WechatEntryIdentityReqDto identityInfo = new WechatEntryIdentityReqDto(); + identityInfo.setIdHolderType(PayCst.ContactPersonType.LEGAL); + 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(List.of(storeInfo.getDoorPic().getWechatId())); + bizStoreInfo.setIndoorPic(List.of(storeInfo.getInsidePic().getWechatId(), storeInfo.getCashierDeskPic().getWechatId())); + salesInfo.setBizStoreInfo(bizStoreInfo); + WechatEntryMiniProgramReqDto miniProgramInfo = new WechatEntryMiniProgramReqDto(); + miniProgramInfo.setMiniProgramAppid("wxd88fffa983758a30"); + salesInfo.setMiniProgramInfo(miniProgramInfo); + 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.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); + +// queryBankBranchList(dto, "1000009561", "110000", offset, limit); +// queryBankBranchList(dto, "1000009561", "29", offset, limit); + +// 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..1b1d914ad --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java @@ -0,0 +1,11 @@ +package com.czg.third.wechat; + +/** + * @author yjjie + * @date 2025/12/26 09:15 + */ +public class WechatPayManager { + + public static void main(String[] args) { + } +} 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..32c95eec2 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java @@ -0,0 +1,112 @@ +package com.czg.third.wechat; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +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 lombok.extern.slf4j.Slf4j; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; +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("Content-Type", "application/json") + .header("Wechatpay-Serial", configDto.getPublicKeyId()) + .body(body); + + case "GET" -> HttpUtil.createGet(configDto.getDomain() + url) + .header("Authorization", authorization) + .header("Content-Type", "application/json") + .header("Wechatpay-Serial", configDto.getPublicKeyId()); + default -> throw new CzgException("不支持的请求方法"); + }; + HttpResponse response = request.execute(); + String s = response.body(); + log.info("微信支付请求:url = {}, method: {}, body: {}, resp: {}", 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 = {},\nsignature:{}", encryptStr, signature); + return signature; + } + + /** + * 获取随机串 + */ + private static String getNonceStr() { + return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); + } + + /** + * 获取时间戳 + */ + 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..78e544e8b --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java @@ -0,0 +1,97 @@ +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; + + /** + * 商户证书序列号 + */ + 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..948400189 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/WechatEntryBankAccountReqDto.java @@ -0,0 +1,75 @@ +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; + + /** + * 【必填】 + * 开户银行 + * 对私银行调用:查询支持个人业务的银行列表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..2347f01ea --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/req/entry/business/sales/WechatEntryStoreInfoReqDto.java @@ -0,0 +1,78 @@ +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; + + /** + * 【必填】 + * 线下场所内部照片 + * 1、请上传门店内部环境照片(可辨识经营内容)。若为停车场等无固定门头的经营场所,可上传停车场内部照片。具体参考【指引文档】; + * 2、请填写通过图片上传API预先上传图片生成好的MediaID。 + */ + @JSONField(name = "indoor_pic") + private List 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..b8b61121d --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/utils/UploadFileUtil.java @@ -0,0 +1,87 @@ +package com.czg.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +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"; + } + + /** + * 下载图片 + * @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/pom.xml b/cash-sdk/pom.xml index 55ee0e62b..b92f22d79 100644 --- a/cash-sdk/pom.xml +++ b/cash-sdk/pom.xml @@ -13,6 +13,7 @@ 第三方内容 czg-pay + aggregation-pay cash-sdk diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopInfoServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopInfoServiceImpl.java index f60642bb3..a873e938a 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopInfoServiceImpl.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopInfoServiceImpl.java @@ -324,6 +324,15 @@ public class ShopInfoServiceImpl extends ServiceImpl i return false; } + @Override + @CacheEvict(key = "#shopId") + public Boolean editEntry(Long shopId, String wechatMerchantId, String alipayMerchantId) { + ShopInfo shopInfo = new ShopInfo(); + shopInfo.setWechatMerchantId(wechatMerchantId); + shopInfo.setAlipayMerchantId(alipayMerchantId); + return update(shopInfo, new QueryWrapper().eq(ShopInfo::getId, shopId)); + } + @Override public ShopDetailDTO detail(Long id) throws CzgException { ShopInfo shopInfo = queryChain().eq(ShopInfo::getId, id == null ? StpKit.USER.getShopId() : id).one(); 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..28498f898 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/dto/AggregateMerchantVO.java @@ -0,0 +1,52 @@ +package com.czg.service.order.dto; + +import com.czg.dto.req.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author ww + */ +@Data +public class AggregateMerchantVO extends AggregateMerchantDto{ + + 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 wechatErrorMsg; + /** + * 微信进件签名地址 + */ + private String wechatSignUrl; + + /** + * {@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/mapper/ShopDirectMerchantMapper.java b/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopDirectMerchantMapper.java new file mode 100644 index 000000000..3b97bdb5a --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/mapper/ShopDirectMerchantMapper.java @@ -0,0 +1,14 @@ +package com.czg.service.order.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.czg.order.entity.ShopDirectMerchant; + +/** + * 商户进件 映射层。 + * + * @author ww + * @since 2026-01-07 + */ +public interface ShopDirectMerchantMapper extends BaseMapper { + +} 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..6519ae2af --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/ShopDirectMerchantService.java @@ -0,0 +1,19 @@ +package com.czg.service.order.service; +import com.czg.dto.req.AggregateMerchantDto; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.mybatisflex.core.service.IService; +import com.czg.order.entity.ShopDirectMerchant; + +/** + * 商户进件 服务层。 + * + * @author ww + * @since 2026-01-07 + */ +public interface ShopDirectMerchantService extends IService { + + + AggregateMerchantVO getEntry(Long shopId); + + boolean entryManager(AggregateMerchantDto reqDto); +} 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..cf146543f --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/ShopDirectMerchantServiceImpl.java @@ -0,0 +1,133 @@ +package com.czg.service.order.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.io.unit.DataSizeUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.EntryManager; +import com.czg.PayCst; +import com.czg.config.RabbitPublisher; +import com.czg.dto.req.*; +import com.czg.service.order.dto.AggregateMerchantVO; +import com.czg.utils.FunUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.czg.order.entity.ShopDirectMerchant; +import com.czg.service.order.service.ShopDirectMerchantService; +import com.czg.service.order.mapper.ShopDirectMerchantMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 商户进件 服务层实现。 + * + * @author ww + * @since 2026-01-07 + */ +@Service +public class ShopDirectMerchantServiceImpl extends ServiceImpl implements ShopDirectMerchantService { + + @Resource + private RabbitPublisher rabbitPublisher; + // 全局原子计数器,按天重置(避免数值过大) + private static final AtomicLong COUNTER = new AtomicLong(0); + // 记录上一次的日期,用于重置计数器 + private static String LAST_DATE = DateUtil.format(new Date(), "yyyyMMdd"); + + @Override + public AggregateMerchantVO getEntry(Long shopId) { + ShopDirectMerchant merchant = getById(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()); + + 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())); + merchant.setWechatStatus(PayCst.EntryStatus.WAIT); + merchant.setAlipayStatus(PayCst.EntryStatus.WAIT); + if (isSave) { + result = save(merchant); + } else { + result = updateById(merchant); + } + //发送进件队列消息 + FunUtils.transactionSafeRun(() -> rabbitPublisher.sendEntryManagerMsg(reqDto.getShopId().toString())); + 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()); + + // 解析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.setCreateTime(entity.getCreateTime()); + vo.setUpdateTime(entity.getUpdateTime()); + vo.setWechatStatus(entity.getWechatStatus()); + vo.setWechatErrorMsg(entity.getWechatErrorMsg()); + vo.setWechatSignUrl(entity.getWechatSignUrl()); + vo.setAlipayStatus(entity.getAlipayStatus()); + vo.setAlipayErrorMsg(entity.getAlipayErrorMsg()); + vo.setAlipaySignUrl(entity.getAlipaySignUrl()); + + return vo; + } +} diff --git a/cash-service/order-service/src/main/resources/mapper/ShopDirectMerchantMapper.xml b/cash-service/order-service/src/main/resources/mapper/ShopDirectMerchantMapper.xml new file mode 100644 index 000000000..ba52848f4 --- /dev/null +++ b/cash-service/order-service/src/main/resources/mapper/ShopDirectMerchantMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/cash-service/pay-service/pom.xml b/cash-service/pay-service/pom.xml index 92db97998..522c91c55 100644 --- a/cash-service/pay-service/pom.xml +++ b/cash-service/pay-service/pom.xml @@ -26,6 +26,11 @@ czg-pay ${project.version} + + com.czg + aggregation-pay + ${project.version} + 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 @@ + + + + +