| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- 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<TRechargeMapper, TRecharge> 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<TRecharge> 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<String, String> 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<RefundGoodsDetail> 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<TRecharge> 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<TRecharge>().eq(TRecharge::getRechargeNo, rechargeNo).eq(TRecharge::getPayStatus, -1));
- }
- }
|