feat:位置付款码支付

This commit is contained in:
dzc committed 2025-06-06 14:42:08 +08:00
1 parent 3b0749da57
commit 6381c3be2f
22 files changed
+1257 -17

No files matched your search

@@ -24,6 +24,8 @@ public enum HardPayTypeEnum {
RESERVE_QR_CODE("7", "线上二维码支付"),
CASH("8", "现金支付"),
HARD_DOOR_FREE_PAY("9", "智能柜免密支付"),
WECHANT_SELF_QR_CODE_PAY("10", "微信付款码支付"),
ALI_SELF_QR_CODE_PAY("11", "支付宝付款码支付"),
;
private final String type;
private final String description;
@@ -36,7 +38,14 @@ public enum HardPayTypeEnum {
CASH
);
public static final List<HardPayTypeEnum> isNotPay = List.of(
public static final List<HardPayTypeEnum> isNotWalletPay = List.of(
STRIDE_DEPT_QR_CODE,
FOREIGN_TICKET_QR_CODE,
WECHANT_SELF_QR_CODE_PAY,
ALI_SELF_QR_CODE_PAY
);
public static final List<HardPayTypeEnum> isNeedCancel = List.of(
STRIDE_DEPT_QR_CODE,
FOREIGN_TICKET_QR_CODE
);
@@ -0,0 +1,28 @@
package com.bsd.pjcloud.hard.api.dto.wechat;
import lombok.Data;
@Data
public class WechatMicropayDto {
/**
* 商户id
*/
private Long merId;
/**
* 订单id
*/
private Long orderId;
/**
* 支付金额(分)
*/
private Long price;
/**
* 微信付款码code
*/
private String wechatQrCode;
}
@@ -0,0 +1,21 @@
package com.bsd.pjcloud.hard.api.dto.wechat;
import lombok.Data;
/**
* 查询订单入参
*/
@Data
public class WechatQueryDto {
/**
* 微信订单号
*/
private String wechantOrderId;
/**
* 商户订单号
*/
private String orderId;
}
@@ -0,0 +1,21 @@
package com.bsd.pjcloud.hard.api.dto.wechat;
import lombok.Data;
/**
* 退款入参
*/
@Data
public class WechatRefundDto {
private String orderId;
private String wechantOrderId;
private String refundId;
private Long orderPrice;
private Long refundPrice;
}
@@ -0,0 +1,17 @@
package com.bsd.pjcloud.hard.api.dto.wechat;
import lombok.Data;
/**
* 微信退款查询入参
*/
@Data
public class WechatRefundQueryDto {
/**
* 退款id
*/
private String refundId;
}
@@ -0,0 +1,30 @@
package com.bsd.pjcloud.hard.api.vo.wechat;
import lombok.Data;
/**
* 微信退款回调加密的返回参数
*/
@Data
public class WechantRefunCallbackSoureVo {
/**
* 公众账号ID
*/
private String appid;
/**
* 退款的商户号
*/
private String mch_id;
/**
* 随机字符串
*/
private String nonce_str;
/**
* 加密信息
*/
private String req_info;
}
@@ -0,0 +1,77 @@
package com.bsd.pjcloud.hard.api.vo.wechat;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
/**
* 微信退款回调返回参数
*/
@Data
public class WechantRefunCallbackVo {
public static final String SUCCESS_CODE = "SUCCESS";
/**
* 微信订单号
*/
private String transaction_id;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 微信退款单号
*/
private String refund_id;
/**
* 商户退款单号
*/
private String out_refund_no;
/**
* 订单金额
*/
private String total_fee;
/**
* 应结订单金额
*/
private String settlement_total_fee;
/**
* 申请退款金额
*/
private String refund_fee;
/**
* 退款金额
*/
private String settlement_refund_fee;
/**
* 退款状态
*/
private String refund_status;
public boolean isAllSuccess() {
return ObjectUtil.equals(refund_status, SUCCESS_CODE);
}
public static String wechantError() {
return "<xml>" +
"<return_code><![CDATA[FAIL]]></return_code>" +
"<return_msg><![CDATA[退款错误]]></return_msg>" +
"</xml>";
}
public static String wechantSuccess() {
return "<xml>" +
"<return_code><![CDATA[SUCCESS]]></return_code>" +
"<return_msg><![CDATA[退款成功]]></return_msg>" +
"</xml>";
}
}
@@ -0,0 +1,43 @@
package com.bsd.pjcloud.hard.api.vo.wechat;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
@Data
public class WechatMicropayVo {
public static final String SUCCESS = "SUCCESS";
private String return_code;
private String return_msg;
private String appid;
private String mch_id;
private String device_info;
private String nonce_str;
private String sign;
private String result_code;
private String err_code;
private String err_code_des;
public boolean isHttpSucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, return_code);
}
public boolean isPaySucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code);
}
public boolean isAllSuccess(){
return isHttpSucess() && isPaySucess();
}
}
@@ -0,0 +1,125 @@
package com.bsd.pjcloud.hard.api.vo.wechat;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
/**
* 查询订单返回
*/
@Data
public class WechatQueryVo {
public static final String SUCCESS = "SUCCESS";
private String return_code;
private String return_msg;
private String appid;
private String mch_id;
private String nonce_str;
private String sign;
private String result_code;
private String err_code;
private String err_code_des;
private String device_info;
/**
* 用户标识
*/
private String openid;
/**
* 是否关注公众账号
*/
private String is_subscribe;
/**
* 交易类型
*/
private String trade_type;
/**
* 交易状态
*/
private String trade_state;
/**
* 付款银行
*/
private String bank_type;
/**
* 标价金额
*/
private String total_fee;
/**
* 应结订单金额
*/
private String settlement_total_fee;
/**
* 标价币种
*/
private String fee_type;
/**
* 现金支付金额
*/
private String cash_fee;
/**
* 现金支付币种
*/
private String cash_fee_type;
/**
* 代金券金额
*/
private String coupon_fee;
/**
* 代金券使用数量
*/
private String coupon_count;
/**
* 微信支付订单号
*/
private String transaction_id;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 附加数据
*/
private String attach;
/**
* 支付完成时间
*/
private String time_end;
/**
* 交易状态描述
*/
private String trade_state_desc;
public boolean isHttpSucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, return_code);
}
public boolean isPaySucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code);
}
}
@@ -0,0 +1,102 @@
package com.bsd.pjcloud.hard.api.vo.wechat;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
/**
* 退款返回
*/
@Data
public class WechatRefundQueryVo {
public static final String SUCCESS = "SUCCESS";
/**
* 业务结果
*/
private String result_code;
/**
* 错误码
*/
private String err_code;
/**
* 错误描述
*/
private String err_code_des;
/**
* 公众账号ID
*/
private String appid;
/**
* 商户号
*/
private String mch_id;
/**
* 随机字符串
*/
private String nonce_str;
/**
* 签名
*/
private String sign;
/**
* 订单总退款次数
*/
private Long total_refund_count;
/**
* 微信订单号
*/
private String transaction_id;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 订单金额
*/
private Long total_fee;
/**
* 应结订单金额
*/
private Long settlement_total_fee;
/**
* 货币种类
*/
private String fee_type;
/**
* 现金支付金额
*/
private Long cash_fee;
/**
* 退款笔数
*/
private Long refund_count;
/**
* 退款总金额
*/
private Long refund_fee;
public boolean isHttpSucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, err_code);
}
public boolean isPaySucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code);
}
}
@@ -0,0 +1,84 @@
package com.bsd.pjcloud.hard.api.vo.wechat;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
/**
* 退款返回
*/
@Data
public class WechatRefundVo {
public static final String SUCCESS = "SUCCESS";
private String return_code;
private String return_msg;
private String result_code;
private String err_code;
private String err_code_des;
private String appid;
private String mch_id;
private String nonce_str;
private String sign;
/**
* 微信支付订单号
*/
private String transaction_id;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 商户退款单号
*/
private String out_refund_no;
/**
* 微信退款单号
*/
private String refund_id;
/**
* 退款金额
*/
private String refund_fee;
/**
* 应结退款金额
*/
private String settlement_refund_fee;
/**
* 标价金额
*/
private String total_fee;
/**
* 应结订单金额
*/
private String settlement_total_fee;
public boolean isHttpSucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, return_code);
}
public boolean isPaySucess() {
return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code);
}
public boolean isAllSuccess() {
return isHttpSucess() && isPaySucess();
}
}
@@ -0,0 +1,43 @@
package com.bsd.pjcloud.hard.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "wechat-pay")
@Data
public class WechatPay {
/**
* 基础路径
*/
private String baseUrl;
/**
* 微信公共平台 appId
*/
private String appId;
/**
* 商户号
*/
private String merchantId;
/**
* 商户证书序列path
*/
private String merchantSerialPath;
/**
* 商户APIV2密钥
*/
private String apiV2Key;
/**
* 退款回调地址
*/
private String refundCallbackUrl;
}
@@ -0,0 +1,40 @@
package com.bsd.pjcloud.hard.controller.wechant;
import com.bsd.pjcloud.common.security.annotation.Inner;
import com.bsd.pjcloud.hard.service.WechatPayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequiredArgsConstructor
@RequestMapping("/hard-wechant-pay")
@Tag(description = "hard-wechant", name = "微信相关")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
@Validated
public class WechantPayController {
private final WechatPayService wechatPayService;
/* ************************************* start callback start ************************************* */
@Inner(value = false)
@PostMapping("/callback/refund")
@Operation(summary = "微信退款回调")
public String refundCallback(HttpEntity<String> httpEntity, @RequestHeader Map<String, String> headers) {
return wechatPayService.refundCallback(httpEntity, headers);
}
/* ************************************* end callback end ************************************* */
}
@@ -63,10 +63,15 @@ public interface HardOrderService extends IService<HardOrderEntity> {
Long cancelHardDoorOrder(Long orderId);
/**
* 快速扣款,离线订单使用
* 快速扣款
*/
void fastReduceBalance(HardOrderEntity order);
/**
* 订单成功
*/
void orderSuccess(HardOrderEntity order, Boolean isPay);
/**
* 计算额外金额
*/
@@ -0,0 +1,46 @@
package com.bsd.pjcloud.hard.service;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatMicropayDto;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatQueryDto;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatRefundDto;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatRefundQueryDto;
import com.bsd.pjcloud.hard.api.vo.wechat.WechatMicropayVo;
import com.bsd.pjcloud.hard.api.vo.wechat.WechatQueryVo;
import com.bsd.pjcloud.hard.api.vo.wechat.WechatRefundQueryVo;
import com.bsd.pjcloud.hard.api.vo.wechat.WechatRefundVo;
import org.springframework.http.HttpEntity;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.Map;
/**
* 微信支付
*/
public interface WechatPayService {
/**
* 付款码支付
*/
WechatMicropayVo micropay(WechatMicropayDto dto);
/**
* 查询订单
*/
WechatQueryVo query(WechatQueryDto dto);
/**
* 申请退款
*/
WechatRefundVo refund(WechatRefundDto dto);
/**
* 查询退款
*/
WechatRefundQueryVo refundQuery(WechatRefundQueryDto dto);
/**
* 退款回调
*/
String refundCallback(HttpEntity<String> httpEntity, @RequestHeader Map<String, String> headers);
}
@@ -196,14 +196,18 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
public Long createOrder(HardCreateOrderDto dto) {
MchManageVo mch = validateCreateOrderDto(dto);
if (ObjectUtil.isEmpty(mch)) throw new RuntimeException("机器绑定的商户不存在");
// 是否需要支付
boolean isPay = isPay(dto.getOrderType(), dto.getOrderPayType());
// 是否需要钱包支付
boolean isWalletPay = isPay(dto.getOrderType(), dto.getOrderPayType());
// 是否需要核销
boolean isVerify = isVerify(dto.getOrderPayType());
// 是否微信支付
boolean isWeChatPay = ObjectUtil.equals(dto.getOrderPayType(), HardPayTypeEnum.WECHANT_SELF_QR_CODE_PAY);
// 是否支付宝支付
boolean isAliPay = ObjectUtil.equals(dto.getOrderPayType(), HardPayTypeEnum.ALI_SELF_QR_CODE_PAY);
// 查询用户
UserInfo userDetail = null;
SysUser sysUser = null;
if (isPay || isVerify) {
if (isWalletPay || isVerify) {
userDetail = userService.getInnerByUserId(dto.getUserId()).getData();
if (ObjectUtil.isEmpty(userDetail)) throw new RuntimeException("用户不存在不存在");
sysUser = userDetail.getSysUser();
@@ -212,7 +216,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
boolean isExtraPrice = isExtraPrice(dto.getSysType());
// 默认使用该商户的钱包如果没有就用饭卡钱包
WalletManageEntity wallet = null;
if (isPay) {
if (isWalletPay) {
if (ObjectUtil.isEmpty(mch.getDefaultWalletTypeId())) {
if (dto.getSysType().equals(HardTypeEnum.CANTEEN)) {
R<WalletManageEntity> walletResult = remoteWalletService.getWallet(dto.getUserId(), WalletTypeEnum.EAT);
@@ -237,7 +241,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
totalPrice = hardCarService.computerCarTotalPrice(dto.getSysType(), dto.getOrderType(), dto.getList());
if (isExtraPrice) extraPrice = computeExtraPrice(totalPrice, mch, sysUser);
payPrice = totalPrice + extraPrice;
if (isPay) {
if (isWalletPay) {
// 如果是支付的话,不能小于等于0
if (payPrice <= 0) throw new RuntimeException("支付金额为0元");
// 判断余额够不够支付
@@ -274,17 +278,26 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
} else {
orderObj.setUserId(-1L);
}
if (isPay) {
if (isWalletPay) {
orderObj.setWalletId(wallet.getId());
}
baseMapper.insert(orderObj);
// 扣减库存
getSelf().fastenerInventory(orderObj, YesNoEnum.YES);
// 钱包扣款
getSelf().fastReduceBalance(orderObj);
if (isVerify) {
// 钱包支付或者核销
if (isVerify || isWalletPay) {
// 钱包扣款
getSelf().fastReduceBalance(orderObj);
// 核销二维码
getSelf().cancelCode(orderObj, userDetail);
}
// 微信支付
if (isWeChatPay) {
}
// 支付宝支付
if (isAliPay) {
}
// 发送mq消息
getSelf().createOrderAfterSendMqMsg(orderObj.getId(), userDetail);
@@ -541,8 +554,12 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
@Override
@Transactional(rollbackFor = Exception.class)
public void fastReduceBalance(HardOrderEntity order) {
boolean isPay = isPay(HardOrderTypeEnum.valueOfByType(order.getOrderType()), HardPayTypeEnum.valueOfByType(order.getPayType()));
if (isPay) {
HardOrderTypeEnum hardOrderType = HardOrderTypeEnum.valueOfByType(order.getOrderType());
HardPayTypeEnum hardPayType = HardPayTypeEnum.valueOfByType(order.getPayType());
boolean isWalletPay = isPay(hardOrderType, hardPayType);
// 是否需要核销
boolean isVerify = isVerify(hardPayType);
if (isWalletPay) {
WalletBalanceTransactionDto reduceBuild = new WalletBalanceTransactionDto();
reduceBuild.setWalletId(order.getWalletId());
reduceBuild.setPrice(order.getPayPrice());
@@ -554,8 +571,23 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
throw new RuntimeException("钱包扣款错误");
}
}
// 订单成功
if (isWalletPay || isVerify) {
getSelf().orderSuccess(order, isWalletPay);
}
}
/**
* 订单成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void orderSuccess(HardOrderEntity order, Boolean isWalletPay) {
if (ObjectUtil.isEmpty(isWalletPay)) {
isWalletPay = isPay(HardOrderTypeEnum.valueOfByType(order.getOrderType()), HardPayTypeEnum.valueOfByType(order.getPayType()));
}
// 订单表订单状态
Integer payPrice = isPay ? order.getTotalPrice() + order.getExtraPrice() : 0;
Integer payPrice = isWalletPay ? order.getTotalPrice() + order.getExtraPrice() : 0;
HardOrderEntity update = HardOrderEntity.builder().id(order.getId()).payTime(LocalDateTime.now()).orderStatus(HardOrderStatusEnum.PAY.getType()).payPrice(payPrice).build();
baseMapper.updateById(update);
// 父购物车订单表订单状态
@@ -653,7 +685,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
HardPayTypeEnum payType = HardPayTypeEnum.valueOfByType(order.getPayType());
if (!ObjectUtil.isAllNotEmpty(orderTypeEnum, payType)) return;
Long id = order.getId();
if (HardPayTypeEnum.isNotPay.contains(payType) && ObjectUtil.isNotEmpty(order.getOrderSourceTypeId())) {
if (HardPayTypeEnum.isNotWalletPay.contains(payType) && ObjectUtil.isNotEmpty(order.getOrderSourceTypeId())) {
id = order.getOrderSourceTypeId();
}
switch (orderTypeEnum) {
@@ -847,7 +879,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
if (!HardOrderTypeEnum.isPay(type)) {
return false;
}
if (HardPayTypeEnum.isNotPay.contains(payType)) {
if (HardPayTypeEnum.isNotWalletPay.contains(payType)) {
return false;
}
return true;
@@ -855,7 +887,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
// 是否需要核销
private boolean isVerify(HardPayTypeEnum payType) {
return HardPayTypeEnum.isNotPay.contains(payType);
return HardPayTypeEnum.isNeedCancel.contains(payType);
}
// 是否需要额外支付(目前只有食堂需要)
@@ -0,0 +1,332 @@
package com.bsd.pjcloud.hard.service.impl.wechat;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.json.JSONUtil;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatMicropayDto;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatQueryDto;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatRefundDto;
import com.bsd.pjcloud.hard.api.dto.wechat.WechatRefundQueryDto;
import com.bsd.pjcloud.hard.api.vo.wechat.*;
import com.bsd.pjcloud.hard.config.WechatPay;
import com.bsd.pjcloud.hard.service.WechatPayService;
import com.bsd.pjcloud.hard.utils.constant.WechantPayConstant;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* 微信支付
*/
@Service
@AllArgsConstructor
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {
private final WechatPay wechatPay;
/**
* 生成微信支付签名
*/
public String generateSign(Map<String, Object> params) {
String apiKey = wechatPay.getApiV2Key();
Map<String, String> filtered = new TreeMap<>();
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (ObjectUtil.isEmpty(entry.getValue()) || ObjectUtil.equals("sign", entry.getKey())) {
continue;
}
filtered.put(entry.getKey(), String.valueOf(entry.getValue()));
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : filtered.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
sb.append("key=").append(apiKey);
try {
String sign = DigestUtils.md5Hex(String.valueOf(sb));
return sign.toUpperCase();
} catch (Exception e) {
log.error("微信支付签名生成失败", e);
}
return "";
}
private WechantPayConstant.Api initApi(WechantPayConstant.Api api) {
api.setBaseUrl(wechatPay.getBaseUrl());
return api;
}
private String getNonceStr() {
int length = 32;
return RandomUtil.randomString(length);
}
private SSLSocketFactory createSSLSocketFactory() throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream certStream = new FileInputStream(wechatPay.getMerchantSerialPath());
keyStore.load(certStream, wechatPay.getMerchantId().toCharArray());
certStream.close();
// 创建KeyManagerFactory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, wechatPay.getMerchantId().toCharArray());
// 创建TrustManagerFactory(信任所有证书)
TrustManager[] trustManagers = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// 创建SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, new SecureRandom());
return sslContext.getSocketFactory();
}
/**
* 解密回调信息解密
*/
private WechantRefunCallbackVo decrypt(String xml) {
try {
WechantRefunCallbackSoureVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(xml)), WechantRefunCallbackSoureVo.class);
String reqInfo = vo.getReq_info();
// 1 对加密串A做base64解码,得到加密串B
byte[] encryptedBytes = Base64.decode(reqInfo);
// 2 对商户key做md5,得到32位小写key
String md5Key = DigestUtils.md5Hex(wechatPay.getApiV2Key()).toLowerCase();
// 3. AES-256-ECB 解密
SecretKeySpec secretKey = new SecretKeySpec(md5Key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
String json = new String(decryptedBytes, StandardCharsets.UTF_8);
WechantRefunCallbackVo entity = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(json)), WechantRefunCallbackVo.class);
log.debug("解密回调信息,json|{}", JSONUtil.toJsonStr(entity));
return entity;
} catch (Exception e) {
e.printStackTrace();
log.error("解密回调信息解密异常", e);
return null;
}
}
/**
* 付款码支付
*/
@Override
public WechatMicropayVo micropay(WechatMicropayDto dto) {
try {
Map<String, Object> params = new HashMap<>();
// 公众账号ID
params.put("appid", wechatPay.getAppId());
// 商户号
params.put("mch_id", wechatPay.getMerchantId());
// 设备号-终端设备号(商户自定义,如门店编号)
params.put("device_info", dto.getMerId());
// 随机字符串
params.put("nonce_str", getNonceStr());
// 签名
params.put("sign", "");
// 签名类型
params.put("sign_type", "MD5");
// 商品描述
params.put("body", "小铺故事超市-超市");
// 商户单号
params.put("out_trade_no", dto.getOrderId());
// 订单金额(分)
params.put("total_fee", dto.getPrice());
// 终端IP
params.put("spbill_create_ip", "220.191.229.131");
// 付款码
params.put("auth_code", dto.getWechatQrCode());
// 生成签名
params.put("sign", generateSign(params));
WechantPayConstant.Api api = initApi(WechantPayConstant.APY);
String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body();
log.debug("付款码支付,json,xml|{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("付款码支付,res为空");
return null;
}
WechatMicropayVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatMicropayVo.class);
log.debug("付款码支付,json,res|{}", JSONUtil.toJsonStr(vo));
if (!vo.isHttpSucess()) {
return null;
}
return vo;
} catch (Exception e) {
log.error("付款码支付,异常", e);
return null;
}
}
/**
* 查询订单
*/
@Override
public WechatQueryVo query(WechatQueryDto dto) {
try {
Map<String, Object> params = new HashMap<>();
// 公众账号ID
params.put("appid", wechatPay.getAppId());
// 商户号
params.put("mch_id", wechatPay.getMerchantId());
// 微信订单号
params.put("transaction_id", dto.getWechantOrderId());
// 商户订单号
params.put("out_trade_no", dto.getOrderId());
// 随机字符串
params.put("nonce_str", getNonceStr());
// 签名
params.put("sign", "");
// 签名类型
params.put("sign_type", "MD5");
// 生成签名
params.put("sign", generateSign(params));
WechantPayConstant.Api api = initApi(WechantPayConstant.QUERY);
String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body();
log.debug("查询订单,json,xml|{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("查询订单,res为空");
return null;
}
WechatQueryVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatQueryVo.class);
log.debug("查询订单,json,res|{}", JSONUtil.toJsonStr(vo));
if (!vo.isHttpSucess()) {
return null;
}
return vo;
} catch (Exception e) {
log.error("查询订单,异常", e);
return null;
}
}
/**
* 申请退款
*/
@Override
public WechatRefundVo refund(WechatRefundDto dto) {
try {
Map<String, Object> params = new HashMap<>();
// 公众账号ID
params.put("appid", wechatPay.getAppId());
// 商户号
params.put("mch_id", wechatPay.getMerchantId());
// 随机字符串
params.put("nonce_str", getNonceStr());
// 签名
params.put("sign", "");
// 签名类型
params.put("sign_type", "MD5");
// 微信支付订单号
params.put("transaction_id", dto.getWechantOrderId());
// 商户订单号
params.put("out_trade_no", dto.getOrderId());
// 商户退款单号
params.put("out_refund_no", dto.getRefundId());
// 订单金额
params.put("total_fee", dto.getOrderPrice());
// 退款金额
params.put("refund_fee", dto.getRefundPrice());
// 退款结果通知url
params.put("notify_url", wechatPay.getRefundCallbackUrl());
// 生成签名
params.put("sign", generateSign(params));
WechantPayConstant.Api api = initApi(WechantPayConstant.REFUND);
api.setSslSocketFactory(createSSLSocketFactory());
String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body();
log.debug("申请退款,json,xml|{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("申请退款,res为空");
return null;
}
WechatRefundVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatRefundVo.class);
log.debug("申请退款,json,res|{}", JSONUtil.toJsonStr(vo));
if (!vo.isHttpSucess()) {
return null;
}
return vo;
} catch (Exception e) {
log.error("申请退款,异常", e);
return null;
}
}
/**
* 查询退款
*/
@Override
public WechatRefundQueryVo refundQuery(WechatRefundQueryDto dto) {
try {
Map<String, Object> params = new HashMap<>();
// 公众账号ID
params.put("appid", wechatPay.getAppId());
// 商户号
params.put("mch_id", wechatPay.getMerchantId());
// 随机字符串
params.put("nonce_str", getNonceStr());
// 签名
params.put("sign", "");
// 签名类型
params.put("sign_type", "MD5");
// 签名类型
params.put("out_refund_no", dto.getRefundId());
// 生成签名
params.put("sign", generateSign(params));
WechantPayConstant.Api api = initApi(WechantPayConstant.REFUND_QUERY);
String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body();
log.debug("查询退款,json,xml|{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("查询退款,res为空");
return null;
}
WechatRefundQueryVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatRefundQueryVo.class);
log.debug("查询退款,json,res|{}", JSONUtil.toJsonStr(vo));
if (!vo.isHttpSucess()) {
return null;
}
return vo;
} catch (Exception e) {
log.error("查询退款,异常", e);
return null;
}
}
/**
* 退款回调
*/
@Override
public String refundCallback(HttpEntity<String> httpEntity, Map<String, String> headers) {
String body = httpEntity.getBody();
WechantRefunCallbackVo decrypt = decrypt(body);
if (ObjectUtil.isEmpty(decrypt)) {
// 告诉微信错误
return WechantRefunCallbackVo.wechantError();
}
return WechantRefunCallbackVo.wechantError();
}
}
@@ -0,0 +1,63 @@
package com.bsd.pjcloud.hard.utils.constant;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpMethod;
import javax.net.ssl.SSLSocketFactory;
/**
* 微信支付api
*/
public class WechantPayConstant {
public static final Api APY = Api.builder().summary("付款码支付").url("/pay/micropay").method(HttpMethod.POST).isNeedCertificate(false).build();
public static final Api QUERY = Api.builder().summary("查询订单").url("/pay/orderquery").method(HttpMethod.POST).isNeedCertificate(false).build();
public static final Api REVERSE = Api.builder().summary("撤销订单").url("/pay/reverse").method(HttpMethod.POST).isNeedCertificate(false).build();
public static final Api REFUND = Api.builder().summary("申请退款").url("/secapi/pay/refund").method(HttpMethod.POST).isNeedCertificate(true).build();
public static final Api REFUND_QUERY = Api.builder().summary("查询退款").url("/pay/refundquery").method(HttpMethod.POST).isNeedCertificate(false).build();
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class Api {
private String summary;
private String baseUrl;
private String url;
private String fullUrl;
private HttpMethod method;
private boolean isNeedCertificate = false;
private SSLSocketFactory sslSocketFactory;
public HttpRequest getHttpRequest() {
String fullUrl = baseUrl + url;
HttpRequest req = switch (method.name()) {
case "GET" -> HttpRequest.get(fullUrl);
case "POST" -> HttpRequest.post(fullUrl);
default -> null;
};
if (ObjectUtil.isNotEmpty(req) && ObjectUtil.isNotEmpty(sslSocketFactory) && isNeedCertificate) {
req.setSSLSocketFactory(sslSocketFactory);
}
return req;
}
}
}
@@ -24,3 +24,11 @@ spring:
# 第三方回调配置
# pay-callback:
# lb-mch-id: 1919673632357584898
wechat-pay:
merchant-id: 1718803142
api-v2-key: EDn3OfIZZFwyS9Obx73XgQ8NHt01piUa
app-id: wx4ec0210f4bf42f9f
base-url: https://api.mch.weixin.qq.com
refund-callback-url: https://rj4jv5rwqs.by.takin.cc/hard/hard-wechant-pay/callback/refund
merchant-serial-path: /Users/dengzhangchen/Desktop/backups/pay/cert/apiclient_cert.p12
+6
View File
@@ -131,6 +131,12 @@
<groupId>com.bsd</groupId>
<artifactId>pjcloud-common-idempotent</artifactId>
</dependency>
<!-- 微信支付 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.17</version>
</dependency>
<dependency>
<groupId>com.bsd</groupId>
<artifactId>pjcloud-hardware-api</artifactId>
@@ -0,0 +1,40 @@
package com.bsd.pjcloud.wallet.controller.wechant;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.security.annotation.Inner;
import com.bsd.pjcloud.wallet.api.dto.WalletActiveDto;
import com.bsd.pjcloud.wallet.service.WechatPayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequiredArgsConstructor
@RequestMapping("/wallet-wechant-pay")
@Tag(description = "wallet-wechant", name = "微信相关")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
@Validated
public class WechantPayController {
private final WechatPayService wechatPayService;
/* ************************************* start callback start ************************************* */
@Inner(value = false)
@PostMapping("/callback/refund")
@Operation(summary = "微信退款回调")
public String refundCallback(HttpEntity<String> httpEntity, @RequestHeader Map<String, String> headers) {
return wechatPayService.refundCallback(httpEntity, headers);
}
/* ************************************* end callback end ************************************* */
}
@@ -0,0 +1,68 @@
package com.bsd.pjcloud.wallet;
import cn.hutool.json.JSONUtil;
import com.bsd.pjcloud.thirdparty.dto.lb.LbH5LoginDto;
import com.bsd.pjcloud.thirdparty.vo.lb.LbH5LoginVo;
import com.bsd.pjcloud.wallet.api.dto.wechat.WechatMicropayDto;
import com.bsd.pjcloud.wallet.api.dto.wechat.WechatQueryDto;
import com.bsd.pjcloud.wallet.api.dto.wechat.WechatRefundDto;
import com.bsd.pjcloud.wallet.api.vo.wechat.WechatMicropayVo;
import com.bsd.pjcloud.wallet.api.vo.wechat.WechatQueryVo;
import com.bsd.pjcloud.wallet.api.vo.wechat.WechatRefundVo;
import com.bsd.pjcloud.wallet.service.WechatPayService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 微信支付配置
*/
@Slf4j
@SpringBootTest(classes = WalletApp.class)
public class WechatPayTest {
@Resource
private WechatPayService wechatPayService;
@Test
public void micropay() throws Exception {
long currentTimeMillis = System.currentTimeMillis();
WechatMicropayDto dto = new WechatMicropayDto();
dto.setMerId(currentTimeMillis);
dto.setOrderId(currentTimeMillis);
dto.setPrice(1L);
dto.setWechatQrCode("132796932599913148");
WechatMicropayVo micropay = wechatPayService.micropay(dto);
if (micropay.isAllSuccess()) {
log.info("支付成功json:{}", JSONUtil.toJsonStr(micropay));
}
}
@Test
public void query() throws Exception {
WechatQueryDto dto = new WechatQueryDto();
dto.setWechantOrderId("4200002719202506059568204561");
WechatQueryVo micropay = wechatPayService.query(dto);
}
@Test
public void rerfund() throws Exception {
String currentOrderId = "4200002648202506062041165177";
WechatRefundDto dto = new WechatRefundDto();
dto.setWechantOrderId(currentOrderId);
dto.setRefundId("refund_" + currentOrderId);
dto.setOrderPrice(1L);
dto.setRefundPrice(1L);
WechatRefundVo micropay = wechatPayService.refund(dto);
if (micropay.isAllSuccess()) {
log.info("退款成功json:{}", JSONUtil.toJsonStr(micropay));
}
}
}