package com.ylx.massage.service.impl; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ijpay.core.IJPayHttpResponse; import com.ijpay.core.enums.RequestMethodEnum; import com.ijpay.core.kit.PayKit; import com.ijpay.core.kit.WxPayKit; import com.ijpay.core.utils.DateTimeZoneUtil; import com.ijpay.wxpay.WxPayApi; import com.ijpay.wxpay.enums.WxDomainEnum; import com.ijpay.wxpay.enums.v3.BasePayApiEnum; import com.ijpay.wxpay.model.CloseOrderModel; import com.ijpay.wxpay.model.v3.*; import com.ylx.common.config.WechatAccountConfig; import com.ylx.common.config.WxPayConfig; import com.ylx.common.core.domain.R; import com.ylx.common.exception.ServiceException; import com.ylx.common.utils.StringUtils; import com.ylx.massage.domain.TConsumptionLog; import com.ylx.massage.domain.TRecharge; import com.ylx.massage.domain.TWxUser; import com.ylx.massage.enums.BillTypeEnum; import com.ylx.massage.mapper.TRechargeMapper; import com.ylx.massage.service.TConsumptionLogService; import com.ylx.massage.service.TRechargeService; import com.ylx.massage.service.TWxUserService; import com.ylx.massage.utils.OrderNumberGenerator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import static com.ylx.common.constant.HttpStatus.SUCCESS; /** * 充值记录表 服务实现类 */ @Service @Slf4j public class TRechargeServiceImpl extends ServiceImpl implements TRechargeService { @Resource private TWxUserService wxUserService; @Resource private OrderNumberGenerator generator; @Resource private TConsumptionLogService consumptionLogService; @Autowired private WxPayConfig wxPayProperties; @Autowired private WechatAccountConfig wechatAccountConfig; @Autowired private TRechargeMapper rechargeMapper; String serialNo; @Override @Transactional(rollbackFor = Exception.class) public TRecharge recharge(TRecharge recharge) { //增加充值记录 if (StringUtils.isBlank(recharge.getcOpenId())) { throw new ServiceException("openId不能为空"); } if (null == recharge.getdMoney() || recharge.getdMoney().compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("充值金额不能为空"); } // LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); // queryWrapper.eq(TRecharge::getcOpenId, recharge.getcOpenId()).eq(TRecharge::getPayStatus, -1).eq(TRecharge::getdMoney, recharge.getdMoney()); // TRecharge one = getOne(queryWrapper); // if (one != null) { // log.info("未支付的充值记录,{}",one.getRechargeNo()); // return one; // } recharge.setRechargeNo(generator.generateNextOrderNumber(OrderNumberGenerator.KEY_PREFIX_RECHAR)); recharge.setDtCreateTime(LocalDateTime.now()); save(recharge); //todo 调用微信支付成功后增加账户金额 return recharge; } @Override @Transactional(rollbackFor = Exception.class) public TRecharge increaseAmount(String rechargeNo) { //增加个人账户金额 TRecharge recharge = getByRechargeNo(rechargeNo); if(recharge == null){ log.error("未支付的充值记录不存在,单号:{}",rechargeNo); return null; } //修改充值状态 recharge.setPayStatus(1); this.updateById(recharge); //增加个人账户金额 TWxUser user = wxUserService.getByOpenId(recharge.getcOpenId()); user.setdBalance(user.getdBalance().add(recharge.getdMoney())); wxUserService.updateById(user); //增加消费记录 TConsumptionLog tConsumptionLog = new TConsumptionLog(); tConsumptionLog.setAmount(recharge.getdMoney()); tConsumptionLog.setBillNo(rechargeNo); tConsumptionLog.setOpenId(recharge.getcOpenId()); tConsumptionLog.setBillType(BillTypeEnum.RECHARGE.getCode()); tConsumptionLog.setNote("微信充值"); consumptionLogService.save(tConsumptionLog); log.info("微信充值成功,充值单号:{},充值金额:{},充值OpenId:{}",rechargeNo,recharge.getdMoney(),user.getcOpenid()); return recharge; } @Override public R getPay(String setOutTradeNo, BigDecimal amount, String openId, String description, String attach) throws Exception { String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3); UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel() // .setAppid(wxPayProperties.getAppId()) .setAppid(wechatAccountConfig.getMpAppId()) .setMchid(wxPayProperties.getMchId()) //商品描述 .setDescription(description) //订单号 .setOut_trade_no(setOutTradeNo) //交易结束时间 .setTime_expire(timeExpire) //附加数据 .setAttach(attach) //通知地址异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http //示例值:https://www.weixin.qq.com/wxpay/pay.php .setNotify_url(wxPayProperties.getNotifyUrl()) //支付金额以分为单位 .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue())) //交易人 .setPayer(new Payer().setOpenid(openId)); log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel)); IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), BasePayApiEnum.JS_API_PAY.toString(), wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), JSONUtil.toJsonStr(unifiedOrderModel) ); log.info("统一下单响应 {}", response); // 根据证书序列号查询对应的证书来验证签名结果 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("verifySignature: {}", verifySignature); if (response.getStatus() == SUCCESS && verifySignature) { String body = response.getBody(); JSONObject jsonObject = JSONUtil.parseObj(body); String prepayId = jsonObject.getStr("prepay_id"); Map map = WxPayKit.jsApiCreateSign(wechatAccountConfig.getMpAppId(), prepayId, wxPayProperties.getCertKeyPath()); log.info("唤起支付参数:{}", map); return R.ok(JSONUtil.toJsonStr(map)); } return R.ok(JSONUtil.toJsonStr(response)); } @Override public String refund(String outRefundNo, String transactionId, String outTradeNo, BigDecimal amount) { try { int i = amount.multiply(new BigDecimal(100)).intValue(); log.info("商户退款单号: {}", outRefundNo); //退款商品 List list = new ArrayList<>(); RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail() //商户侧商品编码 .setMerchant_goods_id("123") //商品名称 .setGoods_name("IJPay 测试") //商品单价 .setUnit_price(1) //商品退款金额 .setRefund_amount(1) //商品退货数量 .setRefund_quantity(1); list.add(refundGoodsDetail); RefundModel refundModel = new RefundModel() //商户退款单号 .setOut_refund_no(outRefundNo) //退款原因 //.setReason("IJPay 测试退款") //回调地址 .setNotify_url(wxPayProperties.getRefundNotify()) //金额信息 .setAmount(new RefundAmount().setRefund(i).setTotal(i).setCurrency("CNY")); //退款商品 //.setGoods_detail(list); //商户订单号 if (StrUtil.isNotEmpty(transactionId)) { refundModel.setTransaction_id(transactionId); } if (StrUtil.isNotEmpty(outTradeNo)) { refundModel.setOut_trade_no(outTradeNo); } log.info("退款参数 {}", JSONUtil.toJsonStr(refundModel)); IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), BasePayApiEnum.REFUND.toString(), wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), JSONUtil.toJsonStr(refundModel) ); // 根据证书序列号查询对应的证书来验证签名结果 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("verifySignature: {}", verifySignature); log.info("退款响应 {}", response); if (verifySignature) { return response.getBody(); } } catch (Exception e) { log.error("系统异常", e); return e.getMessage(); } return null; } @Override public BigDecimal getRecharge(String jsid, Date startDate, Date endDate) { return rechargeMapper.getRecharge(jsid, startDate, endDate); } @Override public boolean closeWeChatOrder(String outTradeNo) { try { // 关闭订单API路径:/v3/pay/transactions/out-trade-no/{out_trade_no}/close String closeOrderApi = String.format("/v3/pay/transactions/out-trade-no/%s/close", outTradeNo); log.info("关闭微信订单,订单号:{},API路径:{}", outTradeNo, closeOrderApi); CloseOrderModel closeOrderModel = new CloseOrderModel(); closeOrderModel.setMch_id(wxPayProperties.getMchId()); IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), closeOrderApi, wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), JSONUtil.toJsonStr(closeOrderModel) ); log.info("关闭微信订单响应:{},状态码:{}", response.getBody(), response.getStatus()); // 200表示成功 if (response.getStatus() == SUCCESS) { return true; } // 微信侧无订单时返回ORDERNOTEXIST错误码(订单已支付或从未创建),视为关闭成功 if (response.getStatus() == 400 && response.getBody() != null && response.getBody().contains("ORDERNOTEXIST")) { log.info("微信侧无此订单,无需关闭,订单号:{}", outTradeNo); return true; } return false; } catch (Exception e) { log.error("关闭微信订单异常,订单号:{},错误:{}", outTradeNo, e.getMessage(), e); // 微信关单失败不影响后续流程,忽略错误 return true; } } @Override public int countSuccessRecharges(String openId, Date rechargeQueryTime) { // 统计用户在指定时间起(含)之后成功的充值次数 // 支付状态为 1 表示已支付(充值成功) if (StrUtil.isBlank(openId)) { return 0; } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TRecharge::getcOpenId, openId) .eq(TRecharge::getPayStatus, 1); // rechargeQueryTime 为 null 表示不限制起点(永久活动/全量统计) if (rechargeQueryTime != null) { LocalDateTime start = LocalDateTime.ofInstant(rechargeQueryTime.toInstant(), ZoneId.systemDefault()); queryWrapper.ge(TRecharge::getDtCreateTime, start); } long cnt = this.count(queryWrapper); return cnt > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) cnt; } /** * 获取证书序列号 * @return String 证书序列号 */ private String getSerialNumber() { if (StrUtil.isEmpty(serialNo)) { // 获取证书序列号 X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath()); if (null != certificate) { serialNo = certificate.getSerialNumber().toString(16).toUpperCase(); // 提前两天检查证书是否有效 boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2); log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN)); } // System.out.println("输出证书信息:\n" + certificate.toString()); // // 输出关键信息,截取部分并进行标记 // System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16)); // System.out.println("版本号:" + certificate.getVersion()); // System.out.println("签发者:" + certificate.getIssuerDN()); // System.out.println("有效起始日期:" + certificate.getNotBefore()); // System.out.println("有效终止日期:" + certificate.getNotAfter()); // System.out.println("主体名:" + certificate.getSubjectDN()); // System.out.println("签名算法:" + certificate.getSigAlgName()); // System.out.println("签名:" + certificate.getSignature().toString()); } System.out.println("serialNo:" + serialNo); return serialNo; } private TRecharge getByRechargeNo(String rechargeNo) { return getOne(new LambdaQueryWrapper().eq(TRecharge::getRechargeNo, rechargeNo).eq(TRecharge::getPayStatus, -1)); } }