diff --git a/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java b/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java index 4bb731155..7f6bd4fab 100644 --- a/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java +++ b/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java @@ -2,28 +2,23 @@ package com.czg.controller.admin; import com.czg.annotation.SaAdminCheckPermission; import com.czg.annotation.SaCheckMainShop; -import com.czg.constant.TableValueConstant; import com.czg.market.dto.MkDistributionConfigDTO; -import com.czg.market.dto.MkEnableConfigDTO; -import com.czg.market.dto.MkRedemptionConfigDTO; +import com.czg.market.service.MkDistributionAmountFlowService; import com.czg.market.service.MkDistributionConfigService; -import com.czg.market.service.MkEnableConfigService; -import com.czg.market.service.MkRedemptionConfigService; -import com.czg.market.vo.MkDistributionConfigVO; -import com.czg.market.vo.MkEnableConfigVO; -import com.czg.market.vo.MkRedemptionCodeVO; -import com.czg.market.vo.MkRedemptionConfigVO; +import com.czg.market.vo.*; +import com.czg.order.dto.MkDistributionPayDTO; import com.czg.resp.CzgResult; import com.czg.sa.StpKit; +import com.czg.utils.AssertUtil; import com.czg.validator.group.UpdateGroup; -import com.mybatisflex.core.paginate.Page; import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.groups.Default; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.math.BigDecimal; +import java.util.Map; + /** * * 分销相关 @@ -34,6 +29,8 @@ import org.springframework.web.bind.annotation.*; public class DistributionController { @Resource private MkDistributionConfigService configService; + @Resource + private MkDistributionAmountFlowService distributionAmountFlowService; /** * 配置信息详情 @@ -41,7 +38,7 @@ public class DistributionController { @SaAdminCheckPermission(value = "distribution:detail", name = "分销配置") @GetMapping public CzgResult detail() { - return CzgResult.success(configService.detail(StpKit.USER.getMainShopId())); + return CzgResult.success(configService.detail(StpKit.USER.getShopId())); } /** @@ -57,5 +54,29 @@ public class DistributionController { } + /** + * 现金充值 + * @param payParam 充值信息 + * @return 是否成功 + */ + @PostMapping("/cashPay") + public CzgResult cashPayOrder(@Validated @RequestBody MkDistributionPayDTO payParam) { + AssertUtil.isNull(payParam.getShopId(), "店铺id不能为空"); + AssertUtil.isNull(payParam.getAmount(), "充值金额不能为空"); + AssertUtil.isTrue(payParam.getAmount().compareTo(BigDecimal.ZERO) == 0, "金额不为0"); + return CzgResult.success(configService.cashPayOrder(StpKit.USER.getLoginIdAsLong(), payParam)); + } + + /** + * 金额记录 + * @param type manual_recharge充值 self_recharge自助充值 refund退款 manual_sub手动扣减 sub统扣减 + * @param key 搜索 + */ + @GetMapping("/flow") + public CzgResult> flow(@RequestParam(required = false) String type, @RequestParam(required = false) String key) { + return CzgResult.success(distributionAmountFlowService.pageInfo(StpKit.USER.getShopId(), type, key)); + } + + } diff --git a/cash-api/market-server/src/main/java/com/czg/controller/user/UDistributionController.java b/cash-api/market-server/src/main/java/com/czg/controller/user/UDistributionController.java new file mode 100644 index 000000000..ca3cdb726 --- /dev/null +++ b/cash-api/market-server/src/main/java/com/czg/controller/user/UDistributionController.java @@ -0,0 +1,33 @@ +package com.czg.controller.user; + +import com.czg.order.dto.MkDistributionPayDTO; +import com.czg.market.service.MkDistributionConfigService; +import com.czg.resp.CzgResult; +import com.czg.sa.StpKit; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * + * 分销相关 + * @author Administrator + */ +@RestController +@RequestMapping("/user/distribution") +public class UDistributionController { + @Resource + private MkDistributionConfigService configService; + + /** + * 分销员购买 + */ + @PostMapping("/pay") + public CzgResult> pay(@Validated @RequestBody MkDistributionPayDTO payDTO) { + return CzgResult.success(configService.pay(StpKit.USER.getLoginIdAsLong(), payDTO)); + } + + +} diff --git a/cash-api/order-server/src/main/java/com/czg/controller/DistributionPayController.java b/cash-api/order-server/src/main/java/com/czg/controller/DistributionPayController.java new file mode 100644 index 000000000..45adf4f27 --- /dev/null +++ b/cash-api/order-server/src/main/java/com/czg/controller/DistributionPayController.java @@ -0,0 +1,72 @@ +package com.czg.controller; + +import com.czg.annotation.Debounce; +import com.czg.order.dto.MkDistributionPayDTO; +import com.czg.resp.CzgResult; +import com.czg.service.order.service.DistributionPayService; +import com.czg.system.service.SysParamsService; +import com.czg.utils.ServletUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 支付 + * + * @author ww + * @description + */ +@RestController +@RequestMapping("/pay/distribution") +public class DistributionPayController { + @Resource + private DistributionPayService payService; + @DubboReference + private SysParamsService paramsService; + + + @PostMapping("/cashPay") + @Debounce(value = "#payParam.checkOrderPay.orderId") + public CzgResult cashPayOrder(@RequestHeader Long shopId, @Validated @RequestBody MkDistributionPayDTO payParam) { + payParam.setShopId(shopId); + return payService.cashPayOrder(payParam); + } + + /** + * 小程序支付 + * payType 必填 支付方式,aliPay 支付宝,wechatPay 微信 + * openId 必填 + */ + @PostMapping("/ltPayOrder") + @Debounce(value = "#payParam.checkOrderPay.orderId") + public CzgResult> ltPayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody MkDistributionPayDTO payParam) { + payParam.setShopId(shopId); + return payService.ltPayOrder(ServletUtil.getClientIP(request), payParam); + } + + /** + * 正扫 + */ + @PostMapping("/scanPay") + @Debounce(value = "#payParam.checkOrderPay.orderId") + public CzgResult> scanPayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody MkDistributionPayDTO payParam) { + payParam.setShopId(shopId); + return payService.scanPayOrder(ServletUtil.getClientIP(request), payParam); + } + + /** + * 反扫 + * authCode 必填 扫描码 + */ + @PostMapping("/microPay") + @Debounce(value = "#payParam.checkOrderPay.orderId") + public CzgResult> microPayOrder(@RequestHeader Long shopId, @Validated @RequestBody MkDistributionPayDTO payParam) { + payParam.setShopId(shopId); + return payService.microPayOrder(payParam); + } + +} diff --git a/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java b/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java index 26b9b2ede..8e18df1fc 100644 --- a/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java @@ -2,6 +2,7 @@ package com.czg.controller; import cn.hutool.core.date.DateUtil; import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.RandomUtil; import com.alibaba.fastjson2.JSONObject; import com.czg.CzgPayUtils; import com.czg.entity.CzgBaseRespParams; @@ -14,7 +15,9 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.math.BigDecimal; import java.util.Date; +import java.util.Map; /** * 回调 @@ -37,6 +40,13 @@ public class NotifyController { private ShopTableOrderStatisticService shopTableOrderStatisticService; + @GetMapping("testOpen") + public Map test1(String code) throws Exception { +// return WxService.v3Pay("oeQYq5LzW-kSxJL9TR4s_UmOmNLE", new BigDecimal("0.01"), "测试", "testZs" + RandomUtil.randomNumbers(20), "test"); + return null; + } + + @RequestMapping("/payCallBack") public String notifyCallBack(@RequestBody CzgBaseRespParams respParams) { JSONObject czg = CzgPayUtils.getCzg(respParams); 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 45675bb4c..dbb2032e2 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 @@ -330,4 +330,9 @@ public class ShopInfo implements Serializable { @Column(ignore = true) private Integer tableClearTime; + /** + * 运营端余额 + */ + private BigDecimal amount; + } 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 885b6f681..6b174de46 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 @@ -6,6 +6,7 @@ import com.czg.account.entity.ShopInfo; import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.service.IService; +import java.math.BigDecimal; import java.util.List; /** @@ -33,4 +34,6 @@ public interface ShopInfoService extends IService { Long getMainIdByShopId(Long shopId); List getByMainIdOrList(Long mainShopId, List shopIdList, String shopName); + + BigDecimal updateAmount(Long id, BigDecimal amount); } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/dto/MkDistributionAmountFlowDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/market/dto/MkDistributionAmountFlowDTO.java new file mode 100644 index 000000000..acd965fe9 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/dto/MkDistributionAmountFlowDTO.java @@ -0,0 +1,77 @@ + +package com.czg.market.dto; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.experimental.Accessors; +import java.io.Serial; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分销运营余额扣减记录 实体类。 + * + * @author ww + * @since 2025-10-27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class MkDistributionAmountFlowDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + /** + * recharge充值 refund退款 sub扣减 + */ + private String type; + + /** + * 主店id + */ + private Long mainShopId; + + /** + * 店铺id + */ + private Long shopId; + + /** + * 变动后金额 + */ + private BigDecimal amount; + + /** + * 变动金额 + */ + private BigDecimal changeAmount; + + /** + * 关联id + */ + private Long sourceId; + + /** + * 备注 + */ + private String remark; + + /** + * 操作人账号 + */ + private String opAccount; + + /** + * 创建时间 + */ + @JSONField(format = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionAmountFlow.java b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionAmountFlow.java new file mode 100644 index 000000000..10ffa8afb --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionAmountFlow.java @@ -0,0 +1,85 @@ +package com.czg.market.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.BigDecimal; +import java.time.LocalDateTime; + +import java.io.Serial; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 分销运营余额扣减记录 实体类。 + * + * @author ww + * @since 2025-10-27 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("mk_distribution_amount_flow") +@Accessors(chain = true) +public class MkDistributionAmountFlow implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Auto) + private Long id; + + /** + * manual_recharge充值 self_recharge自助充值 refund退款 manual_sub手动扣减 sub统扣减 + */ + private String type; + + /** + * 主店id + */ + private Long mainShopId; + + /** + * 店铺id + */ + private Long shopId; + + /** + * 变动后金额 + */ + private BigDecimal amount; + + /** + * 变动金额 + */ + private BigDecimal changeAmount; + + /** + * 关联id + */ + private Long sourceId; + + /** + * 备注 + */ + private String remark; + + /** + * 操作人账号 + */ + private String opAccount; + + /** + * 创建时间 + */ + @Column(onInsertValue = "now()") + private LocalDateTime createTime; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionConfig.java b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionConfig.java index 6cb0e49fa..af730357a 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionConfig.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionConfig.java @@ -97,5 +97,6 @@ public class MkDistributionConfig implements Serializable { * 主店id */ private Long mainShopId; + private Long shopId; } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionLevelConfig.java b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionLevelConfig.java index 2acc8eb85..e6a59a886 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionLevelConfig.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionLevelConfig.java @@ -39,11 +39,7 @@ public class MkDistributionLevelConfig implements Serializable { */ private Long distributionConfigId; - /** - * 主店id - */ - private Long mainShopId; - + private Long shopId; /** * 名称 */ diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionAmountFlowService.java b/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionAmountFlowService.java new file mode 100644 index 000000000..9ae3d480c --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionAmountFlowService.java @@ -0,0 +1,17 @@ +package com.czg.market.service; + +import com.mybatisflex.core.service.IService; +import com.czg.market.entity.MkDistributionAmountFlow; + +import java.util.Map; + +/** + * 分销运营余额扣减记录 服务层。 + * + * @author ww + * @since 2025-10-27 + */ +public interface MkDistributionAmountFlowService extends IService { + + Map pageInfo(Long shopId, String type, String key); +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionConfigService.java b/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionConfigService.java index 05043261e..4628265a5 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionConfigService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionConfigService.java @@ -1,11 +1,13 @@ package com.czg.market.service; import com.czg.market.dto.MkDistributionConfigDTO; +import com.czg.order.dto.MkDistributionPayDTO; import com.czg.market.vo.MkDistributionConfigVO; -import com.czg.market.vo.MkRedemptionConfigVO; import com.mybatisflex.core.service.IService; import com.czg.market.entity.MkDistributionConfig; +import java.util.Map; + /** * 分销配置 服务层。 * @@ -16,4 +18,8 @@ public interface MkDistributionConfigService extends IService pay(long userId, MkDistributionPayDTO payDTO); + + Boolean cashPayOrder(long adminId, MkDistributionPayDTO payParam); } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/vo/MkDistributionAmountFlowVO.java b/cash-common/cash-common-service/src/main/java/com/czg/market/vo/MkDistributionAmountFlowVO.java new file mode 100644 index 000000000..e012499b0 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/vo/MkDistributionAmountFlowVO.java @@ -0,0 +1,18 @@ +package com.czg.market.vo; + +import com.czg.market.entity.MkDistributionAmountFlow; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * @author Administrator + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +public class MkDistributionAmountFlowVO extends MkDistributionAmountFlow implements Serializable { + private String orderNo; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java new file mode 100644 index 000000000..5a263fd91 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/order/dto/MkDistributionPayDTO.java @@ -0,0 +1,32 @@ + +package com.czg.order.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 分销配置 实体类。 + * + * @author ww + * @since 2025-10-25 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class MkDistributionPayDTO implements Serializable { + private Long shopId; + private String platformType; + private Long userId; + private String payType; + private String returnUrl; + private String buyerRemark; + private BigDecimal amount; + private String remark; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/service/WxService.java b/cash-common/cash-common-service/src/main/java/com/czg/system/service/WxService.java new file mode 100644 index 000000000..1282f03c0 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/system/service/WxService.java @@ -0,0 +1,16 @@ +package com.czg.system.service; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * 微信支付相关 + * @author Administrator + */ +public interface WxService { + String getPhone(String code); + + String getOpenId(String code); + + Map v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type); +} diff --git a/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java b/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java index aef6f9cbc..a7755d2e9 100644 --- a/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java +++ b/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java @@ -23,6 +23,41 @@ public interface TableValueConstant { } } + + + interface DistributionConfig { + @Getter + enum OpenType { + PAY("PAY", "支付购买"), + COST("COST", "消费增积分"); + private final String code; + private final String msg; + + OpenType(String code, String msg) { + this.code = code; + this.msg = msg; + } + } + + } + + interface DistributionAmountFlow { + @Getter + enum Type { + MANUAL_RECHARGE("manual_recharge", "手动充值"), + SUB("sub", "系统扣减"), + MANUAL_SUB("manual_sub", "手动扣减"), + SELF_RECHARGE("self_recharge", "自助充值"); + private final String code; + private final String msg; + + Type(String code, String msg) { + this.code = code; + this.msg = msg; + } + } + + } interface EnableConfig { @Getter enum Type { 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 2a418c45c..f24fd757c 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 @@ -41,6 +41,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -445,4 +446,17 @@ public class ShopInfoServiceImpl extends ServiceImpl i return list(queryWrapper); } + + @Override + public BigDecimal updateAmount(Long id, BigDecimal amount) { + ShopInfo shopInfo = getShopInfo(id); + if (shopInfo.getAmount() == null) { + shopInfo.setAmount(BigDecimal.ZERO); + } + shopInfo.setAmount(shopInfo.getAmount().add(amount)); + updateById(shopInfo); + return shopInfo.getAmount(); + } + + } diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionAmountFlowMapper.java b/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionAmountFlowMapper.java new file mode 100644 index 000000000..06540d99d --- /dev/null +++ b/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionAmountFlowMapper.java @@ -0,0 +1,14 @@ +package com.czg.service.market.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.czg.market.entity.MkDistributionAmountFlow; + +/** + * 分销运营余额扣减记录 映射层。 + * + * @author ww + * @since 2025-10-27 + */ +public interface MkDistributionAmountFlowMapper extends BaseMapper { + +} diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionAmountFlowServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionAmountFlowServiceImpl.java new file mode 100644 index 000000000..a754e5b28 --- /dev/null +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionAmountFlowServiceImpl.java @@ -0,0 +1,78 @@ +package com.czg.service.market.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.czg.account.entity.ShopUser; +import com.czg.account.entity.UserInfo; +import com.czg.constant.TableValueConstant; +import com.czg.market.vo.MkDistributionAmountFlowVO; +import com.czg.order.entity.OrderInfo; +import com.czg.utils.MyQueryWrapper; +import com.czg.utils.PageUtil; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.czg.market.entity.MkDistributionAmountFlow; +import com.czg.market.service.MkDistributionAmountFlowService; +import com.czg.service.market.mapper.MkDistributionAmountFlowMapper; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * 分销运营余额扣减记录 服务层实现。 + * + * @author ww + * @since 2025-10-27 + */ +@Service +public class MkDistributionAmountFlowServiceImpl extends ServiceImpl implements MkDistributionAmountFlowService{ + + @Override + public Map pageInfo(Long shopId, String type, String key) { + QueryWrapper queryWrapper = new MyQueryWrapper() + .selectAll(MkDistributionAmountFlow.class) + .leftJoin(OrderInfo.class).on(OrderInfo::getId, MkDistributionAmountFlow::getSourceId) + .leftJoin(ShopUser.class).on(ShopUser::getId, OrderInfo::getUserId) + .eq(MkDistributionAmountFlow::getShopId, shopId) + .select(OrderInfo::getOrderNo); + if (StrUtil.isNotBlank(type)) { + queryWrapper.eq(MkDistributionAmountFlow::getType, type); + } + if (StrUtil.isNotBlank(key)) { + queryWrapper.and(and -> { + and.or(or -> { + or.like(OrderInfo::getOrderNo, key); + }); + and.or(or -> { + or.like(ShopUser::getNickName, key); + }); + and.or(or -> { + or.like(ShopUser::getPhone, key); + }); + }); + } + + Page pageInfo = pageAs(PageUtil.buildPage(), queryWrapper, MkDistributionAmountFlowVO.class); + Map map = BeanUtil.beanToMap(pageInfo); + map.put("totalRecharge", getOne(new QueryWrapper().eq(MkDistributionAmountFlow::getShopId, shopId) + .in(MkDistributionAmountFlow::getType, TableValueConstant.DistributionAmountFlow.Type.MANUAL_RECHARGE.getCode(), TableValueConstant.DistributionAmountFlow.Type.SELF_RECHARGE.getCode()) + .select("sum(change_amount)"))); + map.put("totalSub", getOne(new QueryWrapper().eq(MkDistributionAmountFlow::getShopId, shopId) + .in(MkDistributionAmountFlow::getType, TableValueConstant.DistributionAmountFlow.Type.SUB.getCode(), TableValueConstant.DistributionAmountFlow.Type.MANUAL_SUB.getCode()) + .select("sum(change_amount)"))); + return map; + } + + public static void main(String[] args) { + QueryWrapper queryWrapper = new MyQueryWrapper() + .selectAll(MkDistributionAmountFlow.class) + .leftJoin(OrderInfo.class).on(OrderInfo::getId, MkDistributionAmountFlow::getSourceId) + .leftJoin(ShopUser.class).on(ShopUser::getId, OrderInfo::getUserId) + .select(OrderInfo::getOrderNo); + queryWrapper.and(and -> { + + }); + System.out.println(queryWrapper.toSQL()); + } +} diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionConfigServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionConfigServiceImpl.java index 4a719824f..20c2ad00d 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionConfigServiceImpl.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionConfigServiceImpl.java @@ -1,18 +1,38 @@ package com.czg.service.market.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; +import com.czg.account.entity.ShopInfo; +import com.czg.account.service.ShopInfoService; +import com.czg.constant.TableValueConstant; +import com.czg.exception.CzgException; import com.czg.market.dto.MkDistributionConfigDTO; +import com.czg.market.entity.MkDistributionAmountFlow; +import com.czg.market.service.MkDistributionAmountFlowService; +import com.czg.order.dto.MkDistributionPayDTO; import com.czg.market.entity.MkDistributionLevelConfig; import com.czg.market.service.MkDistributionLevelConfigService; import com.czg.market.vo.MkDistributionConfigVO; +import com.czg.order.entity.OrderPayment; +import com.czg.order.service.OrderPaymentService; +import com.czg.sa.StpKit; +import com.czg.system.service.WxService; +import com.czg.utils.AssertUtil; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.spring.service.impl.ServiceImpl; import com.czg.market.entity.MkDistributionConfig; import com.czg.market.service.MkDistributionConfigService; import com.czg.service.market.mapper.MkDistributionConfigMapper; import jakarta.annotation.Resource; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.util.Map; + /** * 分销配置 服务层实现。 * @@ -20,18 +40,30 @@ import org.springframework.stereotype.Service; * @since 2025-10-25 */ @Service +@CacheConfig(cacheNames = "distributionConfig") public class MkDistributionConfigServiceImpl extends ServiceImpl implements MkDistributionConfigService{ @Resource private MkDistributionLevelConfigService levelConfigService; + @Resource + private MkDistributionAmountFlowService distributionAmountFlowService; + + @DubboReference + private OrderPaymentService orderPaymentService; + @DubboReference + private ShopInfoService shopInfoService; + @DubboReference + private WxService wxService; @Override - public MkDistributionConfigVO detail(Long mainShopId) { - MkDistributionConfig config = getOne(new QueryWrapper().eq(MkDistributionConfig::getMainShopId, mainShopId)); + @Cacheable(key = "#shopId") + public MkDistributionConfigVO detail(Long shopId) { + Long mainIdByShopId = shopInfoService.getMainIdByShopId(shopId); + MkDistributionConfig config = getOne(new QueryWrapper().eq(MkDistributionConfig::getShopId, shopId)); if (config == null) { - config = new MkDistributionConfig().setMainShopId(mainShopId); + config = new MkDistributionConfig().setMainShopId(shopId).setMainShopId(mainIdByShopId); save(config); - config = getOne(new QueryWrapper().eq(MkDistributionConfig::getMainShopId, mainShopId)); + config = getOne(new QueryWrapper().eq(MkDistributionConfig::getShopId, shopId)); } MkDistributionConfigVO configVO = BeanUtil.copyProperties(config, MkDistributionConfigVO.class); @@ -40,14 +72,46 @@ public class MkDistributionConfigServiceImpl extends ServiceImpl pay(long userId, MkDistributionPayDTO payDTO) { + MkDistributionConfigVO detail = detail(payDTO.getShopId()); + AssertUtil.isTrue(detail.getIsEnable() != 1, "分销未开启"); + if (!TableValueConstant.DistributionConfig.OpenType.PAY.getCode().equals(detail.getOpenType())) { + throw new CzgException("当前未开启购买分销配置"); + } + + OrderPayment orderPayment = new OrderPayment().setShopId(payDTO.getShopId()).setSourceId(userId) + .setPayType("distribution").setOrderNo(payDTO.getPlatformType() + IdUtil.getSnowflakeNextId()).setAmount(detail.getPayAmount()); + orderPaymentService.save(orderPayment); + + return Map.of(); + } + + @Override + public Boolean cashPayOrder(long adminId, MkDistributionPayDTO payParam) { + ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId()); + AssertUtil.isNull(shopInfo, "店铺不存在"); + BigDecimal amount = shopInfoService.updateAmount(shopInfo.getId(), payParam.getAmount()); + Long mainShopId = shopInfoService.getMainIdByShopId(shopInfo.getId()); + shopInfoService.updateAmount(shopInfo.getId(), payParam.getAmount()); + + distributionAmountFlowService.save(new MkDistributionAmountFlow() + .setType(payParam.getAmount().compareTo(BigDecimal.ZERO) < 0 ? TableValueConstant.DistributionAmountFlow.Type.MANUAL_SUB.getCode() : + TableValueConstant.DistributionAmountFlow.Type.MANUAL_RECHARGE.getCode()) + .setMainShopId(mainShopId).setShopId(shopInfo.getId()).setAmount(amount).setChangeAmount(payParam.getAmount()) + .setRemark(payParam.getRemark()).setOpAccount(StpKit.USER.getAccount())); + return null; + } } diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionLevelConfigServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionLevelConfigServiceImpl.java index ba6db96e0..26003d46e 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionLevelConfigServiceImpl.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionLevelConfigServiceImpl.java @@ -21,7 +21,7 @@ import java.util.List; public class MkDistributionLevelConfigServiceImpl extends ServiceImpl implements MkDistributionLevelConfigService{ @Override - public void updateByShopId(List levelConfigList, Long mainShopId, Long id) { + public void updateByShopId(List levelConfigList, Long shopId, Long id) { if (levelConfigList.isEmpty()) { remove(new QueryWrapper().eq(MkDistributionLevelConfig::getDistributionConfigId, id)); return; @@ -30,7 +30,7 @@ public class MkDistributionLevelConfigServiceImpl extends ServiceImpl { MkDistributionLevelConfig config = BeanUtil.copyProperties(item, MkDistributionLevelConfig.class); config.setDistributionConfigId(id); - config.setMainShopId(mainShopId); + config.setShopId(shopId); saveOrUpdate(config); }); } diff --git a/cash-service/market-service/src/main/resources/mapper/MkDistributionAmountFlowMapper.xml b/cash-service/market-service/src/main/resources/mapper/MkDistributionAmountFlowMapper.xml new file mode 100644 index 000000000..c295e4f2f --- /dev/null +++ b/cash-service/market-service/src/main/resources/mapper/MkDistributionAmountFlowMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java new file mode 100644 index 000000000..4e4f26849 --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/DistributionPayService.java @@ -0,0 +1,38 @@ +package com.czg.service.order.service; + +import com.czg.order.dto.MkDistributionPayDTO; +import com.czg.resp.CzgResult; + +import java.util.Map; + + +/** + * 支付 + * + * @author ww + */ +public interface DistributionPayService { + /** + * 现金支付 + */ + CzgResult cashPayOrder(MkDistributionPayDTO payParam); + + + /** + * 小程序支付 + */ + CzgResult> ltPayOrder(String clintIp, MkDistributionPayDTO payParam); + + /** + * PC扫码支付 + */ + CzgResult> scanPayOrder(String clintIp, MkDistributionPayDTO payParam); + + /** + * 反扫 + */ + CzgResult> microPayOrder(MkDistributionPayDTO payParam); + + + +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java new file mode 100644 index 000000000..fba0944cb --- /dev/null +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java @@ -0,0 +1,103 @@ +package com.czg.service.order.service.impl; + +import cn.hutool.core.util.IdUtil; +import com.czg.account.entity.ShopUser; +import com.czg.account.entity.UserInfo; +import com.czg.account.service.ShopInfoService; +import com.czg.account.service.ShopUserService; +import com.czg.account.service.UserInfoService; +import com.czg.constant.TableValueConstant; +import com.czg.entity.req.CzgLtPayReq; +import com.czg.exception.CzgException; +import com.czg.market.service.MkDistributionConfigService; +import com.czg.market.vo.MkDistributionConfigVO; +import com.czg.order.dto.MkDistributionPayDTO; +import com.czg.order.entity.OrderInfo; +import com.czg.order.entity.OrderPayment; +import com.czg.order.enums.PayEnums; +import com.czg.order.service.OrderPaymentService; +import com.czg.resp.CzgResult; +import com.czg.service.order.service.DistributionPayService; +import com.czg.service.order.service.PayService; +import com.czg.system.service.WxService; +import com.czg.utils.AssertUtil; +import jakarta.annotation.Resource; +import lombok.Data; +import lombok.experimental.Accessors; +import org.apache.dubbo.config.annotation.DubboReference; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * @author Administrator + */ +public class DistributionPayServiceImpl implements DistributionPayService { + private final BigDecimal MONEY_RATE = new BigDecimal("100"); + + @Resource + private OrderPaymentService orderPaymentService; + @Resource + private MkDistributionConfigService configService; + @Resource + private WxService wxService; + @Resource + private PayServiceImpl payService; + + @DubboReference + private ShopInfoService shopInfoService; + @DubboReference + private ShopUserService shopUserService; + @DubboReference + private UserInfoService userInfoService; + + + @Accessors(chain = true) + @Data + private static class InitInfo { + public OrderPayment payment; + public MkDistributionConfigVO config; + public ShopUser shopUser; + private String openId; + } + + private InitInfo initPayment(Long userId, MkDistributionPayDTO payParam) { + Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId()); + MkDistributionConfigVO detail = configService.detail(payParam.getShopId()); + AssertUtil.isTrue(detail.getIsEnable() != 1, "分销未开启"); + if (!TableValueConstant.DistributionConfig.OpenType.PAY.getCode().equals(detail.getOpenType())) { + throw new CzgException("当前未开启购买分销配置"); + } + + OrderPayment orderPayment = new OrderPayment().setShopId(mainShopId).setSourceId(userId) + .setPayType("distribution").setOrderNo(payParam.getPlatformType() + IdUtil.getSnowflakeNextId()).setAmount(detail.getPayAmount()); + orderPaymentService.save(orderPayment); + + ShopUser shopUserInfo = shopUserService.getShopUserInfo(payParam.getShopId(), userId); + UserInfo userInfo = userInfoService.getById(userId); + return new InitInfo().setConfig(detail).setPayment(orderPayment).setShopUser(shopUserInfo) + .setOpenId("aliPay".equals(payParam.getPayType()) ? userInfo.getAlipayOpenId() : userInfo.getWechatOpenId()); + } + + @Override + public CzgResult cashPayOrder(MkDistributionPayDTO payParam) { + return null; + } + + @Override + public CzgResult> ltPayOrder(String clintIp, MkDistributionPayDTO payParam) { + InitInfo initInfo = initPayment(payParam.getUserId(), payParam); + return payService.ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(initInfo.payment.getOrderNo(), initInfo.payment.getAmount().multiply(MONEY_RATE).longValue(), + payParam.getPayType(), "分销员开通", initInfo.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), "")); + } + + @Override + public CzgResult> scanPayOrder(String clintIp, MkDistributionPayDTO payParam) { + return null; + } + + @Override + public CzgResult> microPayOrder(MkDistributionPayDTO payParam) { + return null; + } +} diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java index ea550f361..6c4276ab1 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java @@ -862,7 +862,7 @@ public class PayServiceImpl implements PayService { return execPayResult(jsPayRespCzgResult); } - private CzgResult> ltPay(@NonNull Long shopId, String payType, CzgLtPayReq bizData) { + public CzgResult> ltPay(@NonNull Long shopId, String payType, CzgLtPayReq bizData) { ShopMerchant shopMerchant = getMerchant(shopId); bizData.setSubAppid("aliPay".equals(payType) ? shopMerchant.getAlipaySmallAppid() : shopMerchant.getWechatSmallAppid()); AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + ("aliPay".equals(payType) ? "支付宝" : "微信") + "小程序"); diff --git a/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java b/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java new file mode 100644 index 000000000..2251d5f45 --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java @@ -0,0 +1,633 @@ +package com.czg.service.Impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import com.czg.exception.CzgException; +import com.czg.service.RedisService; +import com.czg.system.service.SysParamsService; +import com.czg.system.service.WxService; +import com.ijpay.core.IJPayHttpResponse; +import com.ijpay.core.enums.RequestMethodEnum; +import com.ijpay.core.enums.SignType; +import com.ijpay.core.kit.AesUtil; +import com.ijpay.core.kit.PayKit; +import com.ijpay.core.kit.RsaKit; +import com.ijpay.core.kit.WxPayKit; +import com.ijpay.wxpay.WxPayApi; +import com.ijpay.wxpay.enums.WxDomainEnum; +import com.ijpay.wxpay.enums.v3.BasePayApiEnum; +import com.ijpay.wxpay.enums.v3.OtherApiEnum; +import com.ijpay.wxpay.enums.v3.ProfitSharingApiEnum; +import com.ijpay.wxpay.model.ReceiverModel; +import com.ijpay.wxpay.model.v3.*; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.dubbo.config.annotation.DubboService; + +import java.io.ByteArrayInputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * 微信支付service + * @author Administrator + */ +@DubboService +@Slf4j +public class WxServiceImpl implements WxService { + + @Resource + private RedisService autoRedisService; + @DubboReference + private SysParamsService paramsService; + private static RedisService redisService; + + @PostConstruct + public void init() { + // 小程序id + appId = paramsService.getSysParamValue("shop_wx_appid"); + // 小程序secrete + appSecret = paramsService.getSysParamValue("shop_wx_secrete"); + certPath = ""; + // 微信支付公钥 + pubKey = paramsService.getSysParamValue("wx_pub_key"); + // api支付证书私钥 + apiCertKey = paramsService.getSysParamValue("wx_apiclient_key"); + // api支付证书公钥 + apiCert = paramsService.getSysParamValue("wx_apiclient_cert"); + try { + privateKey = RsaKit.loadPrivateKey(apiCertKey); + } catch (Exception e) { + log.warn("微信加载api证书失败"); + } + // 平台证书 + platformCertPath = ""; + // 平台证书编号 + platformCertNo = ""; + // 商户号 + mchId = paramsService.getSysParamValue("wx_mch_id"); + // v3密钥 + apiV3Key = paramsService.getSysParamValue("wx_v3_key"); + apiV2Key = ""; + // 回调地址 + notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay"; + refundNotifyUrl = ""; + transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer"; + redisService = this.autoRedisService; + } + + + // static copies for use in static methods + private static String appId; + private static String appSecret; + private static String certPath; + private static String pubKey; + private static String apiCertKey; + private static PrivateKey privateKey; + private static String apiCert; + private static String platformCertPath; + private static String platformCertNo; + private static String mchId; + private static String apiV3Key; + private static String apiV2Key; + private static String notifyUrl; + private static String refundNotifyUrl; + private static String transferNotifyUrl; + + private static String getAccessToken(boolean refresh) { + Object token = redisService.get("access_token"); + if (!refresh && token instanceof String) { + return (String) token; + } + + String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token", + Map.of("grant_type", "client_credential", "appid", appId, "secret", appSecret) + ); + + log.info("获取access_token响应: {}", response); + JSONObject jsonObject = JSONObject.parseObject(response); + String accessToken = jsonObject.getString("access_token"); + if (accessToken == null) { + throw new RuntimeException("获取access_token失败"); + } + Long expiresIn = jsonObject.getLong("expires_in"); + if (expiresIn == null) { + expiresIn = 7200L; + } + redisService.set("access_token", accessToken, expiresIn - 200); + return accessToken; + } + + @Override + public String getPhone(String code) { + String requestBody = JSONObject.toJSONString(Map.of("code", code)); + String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody); + log.info("获取手机号响应: {}", response); + JSONObject jsonObject = JSONObject.parseObject(response); + Integer errCode = jsonObject.getInteger("errcode"); + + if (Integer.valueOf(0).equals(errCode)) { + return jsonObject.getJSONObject("phone_info").getString("phoneNumber"); + } else if (Integer.valueOf(40001).equals(errCode)) { + getAccessToken(true); + } + throw new RuntimeException("获取手机号失败"); + } + + @Override + public String getOpenId(String code) { + String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", + Map.of("appid", appId, "secret", appSecret, "js_code", code, "grant_type", "authorization_code") + ); + log.info("获取openId响应: {}", response); + JSONObject jsonObject = JSONObject.parseObject(response); + String openId = jsonObject.getString("openid"); + if (openId != null && !openId.isBlank()) { + return openId; + } + throw new RuntimeException("获取openId失败"); + } + + /** + * 使用微信支付平台证书公钥加密敏感信息(OAEP) + */ + private static String encryptByPlatformCert(String content) { + try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertPath)) { + if (certStream == null) { + throw new RuntimeException("未找到微信支付平台证书文件: " + platformCertPath); + } + X509Certificate certificate = PayKit.getCertificate(certStream); + if (certificate == null) throw new RuntimeException("读取证书失败"); + return PayKit.rsaEncryptOAEP(content, certificate); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static String getSerialNumberFromPem(String certContent) { + try { + // 去掉 PEM 头尾并清理空格换行 + String pem = certContent + .replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s+", ""); + + // Base64 解码 + byte[] certBytes = Base64.getDecoder().decode(pem); + + try (ByteArrayInputStream bis = new ByteArrayInputStream(certBytes)) { + X509Certificate certificate = PayKit.getCertificate(bis); + + if (certificate != null) { + String serialNo = certificate.getSerialNumber() + .toString(16) + .toUpperCase(Locale.getDefault()); + + System.out.println("证书序列号:" + serialNo); + + return serialNo; + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("无效的证书", e); + } + return null; + } + + public static void main(String[] args) { +// // 文件路径(支持绝对路径或相对路径) +// String filePath = "D:/certs/apiclient_cert.pem"; +// try (var certStream = new FileInputStream("E:\\workspace\\cashier_server1\\cash-api\\order-server\\src\\main\\resources\\cert.pem")) { +// X509Certificate certificate = PayKit.getCertificate(certStream); +// if (certificate != null) { +// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); +// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); +// log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); +// log.info("证书序列号: {}", serialNo); +// } +// } catch (Exception e) { +// log.error("读取证书失败", e); +// } + + getSerialNumberFromPem("-----BEGIN CERTIFICATE-----\n" + + "MIIEJDCCAwygAwIBAgIUctkT9cVeYPVI02uqwzEw2Xg/QLQwDQYJKoZIhvcNAQEL\n" + + "BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n" + + "FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n" + + "Q0EwHhcNMjUxMDI1MDI0NzU3WhcNMzAxMDI0MDI0NzU3WjB+MRMwEQYDVQQDDAox\n" + + "NzMwMDkxMjAzMRswGQYDVQQKDBLlvq7kv6HllYbmiLfns7vnu58xKjAoBgNVBAsM\n" + + "IemZleilv+i2heaOjOafnOenkeaKgOaciemZkOWFrOWPuDELMAkGA1UEBhMCQ04x\n" + + "ETAPBgNVBAcMCFNoZW5aaGVuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" + + "AQEAqCcrDNKTGCBk2euqL6ENu2akxEdmlQQjlf8AVKdGA4nPp0GkWF+WQxFFxAHo\n" + + "jOLSeOUoxqQ5Nr9wa14Nfv3m6R4UIo+JeG0TkNyzum4GRokwoK70vQa8IXZW9vmM\n" + + "lVce/2mxb8sxcnVfAABPfo2A7L14PKPMvAPF4HgJOi5tnMco1sVlg4uwxejoMl7q\n" + + "CiE6lozFW2IRaZZrtmBJMW3oSxCGuLtaWmkXDyj4tLzAJspDxNMQHCVNzYodPREA\n" + + "OP9Z1Wy2v0xIseoxGnxgefE7LpF5jCZiGLkiOg1Xa79tpX02zZ22A8JvTfPWhKJJ\n" + + "nYV0r3PZi/32IU0bBmURYjuh5QIDAQABo4G5MIG2MAkGA1UdEwQCMAAwCwYDVR0P\n" + + "BAQDAgP4MIGbBgNVHR8EgZMwgZAwgY2ggYqggYeGgYRodHRwOi8vZXZjYS5pdHJ1\n" + + "cy5jb20uY24vcHVibGljL2l0cnVzY3JsP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFE\n" + + "Mzk3NTQ5ODQ2QzAxQzNFOEVCRDImc2c9SEFDQzQ3MUI2NTQyMkUxMkIyN0E5RDMz\n" + + "QTg3QUQxQ0RGNTkyNkUxNDAzNzEwDQYJKoZIhvcNAQELBQADggEBAKLBRR+E8hrK\n" + + "hQB+T0GIqQBgkFaCiakylUbw+jdEdQxJD8gO14tHSOTkqOMf5cZONnZiff2rIn+Y\n" + + "kFjR/lO//v5PsTIsby+siwnoc4MOz9tJ5qNUlleuXi3i/Fb1kU9nW6HC8GJdjZxG\n" + + "YqE3UyS23SG5VnrNeBplLHWS3ttmzduNKbl+180R5SoMIyCT35k0EStcZPlVrXnt\n" + + "MqJftxS2Jl1lDHer62tOTgKRGZFfS6z0DOD97mJfyRJ5agUnJTD0L1LCb3lefNRV\n" + + "KgmAkkE7q9cRRIKf8SifdvFDaGhFFi1ezXI/vorSh3PnpMqd8SCc81/IhWmm09RA\n" + + "RLdEZXoPSmM=\n" + + "-----END CERTIFICATE-----\n"); + } + + private static String getSerialNumber() { + try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certPath)) { + X509Certificate certificate = PayKit.getCertificate(certStream); + if (certificate != null) { + String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); + boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); + log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); + log.info("证书序列号: {}", serialNo); + return serialNo; + } + } catch (Exception e) { + log.error("读取证书失败", e); + } + return null; + } + + private static String getPlatSerialNumber() { + try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertNo)) { + X509Certificate certificate = PayKit.getCertificate(certStream); + if (certificate != null) { + String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); + boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); + log.info("平台证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); + log.info("平台证书序列号: {}", serialNo); + return serialNo; + } + } catch (Exception e) { + log.error("读取平台证书失败", e); + } + return null; + } + + public static JSONObject verifySignature(HttpServletRequest request) { + try { + log.info("开始校验签名并解密"); + String timestamp = request.getHeader("Wechatpay-Timestamp"); + String nonce = request.getHeader("Wechatpay-Nonce"); + String serialNo = request.getHeader("Wechatpay-Serial"); + String signature = request.getHeader("Wechatpay-Signature"); + String result = request.getReader().lines().reduce((a, b) -> a + b).orElse(""); + String resp = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, apiV3Key, platformCertPath); + log.info("解密明文{}", resp); + return JSONObject.parseObject(resp); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void getPlatCert() throws Exception { + String response = WxPayApi.v3( + RequestMethodEnum.GET, + WxDomainEnum.CHINA.toString(), + OtherApiEnum.GET_CERTIFICATES.toString(), + mchId, + getSerialNumber(), + null, + apiCertKey, + "" + ).getBody(); + + JSONObject jsonObject = JSONObject.parseObject(response); + var dataArray = jsonObject.getJSONArray("data"); + var encryptObject = dataArray.getJSONObject(0); + var encryptCertificate = encryptObject.getJSONObject("encrypt_certificate"); + String associatedData = encryptCertificate.getString("associated_data"); + String cipherText = encryptCertificate.getString("ciphertext"); + String nonce = encryptCertificate.getString("nonce"); + AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); + String publicKey = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText); + log.info(publicKey); + } + + @Override + public Map v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) { + if (desc == null) desc = "订单支付"; + UnifiedOrderModel model = new UnifiedOrderModel(); + model.setAppid(appId); + model.setMchid(mchId); + model.setDescription(desc); + model.setOut_trade_no(tradeNo); + int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact(); + model.setAmount(new Amount(total, "CNY")); + model.setNotify_url(notifyUrl + "/" + type); + model.setPayer(new Payer(openId, null, null)); + + String payInfo = JSONObject.toJSONString(model); + log.info("统一下单参数: {}", payInfo); + + IJPayHttpResponse resp = null; + try { + resp = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + BasePayApiEnum.JS_API_PAY.toString(), + mchId, + getSerialNumberFromPem(apiCert), + null, + privateKey, + payInfo + ); + log.info("统一下单响应: {}", resp); + String body = resp.getBody(); + JSONObject jsonObject = JSONObject.parseObject(body); + String prepayId = jsonObject.getString("prepay_id"); + if (StrUtil.isBlank(prepayId)) { + throw new RuntimeException(jsonObject.getString("message")); + } + return WxPayKit.jsApiCreateSign(appId, prepayId, privateKey); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Map v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) { + Map payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder() + .appid(appId) + .mch_id(mchId) + .body(desc) + .out_trade_no(tradeNo) + .total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "") + .fee_type("CNY") + .notify_url(notifyUrl) + .openid(openId) + .trade_type("JSAPI") + .build() + .createSign(apiV2Key, SignType.MD5); + + log.info("统一下单参数: {}", payModel); + String resp = WxPayApi.pushOrder(false, payModel); + log.info("统一下单响应: {}", resp); + + Map resultMap = WxPayKit.xmlToMap(resp); + String returnCode = resultMap.get("return_code"); + String returnMsg = resultMap.get("return_msg"); + if (!WxPayKit.codeIsOk(returnCode)) { + throw new CzgException(returnMsg); + } + String resultCode = resultMap.get("result_code"); + if (!WxPayKit.codeIsOk(resultCode)) { + throw new CzgException(returnMsg); + } + String prepayId = resultMap.get("prepay_id"); + return WxPayKit.prepayIdCreateSign(prepayId, appId, appSecret, SignType.MD5); + } + + public static String refund(String tradeNo, String refundTradeNo, BigDecimal amount) { + int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact(); + RefundModel model = new RefundModel(); + model.setOut_trade_no(tradeNo); + model.setOut_refund_no(refundTradeNo); + model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount)); + model.setNotify_url(refundNotifyUrl); + + String info = JSONObject.toJSONString(model); + log.info("统一退款参数: {}", info); + IJPayHttpResponse response; + try { + response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + BasePayApiEnum.REFUND.toString(), + mchId, + getSerialNumber(), + getSerialNumber(), + apiCertKey, + info + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("统一退款响应 {}", response); + String body = response.getBody(); + JSONObject jsonObject = JSONObject.parseObject(body); + if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) { + throw new CzgException("退款异常," + jsonObject.getString("message")); + } + return jsonObject.getString("refund_id"); + } + + public static String genCode(int shopId, int id, String path) { + Map params = Map.of( + "scene", "id=" + id + "&shopId=" + shopId, + "page", path, + "width", 430 + ); + var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false)) + .body(JSONObject.toJSONString(params)) + .execute(); + + byte[] bodyBytes = response.bodyBytes(); + String str = new String(bodyBytes); + if (str.contains("errmsg")) { + JSONObject json = JSONObject.parseObject(str); + throw new CzgException(json.getString("errmsg")); + } + return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes); + } + + public static String addProfitSharingUser(AddReceivers params) throws Exception { + getPlatCert(); + params.setAppid(appId); + if (params.getName() != null && !params.getName().isBlank()) { + params.setName(encryptByPlatformCert(params.getName())); + } else { + params.setName(null); + } + log.info("添加分账方参数: {}", JSONObject.toJSONString(params)); + var response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + ProfitSharingApiEnum.PROFIT_SHARING_RECEIVERS_ADD.toString(), + mchId, + getSerialNumber(), + platformCertNo, + apiCertKey, + JSONObject.toJSONString(params) + ); + log.info("添加分账方响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("分账添加失败" + resp.getString("message")); + } + return resp.getString("account"); + } + + public static JSONObject getMchConfig(String mchIdParam) throws Exception { + var response = WxPayApi.v3( + RequestMethodEnum.GET, + WxDomainEnum.CHINA.toString(), + String.format(ProfitSharingApiEnum.PROFIT_SHARING_MERCHANT_CONFIGS.toString(), mchIdParam), + mchId, + getSerialNumber(), + getSerialNumber(), + apiCertKey, + "" + ); + log.info("查询分账响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("分账查询失败" + resp.getString("message")); + } + return resp; + } + + /** + * 获取可分账金额 + */ + public static BigDecimal getRemainAmount(String transactionId) { + IJPayHttpResponse response; + try { + response = WxPayApi.v3( + RequestMethodEnum.GET, + WxDomainEnum.CHINA.toString(), + String.format(ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE_QUERY.toString(), transactionId), + mchId, + getSerialNumber(), + getSerialNumber(), + apiCertKey, + "" + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("查询可分账响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("分账查询失败" + resp.getString("message")); + } + return resp.getBigDecimal("unsplit_amount"); + } + + public static void profitSharing(String transactionId, ReceiverModel... receivers) { + for (ReceiverModel r : receivers) { + if (r.getName() != null && !r.getName().isBlank()) { + r.setName(encryptByPlatformCert(r.getName())); + } + } + String tradeNo = IdUtil.simpleUUID(); + ProfitSharingModel model = new ProfitSharingModel(); + model.setAppid(appId); + model.setTransaction_id(transactionId); + model.setReceivers(List.of(receivers)); + model.setUnfreeze_unsplit(true); + model.setOut_order_no(tradeNo); + + String params = JSONObject.toJSONString(model); + log.info("分账参数: {}", params); + IJPayHttpResponse response; + try { + response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + ProfitSharingApiEnum.PROFIT_SHARING_ORDERS.toString(), + mchId, + getSerialNumber(), + platformCertNo, + apiCertKey, + params + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("分账响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("分账失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); + } + } + + public static void unFreezeSharing(String transactionId, String orderNo) { + com.ijpay.wxpay.model.ProfitSharingModel paramsModel = new com.ijpay.wxpay.model.ProfitSharingModel(); + paramsModel.setTransaction_id(transactionId); + paramsModel.setOut_order_no(orderNo); + paramsModel.setDescription("分账解冻"); + String params = JSONObject.toJSONString(paramsModel); + log.info("分账解冻参数: {}", params); + IJPayHttpResponse response; + try { + response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE.toString(), + mchId, + getSerialNumber(), + platformCertNo, + apiCertKey, + params + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("分账解冻响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("分账解冻失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); + } + } + + public static JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) { + String remark = remarkTxt == null ? "佣金" : remarkTxt; + String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt; + Map params = Map.of( + "appid", appId, + "out_bill_no", billNo, + "transfer_scene_id", "1005", + "openid", openId, + "user_name", encryptByPlatformCert(name), + "transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(), + "transfer_remark", remark, + "notify_url", transferNotifyUrl, + "transfer_scene_report_infos", new Object[]{ + Map.of("info_type", "岗位类型", "info_content", "加盟商"), + Map.of("info_type", "报酬说明", "info_content", "测试") + } + ); + log.info("转账到零钱参数: {}", JSONObject.toJSONString(params)); + IJPayHttpResponse response = null; + try { + response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + "/v3/fund-app/mch-transfer/transfer-bills", + mchId, + getSerialNumber(), + platformCertNo, + apiCertKey, + JSONObject.toJSONString(params) + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("转账到零钱响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); + } + return resp; + } +} + diff --git a/cash-service/pay-service/src/main/java/com/czg/service/WxService.java b/cash-service/pay-service/src/main/java/com/czg/service/WxService.java deleted file mode 100644 index 227c341c0..000000000 --- a/cash-service/pay-service/src/main/java/com/czg/service/WxService.java +++ /dev/null @@ -1,554 +0,0 @@ -//package com.czg.service; -// -//import cn.hutool.core.util.IdUtil; -//import cn.hutool.core.util.StrUtil; -//import cn.hutool.http.HttpRequest; -//import cn.hutool.http.HttpUtil; -//import com.alibaba.fastjson.JSONObject; -//import com.czg.exception.CzgException; -//import com.ijpay.core.IJPayHttpResponse; -//import com.ijpay.core.enums.RequestMethodEnum; -//import com.ijpay.core.enums.SignType; -//import com.ijpay.core.kit.AesUtil; -//import com.ijpay.core.kit.PayKit; -//import com.ijpay.core.kit.RsaKit; -//import com.ijpay.core.kit.WxPayKit; -//import com.ijpay.wxpay.WxPayApi; -//import com.ijpay.wxpay.enums.WxDomainEnum; -//import com.ijpay.wxpay.enums.v3.BasePayApiEnum; -//import com.ijpay.wxpay.enums.v3.OtherApiEnum; -//import com.ijpay.wxpay.enums.v3.ProfitSharingApiEnum; -//import com.ijpay.wxpay.enums.v3.TransferApiEnum; -//import com.ijpay.wxpay.model.ReceiverModel; -//import com.ijpay.wxpay.model.v3.*; -//import jakarta.annotation.PostConstruct; -//import jakarta.annotation.Resource; -//import jakarta.servlet.http.HttpServletRequest; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.stereotype.Component; -// -//import java.math.BigDecimal; -//import java.math.RoundingMode; -//import java.nio.charset.StandardCharsets; -//import java.security.cert.X509Certificate; -//import java.util.Base64; -//import java.util.List; -//import java.util.Locale; -//import java.util.Map; -// -//@Component -//@Slf4j -//public class WxService { -// -// @Value("${wx.appId}") -// private String appIdInstance; -// @Value("${wx.appSecret}") -// private String appSecretInstance; -// @Value("${wx.pay.certPath}") -// private String certPathInstance; -// @Value("${wx.pay.certKeyPath}") -// private String certKeyPathInstance; -// @Value("${wx.pay.pubKey}") -// private String pubKeyInstance; -// @Value("${wx.pay.platformCertPath}") -// private String platformCertPathInstance; -// @Value("${wx.pay.platformCertNo}") -// private String platformCertNoInstance; -// @Value("${wx.pay.mchId}") -// private String mchIdInstance; -// @Value("${wx.pay.apiV3Key}") -// private String apiV3KeyInstance; -// @Value("${wx.pay.apiV2Key}") -// private String apiV2KeyInstance; -// @Value("${wx.pay.notifyUrl}") -// private String notifyUrlInstance; -// @Value("${wx.pay.refundNotifyUrl}") -// private String refundNotifyUrlInstance; -// @Value("${wx.pay.transferNotifyUrl}") -// private String transferNotifyUrlInstance; -// -// @Resource -// private RedisService autoRedisService; -// private static RedisService redisService; -// -// @PostConstruct -// public void init() { -// appId = appIdInstance; -// appSecret = appSecretInstance; -// certPath = certPathInstance; -// pubKey = pubKeyInstance; -// certKeyPath = certKeyPathInstance; -// platformCertPath = platformCertPathInstance; -// platformCertNo = platformCertNoInstance; -// mchId = mchIdInstance; -// apiV3Key = apiV3KeyInstance; -// apiV2Key = apiV2KeyInstance; -// notifyUrl = notifyUrlInstance; -// refundNotifyUrl = refundNotifyUrlInstance; -// transferNotifyUrl = transferNotifyUrlInstance; -// redisService = this.autoRedisService; -// } -// -// // static copies for use in static methods -// private static String appId; -// private static String appSecret; -// private static String certPath; -// private static String pubKey; -// private static String certKeyPath; -// private static String platformCertPath; -// private static String platformCertNo; -// private static String mchId; -// private static String apiV3Key; -// private static String apiV2Key; -// private static String notifyUrl; -// private static String refundNotifyUrl; -// private static String transferNotifyUrl; -// -// private static String getAccessToken(boolean refresh) { -// Object token = redisService.get("access_token"); -// if (!refresh && token instanceof String) { -// return (String) token; -// } -// -// String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token", -// Map.of("grant_type", "client_credential", "appid", appId, "secret", appSecret) -// ); -// -// log.info("获取access_token响应: {}", response); -// JSONObject jsonObject = JSONObject.parseObject(response); -// String accessToken = jsonObject.getString("access_token"); -// if (accessToken == null) { -// throw new RuntimeException("获取access_token失败"); -// } -// Long expiresIn = jsonObject.getLong("expires_in"); -// if (expiresIn == null) { -// expiresIn = 7200L; -// } -// redisService.set("access_token", accessToken, expiresIn - 200); -// return accessToken; -// } -// -// public static String getPhone(String code) { -// String requestBody = JSONObject.toJSONString(Map.of("code", code)); -// String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody); -// log.info("获取手机号响应: {}", response); -// JSONObject jsonObject = JSONObject.parseObject(response); -// Integer errCode = jsonObject.getInteger("errcode"); -// -// if (Integer.valueOf(0).equals(errCode)) { -// return jsonObject.getJSONObject("phone_info").getString("phoneNumber"); -// } else if (Integer.valueOf(40001).equals(errCode)) { -// getAccessToken(true); -// } -// throw new RuntimeException("获取手机号失败"); -// } -// -// public static String getOpenId(String code) { -// String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", -// Map.of("appid", appId, "secret", appSecret, "js_code", code, "grant_type", "authorization_code") -// ); -// log.info("获取openId响应: {}", response); -// JSONObject jsonObject = JSONObject.parseObject(response); -// String openId = jsonObject.getString("openid"); -// if (openId != null && !openId.isBlank()) { -// return openId; -// } -// throw new RuntimeException("获取openId失败"); -// } -// -// /** -// * 使用微信支付平台证书公钥加密敏感信息(OAEP) -// */ -// private static String encryptByPlatformCert(String content) { -// try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertPath)) { -// if (certStream == null) { -// throw new RuntimeException("未找到微信支付平台证书文件: " + platformCertPath); -// } -// X509Certificate certificate = PayKit.getCertificate(certStream); -// if (certificate == null) throw new RuntimeException("读取证书失败"); -// return PayKit.rsaEncryptOAEP(content, certificate); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// } -// -// private static String getSerialNumber() { -// try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certPath)) { -// X509Certificate certificate = PayKit.getCertificate(certStream); -// if (certificate != null) { -// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); -// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); -// log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); -// log.info("证书序列号: {}", serialNo); -// return serialNo; -// } -// } catch (Exception e) { -// log.error("读取证书失败", e); -// } -// return null; -// } -// -// private static String getPlatSerialNumber() { -// try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertNo)) { -// X509Certificate certificate = PayKit.getCertificate(certStream); -// if (certificate != null) { -// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); -// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); -// log.info("平台证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); -// log.info("平台证书序列号: {}", serialNo); -// return serialNo; -// } -// } catch (Exception e) { -// log.error("读取平台证书失败", e); -// } -// return null; -// } -// -// public static JSONObject verifySignature(HttpServletRequest request) { -// try { -// log.info("开始校验签名并解密"); -// String timestamp = request.getHeader("Wechatpay-Timestamp"); -// String nonce = request.getHeader("Wechatpay-Nonce"); -// String serialNo = request.getHeader("Wechatpay-Serial"); -// String signature = request.getHeader("Wechatpay-Signature"); -// String result = request.getReader().lines().reduce((a, b) -> a + b).orElse(""); -// String resp = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, apiV3Key, platformCertPath); -// log.info("解密明文{}", resp); -// return JSONObject.parseObject(resp); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// } -// -// public static void getPlatCert() throws Exception { -// String response = WxPayApi.v3( -// RequestMethodEnum.GET, -// WxDomainEnum.CHINA.toString(), -// OtherApiEnum.GET_CERTIFICATES.toString(), -// mchId, -// getSerialNumber(), -// null, -// certKeyPath, -// "" -// ).getBody(); -// -// JSONObject jsonObject = JSONObject.parseObject(response); -// var dataArray = jsonObject.getJSONArray("data"); -// var encryptObject = dataArray.getJSONObject(0); -// var encryptCertificate = encryptObject.getJSONObject("encrypt_certificate"); -// String associatedData = encryptCertificate.getString("associated_data"); -// String cipherText = encryptCertificate.getString("ciphertext"); -// String nonce = encryptCertificate.getString("nonce"); -// AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); -// String publicKey = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText); -// log.info(publicKey); -// } -// -// public static Map pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) throws Exception { -// if (desc == null) desc = "订单支付"; -// UnifiedOrderModel model = new UnifiedOrderModel(); -// model.setAppid(appId); -// model.setMchid(mchId); -// model.setDescription(desc); -// model.setOut_trade_no(tradeNo); -// int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact(); -// model.setAmount(new Amount(total, "CNY")); -// model.setNotify_url(notifyUrl + "/" + type); -// model.setPayer(new Payer(openId, null, null)); -// -// String payInfo = JSONObject.toJSONString(model); -// log.info("统一下单参数: {}", payInfo); -// -// var resp = WxPayApi.v3( -// RequestMethodEnum.POST, -// WxDomainEnum.CHINA.toString(), -// BasePayApiEnum.JS_API_PAY.toString(), -// mchId, -// getSerialNumber(), -// getSerialNumber(), -// certKeyPath, -// payInfo -// ); -// -// log.info("统一下单响应: {}", resp); -// String body = resp.getBody(); -// JSONObject jsonObject = JSONObject.parseObject(body); -// String prepayId = jsonObject.getString("prepay_id"); -// if (StrUtil.isBlank(prepayId)) { -// throw new RuntimeException(jsonObject.getString("message")); -// } -// try { -// return WxPayKit.jsApiCreateSign(appId, prepayId, certKeyPath); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// } -// -// public static Map v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) { -// Map payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder() -// .appid(appId) -// .mch_id(mchId) -// .body(desc) -// .out_trade_no(tradeNo) -// .total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "") -// .fee_type("CNY") -// .notify_url(notifyUrl) -// .openid(openId) -// .trade_type("JSAPI") -// .build() -// .createSign(apiV2Key, SignType.MD5); -// -// log.info("统一下单参数: {}", payModel); -// String resp = WxPayApi.pushOrder(false, payModel); -// log.info("统一下单响应: {}", resp); -// -// Map resultMap = WxPayKit.xmlToMap(resp); -// String returnCode = resultMap.get("return_code"); -// String returnMsg = resultMap.get("return_msg"); -// if (!WxPayKit.codeIsOk(returnCode)) { -// throw new CzgException(returnMsg); -// } -// String resultCode = resultMap.get("result_code"); -// if (!WxPayKit.codeIsOk(resultCode)) { -// throw new CzgException(returnMsg); -// } -// String prepayId = resultMap.get("prepay_id"); -// return WxPayKit.prepayIdCreateSign(prepayId, appId, appSecret, SignType.MD5); -// } -// -// public static String refund(String tradeNo, String refundTradeNo, BigDecimal amount) { -// int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact(); -// RefundModel model = new RefundModel(); -// model.setOut_trade_no(tradeNo); -// model.setOut_refund_no(refundTradeNo); -// model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount)); -// model.setNotify_url(refundNotifyUrl); -// -// String info = JSONObject.toJSONString(model); -// log.info("统一退款参数: {}", info); -// IJPayHttpResponse response; -// try { -// response = WxPayApi.v3( -// RequestMethodEnum.POST, -// WxDomainEnum.CHINA.toString(), -// BasePayApiEnum.REFUND.toString(), -// mchId, -// getSerialNumber(), -// getSerialNumber(), -// certKeyPath, -// info -// ); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// log.info("统一退款响应 {}", response); -// String body = response.getBody(); -// JSONObject jsonObject = JSONObject.parseObject(body); -// if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) { -// throw new CzgException("退款异常," + jsonObject.getString("message")); -// } -// return jsonObject.getString("refund_id"); -// } -// -// public static String genCode(int shopId, int id, String path) { -// Map params = Map.of( -// "scene", "id=" + id + "&shopId=" + shopId, -// "page", path, -// "width", 430 -// ); -// var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false)) -// .body(JSONObject.toJSONString(params)) -// .execute(); -// -// byte[] bodyBytes = response.bodyBytes(); -// String str = new String(bodyBytes); -// if (str.contains("errmsg")) { -// JSONObject json = JSONObject.parseObject(str); -// throw new CzgException(json.getString("errmsg")); -// } -// return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes); -// } -// -// public static String addProfitSharingUser(AddReceivers params) throws Exception { -// getPlatCert(); -// params.setAppid(appId); -// if (params.getName() != null && !params.getName().isBlank()) { -// params.setName(encryptByPlatformCert(params.getName())); -// } else { -// params.setName(null); -// } -// log.info("添加分账方参数: {}", JSONObject.toJSONString(params)); -// var response = WxPayApi.v3( -// RequestMethodEnum.POST, -// WxDomainEnum.CHINA.toString(), -// ProfitSharingApiEnum.PROFIT_SHARING_RECEIVERS_ADD.toString(), -// mchId, -// getSerialNumber(), -// platformCertNo, -// certKeyPath, -// JSONObject.toJSONString(params) -// ); -// log.info("添加分账方响应 {}", response.getBody()); -// JSONObject resp = JSONObject.parseObject(response.getBody()); -// if (resp.containsKey("code")) { -// throw new CzgException("分账添加失败" + resp.getString("message")); -// } -// return resp.getString("account"); -// } -// -// public static JSONObject getMchConfig(String mchIdParam) throws Exception { -// var response = WxPayApi.v3( -// RequestMethodEnum.GET, -// WxDomainEnum.CHINA.toString(), -// String.format(ProfitSharingApiEnum.PROFIT_SHARING_MERCHANT_CONFIGS.toString(), mchIdParam), -// mchId, -// getSerialNumber(), -// getSerialNumber(), -// certKeyPath, -// "" -// ); -// log.info("查询分账响应 {}", response.getBody()); -// JSONObject resp = JSONObject.parseObject(response.getBody()); -// if (resp.containsKey("code")) { -// throw new CzgException("分账查询失败" + resp.getString("message")); -// } -// return resp; -// } -// -// /** -// * 获取可分账金额 -// */ -// public static BigDecimal getRemainAmount(String transactionId) { -// IJPayHttpResponse response; -// try { -// response = WxPayApi.v3( -// RequestMethodEnum.GET, -// WxDomainEnum.CHINA.toString(), -// String.format(ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE_QUERY.toString(), transactionId), -// mchId, -// getSerialNumber(), -// getSerialNumber(), -// certKeyPath, -// "" -// ); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// log.info("查询可分账响应 {}", response.getBody()); -// JSONObject resp = JSONObject.parseObject(response.getBody()); -// if (resp.containsKey("code")) { -// throw new CzgException("分账查询失败" + resp.getString("message")); -// } -// return resp.getBigDecimal("unsplit_amount"); -// } -// -// public static void profitSharing(String transactionId, ReceiverModel... receivers) { -// for (ReceiverModel r : receivers) { -// if (r.getName() != null && !r.getName().isBlank()) { -// r.setName(encryptByPlatformCert(r.getName())); -// } -// } -// String tradeNo = IdUtil.simpleUUID(); -// ProfitSharingModel model = new ProfitSharingModel(); -// model.setAppid(appId); -// model.setTransaction_id(transactionId); -// model.setReceivers(List.of(receivers)); -// model.setUnfreeze_unsplit(true); -// model.setOut_order_no(tradeNo); -// -// String params = JSONObject.toJSONString(model); -// log.info("分账参数: {}", params); -// IJPayHttpResponse response; -// try { -// response = WxPayApi.v3( -// RequestMethodEnum.POST, -// WxDomainEnum.CHINA.toString(), -// ProfitSharingApiEnum.PROFIT_SHARING_ORDERS.toString(), -// mchId, -// getSerialNumber(), -// platformCertNo, -// certKeyPath, -// params -// ); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// log.info("分账响应 {}", response.getBody()); -// JSONObject resp = JSONObject.parseObject(response.getBody()); -// if (resp.containsKey("code")) { -// throw new CzgException("分账失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); -// } -// } -// -// public static void unFreezeSharing(String transactionId, String orderNo) { -// com.ijpay.wxpay.model.ProfitSharingModel paramsModel = new com.ijpay.wxpay.model.ProfitSharingModel(); -// paramsModel.setTransaction_id(transactionId); -// paramsModel.setOut_order_no(orderNo); -// paramsModel.setDescription("分账解冻"); -// String params = JSONObject.toJSONString(paramsModel); -// log.info("分账解冻参数: {}", params); -// IJPayHttpResponse response; -// try { -// response = WxPayApi.v3( -// RequestMethodEnum.POST, -// WxDomainEnum.CHINA.toString(), -// ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE.toString(), -// mchId, -// getSerialNumber(), -// platformCertNo, -// certKeyPath, -// params -// ); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// log.info("分账解冻响应 {}", response.getBody()); -// JSONObject resp = JSONObject.parseObject(response.getBody()); -// if (resp.containsKey("code")) { -// throw new CzgException("分账解冻失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); -// } -// } -// -// public static JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) { -// String remark = remarkTxt == null ? "佣金" : remarkTxt; -// String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt; -// Map params = Map.of( -// "appid", appId, -// "out_bill_no", billNo, -// "transfer_scene_id", "1005", -// "openid", openId, -// "user_name", encryptByPlatformCert(name), -// "transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(), -// "transfer_remark", remark, -// "notify_url", transferNotifyUrl, -// "transfer_scene_report_infos", new Object[]{ -// Map.of("info_type", "岗位类型", "info_content", "加盟商"), -// Map.of("info_type", "报酬说明", "info_content", "测试") -// } -// ); -// log.info("转账到零钱参数: {}", JSONObject.toJSONString(params)); -// IJPayHttpResponse response = null; -// try { -// response = WxPayApi.v3( -// RequestMethodEnum.POST, -// WxDomainEnum.CHINA.toString(), -// "/v3/fund-app/mch-transfer/transfer-bills", -// mchId, -// getSerialNumber(), -// platformCertNo, -// certKeyPath, -// JSONObject.toJSONString(params) -// ); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// log.info("转账到零钱响应 {}", response.getBody()); -// JSONObject resp = JSONObject.parseObject(response.getBody()); -// if (resp.containsKey("code")) { -// throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); -// } -// return resp; -// } -//} -//