订单分账处理
This commit is contained in:
parent
30f35128fd
commit
cf492b32f1
|
|
@ -107,9 +107,10 @@ public class DistributionController {
|
||||||
*/
|
*/
|
||||||
@GetMapping("distributionFlow")
|
@GetMapping("distributionFlow")
|
||||||
public CzgResult<Map<String, Object>> distributionFlow(@RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime,
|
public CzgResult<Map<String, Object>> distributionFlow(@RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime,
|
||||||
@RequestParam(required = false) String key, @RequestParam(required = false) String status) {
|
@RequestParam(required = false) String key, @RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) Long id) {
|
||||||
return CzgResult.success(distributionFlowService.pageInfo(StpKit.USER.getShopId(),
|
return CzgResult.success(distributionFlowService.pageInfo(StpKit.USER.getShopId(),
|
||||||
StrUtil.isNotBlank(startTime) ? DateUtil.parseLocalDateTime(startTime) : null, StrUtil.isNotBlank(endTime) ? DateUtil.parseLocalDateTime(endTime) : null, key, status));
|
StrUtil.isNotBlank(startTime) ? DateUtil.parseLocalDateTime(startTime) : null, StrUtil.isNotBlank(endTime) ? DateUtil.parseLocalDateTime(endTime) : null, key, status, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import com.czg.order.service.OrderInfoService;
|
||||||
import com.czg.order.service.OrderPaymentService;
|
import com.czg.order.service.OrderPaymentService;
|
||||||
import com.czg.order.service.ShopTableOrderStatisticService;
|
import com.czg.order.service.ShopTableOrderStatisticService;
|
||||||
import com.czg.service.Impl.WxServiceImpl;
|
import com.czg.service.Impl.WxServiceImpl;
|
||||||
import com.czg.system.service.WxService;
|
|
||||||
import com.czg.task.StatisticTask;
|
import com.czg.task.StatisticTask;
|
||||||
import com.czg.utils.AssertUtil;
|
import com.czg.utils.AssertUtil;
|
||||||
import com.ijpay.core.kit.AesUtil;
|
import com.ijpay.core.kit.AesUtil;
|
||||||
|
|
@ -50,7 +49,7 @@ public class NotifyController {
|
||||||
@Resource
|
@Resource
|
||||||
private ShopTableOrderStatisticService shopTableOrderStatisticService;
|
private ShopTableOrderStatisticService shopTableOrderStatisticService;
|
||||||
@Resource
|
@Resource
|
||||||
private WxService wxService;
|
private WxServiceImpl wxService;
|
||||||
@Resource
|
@Resource
|
||||||
private MkDistributionUserService distributionUserService;
|
private MkDistributionUserService distributionUserService;
|
||||||
@Resource
|
@Resource
|
||||||
|
|
|
||||||
|
|
@ -107,4 +107,7 @@ public class UserInfo implements Serializable {
|
||||||
private LocalDateTime updateTime;
|
private LocalDateTime updateTime;
|
||||||
private Integer usePayPwd;
|
private Integer usePayPwd;
|
||||||
|
|
||||||
|
private String realName;
|
||||||
|
private String idCard;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ public class MkDistributionFlow implements Serializable {
|
||||||
* 奖励金额
|
* 奖励金额
|
||||||
*/
|
*/
|
||||||
private BigDecimal rewardAmount;
|
private BigDecimal rewardAmount;
|
||||||
|
private String billNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* order订单 recharge充值
|
* order订单 recharge充值
|
||||||
|
|
@ -108,4 +109,7 @@ public class MkDistributionFlow implements Serializable {
|
||||||
private Long sourceShopUserId;
|
private Long sourceShopUserId;
|
||||||
|
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
|
private String resp;
|
||||||
|
private String packageInfo;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,5 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public interface MkDistributionFlowService extends IService<MkDistributionFlow> {
|
public interface MkDistributionFlowService extends IService<MkDistributionFlow> {
|
||||||
|
|
||||||
Map<String, Object> pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status);
|
Map<String, Object> pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status, Long id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
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<String, String> v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type);
|
|
||||||
|
|
||||||
String decryptToString(String associatedData, String nonceStr, String ciphertext);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -65,7 +65,7 @@ public interface TableValueConstant {
|
||||||
enum Status {
|
enum Status {
|
||||||
PENDING("pending", "待入账"),
|
PENDING("pending", "待入账"),
|
||||||
SUCCESS("sub", "已入账"),
|
SUCCESS("sub", "已入账"),
|
||||||
SELF_RECHARGE("self_recharge", "自助充值");
|
SELF_RECHARGE("self_recharge", "自助充值"), FAIL("fail", "失败");
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String msg;
|
private final String msg;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@
|
||||||
<artifactId>cash-common-mq</artifactId>
|
<artifactId>cash-common-mq</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.czg</groupId>
|
||||||
|
<artifactId>pay-service</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,5 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public interface MkDistributionFlowMapper extends BaseMapper<MkDistributionFlow> {
|
public interface MkDistributionFlowMapper extends BaseMapper<MkDistributionFlow> {
|
||||||
|
|
||||||
List<MkDistributionFlowVO> pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String status, String key);
|
List<MkDistributionFlowVO> pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String status, String key, Long id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ public class MkDistributionFlowServiceImpl extends ServiceImpl<MkDistributionFlo
|
||||||
private ShopInfoService shopInfoService;
|
private ShopInfoService shopInfoService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status) {
|
public Map<String, Object> pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status, Long id) {
|
||||||
PageHelper.startPage(PageUtil.buildPageHelp());
|
PageHelper.startPage(PageUtil.buildPageHelp());
|
||||||
List<MkDistributionFlowVO> list = mapper.pageInfo(shopId, startTime, endTime, status, key);
|
List<MkDistributionFlowVO> list = mapper.pageInfo(shopId, startTime, endTime, status, key, id);
|
||||||
Page<MkDistributionFlowVO> page = PageUtil.convert(new PageInfo<>(list));
|
Page<MkDistributionFlowVO> page = PageUtil.convert(new PageInfo<>(list));
|
||||||
Map<String, Object> map = BeanUtil.beanToMap(page);
|
Map<String, Object> map = BeanUtil.beanToMap(page);
|
||||||
map.put("successAmount", getOne(new QueryWrapper().eq(MkDistributionFlow::getShopId, shopId)
|
map.put("successAmount", getOne(new QueryWrapper().eq(MkDistributionFlow::getShopId, shopId)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@ package com.czg.service.market.service.impl;
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.czg.account.entity.ShopInfo;
|
import com.czg.account.entity.ShopInfo;
|
||||||
import com.czg.account.entity.ShopUser;
|
import com.czg.account.entity.ShopUser;
|
||||||
|
import com.czg.account.entity.UserInfo;
|
||||||
import com.czg.account.service.ShopInfoService;
|
import com.czg.account.service.ShopInfoService;
|
||||||
import com.czg.account.service.ShopUserService;
|
import com.czg.account.service.ShopUserService;
|
||||||
|
import com.czg.account.service.UserInfoService;
|
||||||
import com.czg.constant.TableValueConstant;
|
import com.czg.constant.TableValueConstant;
|
||||||
import com.czg.exception.CzgException;
|
import com.czg.exception.CzgException;
|
||||||
import com.czg.market.dto.MkDistributionUserDTO;
|
import com.czg.market.dto.MkDistributionUserDTO;
|
||||||
|
|
@ -27,8 +30,8 @@ import com.czg.order.dto.MkDistributionPayDTO;
|
||||||
import com.czg.order.entity.OrderPayment;
|
import com.czg.order.entity.OrderPayment;
|
||||||
import com.czg.order.service.OrderPaymentService;
|
import com.czg.order.service.OrderPaymentService;
|
||||||
import com.czg.sa.StpKit;
|
import com.czg.sa.StpKit;
|
||||||
|
import com.czg.service.Impl.AppWxServiceImpl;
|
||||||
import com.czg.service.market.mapper.MkDistributionUserMapper;
|
import com.czg.service.market.mapper.MkDistributionUserMapper;
|
||||||
import com.czg.system.service.WxService;
|
|
||||||
import com.czg.utils.AssertUtil;
|
import com.czg.utils.AssertUtil;
|
||||||
import com.czg.utils.CzgRandomUtils;
|
import com.czg.utils.CzgRandomUtils;
|
||||||
import com.czg.utils.PageUtil;
|
import com.czg.utils.PageUtil;
|
||||||
|
|
@ -70,15 +73,18 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
|
||||||
private MkDistributionAmountFlowService distributionAmountFlowService;
|
private MkDistributionAmountFlowService distributionAmountFlowService;
|
||||||
@Resource
|
@Resource
|
||||||
private MkDistributionFlowService distributionFlowService;
|
private MkDistributionFlowService distributionFlowService;
|
||||||
|
@DubboReference
|
||||||
|
private AppWxServiceImpl appWxService;
|
||||||
|
|
||||||
|
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private ShopUserService shopUserService;
|
private ShopUserService shopUserService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
|
private UserInfoService userInfoService;
|
||||||
|
@DubboReference
|
||||||
private OrderPaymentService orderPaymentService;
|
private OrderPaymentService orderPaymentService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private ShopInfoService shopInfoService;
|
private ShopInfoService shopInfoService;
|
||||||
@DubboReference
|
|
||||||
private WxService wxService;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> centerUser(Long userId) {
|
public Map<String, Object> centerUser(Long userId) {
|
||||||
|
|
@ -336,16 +342,28 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
|
||||||
.setLevelId(distributionUser.getDistributionLevelId()).setLevel(level.getLevel()).setSourceNickName(sourceShopUser.getNickName()).setOrderNo(orderNo)
|
.setLevelId(distributionUser.getDistributionLevelId()).setLevel(level.getLevel()).setSourceNickName(sourceShopUser.getNickName()).setOrderNo(orderNo)
|
||||||
.setSourceId(sourceId).setAmount(amount).setType(type).setStatus(flag ? TableValueConstant.DistributionFlow.Status.SUCCESS.getCode() :
|
.setSourceId(sourceId).setAmount(amount).setType(type).setStatus(flag ? TableValueConstant.DistributionFlow.Status.SUCCESS.getCode() :
|
||||||
TableValueConstant.DistributionFlow.Status.PENDING.getCode())
|
TableValueConstant.DistributionFlow.Status.PENDING.getCode())
|
||||||
.setRewardAmount(rewardAmount);
|
.setRewardAmount(rewardAmount).setBillNo(IdUtil.simpleUUID());
|
||||||
distributionFlowService.save(mkDistributionFlow);
|
|
||||||
|
|
||||||
if (flag) {
|
if (flag) {
|
||||||
|
ShopUser shopUser = shopUserService.getById(distributionUser.getShopUserId());
|
||||||
|
UserInfo userInfo = userInfoService.getById(shopUser.getUserId());
|
||||||
distributionAmountFlowService.save(new MkDistributionAmountFlow()
|
distributionAmountFlowService.save(new MkDistributionAmountFlow()
|
||||||
.setType(TableValueConstant.DistributionAmountFlow.Type.SELF_RECHARGE.getCode())
|
.setType(TableValueConstant.DistributionAmountFlow.Type.SELF_RECHARGE.getCode())
|
||||||
.setShopId(config.getShopId()).setAmount(finalAmount).setChangeAmount(amount).setSourceId(mkDistributionFlow.getId())
|
.setShopId(config.getShopId()).setAmount(finalAmount).setChangeAmount(amount).setSourceId(mkDistributionFlow.getId())
|
||||||
.setRemark("自助充值").setOpAccount(StpKit.USER.getAccount()));
|
.setRemark("自助充值").setOpAccount(StpKit.USER.getAccount()));
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = appWxService.transferBalance(userInfo.getWechatOpenId(), userInfo.getRealName(), rewardAmount, "分销奖励", mkDistributionFlow.getBillNo());
|
||||||
|
mkDistributionFlow.setPackageInfo(jsonObject.getString("package_info"));
|
||||||
|
mkDistributionFlow.setResp(jsonObject.toJSONString());
|
||||||
|
}catch (Exception e) {
|
||||||
|
mkDistributionFlow.setResp(e.getMessage());
|
||||||
|
mkDistributionFlow.setStatus(TableValueConstant.DistributionFlow.Status.FAIL.getCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
distributionFlowService.save(mkDistributionFlow);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void distribute(Long sourceId, String orderNo, BigDecimal amount, Long sourceUserId, Long shopId, String type) {
|
public void distribute(Long sourceId, String orderNo, BigDecimal amount, Long sourceUserId, Long shopId, String type) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
<select id="pageInfo" resultType="com.czg.market.vo.MkDistributionFlowVO">
|
<select id="pageInfo" resultType="com.czg.market.vo.MkDistributionFlowVO">
|
||||||
select a.*, b.phone, c.phone as sourcePhone from mk_distribution_flow as a
|
select a.*, b.phone, c.phone as sourcePhone from mk_distribution_flow as a
|
||||||
|
left join mk_distribution_user as d on d.id=a.distribution_user_id
|
||||||
left join tb_shop_user as b on a.shop_user_id=b.id
|
left join tb_shop_user as b on a.shop_user_id=b.id
|
||||||
left join tb_shop_user as c on c.id=a.shop_user_id
|
left join tb_shop_user as c on c.id=a.shop_user_id
|
||||||
where a.shop_id=#{shopId}
|
where a.shop_id=#{shopId}
|
||||||
|
|
@ -26,6 +27,9 @@
|
||||||
or c.nick_name like concat('%',#{key},'%')
|
or c.nick_name like concat('%',#{key},'%')
|
||||||
)
|
)
|
||||||
</if>
|
</if>
|
||||||
|
<if test="id != null">
|
||||||
|
and d.id=#{id}
|
||||||
|
</if>
|
||||||
order by a.create_time desc
|
order by a.create_time desc
|
||||||
</select>
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ import com.czg.order.dto.MkDistributionPayDTO;
|
||||||
import com.czg.order.entity.OrderPayment;
|
import com.czg.order.entity.OrderPayment;
|
||||||
import com.czg.order.service.OrderPaymentService;
|
import com.czg.order.service.OrderPaymentService;
|
||||||
import com.czg.resp.CzgResult;
|
import com.czg.resp.CzgResult;
|
||||||
|
import com.czg.service.Impl.WxServiceImpl;
|
||||||
import com.czg.service.order.service.DistributionPayService;
|
import com.czg.service.order.service.DistributionPayService;
|
||||||
import com.czg.system.service.WxService;
|
|
||||||
import com.czg.utils.AssertUtil;
|
import com.czg.utils.AssertUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -41,7 +41,7 @@ public class DistributionPayServiceImpl implements DistributionPayService {
|
||||||
@Resource
|
@Resource
|
||||||
private MkDistributionConfigService configService;
|
private MkDistributionConfigService configService;
|
||||||
@Resource
|
@Resource
|
||||||
private WxService wxService;
|
private WxServiceImpl wxService;
|
||||||
@Resource
|
@Resource
|
||||||
private PayServiceImpl payService;
|
private PayServiceImpl payService;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.czg.service.Impl;
|
||||||
|
|
||||||
|
import com.czg.service.RedisService;
|
||||||
|
import com.czg.system.service.SysParamsService;
|
||||||
|
import com.czg.service.WxService;
|
||||||
|
import com.ijpay.core.kit.RsaKit;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.dubbo.config.annotation.DubboReference;
|
||||||
|
import org.apache.dubbo.config.annotation.DubboService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付service
|
||||||
|
* @author Administrator
|
||||||
|
*/
|
||||||
|
@DubboService
|
||||||
|
@Slf4j
|
||||||
|
public class AppWxServiceImpl implements WxService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisService autoRedisService;
|
||||||
|
@DubboReference
|
||||||
|
private SysParamsService paramsService;
|
||||||
|
private static RedisService redisService;
|
||||||
|
private WxService.Config config = new Config();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Config getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RedisService getRedisService() {
|
||||||
|
return redisService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// 小程序id
|
||||||
|
config.appId = paramsService.getSysParamValue("shop_wx_appid");
|
||||||
|
// 小程序secrete
|
||||||
|
config.appSecret = paramsService.getSysParamValue("shop_wx_secrete");
|
||||||
|
config.certPath = "";
|
||||||
|
// 微信支付公钥
|
||||||
|
config.pubKey = paramsService.getSysParamValue("wx_pub_key");
|
||||||
|
// api支付证书私钥
|
||||||
|
config.apiCertKey = paramsService.getSysParamValue("wx_apiclient_key");
|
||||||
|
// api支付证书公钥
|
||||||
|
config.apiCert = paramsService.getSysParamValue("wx_apiclient_cert");
|
||||||
|
try {
|
||||||
|
config.privateKey = RsaKit.loadPrivateKey(config.apiCertKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("微信加载api证书失败");
|
||||||
|
}
|
||||||
|
// 平台证书
|
||||||
|
config.platformCertPath = "";
|
||||||
|
// 平台证书编号
|
||||||
|
config.platformCertNo = "";
|
||||||
|
// 商户号
|
||||||
|
config.mchId = paramsService.getSysParamValue("wx_mch_id");
|
||||||
|
// v3密钥
|
||||||
|
config.apiV3Key = paramsService.getSysParamValue("wx_v3_key");
|
||||||
|
config.apiV2Key = "";
|
||||||
|
// 回调地址
|
||||||
|
config.notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay";
|
||||||
|
config.refundNotifyUrl = "";
|
||||||
|
config.transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer";
|
||||||
|
redisService = this.autoRedisService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,43 +4,19 @@ import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpRequest;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.czg.exception.CzgException;
|
import com.czg.exception.CzgException;
|
||||||
import com.czg.service.RedisService;
|
import com.czg.service.RedisService;
|
||||||
import com.czg.system.service.SysParamsService;
|
import com.czg.system.service.SysParamsService;
|
||||||
import com.czg.system.service.WxService;
|
import com.czg.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.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.PostConstruct;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.dubbo.config.annotation.DubboReference;
|
import org.apache.dubbo.config.annotation.DubboReference;
|
||||||
import org.apache.dubbo.config.annotation.DubboService;
|
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.GeneralSecurityException;
|
|
||||||
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
|
* 微信支付service
|
||||||
|
|
@ -50,599 +26,56 @@ import java.util.Map;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WxServiceImpl implements WxService {
|
public class WxServiceImpl implements WxService {
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisService autoRedisService;
|
private RedisService autoRedisService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private SysParamsService paramsService;
|
private SysParamsService paramsService;
|
||||||
private static RedisService redisService;
|
private static RedisService redisService;
|
||||||
|
private WxService.Config config = new Config();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Config getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RedisService getRedisService() {
|
||||||
|
return redisService;
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
// 小程序id
|
// 小程序id
|
||||||
appId = paramsService.getSysParamValue("shop_wx_appid");
|
config.appId = paramsService.getSysParamValue("shop_wx_appid");
|
||||||
// 小程序secrete
|
// 小程序secrete
|
||||||
appSecret = paramsService.getSysParamValue("shop_wx_secrete");
|
config.appSecret = paramsService.getSysParamValue("shop_wx_secrete");
|
||||||
certPath = "";
|
config.certPath = "";
|
||||||
// 微信支付公钥
|
// 微信支付公钥
|
||||||
pubKey = paramsService.getSysParamValue("wx_pub_key");
|
config.pubKey = paramsService.getSysParamValue("wx_pub_key");
|
||||||
// api支付证书私钥
|
// api支付证书私钥
|
||||||
apiCertKey = paramsService.getSysParamValue("wx_apiclient_key");
|
config.apiCertKey = paramsService.getSysParamValue("wx_apiclient_key");
|
||||||
// api支付证书公钥
|
// api支付证书公钥
|
||||||
apiCert = paramsService.getSysParamValue("wx_apiclient_cert");
|
config.apiCert = paramsService.getSysParamValue("wx_apiclient_cert");
|
||||||
try {
|
try {
|
||||||
privateKey = RsaKit.loadPrivateKey(apiCertKey);
|
config.privateKey = RsaKit.loadPrivateKey(config.apiCertKey);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("微信加载api证书失败");
|
log.warn("微信加载api证书失败");
|
||||||
}
|
}
|
||||||
// 平台证书
|
// 平台证书
|
||||||
platformCertPath = "";
|
config.platformCertPath = "";
|
||||||
// 平台证书编号
|
// 平台证书编号
|
||||||
platformCertNo = "";
|
config.platformCertNo = "";
|
||||||
// 商户号
|
// 商户号
|
||||||
mchId = paramsService.getSysParamValue("wx_mch_id");
|
config.mchId = paramsService.getSysParamValue("wx_mch_id");
|
||||||
// v3密钥
|
// v3密钥
|
||||||
apiV3Key = paramsService.getSysParamValue("wx_v3_key");
|
config.apiV3Key = paramsService.getSysParamValue("wx_v3_key");
|
||||||
apiV2Key = "";
|
config.apiV2Key = "";
|
||||||
// 回调地址
|
// 回调地址
|
||||||
notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay";
|
config.notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay";
|
||||||
refundNotifyUrl = "";
|
config.refundNotifyUrl = "";
|
||||||
transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer";
|
config.transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer";
|
||||||
redisService = this.autoRedisService;
|
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;
|
|
||||||
public 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 String decryptToString(String associatedData, String nonceStr, String ciphertext) {
|
|
||||||
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
|
|
||||||
try {
|
|
||||||
return aesUtil.decryptToString(
|
|
||||||
associatedData.getBytes(StandardCharsets.UTF_8),
|
|
||||||
nonceStr.getBytes(StandardCharsets.UTF_8),
|
|
||||||
ciphertext
|
|
||||||
);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> 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;
|
|
||||||
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<String, String> v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) {
|
|
||||||
Map<String, String> 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<String, String> 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<String, Object> 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<String, Object> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,392 @@
|
||||||
|
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.fastjson2.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.WxPayKit;
|
||||||
|
import com.ijpay.wxpay.WxPayApi;
|
||||||
|
import com.ijpay.wxpay.enums.WxDomainEnum;
|
||||||
|
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
|
||||||
|
import com.ijpay.wxpay.model.v3.*;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付相关
|
||||||
|
* @author Administrator
|
||||||
|
*/
|
||||||
|
public interface WxService {
|
||||||
|
Logger log = LoggerFactory.getLogger(WxService.class);
|
||||||
|
@Data
|
||||||
|
class Config {
|
||||||
|
public String appId;
|
||||||
|
public String appSecret;
|
||||||
|
public String certPath;
|
||||||
|
public String pubKey;
|
||||||
|
public String apiCertKey;
|
||||||
|
public PrivateKey privateKey;
|
||||||
|
public String apiCert;
|
||||||
|
public String platformCertPath;
|
||||||
|
public String platformCertNo;
|
||||||
|
public String mchId;
|
||||||
|
public String apiV3Key;
|
||||||
|
public String apiV2Key;
|
||||||
|
public String notifyUrl;
|
||||||
|
public String refundNotifyUrl;
|
||||||
|
public String transferNotifyUrl;
|
||||||
|
}
|
||||||
|
Config getConfig();
|
||||||
|
RedisService getRedisService();
|
||||||
|
|
||||||
|
|
||||||
|
default 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("获取手机号失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
default String getAccessToken(boolean refresh) {
|
||||||
|
Object token = getRedisService().get("access_token");
|
||||||
|
Config config = getConfig();
|
||||||
|
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", config.appId, "secret", config.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;
|
||||||
|
}
|
||||||
|
getRedisService().set("access_token", accessToken, expiresIn - 200);
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用微信支付平台证书公钥加密敏感信息(OAEP)
|
||||||
|
*/
|
||||||
|
default String encryptByPlatformCert(String content) {
|
||||||
|
Config config = getConfig();
|
||||||
|
|
||||||
|
try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(config.getPlatformCertPath())) {
|
||||||
|
if (certStream == null) {
|
||||||
|
throw new RuntimeException("未找到微信支付平台证书文件: " + config.getPlatformCertPath());
|
||||||
|
}
|
||||||
|
X509Certificate certificate = PayKit.getCertificate(certStream);
|
||||||
|
if (certificate == null) {
|
||||||
|
throw new RuntimeException("读取证书失败");
|
||||||
|
}
|
||||||
|
return PayKit.rsaEncryptOAEP(content, certificate);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
default String getSerialNumber() {
|
||||||
|
Config config = getConfig();
|
||||||
|
try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(config.getCertPath())) {
|
||||||
|
X509Certificate certificate = PayKit.getCertificate(certStream);
|
||||||
|
if (certificate != null) {
|
||||||
|
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
|
||||||
|
boolean isValid = PayKit.checkCertificateIsValid(certificate, config.getMchId(), -2);
|
||||||
|
log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
|
||||||
|
log.info("证书序列号: {}", serialNo);
|
||||||
|
return serialNo;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("读取证书失败", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default JSONObject verifySignature(HttpServletRequest request) {
|
||||||
|
Config config = getConfig();
|
||||||
|
|
||||||
|
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, config.getApiV3Key(), config.getPlatformCertPath());
|
||||||
|
log.info("解密明文{}", resp);
|
||||||
|
return JSONObject.parseObject(resp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default String decryptToString(String associatedData, String nonceStr, String ciphertext) {
|
||||||
|
AesUtil aesUtil = new AesUtil(getConfig().getApiV3Key().getBytes(StandardCharsets.UTF_8));
|
||||||
|
try {
|
||||||
|
return aesUtil.decryptToString(
|
||||||
|
associatedData.getBytes(StandardCharsets.UTF_8),
|
||||||
|
nonceStr.getBytes(StandardCharsets.UTF_8),
|
||||||
|
ciphertext
|
||||||
|
);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default String getOpenId(String code) {
|
||||||
|
Config config = getConfig();
|
||||||
|
String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session",
|
||||||
|
Map.of("appid", config.getAppId() , "secret", config.getAppSecret(), "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失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
default Map<String, String> v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) {
|
||||||
|
Config config = getConfig();
|
||||||
|
if (desc == null) desc = "订单支付";
|
||||||
|
UnifiedOrderModel model = new UnifiedOrderModel();
|
||||||
|
model.setAppid(config.getAppId());
|
||||||
|
model.setMchid(config.getMchId());
|
||||||
|
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(config.getNotifyUrl() + "/" + type);
|
||||||
|
model.setPayer(new Payer(openId, null, null));
|
||||||
|
|
||||||
|
String payInfo = JSONObject.toJSONString(model);
|
||||||
|
log.info("统一下单参数: {}", payInfo);
|
||||||
|
|
||||||
|
IJPayHttpResponse resp;
|
||||||
|
try {
|
||||||
|
resp = WxPayApi.v3(
|
||||||
|
RequestMethodEnum.POST,
|
||||||
|
WxDomainEnum.CHINA.toString(),
|
||||||
|
BasePayApiEnum.JS_API_PAY.toString(),
|
||||||
|
config.mchId,
|
||||||
|
getSerialNumberFromPem(config.apiCert),
|
||||||
|
null,
|
||||||
|
config.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(config.appId, prepayId, config.privateKey);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default Map<String, String> v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) {
|
||||||
|
Config config = getConfig();
|
||||||
|
Map<String, String> payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder()
|
||||||
|
.appid(config.appId)
|
||||||
|
.mch_id(config.mchId)
|
||||||
|
.body(desc)
|
||||||
|
.out_trade_no(tradeNo)
|
||||||
|
.total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "")
|
||||||
|
.fee_type("CNY")
|
||||||
|
.notify_url(config.notifyUrl)
|
||||||
|
.openid(openId)
|
||||||
|
.trade_type("JSAPI")
|
||||||
|
.build()
|
||||||
|
.createSign(config.apiV2Key, SignType.MD5);
|
||||||
|
|
||||||
|
log.info("统一下单参数: {}", payModel);
|
||||||
|
String resp = WxPayApi.pushOrder(false, payModel);
|
||||||
|
log.info("统一下单响应: {}", resp);
|
||||||
|
|
||||||
|
Map<String, String> 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, config.appId, config.appSecret, SignType.MD5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default String refund(String tradeNo, String refundTradeNo, BigDecimal amount) {
|
||||||
|
Config config = getConfig();
|
||||||
|
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(config.refundNotifyUrl);
|
||||||
|
|
||||||
|
String info = JSONObject.toJSONString(model);
|
||||||
|
log.info("统一退款参数: {}", info);
|
||||||
|
IJPayHttpResponse response;
|
||||||
|
try {
|
||||||
|
response = WxPayApi.v3(
|
||||||
|
RequestMethodEnum.POST,
|
||||||
|
WxDomainEnum.CHINA.toString(),
|
||||||
|
BasePayApiEnum.REFUND.toString(),
|
||||||
|
config.mchId,
|
||||||
|
getSerialNumber(),
|
||||||
|
getSerialNumber(),
|
||||||
|
config.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");
|
||||||
|
}
|
||||||
|
|
||||||
|
default String genCode(int shopId, int id, String path) {
|
||||||
|
Map<String, Object> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
default JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) {
|
||||||
|
Config config = getConfig();
|
||||||
|
String remark = remarkTxt == null ? "佣金" : remarkTxt;
|
||||||
|
String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt;
|
||||||
|
Map<String, Object> params = Map.of(
|
||||||
|
"appid", config.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", config.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",
|
||||||
|
config.mchId,
|
||||||
|
getSerialNumber(),
|
||||||
|
config.platformCertNo,
|
||||||
|
config.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue