Pu Zhibing
2025-01-01 513961ecebcfd0619ede7a7edb7ac5e27de28d26
增加第三方支付
13个文件已修改
1个文件已删除
49个文件已添加
5044 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/WithdrawalRequests.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/Shop.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/ShopBalanceStatement.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ObsUploadUtil.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/WithdrawalRequestsController.java 117 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/WithdrawalRequestsServiceImpl.java 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/MD5AndKL.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/PaymentUtil.java 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/TransferUtil.java 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/AccountBalanceQueryResult.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/CloseOrderResult.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/FrpCodeEnum.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/QueryOrderResult.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/QueryRefundResult.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/RefundCallbackResult.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/RefundResult.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePay.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePayCallbackResult.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePayQueryResult.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePayResult.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/UniPayCallbackResult.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/UniPayResult.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/ShoppingCartController.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/ShoppingCartService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ShoppingCartServiceImpl.java 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/TaskUtil.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/MD5AndKL.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/PaymentUtil.java 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/TransferUtil.java 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/AccountBalanceQueryResult.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/CloseOrderResult.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/FrpCodeEnum.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/QueryOrderResult.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/QueryRefundResult.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/RefundCallbackResult.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/RefundResult.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePay.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePayCallbackResult.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePayQueryResult.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePayResult.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/UniPayCallbackResult.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/UniPayResult.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/ShopController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/ShopWithdrawController.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/ShopService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/ShopServiceImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/MD5AndKL.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/PaymentUtil.java 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/TransferUtil.java 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/AccountBalanceQueryResult.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/CloseOrderResult.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/FrpCodeEnum.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/QueryOrderResult.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/QueryRefundResult.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/RefundCallbackResult.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/RefundResult.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePay.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePayCallbackResult.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePayQueryResult.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePayResult.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/UniPayCallbackResult.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/UniPayResult.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/SaveWithdrawalAccount.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/WithdrawalRequests.java
@@ -49,6 +49,14 @@
    @TableField("withdrawal_amount")
    private BigDecimal withdrawalAmount;
    @ApiModelProperty(value = "到账金额")
    @TableField("arrival_amount")
    private BigDecimal arrivalAmount;
    @ApiModelProperty(value = "手续费")
    @TableField("service_charge")
    private BigDecimal serviceCharge;
    @ApiModelProperty(value = "1微信2银行卡")
    @TableField("withdrawal_method")
    private Integer withdrawalMethod;
@@ -64,6 +72,14 @@
    @ApiModelProperty(value = "审核状态 1'待审核',2'审核通过',3'审核拒绝' ")
    @TableField("audit_status")
    private Integer auditStatus;
    @ApiModelProperty(value = "状态(1=处理中,2=成功)")
    @TableField("status")
    private Integer status;
    @ApiModelProperty(value = "到账时间")
    @TableField("arrival_time")
    private LocalDateTime arrivalTime;
    @TableField(exist = false)
    private String userName;
    @TableField(exist = false)
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/Shop.java
@@ -169,6 +169,26 @@
    @ApiModelProperty(value = "添加时间")
    @TableField("create_time")
    private LocalDateTime createTime;
    @ApiModelProperty("报备商户号")
    @TableField("tradeMerchantNo")
    private String tradeMerchantNo;
    @ApiModelProperty("收款银行卡号")
    @TableField("receiverAccountNoEnc")
    private String receiverAccountNoEnc;
    @ApiModelProperty("收款银行卡持卡人名称")
    @TableField("receiverNameEnc")
    private String receiverNameEnc;
    @ApiModelProperty("账户类型(对私账户201,对公账户204)")
    @TableField("receiverAccountType")
    private Integer receiverAccountType;
    @ApiModelProperty("收款账户联行号")
    @TableField("receiverBankChannelNo")
    private String receiverBankChannelNo;
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/ShopBalanceStatement.java
@@ -39,9 +39,9 @@
    @TableField("shop_id")
    private Integer shopId;
    @ApiModelProperty(value = "变动类型(1=门店分佣,2=下级门店分佣,3=门店服务费)")
    @ApiModelProperty(value = "变动类型(1=门店分佣,2=下级门店分佣,3=门店服务费,4=关联用户分佣,5=提现)")
    @TableField("type")
    @Excel(name = "变更类型",readConverterExp = "1=门店分佣,2=下级门店分佣,3=门店服务费,4=关联用户分佣")
    @Excel(name = "变更类型",readConverterExp = "1=门店分佣,2=下级门店分佣,3=门店服务费,4=关联用户分佣,5=提现")
    private Integer type;
    @ApiModelProperty(value = "历史余额")
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ObsUploadUtil.java
File was deleted
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/WithdrawalRequestsController.java
@@ -1,6 +1,7 @@
package com.ruoyi.account.controller;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ruoyi.account.api.model.AppUser;
import com.ruoyi.account.api.model.BalanceChangeRecord;
@@ -8,7 +9,13 @@
import com.ruoyi.account.dto.WithQuery;
import com.ruoyi.account.dto.WithdrawalRequestsDTO;
import com.ruoyi.account.service.AppUserService;
import com.ruoyi.account.service.BalanceChangeRecordService;
import com.ruoyi.account.service.WithdrawalRequestsService;
import com.ruoyi.account.util.payment.TransferUtil;
import com.ruoyi.account.util.payment.model.AccountBalanceQueryResult;
import com.ruoyi.account.util.payment.model.SinglePay;
import com.ruoyi.account.util.payment.model.SinglePayCallbackResult;
import com.ruoyi.account.util.payment.model.SinglePayResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.web.domain.AjaxResult;
import io.swagger.annotations.Api;
@@ -17,6 +24,9 @@
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
/**
 * <p>
@@ -36,6 +46,9 @@
    @Resource
    private AppUserService appUserService;
    @Resource
    private BalanceChangeRecordService balanceChangeRecordService;
    /**
     * 提现申请
     */
@@ -46,6 +59,9 @@
        return AjaxResult.success();
    }
    @PostMapping("/page")
    @ApiOperation(value = "提现申请列表", tags = {"后台"})
    public R<IPage<WithdrawalRequests>> page(@RequestBody WithQuery withQuery){
@@ -55,24 +71,101 @@
        }
        return R.ok(withdrawalRequestsIPage);
    }
    @PostMapping("/auth")
    @ApiOperation(value = "提现申请审批", tags = {"后台"})
    public R<IPage<WithdrawalRequests>> auth(@RequestParam Long id,@ApiParam("2'审核通过',3'审核拒绝'") Integer auditStatus){
        WithdrawalRequests byId = withdrawalRequestsService.getById(id);
        if (auditStatus==2){
            //将用户待审核金额减少,已提现金额增加
            AppUser byId1 = appUserService.getById(byId.getAppUserId());
            byId1.setWithdrawnAmount(byId1.getWithdrawnAmount().add(byId.getWithdrawalAmount()));
            appUserService.updateById(byId1);
            //执行对应的第三方提现 todo
    public R auth(@RequestParam Long id,@ApiParam("2'审核通过',3'审核拒绝'") Integer auditStatus){
        WithdrawalRequests withdrawal = withdrawalRequestsService.getById(id);
        BigDecimal withdrawalAmount = withdrawal.getWithdrawalAmount();
        if(withdrawal.getAuditStatus() != 1){
            return R.fail("不能重复审核");
        }
        //通过
        byId.setAuditStatus(auditStatus);
        withdrawalRequestsService.updateById(byId);
        if (auditStatus==2){
            //先检查账户余额是否充足
            AccountBalanceQueryResult accountBalanceQueryResult = TransferUtil.accountBalanceQuery();
            if(null == accountBalanceQueryResult){
                return R.fail("查询账户余额出错");
            }
            Double useAbleSettAmount = accountBalanceQueryResult.getUseAbleSettAmount();
            if(useAbleSettAmount < withdrawal.getArrivalAmount().doubleValue()){
                return R.fail("账户可用余额不足,请先补充账户余额");
            }
            //执行转账操作
            if(withdrawal.getWithdrawalMethod() == 2){
                //银行卡转账
                SinglePay singlePay = new SinglePay();
                singlePay.setTradeMerchantNo("");
                singlePay.setMerchantOrderNo(withdrawal.getId().toString());
                singlePay.setReceiverAccountNoEnc(withdrawal.getBankCardNumber());
                singlePay.setReceiverNameEnc(withdrawal.getAccountHolder());
                singlePay.setReceiverAccountType(201);
                singlePay.setPaidAmount(withdrawal.getArrivalAmount().doubleValue());
                singlePay.setPaidDesc("账户余额提现");
                singlePay.setPaidUse("205");
                singlePay.setCallbackUrl("/account/withdrawal-requests/withdrawalCallback");
                SinglePayResult singlePayResult = TransferUtil.singlePay(singlePay);
                if(null == singlePayResult){
                    return R.fail("转账失败");
                }
                withdrawal.setStatus(1);
            }else{
                //微信转账
            }
        }
        if(3 == auditStatus){
            //回退扣除的金额,添加明细记录
            //修改用户的可提现金额
            AppUser appUser = appUserService.getById(withdrawal.getAppUserId());
            BigDecimal withdrawableAmount = appUser.getWithdrawableAmount();
            BigDecimal withdrawnAmount = appUser.getWithdrawnAmount();
            BigDecimal balance = appUser.getBalance();
            appUser.setWithdrawableAmount(withdrawableAmount.add(withdrawalAmount).setScale(2, RoundingMode.HALF_EVEN));
            appUser.setWithdrawnAmount(withdrawnAmount.subtract(withdrawalAmount).setScale(2, RoundingMode.HALF_EVEN));
            appUser.setBalance(appUser.getBalance().add(withdrawalAmount).setScale(2, RoundingMode.HALF_EVEN));
            appUserService.updateById(appUser);
            //添加变动明细
            BalanceChangeRecord balanceChangeRecord = new BalanceChangeRecord();
            balanceChangeRecord.setAppUserId(appUser.getId());
            balanceChangeRecord.setOrderId(withdrawal.getId());
            balanceChangeRecord.setChangeType(2);
            balanceChangeRecord.setBeforeAmount(balance);
            balanceChangeRecord.setChangeAmount(withdrawalAmount);
            balanceChangeRecord.setAfterAmount(appUser.getBalance());
            balanceChangeRecord.setDelFlag(0);
            balanceChangeRecord.setCreateTime(LocalDateTime.now());
            balanceChangeRecordService.save(balanceChangeRecord);
        }
        withdrawal.setAuditStatus(auditStatus);
        withdrawalRequestsService.updateById(withdrawal);
        return R.ok();
    }
    /**
     * 提现审核通过后转账回调通知
     * @param singlePayCallbackResult
     */
    @ResponseBody
    @PostMapping("/withdrawalCallback")
    public Object withdrawalCallback(@RequestBody SinglePayCallbackResult singlePayCallbackResult){
        Integer status = singlePayCallbackResult.getStatus();
        if(203 == status){
            String merchantOrderNo = singlePayCallbackResult.getMerchantOrderNo();
            WithdrawalRequests withdrawalRequests = withdrawalRequestsService.getById(merchantOrderNo);
            if(1 == withdrawalRequests.getStatus()){
                withdrawalRequests.setStatus(2);
                withdrawalRequests.setArrivalTime(LocalDateTime.now());
                withdrawalRequestsService.updateById(withdrawalRequests);
            }
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("statusCode", 2001);
            return jsonObject;
        }
        return new JSONObject();
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/WithdrawalRequestsServiceImpl.java
@@ -5,11 +5,14 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.feignClient.AppUserClient;
import com.ruoyi.account.api.model.AppUser;
import com.ruoyi.account.api.model.BalanceChangeRecord;
import com.ruoyi.account.api.model.UserClickLog;
import com.ruoyi.account.api.model.WithdrawalRequests;
import com.ruoyi.account.dto.WithQuery;
import com.ruoyi.account.dto.WithdrawalRequestsDTO;
import com.ruoyi.account.mapper.WithdrawalRequestsMapper;
import com.ruoyi.account.service.AppUserService;
import com.ruoyi.account.service.BalanceChangeRecordService;
import com.ruoyi.account.service.VipSettingService;
import com.ruoyi.account.service.WithdrawalRequestsService;
import com.ruoyi.common.core.exception.ServiceException;
@@ -23,6 +26,7 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
/**
 * <p>
@@ -37,23 +41,26 @@
    @Resource
    private TokenService tokenService;
    @Resource
    private AppUserClient appUserClient;
    private AppUserService appUserService;
    @Resource
    private VipSettingService vipSettingService;
    @Resource
    private BalanceChangeRecordService balanceChangeRecordService;
    public static final BigDecimal MAX_WITHDRAWAL_AMOUNT = new BigDecimal("200");
    public static final BigDecimal VIP_WITHDRAWAL_FEE_DENOMINATOR = new BigDecimal("100");
    @Override
    public void withdrawalApply(WithdrawalRequestsDTO params) {
        if (params.getWithdrawalAmount().compareTo(MAX_WITHDRAWAL_AMOUNT) > 0) {
        BigDecimal withdrawalAmount = params.getWithdrawalAmount();
        if (withdrawalAmount.compareTo(MAX_WITHDRAWAL_AMOUNT) > 0) {
            throw new ServiceException("提现失败,单次提现金额不能超过200元!");
        }
        LoginUser loginUserApplet = tokenService.getLoginUserApplet();
        AppUser appUser = appUserClient.getAppUserById(loginUserApplet.getUserid());
        if (appUser.getWithdrawableAmount().compareTo(params.getWithdrawalAmount()) < 0) {
        AppUser appUser = appUserService.getById(loginUserApplet.getUserid());
        if (appUser.getWithdrawableAmount().compareTo(withdrawalAmount) < 0) {
            throw new ServiceException("提现失败,可提现金额不足!");
        }
@@ -63,22 +70,46 @@
            throw new ServiceException("提现失败,当前会员等级不允许提现!");
        }
        BigDecimal vipWithdrawalMinAmount = vipSetting.getVipWithdrawalMinAmount();
        if (params.getWithdrawalAmount().compareTo(vipWithdrawalMinAmount) < 0) {
        if (withdrawalAmount.compareTo(vipWithdrawalMinAmount) < 0) {
            throw new ServiceException("提现失败,提现金额不能小于" + vipWithdrawalMinAmount + "元!");
        }
        // 提现手续费
        BigDecimal vipWithdrawalFee = vipSetting.getVipWithdrawalFee()
                .divide(VIP_WITHDRAWAL_FEE_DENOMINATOR, 2, RoundingMode.HALF_UP);
        // 减去手续费
        BigDecimal multiply = params.getWithdrawalAmount().multiply(vipWithdrawalFee);
        params.setWithdrawalAmount(params.getWithdrawalAmount()
                .subtract(params.getWithdrawalAmount().multiply(vipWithdrawalFee)));
                .subtract(multiply));
        WithdrawalRequests withdrawalRequests = new WithdrawalRequests();
        BeanUtils.copyBeanProp(withdrawalRequests, params);
        withdrawalRequests.setWithdrawalAmount(withdrawalAmount);
        withdrawalRequests.setArrivalAmount(withdrawalRequests.getWithdrawalAmount());
        withdrawalRequests.setServiceCharge(multiply);
        withdrawalRequests.setDelFlag(0);
        withdrawalRequests.setAppUserId(SecurityUtils.getUserId());
        withdrawalRequests.setAuditStatus(1);
        save(withdrawalRequests);
        //修改用户的可提现金额
        BigDecimal withdrawableAmount = appUser.getWithdrawableAmount();
        BigDecimal withdrawnAmount = appUser.getWithdrawnAmount();
        BigDecimal balance = appUser.getBalance();
        appUser.setWithdrawableAmount(withdrawableAmount.subtract(withdrawalAmount).setScale(2, RoundingMode.HALF_EVEN));
        appUser.setWithdrawnAmount(withdrawnAmount.add(withdrawalAmount).setScale(2, RoundingMode.HALF_EVEN));
        appUser.setBalance(appUser.getBalance().subtract(withdrawalAmount).setScale(2, RoundingMode.HALF_EVEN));
        appUserService.updateById(appUser);
        //添加变动明细
        BalanceChangeRecord balanceChangeRecord = new BalanceChangeRecord();
        balanceChangeRecord.setAppUserId(appUser.getId());
        balanceChangeRecord.setOrderId(withdrawalRequests.getId());
        balanceChangeRecord.setChangeType(2);
        balanceChangeRecord.setBeforeAmount(balance);
        balanceChangeRecord.setChangeAmount(withdrawalAmount);
        balanceChangeRecord.setAfterAmount(appUser.getBalance());
        balanceChangeRecord.setDelFlag(0);
        balanceChangeRecord.setCreateTime(LocalDateTime.now());
        balanceChangeRecordService.save(balanceChangeRecord);
    }
    @Override
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/MD5AndKL.java
New file
@@ -0,0 +1,112 @@
package com.ruoyi.account.util.payment;
import java.security.MessageDigest;
public class MD5AndKL {
    /**
     * MD5加码。32位
     *
     * @param inStr
     * @return
     */
    public static String MD5(String inStr) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
        byte[] md5Bytes = md5.digest(inStr.getBytes());
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
    /**
     * 可逆的加密算法
     *
     * @param inStr
     * @return
     */
    public static String KL(String inStr) {
        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        return s;
    }
    /**
     * 加密后解密
     *
     * @param inStr
     * @return
     */
    public static String JM(String inStr) {
        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String k = new String(a);
        return k;
    }
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));
        return resultSb.toString();
    }
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname)){
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            }else{
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return resultString;
    }
    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
    public static void main(String args[]) {
        System.out.println("MD5后再加密:" + KL(MD5("123456")));
        System.out.println(MD5("123456"));
        // System.out.println("加密:" + KL(MD5("123456")));
        // s = KL(s);
        // System.out.println("解密:" + KL("81dc9bdb52d04dc20036dbd8313ed055"));
        // System.out.println("解密:" + JM(KL(s)));
        // System.out.println("解密为MD5后的:" + KL(KL(MD5(s))));
        // System.out.println(JM("5d62957bb57d3e49dcf48a0df064be4c"));
        // System.out.println(MD5AndKL.KL(MD5AndKL.MD5("admin"+"87654321")));
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/PaymentUtil.java
New file
@@ -0,0 +1,277 @@
package com.ruoyi.account.util.payment;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.account.util.payment.model.*;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
/**
 * 支付工具类
 * @author zhibing.pu
 * @Date 2024/12/27 17:00
 */
@Slf4j
public class PaymentUtil {
    //微信公众号、微信小程序、微信 APP+/H5、云微小程序支付
    private static final String appId = "wxdeed472c98e42a54";
    /**
     * 商户密钥
     */
    private static final String key = "925899fcc374430f9e4b4ba3db05b448";
    /**
     * 商户号
     */
    private static final String merchantNo = "888122600004175";
    /**
     * 支付回调地址
     */
    private static final String callbackUrl = "http://221.182.45.100:9000";
    /**
     * 支付
     * @param orderNo           商户订单号
     * @param amount            订单金额
     * @param productName       商品名称
     * @param productDesc       商品描述
     * @param mp                公用回传参数
     * @param notifyUrl         服务器异步通知地址
     * @param openId            微信 Openid
     * @param tradeMerchantNo   报备商户号
     * @return
     */
    public static UniPayResult uniPay(String orderNo, Double amount, String productName, String productDesc, String mp, String notifyUrl, String openId, String tradeMerchantNo){
        String url = "https://trade.joinpay.com/tradeRt/uniPay";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.5");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //订单金额
        body.put("p3_Amount", amount);
        //交易币种
        body.put("p4_Cur", "1");
        //商品名称
        body.put("p5_ProductName", productName);
        //商品描述
        body.put("p6_ProductDesc", productDesc);
        //公用回传参数
        body.put("p7_Mp", mp);
        //服务器异步通知地址
        body.put("p9_NotifyUrl", callbackUrl + notifyUrl);
        //交易类型
        body.put("q1_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
        //微信 Openid
        body.put("q5_OpenId", openId);
        //APPID
        body.put("q7_AppId", appId);
        //报备商户号
        body.put("qa_TradeMerchantNo", tradeMerchantNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("支付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("支付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("支付接口异常:" + execute.body());
            return null;
        }
        UniPayResult uniPayResult = JSON.parseObject(execute.body(), UniPayResult.class);
        return uniPayResult;
    }
    /**
     * 查询支付订单
     * @param orderNo   订单号
     * @return
     */
    public static QueryOrderResult queryOrder(String orderNo){
        String url = "https://trade.joinpay.com/tradeRt/queryOrder";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.5");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("查询支付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("查询支付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("查询支付接口异常:" + execute.body());
            return null;
        }
        QueryOrderResult uniPayResult = JSON.parseObject(execute.body(), QueryOrderResult.class);
        return uniPayResult;
    }
    /**
     * 退款
     * @param orderNo           支付订单号
     * @param refundOrderNo     退款订单号
     * @param refundAmount      退款金额
     * @param notifyUrl         异步通知地址
     * @return
     */
    public static RefundResult refund(String orderNo, String refundOrderNo, Double refundAmount, String notifyUrl){
        String url = "https://trade.joinpay.com/tradeRt/refund";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.3");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //商户退款订单号
        body.put("p3_RefundOrderNo", refundOrderNo);
        //退款金额
        body.put("p4_RefundAmount", refundAmount);
        //服务器异步通知地址
        body.put("p6_NotifyUrl", notifyUrl);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("退款接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("退款接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("退款接口异常:" + execute.body());
            return null;
        }
        RefundResult uniPayResult = JSON.parseObject(execute.body(), RefundResult.class);
        return uniPayResult;
    }
    /**
     * 查询退款订单
     * @param refundOrderNo 退款订单号
     * @return
     */
    public static QueryRefundResult queryRefund(String refundOrderNo){
        String url = "https://trade.joinpay.com/tradeRt/refund";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.3");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户退款订单号
        body.put("p2_RefundOrderNo", refundOrderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("退款接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("退款接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("退款接口异常:" + execute.body());
            return null;
        }
        QueryRefundResult uniPayResult = JSON.parseObject(execute.body(), QueryRefundResult.class);
        return uniPayResult;
    }
    /**
     * 关闭订单(仅支持微信和支付宝)
     * @param orderNo   订单号
     * @return
     */
    public static CloseOrderResult closeOrder(String orderNo){
        String url = "https://www.joinpay.com/trade/closeOrder.action";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //交易类型
        body.put("p3_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("关闭订单接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("关闭订单接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("关闭订单接口异常:" + execute.body());
            return null;
        }
        CloseOrderResult uniPayResult = JSON.parseObject(execute.body(), CloseOrderResult.class);
        return uniPayResult;
    }
    public static String sign(JSONObject body) throws Exception{
        Set<Map.Entry<String, Object>> entries = body.entrySet();
        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (item.getKey() != null || item.getKey() != "") {
                Object val = item.getValue();
                if (!(val == "" || val == null)) {
                    sb.append(val);
                }
            }
        }
        sb.append(key);
        return MD5AndKL.MD5(sb.toString());
    }
    public static void main(String[] args) {
        UniPayResult uniPayResult = PaymentUtil.uniPay("123456", 0.01D, "测试商品", "这是用于对接支付测试的商品描述", "", "", "", "");
        System.err.println(JSON.toJSONString(uniPayResult));
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/TransferUtil.java
New file
@@ -0,0 +1,233 @@
package com.ruoyi.account.util.payment;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.account.util.payment.model.AccountBalanceQueryResult;
import com.ruoyi.account.util.payment.model.SinglePay;
import com.ruoyi.account.util.payment.model.SinglePayQueryResult;
import com.ruoyi.account.util.payment.model.SinglePayResult;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
 * 转账代付工具类
 * @author zhibing.pu
 * @Date 2024/12/30 19:54
 */
@Slf4j
public class TransferUtil {
    /**
     * 商户密钥
     */
    private static final String key = "925899fcc374430f9e4b4ba3db05b448";
    /**
     * 商户号
     */
    private static final String merchantNo = "888122600004175";
    private static final String format = "yyyy-MM-dd HH:mm:ss";
    /**
     * 支付回调地址
     */
    private static final String callbackUrl = "http://221.182.45.100:9000";
    /**
     * 单笔代付
     * @param singlePay
     * @return
     */
    public static SinglePayResult singlePay(SinglePay singlePay){
        String url = "https://www.joinpay.com/payment/pay/singlePay";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        //报备商户号
        body.put("tradeMerchantNo", singlePay.getTradeMerchantNo());
        //产品类型
        body.put("productCode", "BANK_PAY_DAILY_ORDER");
        //交易请求时间
        body.put("requestTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern(format)));
        //商户订单号
        body.put("merchantOrderNo", singlePay.getMerchantOrderNo());
        //收款账户号,收款人银行卡卡号
        body.put("receiverAccountNoEnc", singlePay.getReceiverAccountNoEnc());
        //收款人,收款人银行卡持卡人名称
        body.put("receiverNameEnc", singlePay.getReceiverNameEnc());
        //账户类型
        body.put("receiverAccountType", singlePay.getReceiverAccountType());
        //收款账户联行号 对公账户必须填写此字段
        body.put("receiverBankChannelNo", singlePay.getReceiverBankChannelNo());
        //交易金额
        body.put("paidAmount", singlePay.getPaidAmount());
        //币种
        body.put("currency", "201");
        //是否复核 复核:201,不复核:202
        body.put("isChecked", "202");
        //代付说明
        body.put("paidDesc", singlePay.getPaidDesc());
        //代付用途
        /**
         * 工资奖金 201
         * 活动经费 202
         * 养老金 203
         * 货款 204
         * 劳务费 205
         * 保险理财 206
         * 资金下发 207
         * 营业款 208
         * 退回款项 210
         * 消费款项 211
         */
        body.put("paidUse", singlePay.getPaidUse());
        //商户通知地址
        body.put("callbackUrl", callbackUrl + singlePay.getCallbackUrl());
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("单笔代付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("单笔代付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("单笔代付接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("单笔代付接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("单笔代付接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        SinglePayResult uniPayResult = jsonObject.getObject("data", SinglePayResult.class);
        return uniPayResult;
    }
    /**
     * 单笔代付查询接口
     * @param merchantOrderNo   订单号
     * @return
     */
    public static SinglePayQueryResult singlePayQuery(String merchantOrderNo){
        String url = "https://www.joinpay.com/payment/pay/singlePayQuery";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        //商户订单号
        body.put("merchantOrderNo", merchantOrderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("单笔代付查询接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("单笔代付查询接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("单笔代付查询接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("单笔代付查询接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("单笔代付查询接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        SinglePayQueryResult uniPayResult = jsonObject.getObject("data", SinglePayQueryResult.class);
        return uniPayResult;
    }
    /**
     * 可取余额查询
     * @return
     */
    public static AccountBalanceQueryResult accountBalanceQuery(){
        String url = "https://www.joinpay.com/payment/pay/accountBalanceQuery";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("可取余额查询接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("可取余额查询接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("可取余额查询接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("可取余额查询接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("可取余额查询接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        AccountBalanceQueryResult uniPayResult = jsonObject.getObject("data", AccountBalanceQueryResult.class);
        return uniPayResult;
    }
    public static String sign(JSONObject body) throws Exception{
        Set<Map.Entry<String, Object>> entries = body.entrySet();
        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (item.getKey() != null || item.getKey() != "") {
                Object val = item.getValue();
                if (!(val == "" || val == null)) {
                    sb.append(val);
                }
            }
        }
        sb.append(key);
        return MD5AndKL.MD5(sb.toString());
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/AccountBalanceQueryResult.java
New file
@@ -0,0 +1,43 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/31 10:34
 */
@Data
public class AccountBalanceQueryResult {
    /**
     * 商户号
     */
    private String userNo;
    /**
     * 商户名称
     */
    private String userName;
    /**
     * 币种
     */
    private Integer currency;
    /**
     * 可取金额
     */
    private Double useAbleSettAmount;
    /**
     * 可结算冻结金额
     */
    private Double availableSettAmountFrozen;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/CloseOrderResult.java
New file
@@ -0,0 +1,45 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2025/1/1 10:15
 */
@Data
public class CloseOrderResult {
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 业务结果 100:成功,101:失败
     */
    private String ra_Status;
    /**
     * 响应码
     * 0 系统连接超时
     * 4 服务不可用
     * 100 关单成功
     * 101 失败,详见响应码描述
     * 10080000 系统异常
     * 10080002 验证签名失败
     * 10080003 订单号不正确
     * 10080042 交易类型不合法
     * 10083001 订单正在处理中
     * 10083002 该订单请求多次交易
     * 10083003 订单已关闭,无需关单操作
     * 10083003 交易成功,无需关单操作
     * 10083004 通道系统异常,请用相同参数重新请求
     * 10083005 通道其他异常信息
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/FrpCodeEnum.java
New file
@@ -0,0 +1,57 @@
package com.ruoyi.account.util.payment.model;
/**
 * 支付类型枚举
 * @author zhibing.pu
 * @Date 2024/12/27 17:30
 */
public enum FrpCodeEnum {
    ALIPAY_NATIVE("支付宝扫码(主扫)", "ALIPAY_NATIVE"),
    ALIPAY_CARD("支付宝刷卡(被扫)", "ALIPAY_CARD"),
    ALIPAY_H5("支付宝 H5", "ALIPAY_H5"),
    ALIPAY_FWC("支付宝服务窗", "ALIPAY_FWC"),
    ALIPAY_SYT("支付宝收银台", "ALIPAY_SYT"),
    WEIXIN_NATIVE("微信扫码(主扫)", "WEIXIN_NATIVE"),
    WEIXIN_CARD("微信刷卡(被扫)", "WEIXIN_CARD"),
    WEIXIN_APP3("微信 APP+支付", "WEIXIN_APP3"),
    WEIXIN_H5_PLUS("微信 H5 支付", "WEIXIN_H5_PLUS"),
    WEIXIN_GZH("微信公众号支付", "WEIXIN_GZH"),
    WEIXIN_XCX("微信小程序支付", "WEIXIN_XCX"),
    QQ_NATIVE("QQ 扫码(主扫)", "QQ_NATIVE"),
    QQ_CARD("QQ 刷卡(被扫)", "QQ_CARD"),
    QQ_APP("QQ APP 支付", "QQ_APP"),
    QQ_H5("QQH5 支付", "QQ_H5"),
    QQ_GZH("QQ 公众号支付", "QQ_GZH"),
    UNIONPAY_NATIVE("银联扫码(主扫)", "UNIONPAY_NATIVE"),
    UNIONPAY_CARD("银联刷卡(被扫)", "UNIONPAY_CARD"),
    UNIONPAY_APP("银联 APP 支付", "UNIONPAY_APP"),
    UNIONPAY_H5("银联 H5", "UNIONPAY_H5"),
    UNIONPAY_SYT("银联统一收银台", "UNIONPAY_SYT"),
    UNIONPAY_WXMP("银联云微小程序(无感支付)", "UNIONPAY_WXMP")
    ;
    private String name;
    private String code;
    FrpCodeEnum(String name, String code) {
        this.name = name;
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/QueryOrderResult.java
New file
@@ -0,0 +1,87 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:30
 */
@Data
public class QueryOrderResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 商品名称
     */
    private String r4_ProductName;
    /**
     * 交易流水号
     */
    private String r5_TrxNo;
    /**
     * 银行流水号
     */
    private String r6_BankTrxNo;
    /**
     * 订单手续费
     */
    private Double r7_Fee;
    /**
     * 交易类型
     */
    private String r8_FrpCode;
    /**
     * 订单状态 100:成功,101:失败,102:已创建,105:订单已关闭
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 用户标识
     */
    private String rd_OpenId;
    /**
     * 平台优惠金额
     */
    private Double re_DiscountAmount;
    /**
     * 支付时间
     */
    private String rf_PayTime;
    /**
     * 卡类型
     */
    private String rh_cardType;
    /**
     * 银行编码
     */
    private String rj_BankCode;
    /**
     * 签约 ID
     */
    private String rl_ContractId;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/QueryRefundResult.java
New file
@@ -0,0 +1,62 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:56
 */
@Data
public class QueryRefundResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户退款订单号
     */
    private String r2_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r3_RefundAmount;
    /**
     * 退款流水号
     */
    private String r4_RefundTrxNo;
    /**
     * 退款完成时间
     */
    private String r5_RefundCompleteTime;
    /**
     * 退款渠道
     */
    private String r8_RefundWay;
    /**
     * 退款入账账户
     */
    private String r9_ReceiveAccountNo;
    /**
     * 退款状态
     * 100:退款成功
     * 101:退款失败
     * 102:退款处理中
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/RefundCallbackResult.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:50
 */
@Data
public class RefundCallbackResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 商户退款订单号
     */
    private String r3_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r4_RefundAmount;
    /**
     * 商户退款流水号
     */
    private String r5_RefundTrxNo;
    /**
     * 退款完成时间
     */
    private String r6_RefundCompleteTime;
    /**
     * 退款渠道
     */
    private String r7_RefundWay;
    /**
     * 退款入账账户
     */
    private String r8_ReceiveAccountNo;
    /**
     * 退款状态 100:成功;101:失败
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/RefundResult.java
New file
@@ -0,0 +1,59 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:43
 */
@Data
public class RefundResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 商户退款订单号
     */
    private String r3_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r4_RefundAmount;
    /**
     * 商户退款流水号
     */
    private String r5_RefundTrxNo;
    /**
     * 退款申请状态
     * 100:成功,
     * 101:失败 。
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 营销退款金额
     */
    private Double rd_MarketRefAmount;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePay.java
New file
@@ -0,0 +1,61 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 20:28
 */
@Data
public class SinglePay {
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 收款账户号,收款人银行卡卡号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人,收款人银行卡持卡人名称
     */
    private String receiverNameEnc;
    /**
     * 账户类型 对私账户201,对公账户204
     */
    private Integer receiverAccountType;
    /**
     * 收款账户联行号 对公账户必须填写此字段
     */
    private String receiverBankChannelNo;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 代付说明
     */
    private String paidDesc;
    /**
     * 代付用途
     * 工资奖金 201
     * 活动经费 202
     * 养老金 203
     * 货款 204
     * 劳务费 205
     * 保险理财 206
     * 资金下发 207
     * 营业款 208
     * 退回款项 210
     * 消费款项 211
     */
    private String paidUse;
    /**
     * 商户通知地址
     */
    private String callbackUrl;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePayCallbackResult.java
New file
@@ -0,0 +1,68 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 *
 * @author zhibing.pu
 * @Date 2024/12/30 20:39
 */
@Data
public class SinglePayCallbackResult {
    /**
     * 交易状态
     * 201 批次已创建 汇聚受理并创建该批次的初始状态
     * 202 处理中 批次正在处理中状态
     * 203 处理完成 批次中的每笔明细都明确了代付结果
     * 204 批次不存在 汇聚未受理该批次的请求,找不到该批次,明确失败
     */
    private Integer status;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorCodeDesc;
    /**
     * 商户编号
     */
    private String userNo;
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 平台流水号
     */
    private String platformSerialNo;
    /**
     * 收款账户号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人
     */
    private String receiverNameEnc;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 手续费
     */
    private String fee;
    /**
     * 完成时间
     */
    private String completeTime;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePayQueryResult.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/31 10:10
 */
@Data
public class SinglePayQueryResult {
    /**
     * 单笔代付查询请求的交易状态
     */
    private Integer status;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 商户号
     */
    private String userNo;
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 平台流水号
     */
    private String platformSerialNo;
    /**
     * 收款账户号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人
     */
    private String receiverNameEnc;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 手续费
     */
    private Double fee;
    /**
     * 完成时间
     */
    private String completeTime;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/SinglePayResult.java
New file
@@ -0,0 +1,31 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 20:36
 */
@Data
public class SinglePayResult {
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 商户编号
     */
    private String userNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/UniPayCallbackResult.java
New file
@@ -0,0 +1,93 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:00
 */
@Data
public class UniPayCallbackResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 交易币种
     */
    private String r4_Cur;
    /**
     * 公用回传参数
     */
    private String r5_Mp;
    /**
     * 支付状态
     * 100:支付成功;
     * 101:支付失败。
     */
    private String r6_Status;
    /**
     * 交易流水号
     */
    private String r7_TrxNo;
    /**
     * 银行订单号
     */
    private String r8_BankOrderNo;
    /**
     * 银行流水号
     */
    private String r9_BankTrxNo;
    /**
     * 支付时间
     */
    private String ra_PayTime;
    /**
     * 交易结果通知时间
     */
    private String rb_DealTime;
    /**
     * 银行编码
     */
    private String rc_BankCode;
    /**
     * 用户标识
     */
    private String rd_OpenId;
    /**
     * 平台优惠金额
     */
    private Double re_DiscountAmount;
    /**
     * 卡类型
     */
    private String rh_cardType;
    /**
     * 订单手续费
     */
    private Double rj_Fee;
    /**
     * 交易类型
     */
    private String rk_FrpCode;
    /**
     * 签约 ID
     */
    private String rl_ContractId;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/payment/model/UniPayResult.java
New file
@@ -0,0 +1,77 @@
package com.ruoyi.account.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 10:47
 */
@Data
public class UniPayResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 币种
     */
    private String r4_Cur;
    /**
     * 公用回传参数
     */
    private String r5_Mp;
    /**
     * 交易类型
     */
    private String r6_FrpCode;
    /**
     * 交易流水号
     */
    private String r7_TrxNo;
    /**
     * 银行商户编码
     */
    private String r8_MerchantBankCode;
    /**
     * 响应码,返回 100 时表示成功
     */
    private String ra_Code;
    /**
     * 响应码描述
     */
    private String rb_CodeMsg;
    /**
     * 1.主扫支付返回二维码地址。
     * 2.支付宝 H5,mode1/2/3 参考请求参数q9_TransactionModel 说明。
     * 3.微信 H5_PLUS,获取支付信息的 openlink,通过手机端浏览器跳转并唤起微信 APP客户端,直接打开对应的小程序进行支付。
     * 3.公众号支付:需要商户参考微信的官方文档 JSAPI 支付接口进行处理,详情请见:https://pay.weixin.qq.com/wiki/doc/api/index.html
     * 4.微信小程序支付返回支付信息。
     * 5.支付宝收银台返回支付宝收银台跳转链接,通过请求该链接跳转至支付宝。
     * 6.微信 app3 支付,返回预支付信息,集成微信 SDK 唤起小程序进行支付。
     * 7.支付宝服务窗支付返回银联交易号 trade_no,可用以唤起支付宝 APP,调起支付宝APP 收银台。
     * 8.银联 app 或银联统一收银台支付,返回预支付信息用此网址的接口调起支付。https://open.unionpay.com/tjweb/acproduct/list?apiservId=450#nav02
     * 9.银联云微小程序返回跳转地址,格式:{“cqpMpAppId”:”云闪付小程序 id”,”cqpMpPath”:”云闪付小程序 path”}
     * 10.其他类型支付返回支付信息。
     */
    private String rc_Result;
    /**
     * 二维码图片码
     */
    private String rd_Pic;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/ShoppingCartController.java
@@ -9,6 +9,7 @@
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.order.service.ShoppingCartService;
import com.ruoyi.order.util.payment.model.UniPayCallbackResult;
import com.ruoyi.order.vo.*;
import com.ruoyi.other.api.domain.GoodsShop;
import com.ruoyi.other.api.domain.Shop;
@@ -24,6 +25,9 @@
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -112,6 +116,52 @@
        return shoppingCartService.shoppingCartPayment(shoppingCartPayment);
    }
    /**
     * 订单支付回调通知
     */
    @ResponseBody
    @GetMapping("/shoppingCartPaymentCallback")
    public void shoppingCartPaymentCallback(UniPayCallbackResult uniPayCallbackResult, HttpServletResponse response){
        R callback = shoppingCartService.shoppingCartPaymentCallback(uniPayCallbackResult);
        if(callback.getCode() == 200){
            response.setStatus(200);
            PrintWriter out = null;
            try {
                out = response.getWriter();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            out.println("success");
            out.flush();
            out.close();
        }
    }
    /**
     * 快递费支付回调
     * @param uniPayCallbackResult
     * @param response
     */
    @ResponseBody
    @GetMapping("/shoppingCartMaterialFlowPaymentCallback")
    public void shoppingCartMaterialFlowPaymentCallback(UniPayCallbackResult uniPayCallbackResult, HttpServletResponse response){
        R callback = shoppingCartService.shoppingCartMaterialFlowPaymentCallback(uniPayCallbackResult);
        if(callback.getCode() == 200){
            response.setStatus(200);
            PrintWriter out = null;
            try {
                out = response.getWriter();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            out.println("success");
            out.flush();
            out.close();
        }
    }
    @ResponseBody
    @GetMapping("/getVerifiableShop")
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/ShoppingCartService.java
@@ -4,6 +4,7 @@
import com.ruoyi.account.api.model.AppUser;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.order.model.ShoppingCart;
import com.ruoyi.order.util.payment.model.UniPayCallbackResult;
import com.ruoyi.order.vo.*;
import java.util.List;
@@ -49,5 +50,28 @@
     */
    R shoppingCartPayment(ShoppingCartPayment shoppingCartPayment);
    /**
     * 订单支付回调处理逻辑
     * @param uniPayCallbackResult
     * @return
     */
    R shoppingCartPaymentCallback(UniPayCallbackResult uniPayCallbackResult);
    /**
     * 订单物流支付回调处理
     * @param uniPayCallbackResult
     * @return
     */
    R shoppingCartMaterialFlowPaymentCallback(UniPayCallbackResult uniPayCallbackResult);
    Price getPrice(AppUser appUser, Integer goodsId, Integer shopId);
    /**
     * 定时任务关闭订单
     */
    void closeOrder();
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/ShoppingCartServiceImpl.java
@@ -19,12 +19,18 @@
import com.ruoyi.order.model.OrderGood;
import com.ruoyi.order.model.ShoppingCart;
import com.ruoyi.order.service.*;
import com.ruoyi.order.util.payment.PaymentUtil;
import com.ruoyi.order.util.payment.model.CloseOrderResult;
import com.ruoyi.order.util.payment.model.UniPayCallbackResult;
import com.ruoyi.order.util.payment.model.UniPayResult;
import com.ruoyi.order.vo.*;
import com.ruoyi.other.api.domain.*;
import com.ruoyi.other.api.feignClient.*;
import com.ruoyi.other.api.vo.GetGoodsBargainPrice;
import com.ruoyi.other.api.vo.GetGoodsShopByGoodsIds;
import com.ruoyi.other.api.vo.GetSeckillActivityInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -32,9 +38,11 @@
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
@@ -106,6 +114,11 @@
    
    @Resource
    private OrderBalancePaymentService orderBalancePaymentService;
    @Resource
    private RedisTemplate redisTemplate;
    
    
    
@@ -726,9 +739,6 @@
        for (MyShoppingCartVo myShoppingCartVo : goodsList) {
            earnPoint += (myShoppingCartVo.getEarnSpendingPoints() * myShoppingCartVo.getNumber());
        }
        if(null != shoppingCartPayment.getUserAddressId()){
            userAddressClient.getUserAddressById(shoppingCartPayment.getUserAddressId()).getData();
        }
        //获取快递策略,计算快递费
        BigDecimal expressFee = BigDecimal.ZERO;
@@ -867,10 +877,18 @@
        //现金支付
        paymentMoney = paymentMoney.add(expressFee).setScale(2, RoundingMode.HALF_EVEN);
        if(1 == shoppingCartPayment.getPaymentType()){
            //调起微信支付 TODO 待完善
            //调起微信支付
            String goodsNames = goodsList.stream().map(MyShoppingCartVo::getName).collect(Collectors.joining("\n"));
            UniPayResult uniPayResult = PaymentUtil.uniPay(order.getOrderNumber(), paymentMoney.doubleValue(), order.getOrderType() == 1 ? "购买服务商品" : "购买单品商品",
                    goodsNames, "", "/order/shopping-cart/shoppingCartPaymentCallback", appUser.getWxOpenid(), "");
            if(null == uniPayResult || !"100".equals(uniPayResult.getRa_Code())){
                return R.fail(null == uniPayResult ? "支付失败" : uniPayResult.getRb_CodeMsg());
            }
            String rc_result = uniPayResult.getRc_Result();
            //将支付数据添加到redis队列中,便于定时任务去校验是否完成支付,没有完成支付支付,15分钟后关闭订单。
            long second = LocalDateTime.now().plusMinutes(15).toEpochSecond(ZoneOffset.UTC);
            redisTemplate.opsForZSet().add("OrderPayment", order.getOrderNumber(), second);
            return R.ok(rc_result);
        }
        //账户余额
        BigDecimal redPacketAmount = BigDecimal.ZERO;
@@ -956,6 +974,23 @@
        }
        //积分支付
        if(3 == shoppingCartPayment.getPaymentType()){
            //先完成快递费支付后再处理后续的逻辑
            if(expressFee.compareTo(BigDecimal.ZERO) > 0){
                if(shoppingCartPayment.getFreightPaymentType() == 1){
                    //调起微信支付
                    UniPayResult uniPayResult = PaymentUtil.uniPay(order.getOrderNumber() + appUser.getId(), expressFee.doubleValue(), order.getOrderType() == 1 ? "购买服务商品快递费" : "购买单品商品快递费",
                            "快递费", "", "/order/shopping-cart/shoppingCartMaterialFlowPaymentCallback", appUser.getWxOpenid(), "");
                    if(null == uniPayResult || !"100".equals(uniPayResult.getRa_Code())){
                        return R.fail(null == uniPayResult ? "支付失败" : uniPayResult.getRb_CodeMsg());
                    }
                    String rc_result = uniPayResult.getRc_Result();
                    //将支付数据添加到redis队列中,便于定时任务去校验是否完成支付,没有完成支付支付,15分钟后关闭订单。
                    long second = LocalDateTime.now().plusMinutes(15).toEpochSecond(ZoneOffset.UTC);
                    redisTemplate.opsForZSet().add("MaterialFlowPayment", order.getOrderNumber() + appUser.getId(), second);
                    return R.ok(rc_result);
                }
            }
            Integer lavePoint = appUser.getLavePoint();
            PointSetting pointSetting = pointSettingClient.getPointSetting(appUser.getVipId()).getData();
            int earnPoint1 = earnPoint;
@@ -989,9 +1024,6 @@
            userPointClient.saveUserPoint(userPoint);
            //如果有运费,需要先扣除账户积分,再进行支付。支付成功后修改订单状态,未支付成功则回退积分,删除的订单
            if(expressFee.compareTo(BigDecimal.ZERO) > 0){
                if(shoppingCartPayment.getFreightPaymentType() == 1){
                    //调起微信支付
                }
                if(shoppingCartPayment.getFreightPaymentType() == 2){
                    BigDecimal totalRedPacketAmount = appUser.getTotalRedPacketAmount();
                    BigDecimal totalDistributionAmount = appUser.getTotalDistributionAmount();
@@ -1077,4 +1109,163 @@
    }
    /**
     * 线上支付回调逻辑处理
     * @param uniPayCallbackResult
     * @return
     */
    @Override
    public R shoppingCartPaymentCallback(UniPayCallbackResult uniPayCallbackResult) {
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, uniPayCallbackResult.getR2_OrderNo()));
        if(null == order || order.getPayStatus() == 2){
            return R.ok();
        }
        Integer earnPoint = order.getGetPoint();
        AppUser appUser = appUserClient.getAppUserById(order.getAppUserId());
        BigDecimal paymentMoney = order.getPaymentAmount();
        //构建积分流水记录
        if(earnPoint > 0){
            PointSetting pointSetting = pointSettingClient.getPointSetting(appUser.getVipId()).getData();
            int earnPoint1 = earnPoint;
            if(null != pointSetting && 1 == pointSetting.getBuyPointOpen()){
                earnPoint1 = new BigDecimal(earnPoint1).multiply(pointSetting.getBuyPoint().divide(new BigDecimal(100))).intValue();
            }
            appUser.setShopPoint(appUser.getShopPoint() + earnPoint);
            appUser.setLavePoint(appUser.getLavePoint() + earnPoint);
            appUser.setTotalPoint(appUser.getTotalPoint() + earnPoint);
            appUser.setAvailablePoint(appUser.getAvailablePoint() + earnPoint1);
            UserPoint userPoint = new UserPoint();
            userPoint.setType(1);
            userPoint.setHistoricalPoint(appUser.getLavePoint() - earnPoint);
            userPoint.setVariablePoint(earnPoint);
            userPoint.setBalance(appUser.getLavePoint());
            userPoint.setCreateTime(LocalDateTime.now());
            userPoint.setAppUserId(appUser.getId());
            userPoint.setObjectId(order.getId());
            userPointClient.saveUserPoint(userPoint);
        }
        appUser.setShopAmount(appUser.getShopAmount().add(paymentMoney).setScale(2, RoundingMode.HALF_EVEN));
        appUser.setLastShopTime(LocalDateTime.now());
        appUserClient.editAppUserById(appUser);
        //变更等级
        appUserClient.vipUpgrade(appUser.getId());
        //修改订支付状态
        order.setPayStatus(2);
        //自提
        if(order.getOrderType() == 1 && StringUtils.isEmpty(order.getAddressJson())){
            order.setOrderStatus(2);
        }
        orderService.updateById(order);
        //删除购物车数据
        Long userid = tokenService.getLoginUserApplet().getUserid();
        List<OrderGood> list = orderGoodService.list(new LambdaQueryWrapper<OrderGood>().eq(OrderGood::getOrderId, order.getId()));
        List<Integer> goodsIds = list.stream().map(OrderGood::getGoodsId).collect(Collectors.toList());
        this.remove(new LambdaQueryWrapper<ShoppingCart>().eq(ShoppingCart::getAppUserId, userid).in(ShoppingCart::getGoodsId, goodsIds));
        return R.ok();
    }
    /**
     * 订单物流支付回调处理逻辑
     * @param uniPayCallbackResult
     * @return
     */
    @Override
    public R shoppingCartMaterialFlowPaymentCallback(UniPayCallbackResult uniPayCallbackResult) {
        String r2_orderNo = uniPayCallbackResult.getR2_OrderNo();
        r2_orderNo = r2_orderNo.substring(0, 23);
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, r2_orderNo));
        if(null == order || order.getPayStatus() == 2){
            return R.ok();
        }
        Integer earnPoint = order.getGetPoint();
        AppUser appUser = appUserClient.getAppUserById(order.getAppUserId());
        Integer lavePoint = appUser.getLavePoint();
        Integer orderPoint = order.getPoint();
        PointSetting pointSetting = pointSettingClient.getPointSetting(appUser.getVipId()).getData();
        int earnPoint1 = earnPoint;
        //计算可用积分比例
        if(null != pointSetting && 1 == pointSetting.getBuyPointOpen()){
            earnPoint1 = new BigDecimal(earnPoint1).multiply(pointSetting.getBuyPoint().divide(new BigDecimal(100))).intValue();
        }
        //扣减订单支付积分
        appUser.setLavePoint(appUser.getLavePoint() - orderPoint);
        appUser.setAvailablePoint(appUser.getAvailablePoint() - orderPoint);
        appUser.setShopPoint(appUser.getShopPoint() + earnPoint);
        appUser.setLavePoint(appUser.getLavePoint() + earnPoint);
        appUser.setAvailablePoint(appUser.getAvailablePoint() + earnPoint1);
        appUser.setTotalPoint(appUser.getTotalPoint() + earnPoint);
        appUser.setLastShopTime(LocalDateTime.now());
        appUserClient.editAppUserById(appUser);
        //变更等级
        appUserClient.vipUpgrade(appUser.getId());
        //构建积分流水记录
        UserPoint userPoint = new UserPoint();
        userPoint.setType(1);
        userPoint.setHistoricalPoint(lavePoint);
        Integer point = appUser.getLavePoint() - lavePoint;
        userPoint.setVariablePoint(point >= 0 ? point : point * -1);
        userPoint.setBalance(appUser.getLavePoint());
        userPoint.setCreateTime(LocalDateTime.now());
        userPoint.setAppUserId(appUser.getId());
        userPoint.setObjectId(order.getId());
        userPointClient.saveUserPoint(userPoint);
        //修改订支付状态
        order.setPayStatus(2);
        //自提
        if(order.getOrderType() == 1 && StringUtils.isEmpty(order.getAddressJson())){
            order.setOrderStatus(2);
        }
        orderService.updateById(order);
        //删除购物车数据
        Long userid = tokenService.getLoginUserApplet().getUserid();
        List<OrderGood> list = orderGoodService.list(new LambdaQueryWrapper<OrderGood>().eq(OrderGood::getOrderId, order.getId()));
        List<Integer> goodsIds = list.stream().map(OrderGood::getGoodsId).collect(Collectors.toList());
        this.remove(new LambdaQueryWrapper<ShoppingCart>().eq(ShoppingCart::getAppUserId, userid).in(ShoppingCart::getGoodsId, goodsIds));
        return R.ok();
    }
    /**
     * 定时任务关闭订单
     */
    @Override
    public void closeOrder() {
        //订单支付数据
        long second = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
        Set<String> orderPayment = redisTemplate.opsForZSet().range("OrderPayment", 0, second);
        for (String code : orderPayment) {
            Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, code));
            if(null == order || order.getPayStatus() != 1){
                continue;
            }
            //开始执行关闭订单操作
            CloseOrderResult closeOrderResult = PaymentUtil.closeOrder(code);
            if((null == closeOrderResult || !closeOrderResult.getRa_Status().equals("100")) &&
                    Arrays.asList("0", "4", "101", "10080000", "10080002", "10083004", "10083005").contains(closeOrderResult.getRb_Code())){
                redisTemplate.opsForZSet().add("OrderPayment", code, 0);
                log.error("关闭订单失败:{}---->{}", code, JSON.toJSONString(closeOrderResult));
            }
        }
        //快递支付
        Set<String> materialFlowPayment = redisTemplate.opsForZSet().range("MaterialFlowPayment", 0, second);
        for (String code : materialFlowPayment) {
            code = code.substring(0, 23);
            Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, code));
            if(null == order || order.getPayStatus() != 1){
                continue;
            }
            //开始执行关闭订单操作
            CloseOrderResult closeOrderResult = PaymentUtil.closeOrder(code);
            if((null == closeOrderResult || !closeOrderResult.getRa_Status().equals("100")) &&
                    Arrays.asList("0", "4", "101", "10080000", "10080002", "10083004", "10083005").contains(closeOrderResult.getRb_Code())){
                redisTemplate.opsForZSet().add("MaterialFlowPayment", code, 0);
                log.error("关闭订单失败:{}---->{}", code, JSON.toJSONString(closeOrderResult));
            }
        }
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/TaskUtil.java
@@ -1,6 +1,7 @@
package com.ruoyi.order.util;
import com.ruoyi.order.service.CommissionService;
import com.ruoyi.order.service.ShoppingCartService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -17,9 +18,16 @@
    @Resource
    private CommissionService commissionService;
    @Resource
    private ShoppingCartService shoppingCartService;
    @Scheduled(fixedRate = 60000)
    public void taskMonth() {
        commissionService.calculationCommission();
        shoppingCartService.closeOrder();
    }
    // 每天晚上23:59:59执行的定时任务
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/MD5AndKL.java
New file
@@ -0,0 +1,112 @@
package com.ruoyi.order.util.payment;
import java.security.MessageDigest;
public class MD5AndKL {
    /**
     * MD5加码。32位
     *
     * @param inStr
     * @return
     */
    public static String MD5(String inStr) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
        byte[] md5Bytes = md5.digest(inStr.getBytes());
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
    /**
     * 可逆的加密算法
     *
     * @param inStr
     * @return
     */
    public static String KL(String inStr) {
        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        return s;
    }
    /**
     * 加密后解密
     *
     * @param inStr
     * @return
     */
    public static String JM(String inStr) {
        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String k = new String(a);
        return k;
    }
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));
        return resultSb.toString();
    }
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname)){
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            }else{
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return resultString;
    }
    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
    public static void main(String args[]) {
        System.out.println("MD5后再加密:" + KL(MD5("123456")));
        System.out.println(MD5("123456"));
        // System.out.println("加密:" + KL(MD5("123456")));
        // s = KL(s);
        // System.out.println("解密:" + KL("81dc9bdb52d04dc20036dbd8313ed055"));
        // System.out.println("解密:" + JM(KL(s)));
        // System.out.println("解密为MD5后的:" + KL(KL(MD5(s))));
        // System.out.println(JM("5d62957bb57d3e49dcf48a0df064be4c"));
        // System.out.println(MD5AndKL.KL(MD5AndKL.MD5("admin"+"87654321")));
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/PaymentUtil.java
New file
@@ -0,0 +1,276 @@
package com.ruoyi.order.util.payment;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.order.util.payment.model.*;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
/**
 * 支付工具类
 * @author zhibing.pu
 * @Date 2024/12/27 17:00
 */
@Slf4j
public class PaymentUtil {
    //微信公众号、微信小程序、微信 APP+/H5、云微小程序支付
    private static final String appId = "wxdeed472c98e42a54";
    /**
     * 商户密钥
     */
    private static final String key = "925899fcc374430f9e4b4ba3db05b448";
    /**
     * 商户号
     */
    private static final String merchantNo = "888122600004175";
    /**
     * 支付回调地址
     */
    private static final String callbackUrl = "http://221.182.45.100:9000";
    /**
     * 支付
     * @param orderNo           商户订单号
     * @param amount            订单金额
     * @param productName       商品名称
     * @param productDesc       商品描述
     * @param mp                公用回传参数
     * @param notifyUrl         服务器异步通知地址
     * @param openId            微信 Openid
     * @param tradeMerchantNo   报备商户号
     * @return
     */
    public static UniPayResult uniPay(String orderNo, Double amount, String productName, String productDesc, String mp, String notifyUrl, String openId, String tradeMerchantNo){
        String url = "https://trade.joinpay.com/tradeRt/uniPay";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.5");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //订单金额
        body.put("p3_Amount", amount);
        //交易币种
        body.put("p4_Cur", "1");
        //商品名称
        body.put("p5_ProductName", productName);
        //商品描述
        body.put("p6_ProductDesc", productDesc);
        //公用回传参数
        body.put("p7_Mp", mp);
        //服务器异步通知地址
        body.put("p9_NotifyUrl", callbackUrl + notifyUrl);
        //交易类型
        body.put("q1_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
        //微信 Openid
        body.put("q5_OpenId", openId);
        //APPID
        body.put("q7_AppId", appId);
        //报备商户号
        body.put("qa_TradeMerchantNo", tradeMerchantNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("支付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("支付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("支付接口异常:" + execute.body());
            return null;
        }
        UniPayResult uniPayResult = JSON.parseObject(execute.body(), UniPayResult.class);
        return uniPayResult;
    }
    /**
     * 查询支付订单
     * @param orderNo   订单号
     * @return
     */
    public static QueryOrderResult queryOrder(String orderNo){
        String url = "https://trade.joinpay.com/tradeRt/queryOrder";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.5");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("查询支付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("查询支付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("查询支付接口异常:" + execute.body());
            return null;
        }
        QueryOrderResult uniPayResult = JSON.parseObject(execute.body(), QueryOrderResult.class);
        return uniPayResult;
    }
    /**
     * 退款
     * @param orderNo           支付订单号
     * @param refundOrderNo     退款订单号
     * @param refundAmount      退款金额
     * @param notifyUrl         异步通知地址
     * @return
     */
    public static RefundResult refund(String orderNo, String refundOrderNo, Double refundAmount, String notifyUrl){
        String url = "https://trade.joinpay.com/tradeRt/refund";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.3");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //商户退款订单号
        body.put("p3_RefundOrderNo", refundOrderNo);
        //退款金额
        body.put("p4_RefundAmount", refundAmount);
        //服务器异步通知地址
        body.put("p6_NotifyUrl", notifyUrl);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("退款接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("退款接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("退款接口异常:" + execute.body());
            return null;
        }
        RefundResult uniPayResult = JSON.parseObject(execute.body(), RefundResult.class);
        return uniPayResult;
    }
    /**
     * 查询退款订单
     * @param refundOrderNo 退款订单号
     * @return
     */
    public static QueryRefundResult queryRefund(String refundOrderNo){
        String url = "https://trade.joinpay.com/tradeRt/refund";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.3");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户退款订单号
        body.put("p2_RefundOrderNo", refundOrderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("退款接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("退款接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("退款接口异常:" + execute.body());
            return null;
        }
        QueryRefundResult uniPayResult = JSON.parseObject(execute.body(), QueryRefundResult.class);
        return uniPayResult;
    }
    /**
     * 关闭订单(仅支持微信和支付宝)
     * @param orderNo   订单号
     * @return
     */
    public static CloseOrderResult closeOrder(String orderNo){
        String url = "https://www.joinpay.com/trade/closeOrder.action";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //交易类型
        body.put("p3_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("关闭订单接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("关闭订单接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("关闭订单接口异常:" + execute.body());
            return null;
        }
        CloseOrderResult uniPayResult = JSON.parseObject(execute.body(), CloseOrderResult.class);
        return uniPayResult;
    }
    public static String sign(JSONObject body) throws Exception{
        Set<Map.Entry<String, Object>> entries = body.entrySet();
        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (item.getKey() != null || item.getKey() != "") {
                Object val = item.getValue();
                if (!(val == "" || val == null)) {
                    sb.append(val);
                }
            }
        }
        sb.append(key);
        return MD5AndKL.MD5(sb.toString());
    }
    public static void main(String[] args) {
        UniPayResult uniPayResult = PaymentUtil.uniPay("123456", 0.01D, "测试商品", "这是用于对接支付测试的商品描述", "", "", "", "");
        System.err.println(JSON.toJSONString(uniPayResult));
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/TransferUtil.java
New file
@@ -0,0 +1,226 @@
package com.ruoyi.order.util.payment;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.order.util.payment.model.*;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
 * 转账代付工具类
 * @author zhibing.pu
 * @Date 2024/12/30 19:54
 */
@Slf4j
public class TransferUtil {
    /**
     * 商户密钥
     */
    private static final String key = "925899fcc374430f9e4b4ba3db05b448";
    /**
     * 商户号
     */
    private static final String merchantNo = "888122600004175";
    private static final String format = "yyyy-MM-dd HH:mm:ss";
    /**
     * 单笔代付
     * @param singlePay
     * @return
     */
    public static SinglePayResult singlePay(SinglePay singlePay){
        String url = "https://www.joinpay.com/payment/pay/singlePay";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        //报备商户号
        body.put("tradeMerchantNo", singlePay.getTradeMerchantNo());
        //产品类型
        body.put("productCode", "BANK_PAY_DAILY_ORDER");
        //交易请求时间
        body.put("requestTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern(format)));
        //商户订单号
        body.put("merchantOrderNo", singlePay.getMerchantOrderNo());
        //收款账户号,收款人银行卡卡号
        body.put("receiverAccountNoEnc", singlePay.getReceiverAccountNoEnc());
        //收款人,收款人银行卡持卡人名称
        body.put("receiverNameEnc", singlePay.getReceiverNameEnc());
        //账户类型
        body.put("receiverAccountType", singlePay.getReceiverAccountType());
        //收款账户联行号 对公账户必须填写此字段
        body.put("receiverBankChannelNo", singlePay.getReceiverBankChannelNo());
        //交易金额
        body.put("paidAmount", singlePay.getPaidAmount());
        //币种
        body.put("currency", "201");
        //是否复核 复核:201,不复核:202
        body.put("isChecked", "202");
        //代付说明
        body.put("paidDesc", singlePay.getPaidDesc());
        //代付用途
        /**
         * 工资奖金 201
         * 活动经费 202
         * 养老金 203
         * 货款 204
         * 劳务费 205
         * 保险理财 206
         * 资金下发 207
         * 营业款 208
         * 退回款项 210
         * 消费款项 211
         */
        body.put("paidUse", singlePay.getPaidUse());
        //商户通知地址
        body.put("callbackUrl", singlePay.getCallbackUrl());
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("单笔代付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("单笔代付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("单笔代付接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("单笔代付接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("单笔代付接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        SinglePayResult uniPayResult = jsonObject.getObject("data", SinglePayResult.class);
        return uniPayResult;
    }
    /**
     * 单笔代付查询接口
     * @param merchantOrderNo   订单号
     * @return
     */
    public static SinglePayQueryResult singlePayQuery(String merchantOrderNo){
        String url = "https://www.joinpay.com/payment/pay/singlePayQuery";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        //商户订单号
        body.put("merchantOrderNo", merchantOrderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("单笔代付查询接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("单笔代付查询接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("单笔代付查询接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("单笔代付查询接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("单笔代付查询接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        SinglePayQueryResult uniPayResult = jsonObject.getObject("data", SinglePayQueryResult.class);
        return uniPayResult;
    }
    /**
     * 可取余额查询
     * @return
     */
    public static AccountBalanceQueryResult accountBalanceQuery(){
        String url = "https://www.joinpay.com/payment/pay/accountBalanceQuery";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("可取余额查询接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("可取余额查询接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("可取余额查询接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("可取余额查询接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("可取余额查询接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        AccountBalanceQueryResult uniPayResult = jsonObject.getObject("data", AccountBalanceQueryResult.class);
        return uniPayResult;
    }
    public static String sign(JSONObject body) throws Exception{
        Set<Map.Entry<String, Object>> entries = body.entrySet();
        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (item.getKey() != null || item.getKey() != "") {
                Object val = item.getValue();
                if (!(val == "" || val == null)) {
                    sb.append(val);
                }
            }
        }
        sb.append(key);
        return MD5AndKL.MD5(sb.toString());
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/AccountBalanceQueryResult.java
New file
@@ -0,0 +1,43 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/31 10:34
 */
@Data
public class AccountBalanceQueryResult {
    /**
     * 商户号
     */
    private String userNo;
    /**
     * 商户名称
     */
    private String userName;
    /**
     * 币种
     */
    private Integer currency;
    /**
     * 可取金额
     */
    private Double useAbleSettAmount;
    /**
     * 可结算冻结金额
     */
    private Double availableSettAmountFrozen;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/CloseOrderResult.java
New file
@@ -0,0 +1,45 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2025/1/1 10:15
 */
@Data
public class CloseOrderResult {
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 业务结果 100:成功,101:失败
     */
    private String ra_Status;
    /**
     * 响应码
     * 0 系统连接超时
     * 4 服务不可用
     * 100 关单成功
     * 101 失败,详见响应码描述
     * 10080000 系统异常
     * 10080002 验证签名失败
     * 10080003 订单号不正确
     * 10080042 交易类型不合法
     * 10083001 订单正在处理中
     * 10083002 该订单请求多次交易
     * 10083003 订单已关闭,无需关单操作
     * 10083003 交易成功,无需关单操作
     * 10083004 通道系统异常,请用相同参数重新请求
     * 10083005 通道其他异常信息
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/FrpCodeEnum.java
New file
@@ -0,0 +1,57 @@
package com.ruoyi.order.util.payment.model;
/**
 * 支付类型枚举
 * @author zhibing.pu
 * @Date 2024/12/27 17:30
 */
public enum FrpCodeEnum {
    ALIPAY_NATIVE("支付宝扫码(主扫)", "ALIPAY_NATIVE"),
    ALIPAY_CARD("支付宝刷卡(被扫)", "ALIPAY_CARD"),
    ALIPAY_H5("支付宝 H5", "ALIPAY_H5"),
    ALIPAY_FWC("支付宝服务窗", "ALIPAY_FWC"),
    ALIPAY_SYT("支付宝收银台", "ALIPAY_SYT"),
    WEIXIN_NATIVE("微信扫码(主扫)", "WEIXIN_NATIVE"),
    WEIXIN_CARD("微信刷卡(被扫)", "WEIXIN_CARD"),
    WEIXIN_APP3("微信 APP+支付", "WEIXIN_APP3"),
    WEIXIN_H5_PLUS("微信 H5 支付", "WEIXIN_H5_PLUS"),
    WEIXIN_GZH("微信公众号支付", "WEIXIN_GZH"),
    WEIXIN_XCX("微信小程序支付", "WEIXIN_XCX"),
    QQ_NATIVE("QQ 扫码(主扫)", "QQ_NATIVE"),
    QQ_CARD("QQ 刷卡(被扫)", "QQ_CARD"),
    QQ_APP("QQ APP 支付", "QQ_APP"),
    QQ_H5("QQH5 支付", "QQ_H5"),
    QQ_GZH("QQ 公众号支付", "QQ_GZH"),
    UNIONPAY_NATIVE("银联扫码(主扫)", "UNIONPAY_NATIVE"),
    UNIONPAY_CARD("银联刷卡(被扫)", "UNIONPAY_CARD"),
    UNIONPAY_APP("银联 APP 支付", "UNIONPAY_APP"),
    UNIONPAY_H5("银联 H5", "UNIONPAY_H5"),
    UNIONPAY_SYT("银联统一收银台", "UNIONPAY_SYT"),
    UNIONPAY_WXMP("银联云微小程序(无感支付)", "UNIONPAY_WXMP")
    ;
    private String name;
    private String code;
    FrpCodeEnum(String name, String code) {
        this.name = name;
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/QueryOrderResult.java
New file
@@ -0,0 +1,87 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:30
 */
@Data
public class QueryOrderResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 商品名称
     */
    private String r4_ProductName;
    /**
     * 交易流水号
     */
    private String r5_TrxNo;
    /**
     * 银行流水号
     */
    private String r6_BankTrxNo;
    /**
     * 订单手续费
     */
    private Double r7_Fee;
    /**
     * 交易类型
     */
    private String r8_FrpCode;
    /**
     * 订单状态 100:成功,101:失败,102:已创建,105:订单已关闭
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 用户标识
     */
    private String rd_OpenId;
    /**
     * 平台优惠金额
     */
    private Double re_DiscountAmount;
    /**
     * 支付时间
     */
    private String rf_PayTime;
    /**
     * 卡类型
     */
    private String rh_cardType;
    /**
     * 银行编码
     */
    private String rj_BankCode;
    /**
     * 签约 ID
     */
    private String rl_ContractId;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/QueryRefundResult.java
New file
@@ -0,0 +1,62 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:56
 */
@Data
public class QueryRefundResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户退款订单号
     */
    private String r2_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r3_RefundAmount;
    /**
     * 退款流水号
     */
    private String r4_RefundTrxNo;
    /**
     * 退款完成时间
     */
    private String r5_RefundCompleteTime;
    /**
     * 退款渠道
     */
    private String r8_RefundWay;
    /**
     * 退款入账账户
     */
    private String r9_ReceiveAccountNo;
    /**
     * 退款状态
     * 100:退款成功
     * 101:退款失败
     * 102:退款处理中
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/RefundCallbackResult.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:50
 */
@Data
public class RefundCallbackResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 商户退款订单号
     */
    private String r3_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r4_RefundAmount;
    /**
     * 商户退款流水号
     */
    private String r5_RefundTrxNo;
    /**
     * 退款完成时间
     */
    private String r6_RefundCompleteTime;
    /**
     * 退款渠道
     */
    private String r7_RefundWay;
    /**
     * 退款入账账户
     */
    private String r8_ReceiveAccountNo;
    /**
     * 退款状态 100:成功;101:失败
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/RefundResult.java
New file
@@ -0,0 +1,59 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:43
 */
@Data
public class RefundResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 商户退款订单号
     */
    private String r3_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r4_RefundAmount;
    /**
     * 商户退款流水号
     */
    private String r5_RefundTrxNo;
    /**
     * 退款申请状态
     * 100:成功,
     * 101:失败 。
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 营销退款金额
     */
    private Double rd_MarketRefAmount;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePay.java
New file
@@ -0,0 +1,61 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 20:28
 */
@Data
public class SinglePay {
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 收款账户号,收款人银行卡卡号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人,收款人银行卡持卡人名称
     */
    private String receiverNameEnc;
    /**
     * 账户类型 对私账户201,对公账户204
     */
    private Integer receiverAccountType;
    /**
     * 收款账户联行号 对公账户必须填写此字段
     */
    private String receiverBankChannelNo;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 代付说明
     */
    private String paidDesc;
    /**
     * 代付用途
     * 工资奖金 201
     * 活动经费 202
     * 养老金 203
     * 货款 204
     * 劳务费 205
     * 保险理财 206
     * 资金下发 207
     * 营业款 208
     * 退回款项 210
     * 消费款项 211
     */
    private String paidUse;
    /**
     * 商户通知地址
     */
    private String callbackUrl;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePayCallbackResult.java
New file
@@ -0,0 +1,64 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 *
 * @author zhibing.pu
 * @Date 2024/12/30 20:39
 */
@Data
public class SinglePayCallbackResult {
    /**
     * 交易状态
     */
    private Integer status;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorCodeDesc;
    /**
     * 商户编号
     */
    private String userNo;
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 平台流水号
     */
    private String platformSerialNo;
    /**
     * 收款账户号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人
     */
    private String receiverNameEnc;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 手续费
     */
    private String fee;
    /**
     * 完成时间
     */
    private String completeTime;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePayQueryResult.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/31 10:10
 */
@Data
public class SinglePayQueryResult {
    /**
     * 单笔代付查询请求的交易状态
     */
    private Integer status;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 商户号
     */
    private String userNo;
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 平台流水号
     */
    private String platformSerialNo;
    /**
     * 收款账户号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人
     */
    private String receiverNameEnc;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 手续费
     */
    private Double fee;
    /**
     * 完成时间
     */
    private String completeTime;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/SinglePayResult.java
New file
@@ -0,0 +1,31 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 20:36
 */
@Data
public class SinglePayResult {
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 商户编号
     */
    private String userNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/UniPayCallbackResult.java
New file
@@ -0,0 +1,93 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:00
 */
@Data
public class UniPayCallbackResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 交易币种
     */
    private String r4_Cur;
    /**
     * 公用回传参数
     */
    private String r5_Mp;
    /**
     * 支付状态
     * 100:支付成功;
     * 101:支付失败。
     */
    private String r6_Status;
    /**
     * 交易流水号
     */
    private String r7_TrxNo;
    /**
     * 银行订单号
     */
    private String r8_BankOrderNo;
    /**
     * 银行流水号
     */
    private String r9_BankTrxNo;
    /**
     * 支付时间
     */
    private String ra_PayTime;
    /**
     * 交易结果通知时间
     */
    private String rb_DealTime;
    /**
     * 银行编码
     */
    private String rc_BankCode;
    /**
     * 用户标识
     */
    private String rd_OpenId;
    /**
     * 平台优惠金额
     */
    private Double re_DiscountAmount;
    /**
     * 卡类型
     */
    private String rh_cardType;
    /**
     * 订单手续费
     */
    private Double rj_Fee;
    /**
     * 交易类型
     */
    private String rk_FrpCode;
    /**
     * 签约 ID
     */
    private String rl_ContractId;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/model/UniPayResult.java
New file
@@ -0,0 +1,77 @@
package com.ruoyi.order.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 10:47
 */
@Data
public class UniPayResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 币种
     */
    private String r4_Cur;
    /**
     * 公用回传参数
     */
    private String r5_Mp;
    /**
     * 交易类型
     */
    private String r6_FrpCode;
    /**
     * 交易流水号
     */
    private String r7_TrxNo;
    /**
     * 银行商户编码
     */
    private String r8_MerchantBankCode;
    /**
     * 响应码,返回 100 时表示成功
     */
    private String ra_Code;
    /**
     * 响应码描述
     */
    private String rb_CodeMsg;
    /**
     * 1.主扫支付返回二维码地址。
     * 2.支付宝 H5,mode1/2/3 参考请求参数q9_TransactionModel 说明。
     * 3.微信 H5_PLUS,获取支付信息的 openlink,通过手机端浏览器跳转并唤起微信 APP客户端,直接打开对应的小程序进行支付。
     * 3.公众号支付:需要商户参考微信的官方文档 JSAPI 支付接口进行处理,详情请见:https://pay.weixin.qq.com/wiki/doc/api/index.html
     * 4.微信小程序支付返回支付信息。
     * 5.支付宝收银台返回支付宝收银台跳转链接,通过请求该链接跳转至支付宝。
     * 6.微信 app3 支付,返回预支付信息,集成微信 SDK 唤起小程序进行支付。
     * 7.支付宝服务窗支付返回银联交易号 trade_no,可用以唤起支付宝 APP,调起支付宝APP 收银台。
     * 8.银联 app 或银联统一收银台支付,返回预支付信息用此网址的接口调起支付。https://open.unionpay.com/tjweb/acproduct/list?apiservId=450#nav02
     * 9.银联云微小程序返回跳转地址,格式:{“cqpMpAppId”:”云闪付小程序 id”,”cqpMpPath”:”云闪付小程序 path”}
     * 10.其他类型支付返回支付信息。
     */
    private String rc_Result;
    /**
     * 二维码图片码
     */
    private String rd_Pic;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/ShopController.java
@@ -16,6 +16,7 @@
import com.ruoyi.other.service.ShopScoreService;
import com.ruoyi.other.service.ShopService;
import com.ruoyi.other.vo.NearbyShopVO;
import com.ruoyi.other.vo.SaveWithdrawalAccount;
import com.ruoyi.other.vo.ShopDetailVO;
import com.ruoyi.other.vo.ShopStatistics;
import com.ruoyi.system.api.domain.SysUser;
@@ -312,5 +313,14 @@
    public void updateShop(@RequestBody Shop shop){
        shopService.updateById(shop);
    }
    @PostMapping("/saveWithdrawalAccount")
    @ApiOperation(value = "保存提现账户", tags = {"门店后台-财务统计-提现明细"})
    public R saveWithdrawalAccount(SaveWithdrawalAccount saveWithdrawalAccount) {
        shopService.saveWithdrawalAccount(saveWithdrawalAccount);
        return R.ok();
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/ShopWithdrawController.java
@@ -1,17 +1,30 @@
package com.ruoyi.other.controller;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.api.feignClient.AppUserClient;
import com.ruoyi.account.api.model.AppUser;
import com.ruoyi.account.api.model.BalanceChangeRecord;
import com.ruoyi.account.api.model.WithdrawalRequests;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.other.api.domain.Shop;
import com.ruoyi.other.api.domain.ShopBalanceStatement;
import com.ruoyi.other.api.domain.ShopWithdraw;
import com.ruoyi.other.dto.ShopBalanceDto;
import com.ruoyi.other.service.ShopBalanceStatementService;
import com.ruoyi.other.service.ShopService;
import com.ruoyi.other.service.ShopWithdrawService;
import com.ruoyi.other.util.payment.TransferUtil;
import com.ruoyi.other.util.payment.model.AccountBalanceQueryResult;
import com.ruoyi.other.util.payment.model.SinglePay;
import com.ruoyi.other.util.payment.model.SinglePayCallbackResult;
import com.ruoyi.other.util.payment.model.SinglePayResult;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -20,6 +33,7 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
/**
@@ -41,6 +55,15 @@
    private ShopService shopService;
    @Resource
    private TokenService tokenService;
    @Resource
    private ShopBalanceStatementService shopBalanceStatementService;
    @Resource
    private AppUserClient appUserClient;
    /**
     * 提现申请列表
@@ -78,13 +101,18 @@
        Shop byId = shopService.getById(objectId);
        return R.ok(byId);
    }
    @GetMapping("/shop/with")
    @ApiOperation(value = "提现申请", notes = "提现申请列表", tags = {"门店后台"})
    public R<Shop> shopwith(@RequestParam BigDecimal money){
        Integer objectId = tokenService.getLoginUser().getSysUser().getObjectId();
        Shop byId = shopService.getById(objectId);
        if (money.compareTo(byId.getCanWithdrawMoney())>0){
    public R shopwith(@RequestParam BigDecimal money){
        SysUser sysUser = tokenService.getLoginUser().getSysUser();
        Integer objectId = sysUser.getObjectId();
        Shop shop = shopService.getById(objectId);
        if (money.compareTo(shop.getCanWithdrawMoney())>0){
            return R.fail("提现金额不能大于可提现金额");
        }
        if(StringUtils.isEmpty(shop.getReceiverAccountNoEnc())){
            return R.fail("请完善账户信息后再申请提现!");
        }
        ShopWithdraw shopWithdraw = new ShopWithdraw();
        shopWithdraw.setShopId(objectId);
@@ -92,8 +120,26 @@
        shopWithdraw.setAuditStatus(0);
        shopWithdraw.setStatus(1);
        shopWithdrawService.save(shopWithdraw);
        return R.ok(byId);
        //扣除账户余额及添加变动明细
        BigDecimal balance = shop.getBalance();
        BigDecimal canWithdrawMoney = shop.getCanWithdrawMoney();
        BigDecimal withdrawMoney = shop.getWithdrawMoney();
        shop.setBalance(balance.subtract(money).setScale(2, RoundingMode.HALF_EVEN));
        shop.setCanWithdrawMoney(canWithdrawMoney.subtract(money).setScale(2, RoundingMode.HALF_EVEN));
        shop.setWithdrawMoney(withdrawMoney.add(money).setScale(2, RoundingMode.HALF_EVEN));
        shopService.updateById(shop);
        //添加门店变动明细
        ShopBalanceStatement shopBalanceStatement = new ShopBalanceStatement();
        shopBalanceStatement.setShopId(shop.getId());
        shopBalanceStatement.setType(5);
        shopBalanceStatement.setHistoricalBalance(balance);
        shopBalanceStatement.setVariableAmount(money);
        shopBalanceStatement.setBalance(shop.getBalance());
        shopBalanceStatement.setCreateUserId(sysUser.getUserId());
        shopBalanceStatement.setCreateTime(LocalDateTime.now());
        shopBalanceStatement.setObjectId(shopWithdraw.getId());
        shopBalanceStatementService.save(shopBalanceStatement);
        return R.ok();
    }
@@ -105,6 +151,59 @@
    public R<Void> audit(@RequestBody ShopWithdraw shopWithdraw) {
        LoginUser loginUser = tokenService.getLoginUser();
        ShopWithdraw shopWithdraw1 = shopWithdrawService.getById(shopWithdraw.getId());
        if(0 != shopWithdraw1.getAuditStatus()){
            return R.fail("不能重复审核");
        }
        Shop shop = shopService.getById(shopWithdraw1.getShopId());
        BigDecimal money = shopWithdraw1.getMoney();
        if(1 == shopWithdraw.getAuditStatus()){
            //先检查账户余额是否充足
            AccountBalanceQueryResult accountBalanceQueryResult = TransferUtil.accountBalanceQuery();
            if(null == accountBalanceQueryResult){
                return R.fail("查询账户余额出错");
            }
            Double useAbleSettAmount = accountBalanceQueryResult.getUseAbleSettAmount();
            if(useAbleSettAmount < shopWithdraw1.getMoney().doubleValue()){
                return R.fail("账户可用余额不足,请先补充账户余额");
            }
            //银行卡转账
            SinglePay singlePay = new SinglePay();
            singlePay.setTradeMerchantNo("");
            singlePay.setMerchantOrderNo(shopWithdraw1.getId().toString());
            singlePay.setReceiverAccountNoEnc(shop.getReceiverAccountNoEnc());
            singlePay.setReceiverNameEnc(shop.getReceiverNameEnc());
            singlePay.setReceiverAccountType(shop.getReceiverAccountType());
            singlePay.setPaidAmount(shopWithdraw1.getMoney().doubleValue());
            singlePay.setPaidDesc("账户余额提现");
            singlePay.setPaidUse("208");
            singlePay.setCallbackUrl("/other/shop-withdraw/withdrawalCallback");
            SinglePayResult singlePayResult = TransferUtil.singlePay(singlePay);
            if(null == singlePayResult){
                return R.fail("转账失败");
            }
            shopWithdraw1.setStatus(1);
        }
        if(2 == shopWithdraw.getAuditStatus()){
            //回退金额和添加变动明细
            BigDecimal balance = shop.getBalance();
            BigDecimal canWithdrawMoney = shop.getCanWithdrawMoney();
            BigDecimal withdrawMoney = shop.getWithdrawMoney();
            shop.setBalance(balance.add(money).setScale(2, RoundingMode.HALF_EVEN));
            shop.setCanWithdrawMoney(canWithdrawMoney.add(money).setScale(2, RoundingMode.HALF_EVEN));
            shop.setWithdrawMoney(withdrawMoney.subtract(money).setScale(2, RoundingMode.HALF_EVEN));
            shopService.updateById(shop);
            //添加门店变动明细
            ShopBalanceStatement shopBalanceStatement = new ShopBalanceStatement();
            shopBalanceStatement.setShopId(shop.getId());
            shopBalanceStatement.setType(5);
            shopBalanceStatement.setHistoricalBalance(balance);
            shopBalanceStatement.setVariableAmount(money);
            shopBalanceStatement.setBalance(shop.getBalance());
            shopBalanceStatement.setCreateUserId(loginUser.getUserid());
            shopBalanceStatement.setCreateTime(LocalDateTime.now());
            shopBalanceStatement.setObjectId(shopWithdraw.getId());
            shopBalanceStatementService.save(shopBalanceStatement);
        }
        shopWithdraw1.setAuditStatus(shopWithdraw.getAuditStatus());
        shopWithdraw1.setAuditUserId(loginUser.getUserid());
        shopWithdraw1.setAuditTime(LocalDateTime.now());
@@ -115,6 +214,27 @@
    /**
     * 提现审核通过后转账回调通知
     * @param singlePayCallbackResult
     */
    @ResponseBody
    @PostMapping("/withdrawalCallback")
    public Object withdrawalCallback(@RequestBody SinglePayCallbackResult singlePayCallbackResult){
        Integer status = singlePayCallbackResult.getStatus();
        if(203 == status){
            String merchantOrderNo = singlePayCallbackResult.getMerchantOrderNo();
            ShopWithdraw shopWithdraw = shopWithdrawService.getById(merchantOrderNo);
            if(1 == shopWithdraw.getStatus()){
                shopWithdraw.setStatus(2);
                shopWithdraw.setArrivalTime(LocalDateTime.now());
                shopWithdrawService.updateById(shopWithdraw);
            }
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("statusCode", 2001);
            return jsonObject;
        }
        return new JSONObject();
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/ShopService.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.other.api.domain.Shop;
import com.ruoyi.other.vo.NearbyShopVO;
import com.ruoyi.other.vo.SaveWithdrawalAccount;
import com.ruoyi.other.vo.ShopDetailVO;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.RequestParam;
@@ -29,4 +30,9 @@
    Boolean cheUserByPhone(String phone);
    /**
     * 保存提现账户
     * @param saveWithdrawalAccount
     */
    void saveWithdrawalAccount(SaveWithdrawalAccount saveWithdrawalAccount);
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/ShopServiceImpl.java
@@ -16,7 +16,10 @@
import com.ruoyi.other.service.ShopScoreService;
import com.ruoyi.other.service.ShopService;
import com.ruoyi.other.vo.NearbyShopVO;
import com.ruoyi.other.vo.SaveWithdrawalAccount;
import com.ruoyi.other.vo.ShopDetailVO;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.feignClient.SysUserClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -46,6 +49,8 @@
    private AppUserClient appUserClient;
    @Resource
    private TokenService tokenService;
    @Resource
    private SysUserClient sysUserClient;
    @Override
@@ -128,4 +133,23 @@
        }
        return r.getData() != null;
    }
    /**
     * 保存提现账户
     * @param saveWithdrawalAccount
     */
    @Override
    public void saveWithdrawalAccount(SaveWithdrawalAccount saveWithdrawalAccount) {
        Long userid = tokenService.getLoginUser().getUserid();
        SysUser sysUser = sysUserClient.getSysUser(userid).getData();
        Shop shop = this.getById(sysUser.getObjectId());
        if(null != shop){
            shop.setReceiverAccountNoEnc(saveWithdrawalAccount.getReceiverAccountNoEnc());
            shop.setReceiverNameEnc(saveWithdrawalAccount.getReceiverNameEnc());
            shop.setReceiverAccountType(saveWithdrawalAccount.getReceiverAccountType());
            shop.setReceiverBankChannelNo(saveWithdrawalAccount.getReceiverBankChannelNo());
            this.updateById(shop);
        }
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/MD5AndKL.java
New file
@@ -0,0 +1,112 @@
package com.ruoyi.other.util.payment;
import java.security.MessageDigest;
public class MD5AndKL {
    /**
     * MD5加码。32位
     *
     * @param inStr
     * @return
     */
    public static String MD5(String inStr) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
        byte[] md5Bytes = md5.digest(inStr.getBytes());
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
    /**
     * 可逆的加密算法
     *
     * @param inStr
     * @return
     */
    public static String KL(String inStr) {
        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        return s;
    }
    /**
     * 加密后解密
     *
     * @param inStr
     * @return
     */
    public static String JM(String inStr) {
        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String k = new String(a);
        return k;
    }
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));
        return resultSb.toString();
    }
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname)){
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            }else{
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return resultString;
    }
    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
    public static void main(String args[]) {
        System.out.println("MD5后再加密:" + KL(MD5("123456")));
        System.out.println(MD5("123456"));
        // System.out.println("加密:" + KL(MD5("123456")));
        // s = KL(s);
        // System.out.println("解密:" + KL("81dc9bdb52d04dc20036dbd8313ed055"));
        // System.out.println("解密:" + JM(KL(s)));
        // System.out.println("解密为MD5后的:" + KL(KL(MD5(s))));
        // System.out.println(JM("5d62957bb57d3e49dcf48a0df064be4c"));
        // System.out.println(MD5AndKL.KL(MD5AndKL.MD5("admin"+"87654321")));
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/PaymentUtil.java
New file
@@ -0,0 +1,277 @@
package com.ruoyi.other.util.payment;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.other.util.payment.model.*;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
/**
 * 支付工具类
 * @author zhibing.pu
 * @Date 2024/12/27 17:00
 */
@Slf4j
public class PaymentUtil {
    //微信公众号、微信小程序、微信 APP+/H5、云微小程序支付
    private static final String appId = "wxdeed472c98e42a54";
    /**
     * 商户密钥
     */
    private static final String key = "925899fcc374430f9e4b4ba3db05b448";
    /**
     * 商户号
     */
    private static final String merchantNo = "888122600004175";
    /**
     * 支付回调地址
     */
    private static final String callbackUrl = "http://221.182.45.100:9000";
    /**
     * 支付
     * @param orderNo           商户订单号
     * @param amount            订单金额
     * @param productName       商品名称
     * @param productDesc       商品描述
     * @param mp                公用回传参数
     * @param notifyUrl         服务器异步通知地址
     * @param openId            微信 Openid
     * @param tradeMerchantNo   报备商户号
     * @return
     */
    public static UniPayResult uniPay(String orderNo, Double amount, String productName, String productDesc, String mp, String notifyUrl, String openId, String tradeMerchantNo){
        String url = "https://trade.joinpay.com/tradeRt/uniPay";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.5");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //订单金额
        body.put("p3_Amount", amount);
        //交易币种
        body.put("p4_Cur", "1");
        //商品名称
        body.put("p5_ProductName", productName);
        //商品描述
        body.put("p6_ProductDesc", productDesc);
        //公用回传参数
        body.put("p7_Mp", mp);
        //服务器异步通知地址
        body.put("p9_NotifyUrl", callbackUrl + notifyUrl);
        //交易类型
        body.put("q1_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
        //微信 Openid
        body.put("q5_OpenId", openId);
        //APPID
        body.put("q7_AppId", appId);
        //报备商户号
        body.put("qa_TradeMerchantNo", tradeMerchantNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("支付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("支付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("支付接口异常:" + execute.body());
            return null;
        }
        UniPayResult uniPayResult = JSON.parseObject(execute.body(), UniPayResult.class);
        return uniPayResult;
    }
    /**
     * 查询支付订单
     * @param orderNo   订单号
     * @return
     */
    public static QueryOrderResult queryOrder(String orderNo){
        String url = "https://trade.joinpay.com/tradeRt/queryOrder";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.5");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("查询支付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("查询支付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("查询支付接口异常:" + execute.body());
            return null;
        }
        QueryOrderResult uniPayResult = JSON.parseObject(execute.body(), QueryOrderResult.class);
        return uniPayResult;
    }
    /**
     * 退款
     * @param orderNo           支付订单号
     * @param refundOrderNo     退款订单号
     * @param refundAmount      退款金额
     * @param notifyUrl         异步通知地址
     * @return
     */
    public static RefundResult refund(String orderNo, String refundOrderNo, Double refundAmount, String notifyUrl){
        String url = "https://trade.joinpay.com/tradeRt/refund";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.3");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //商户退款订单号
        body.put("p3_RefundOrderNo", refundOrderNo);
        //退款金额
        body.put("p4_RefundAmount", refundAmount);
        //服务器异步通知地址
        body.put("p6_NotifyUrl", notifyUrl);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("退款接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("退款接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("退款接口异常:" + execute.body());
            return null;
        }
        RefundResult uniPayResult = JSON.parseObject(execute.body(), RefundResult.class);
        return uniPayResult;
    }
    /**
     * 查询退款订单
     * @param refundOrderNo 退款订单号
     * @return
     */
    public static QueryRefundResult queryRefund(String refundOrderNo){
        String url = "https://trade.joinpay.com/tradeRt/refund";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //版本号
        body.put("p0_Version", "2.3");
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户退款订单号
        body.put("p2_RefundOrderNo", refundOrderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("退款接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("退款接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("退款接口异常:" + execute.body());
            return null;
        }
        QueryRefundResult uniPayResult = JSON.parseObject(execute.body(), QueryRefundResult.class);
        return uniPayResult;
    }
    /**
     * 关闭订单(仅支持微信和支付宝)
     * @param orderNo   订单号
     * @return
     */
    public static CloseOrderResult closeOrder(String orderNo){
        String url = "https://www.joinpay.com/trade/closeOrder.action";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("p1_MerchantNo", merchantNo);
        //商户订单号
        body.put("p2_OrderNo", orderNo);
        //交易类型
        body.put("p3_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("关闭订单接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("关闭订单接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("关闭订单接口异常:" + execute.body());
            return null;
        }
        CloseOrderResult uniPayResult = JSON.parseObject(execute.body(), CloseOrderResult.class);
        return uniPayResult;
    }
    public static String sign(JSONObject body) throws Exception{
        Set<Map.Entry<String, Object>> entries = body.entrySet();
        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (item.getKey() != null || item.getKey() != "") {
                Object val = item.getValue();
                if (!(val == "" || val == null)) {
                    sb.append(val);
                }
            }
        }
        sb.append(key);
        return MD5AndKL.MD5(sb.toString());
    }
    public static void main(String[] args) {
        UniPayResult uniPayResult = PaymentUtil.uniPay("123456", 0.01D, "测试商品", "这是用于对接支付测试的商品描述", "", "", "", "");
        System.err.println(JSON.toJSONString(uniPayResult));
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/TransferUtil.java
New file
@@ -0,0 +1,229 @@
package com.ruoyi.other.util.payment;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.other.util.payment.model.AccountBalanceQueryResult;
import com.ruoyi.other.util.payment.model.SinglePay;
import com.ruoyi.other.util.payment.model.SinglePayQueryResult;
import com.ruoyi.other.util.payment.model.SinglePayResult;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
 * 转账代付工具类
 * @author zhibing.pu
 * @Date 2024/12/30 19:54
 */
@Slf4j
public class TransferUtil {
    /**
     * 商户密钥
     */
    private static final String key = "925899fcc374430f9e4b4ba3db05b448";
    /**
     * 商户号
     */
    private static final String merchantNo = "888122600004175";
    private static final String format = "yyyy-MM-dd HH:mm:ss";
    /**
     * 单笔代付
     * @param singlePay
     * @return
     */
    public static SinglePayResult singlePay(SinglePay singlePay){
        String url = "https://www.joinpay.com/payment/pay/singlePay";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        //报备商户号
        body.put("tradeMerchantNo", singlePay.getTradeMerchantNo());
        //产品类型
        body.put("productCode", "BANK_PAY_DAILY_ORDER");
        //交易请求时间
        body.put("requestTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern(format)));
        //商户订单号
        body.put("merchantOrderNo", singlePay.getMerchantOrderNo());
        //收款账户号,收款人银行卡卡号
        body.put("receiverAccountNoEnc", singlePay.getReceiverAccountNoEnc());
        //收款人,收款人银行卡持卡人名称
        body.put("receiverNameEnc", singlePay.getReceiverNameEnc());
        //账户类型
        body.put("receiverAccountType", singlePay.getReceiverAccountType());
        //收款账户联行号 对公账户必须填写此字段
        body.put("receiverBankChannelNo", singlePay.getReceiverBankChannelNo());
        //交易金额
        body.put("paidAmount", singlePay.getPaidAmount());
        //币种
        body.put("currency", "201");
        //是否复核 复核:201,不复核:202
        body.put("isChecked", "202");
        //代付说明
        body.put("paidDesc", singlePay.getPaidDesc());
        //代付用途
        /**
         * 工资奖金 201
         * 活动经费 202
         * 养老金 203
         * 货款 204
         * 劳务费 205
         * 保险理财 206
         * 资金下发 207
         * 营业款 208
         * 退回款项 210
         * 消费款项 211
         */
        body.put("paidUse", singlePay.getPaidUse());
        //商户通知地址
        body.put("callbackUrl", singlePay.getCallbackUrl());
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("单笔代付接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("单笔代付接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("单笔代付接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("单笔代付接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("单笔代付接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        SinglePayResult uniPayResult = jsonObject.getObject("data", SinglePayResult.class);
        return uniPayResult;
    }
    /**
     * 单笔代付查询接口
     * @param merchantOrderNo   订单号
     * @return
     */
    public static SinglePayQueryResult singlePayQuery(String merchantOrderNo){
        String url = "https://www.joinpay.com/payment/pay/singlePayQuery";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        //商户订单号
        body.put("merchantOrderNo", merchantOrderNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("单笔代付查询接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("单笔代付查询接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("单笔代付查询接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("单笔代付查询接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("单笔代付查询接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        SinglePayQueryResult uniPayResult = jsonObject.getObject("data", SinglePayQueryResult.class);
        return uniPayResult;
    }
    /**
     * 可取余额查询
     * @return
     */
    public static AccountBalanceQueryResult accountBalanceQuery(){
        String url = "https://www.joinpay.com/payment/pay/accountBalanceQuery";
        HttpRequest post = HttpUtil.createPost(url);
        JSONObject body = new JSONObject();
        //商户编号
        body.put("userNo", merchantNo);
        String sign = null;
        try {
            sign = sign(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        body.put("hmac", sign);
        post.body(body.toString());
        log.info("可取余额查询接口请求参数:" + body);
        HttpResponse execute = post.execute();
        log.info("可取余额查询接口请求响应:" + execute.body());
        if(200 != execute.getStatus()){
            log.error("可取余额查询接口异常:" + execute.body());
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        String statusCode = jsonObject.getString("statusCode");
        if(!"2001".equals(statusCode) && !"2003".equals(statusCode)){
            log.error("可取余额查询接口异常:" + jsonObject.getString("message"));
            return null;
        }
        if("2003".equals(statusCode)){
            //汇聚不能确定订单状态,建议 10 分钟后发起查询确认。
            log.error("可取余额查询接口异常:汇聚不能确定订单状态,建议 10 分钟后发起查询确认。");
            return null;
        }
        AccountBalanceQueryResult uniPayResult = jsonObject.getObject("data", AccountBalanceQueryResult.class);
        return uniPayResult;
    }
    public static String sign(JSONObject body) throws Exception{
        Set<Map.Entry<String, Object>> entries = body.entrySet();
        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (item.getKey() != null || item.getKey() != "") {
                Object val = item.getValue();
                if (!(val == "" || val == null)) {
                    sb.append(val);
                }
            }
        }
        sb.append(key);
        return MD5AndKL.MD5(sb.toString());
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/AccountBalanceQueryResult.java
New file
@@ -0,0 +1,43 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/31 10:34
 */
@Data
public class AccountBalanceQueryResult {
    /**
     * 商户号
     */
    private String userNo;
    /**
     * 商户名称
     */
    private String userName;
    /**
     * 币种
     */
    private Integer currency;
    /**
     * 可取金额
     */
    private Double useAbleSettAmount;
    /**
     * 可结算冻结金额
     */
    private Double availableSettAmountFrozen;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/CloseOrderResult.java
New file
@@ -0,0 +1,45 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2025/1/1 10:15
 */
@Data
public class CloseOrderResult {
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 业务结果 100:成功,101:失败
     */
    private String ra_Status;
    /**
     * 响应码
     * 0 系统连接超时
     * 4 服务不可用
     * 100 关单成功
     * 101 失败,详见响应码描述
     * 10080000 系统异常
     * 10080002 验证签名失败
     * 10080003 订单号不正确
     * 10080042 交易类型不合法
     * 10083001 订单正在处理中
     * 10083002 该订单请求多次交易
     * 10083003 订单已关闭,无需关单操作
     * 10083003 交易成功,无需关单操作
     * 10083004 通道系统异常,请用相同参数重新请求
     * 10083005 通道其他异常信息
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/FrpCodeEnum.java
New file
@@ -0,0 +1,57 @@
package com.ruoyi.other.util.payment.model;
/**
 * 支付类型枚举
 * @author zhibing.pu
 * @Date 2024/12/27 17:30
 */
public enum FrpCodeEnum {
    ALIPAY_NATIVE("支付宝扫码(主扫)", "ALIPAY_NATIVE"),
    ALIPAY_CARD("支付宝刷卡(被扫)", "ALIPAY_CARD"),
    ALIPAY_H5("支付宝 H5", "ALIPAY_H5"),
    ALIPAY_FWC("支付宝服务窗", "ALIPAY_FWC"),
    ALIPAY_SYT("支付宝收银台", "ALIPAY_SYT"),
    WEIXIN_NATIVE("微信扫码(主扫)", "WEIXIN_NATIVE"),
    WEIXIN_CARD("微信刷卡(被扫)", "WEIXIN_CARD"),
    WEIXIN_APP3("微信 APP+支付", "WEIXIN_APP3"),
    WEIXIN_H5_PLUS("微信 H5 支付", "WEIXIN_H5_PLUS"),
    WEIXIN_GZH("微信公众号支付", "WEIXIN_GZH"),
    WEIXIN_XCX("微信小程序支付", "WEIXIN_XCX"),
    QQ_NATIVE("QQ 扫码(主扫)", "QQ_NATIVE"),
    QQ_CARD("QQ 刷卡(被扫)", "QQ_CARD"),
    QQ_APP("QQ APP 支付", "QQ_APP"),
    QQ_H5("QQH5 支付", "QQ_H5"),
    QQ_GZH("QQ 公众号支付", "QQ_GZH"),
    UNIONPAY_NATIVE("银联扫码(主扫)", "UNIONPAY_NATIVE"),
    UNIONPAY_CARD("银联刷卡(被扫)", "UNIONPAY_CARD"),
    UNIONPAY_APP("银联 APP 支付", "UNIONPAY_APP"),
    UNIONPAY_H5("银联 H5", "UNIONPAY_H5"),
    UNIONPAY_SYT("银联统一收银台", "UNIONPAY_SYT"),
    UNIONPAY_WXMP("银联云微小程序(无感支付)", "UNIONPAY_WXMP")
    ;
    private String name;
    private String code;
    FrpCodeEnum(String name, String code) {
        this.name = name;
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/QueryOrderResult.java
New file
@@ -0,0 +1,87 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:30
 */
@Data
public class QueryOrderResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 商品名称
     */
    private String r4_ProductName;
    /**
     * 交易流水号
     */
    private String r5_TrxNo;
    /**
     * 银行流水号
     */
    private String r6_BankTrxNo;
    /**
     * 订单手续费
     */
    private Double r7_Fee;
    /**
     * 交易类型
     */
    private String r8_FrpCode;
    /**
     * 订单状态 100:成功,101:失败,102:已创建,105:订单已关闭
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 用户标识
     */
    private String rd_OpenId;
    /**
     * 平台优惠金额
     */
    private Double re_DiscountAmount;
    /**
     * 支付时间
     */
    private String rf_PayTime;
    /**
     * 卡类型
     */
    private String rh_cardType;
    /**
     * 银行编码
     */
    private String rj_BankCode;
    /**
     * 签约 ID
     */
    private String rl_ContractId;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/QueryRefundResult.java
New file
@@ -0,0 +1,62 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:56
 */
@Data
public class QueryRefundResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户退款订单号
     */
    private String r2_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r3_RefundAmount;
    /**
     * 退款流水号
     */
    private String r4_RefundTrxNo;
    /**
     * 退款完成时间
     */
    private String r5_RefundCompleteTime;
    /**
     * 退款渠道
     */
    private String r8_RefundWay;
    /**
     * 退款入账账户
     */
    private String r9_ReceiveAccountNo;
    /**
     * 退款状态
     * 100:退款成功
     * 101:退款失败
     * 102:退款处理中
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/RefundCallbackResult.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:50
 */
@Data
public class RefundCallbackResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 商户退款订单号
     */
    private String r3_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r4_RefundAmount;
    /**
     * 商户退款流水号
     */
    private String r5_RefundTrxNo;
    /**
     * 退款完成时间
     */
    private String r6_RefundCompleteTime;
    /**
     * 退款渠道
     */
    private String r7_RefundWay;
    /**
     * 退款入账账户
     */
    private String r8_ReceiveAccountNo;
    /**
     * 退款状态 100:成功;101:失败
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/RefundResult.java
New file
@@ -0,0 +1,59 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:43
 */
@Data
public class RefundResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 商户退款订单号
     */
    private String r3_RefundOrderNo;
    /**
     * 退款金额
     */
    private Double r4_RefundAmount;
    /**
     * 商户退款流水号
     */
    private String r5_RefundTrxNo;
    /**
     * 退款申请状态
     * 100:成功,
     * 101:失败 。
     */
    private String ra_Status;
    /**
     * 响应码
     */
    private String rb_Code;
    /**
     * 响应码描述
     */
    private String rc_CodeMsg;
    /**
     * 营销退款金额
     */
    private Double rd_MarketRefAmount;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePay.java
New file
@@ -0,0 +1,61 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 20:28
 */
@Data
public class SinglePay {
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 收款账户号,收款人银行卡卡号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人,收款人银行卡持卡人名称
     */
    private String receiverNameEnc;
    /**
     * 账户类型 对私账户201,对公账户204
     */
    private Integer receiverAccountType;
    /**
     * 收款账户联行号 对公账户必须填写此字段
     */
    private String receiverBankChannelNo;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 代付说明
     */
    private String paidDesc;
    /**
     * 代付用途
     * 工资奖金 201
     * 活动经费 202
     * 养老金 203
     * 货款 204
     * 劳务费 205
     * 保险理财 206
     * 资金下发 207
     * 营业款 208
     * 退回款项 210
     * 消费款项 211
     */
    private String paidUse;
    /**
     * 商户通知地址
     */
    private String callbackUrl;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePayCallbackResult.java
New file
@@ -0,0 +1,64 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 *
 * @author zhibing.pu
 * @Date 2024/12/30 20:39
 */
@Data
public class SinglePayCallbackResult {
    /**
     * 交易状态
     */
    private Integer status;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorCodeDesc;
    /**
     * 商户编号
     */
    private String userNo;
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 平台流水号
     */
    private String platformSerialNo;
    /**
     * 收款账户号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人
     */
    private String receiverNameEnc;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 手续费
     */
    private String fee;
    /**
     * 完成时间
     */
    private String completeTime;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePayQueryResult.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/31 10:10
 */
@Data
public class SinglePayQueryResult {
    /**
     * 单笔代付查询请求的交易状态
     */
    private Integer status;
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 商户号
     */
    private String userNo;
    /**
     * 报备商户号
     */
    private String tradeMerchantNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 平台流水号
     */
    private String platformSerialNo;
    /**
     * 收款账户号
     */
    private String receiverAccountNoEnc;
    /**
     * 收款人
     */
    private String receiverNameEnc;
    /**
     * 交易金额
     */
    private Double paidAmount;
    /**
     * 手续费
     */
    private Double fee;
    /**
     * 完成时间
     */
    private String completeTime;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/SinglePayResult.java
New file
@@ -0,0 +1,31 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 20:36
 */
@Data
public class SinglePayResult {
    /**
     * 错误码
     */
    private String errorCode;
    /**
     * 错误描述
     */
    private String errorDesc;
    /**
     * 商户编号
     */
    private String userNo;
    /**
     * 商户订单号
     */
    private String merchantOrderNo;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/UniPayCallbackResult.java
New file
@@ -0,0 +1,93 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 11:00
 */
@Data
public class UniPayCallbackResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 交易币种
     */
    private String r4_Cur;
    /**
     * 公用回传参数
     */
    private String r5_Mp;
    /**
     * 支付状态
     * 100:支付成功;
     * 101:支付失败。
     */
    private String r6_Status;
    /**
     * 交易流水号
     */
    private String r7_TrxNo;
    /**
     * 银行订单号
     */
    private String r8_BankOrderNo;
    /**
     * 银行流水号
     */
    private String r9_BankTrxNo;
    /**
     * 支付时间
     */
    private String ra_PayTime;
    /**
     * 交易结果通知时间
     */
    private String rb_DealTime;
    /**
     * 银行编码
     */
    private String rc_BankCode;
    /**
     * 用户标识
     */
    private String rd_OpenId;
    /**
     * 平台优惠金额
     */
    private Double re_DiscountAmount;
    /**
     * 卡类型
     */
    private String rh_cardType;
    /**
     * 订单手续费
     */
    private Double rj_Fee;
    /**
     * 交易类型
     */
    private String rk_FrpCode;
    /**
     * 签约 ID
     */
    private String rl_ContractId;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/model/UniPayResult.java
New file
@@ -0,0 +1,77 @@
package com.ruoyi.other.util.payment.model;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2024/12/30 10:47
 */
@Data
public class UniPayResult {
    /**
     * 版本号
     */
    private Double r0_Version;
    /**
     * 商户编号
     */
    private String r1_MerchantNo;
    /**
     * 商户订单号
     */
    private String r2_OrderNo;
    /**
     * 支付金额
     */
    private Double r3_Amount;
    /**
     * 币种
     */
    private String r4_Cur;
    /**
     * 公用回传参数
     */
    private String r5_Mp;
    /**
     * 交易类型
     */
    private String r6_FrpCode;
    /**
     * 交易流水号
     */
    private String r7_TrxNo;
    /**
     * 银行商户编码
     */
    private String r8_MerchantBankCode;
    /**
     * 响应码,返回 100 时表示成功
     */
    private String ra_Code;
    /**
     * 响应码描述
     */
    private String rb_CodeMsg;
    /**
     * 1.主扫支付返回二维码地址。
     * 2.支付宝 H5,mode1/2/3 参考请求参数q9_TransactionModel 说明。
     * 3.微信 H5_PLUS,获取支付信息的 openlink,通过手机端浏览器跳转并唤起微信 APP客户端,直接打开对应的小程序进行支付。
     * 3.公众号支付:需要商户参考微信的官方文档 JSAPI 支付接口进行处理,详情请见:https://pay.weixin.qq.com/wiki/doc/api/index.html
     * 4.微信小程序支付返回支付信息。
     * 5.支付宝收银台返回支付宝收银台跳转链接,通过请求该链接跳转至支付宝。
     * 6.微信 app3 支付,返回预支付信息,集成微信 SDK 唤起小程序进行支付。
     * 7.支付宝服务窗支付返回银联交易号 trade_no,可用以唤起支付宝 APP,调起支付宝APP 收银台。
     * 8.银联 app 或银联统一收银台支付,返回预支付信息用此网址的接口调起支付。https://open.unionpay.com/tjweb/acproduct/list?apiservId=450#nav02
     * 9.银联云微小程序返回跳转地址,格式:{“cqpMpAppId”:”云闪付小程序 id”,”cqpMpPath”:”云闪付小程序 path”}
     * 10.其他类型支付返回支付信息。
     */
    private String rc_Result;
    /**
     * 二维码图片码
     */
    private String rd_Pic;
    /**
     * 签名数据
     */
    private String hmac;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/SaveWithdrawalAccount.java
New file
@@ -0,0 +1,24 @@
package com.ruoyi.other.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2025/1/1 11:54
 */
@Data
public class SaveWithdrawalAccount {
    @ApiModelProperty("收款银行卡号")
    private String receiverAccountNoEnc;
    @ApiModelProperty("收款银行卡持卡人名称")
    private String receiverNameEnc;
    @ApiModelProperty("账户类型(对私账户201,对公账户204)")
    private Integer receiverAccountType;
    @ApiModelProperty("收款账户联行号")
    private String receiverBankChannelNo;
}