springboot 使用微信支付

参考:Java实现微信支付_被编程征服的秃发女子的博客-CSDN博客

一.微信支付流程

支付流程:

用户点击支付按钮调用接口 [/deposit] =>

返回给小程序payInfo和订单编号orderNum =>

小程序端拿着payInfo和orderNum弹出支付 =>

等待用户支付完成调用支付完成接口 [/deposit/success],支付完成接口中验证是否存在订单编号,存在则订单充值成功,插入流水和增加余额。

提现流程:

用户点击提现调用接口 [/withdraw] 等待后台管理审核 =>

后台管理调用接口 [/agree] 同意提现、 [/reject] 拒绝提现,同时操作流水。

需要注意的是,在这个项目中,用户提现使用的是微信的 转账到账户 ,而不是网上大部分教程中的 企业付款到个人。

参考:Java整合微信商家转账到个人&开通流程 -CSDN博客_微信商户转账到个人

二.项目导入微信支付

1.引入依赖 并 更新 Maven

<!-- 微信支付 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-pay-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.Yaml中添加以下

project:
weixin:
#appid
app-id: 'xxxx'
#密钥
app-secret: 'xxxx'
# 商户号
mch-id: 111111
# 商户密钥
mch-key: xxxxx
# 用户付款回调地址 保证外网能访问
pay-notify-url: https://test.cn/recycle/wx/pay/notify
# 用户退款回调地址
refund-notify-url: https://test.cn/recycle/wx/pay/refund/notify
# p12证书的位置,可以绝对路径,可以指定类路径 以classpath:开头(classpath为项目resource文件夹)
cert-path: classpath:apiclient_cert.pem
key-path: classpath:apiclient_key.pem

3. 新建一个属性类 WeChatProperties

@Configuration
@ConfigurationProperties(prefix = "project.weixin")
@Data
public class WeChatProperties {
private String appId;
private String appSecret;
private String mchId;
private String mchKey;
private String payNotifyUrl;
private String refundNotifyUrl;
private String keyPath;
private String certPath;
}

4.新建配置类 WeChatConfig

@Configuration
public class WeChatConfig {
@Autowired
private WeChatProperties properties;
@Bean
public WxPayConfig wxPayConfig() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(properties.getAppId());
payConfig.setMchId(properties.getMchId());
payConfig.setMchKey(properties.getMchKey());
payConfig.setNotifyUrl(properties.getPayNotifyUrl());
payConfig.setApiV3Key(properties.getMchKey());
payConfig.setPrivateKeyPath(properties.getKeyPath());
payConfig.setPrivateCertPath(properties.getCertPath());
payConfig.setTradeType("JSAPI");
payConfig.setSignType("MD5");
return payConfig;
}
@Bean
public WxPayService wxPayService(WxPayConfig payConfig) {
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
@Bean
public WxMaService wxMaService(){
WxMaServiceImpl wxMaService = new WxMaServiceImpl();
wxMaService.setWxMaConfig(wxMaConfigMemory());
return wxMaService;
}
@Bean
public WxMaConfig wxMaConfigMemory(){
WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl();
wxMaConfig.setAppid(properties.getAppId());
wxMaConfig.setSecret(properties.getAppSecret());
return wxMaConfig;
}
}

至此,微信支付相关的配置完了。

5.添加 WeChatPayService和实现类

service:

public interface WeChatPayService {
//用户支付
WxPayReturnInfoVO weChatPay(String openId, String orderNum, Integer totalFee, String desc);
//用户退款(退款支付的订单)
Boolean refund(String orderNum, String refundNum, Integer totalFee, String desc);
//转账到用户
void entPay(String openId, String tradeNo, Integer amount, String ip);
}

serviceImpl:

@Slf4j
@Service
public class WeChatPayServiceImpl implements WeChatPayService {
@Autowired
WxPayService wxPayService;
@Autowired
WeChatProperties weChatProperties;
private static final String REFUND_SUCCESS = "SUCCESS";
@Override
public WxPayReturnInfoVO weChatPay(String openId, String orderNum, Integer totalFee, String desc) {
/**
* 系统内部业务逻辑
*/
// 构建支付参数
final WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = WxPayUnifiedOrderRequest.newBuilder()
//调起支付的人的 openId
.openid(openId)
//用户生成的唯一订单编号
.outTradeNo(orderNum)
//订单金额 单位分
.totalFee(totalFee)
//商品描述
.body(desc)
//获取本地IP
.spbillCreateIp(InetAddress.getLoopbackAddress().getHostAddress())
//回调的 URL 地址
.notifyUrl(weChatProperties.getPayNotifyUrl())
.build();
WxPayUnifiedOrderResult wxPayUnifiedOrderResult = null;
try {
wxPayUnifiedOrderResult = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest);
} catch (WxPayException e) {
e.printStackTrace();
log.error("微信支付调起失败!");
}
//组合参数构建支付
Map<String, String> paySignInfo = new HashMap<>(5);
String timeStamp = createTimestamp();
String nonceStr = String.valueOf(System.currentTimeMillis());
paySignInfo.put("appId", weChatProperties.getAppId());
paySignInfo.put("nonceStr", nonceStr);
paySignInfo.put("timeStamp", timeStamp);
paySignInfo.put("signType", "MD5");
paySignInfo.put("package", "prepay_id=" + wxPayUnifiedOrderResult.getPrepayId());
String[] signInfo = new String[0];
String paySign = SignUtils.createSign(paySignInfo, "MD5", weChatProperties.getMchKey(), signInfo);
//组合支付参数
WxPayReturnInfoVO returnPayInfoVO = new WxPayReturnInfoVO();
returnPayInfoVO.setAppId(weChatProperties.getAppId());
returnPayInfoVO.setNonceStr(nonceStr);
returnPayInfoVO.setPaySign(paySign);
returnPayInfoVO.setSignType("MD5");
returnPayInfoVO.setPrepayId("prepay_id=" + wxPayUnifiedOrderResult.getPrepayId());
returnPayInfoVO.setTimeStamp(timeStamp);
return returnPayInfoVO;
}
/**
* 商城订单退款
* @param orderNum
* @param refundNum
* @param totalFee
* @param desc
* @return
*/
@Override
public Boolean refund(String orderNum,String refundNum,Integer totalFee,String desc) {
//申请退款
WxPayRefundRequest refundInfo = WxPayRefundRequest.newBuilder()
// 支付订单号
.outTradeNo(orderNum)
// 退款订单号
.outRefundNo(refundNum)
// 支付金额 单位分
.totalFee(totalFee)
// 退款金额 单位分
.refundFee(totalFee)
// 微信退款回调地址
.notifyUrl(weChatProperties.getRefundNotifyUrl())
.refundAccount("REFUND_SOURCE_RECHARGE_FUNDS")
.build();
WxPayRefundResult wxPayRefundResult;
try {
wxPayRefundResult = wxPayService.refund(refundInfo);
//判断退款信息是否正确
if (REFUND_SUCCESS.equals(wxPayRefundResult.getReturnCode())
&& REFUND_SUCCESS.equals(wxPayRefundResult.getResultCode())) {
/**
* 系统内部业务逻辑
*/
return true;
}
} catch (WxPayException e) {
log.error("微信退款接口错误信息= {}", e);
System.err.println(e.getMessage());
return false;
}
return false;
}
/**
* 转账到个人(提现用)
* @param openId
* @param tradeNo
* @param amount
* @param ip
*/
@Override
public void entPay(String openId, String tradeNo, Integer amount, String ip) {
TransferService transferService = wxPayService.getTransferService();
//获取appId
String appId = wxPayService.getConfig().getAppId();
//创建批次对象
TransferBatchesRequest transferBatchesRequest=new TransferBatchesRequest();
transferBatchesRequest.setAppid(appId);
//设置批次名称 可不写
transferBatchesRequest.setBatchName("测试批次");
//设置批次备注 可不写
transferBatchesRequest.setBatchRemark("测试");
//设置该批次编号
transferBatchesRequest.setOutBatchNo(DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(3));
//设置该批次总个数
transferBatchesRequest.setTotalNum(1);
//设置该批次总金额
transferBatchesRequest.setTotalAmount(amount);
//创建收款人请求对象
ArrayList<TransferBatchesRequest.TransferDetail> transferDetails = new ArrayList<>();
TransferBatchesRequest.TransferDetail transferDetail=new TransferBatchesRequest.TransferDetail();
//转账的编号
transferDetail.setOutDetailNo(tradeNo);
//转账的金额
transferDetail.setTransferAmount(amount);
//转账的注释
transferDetail.setTransferRemark("用户提现");
//以实际微信公众号那边的openid为准
transferDetail.setOpenid(openId);
//把收款人对象放到批次里面
transferDetails.add(transferDetail);
transferBatchesRequest.setTransferDetailList(transferDetails);
TransferBatchesResult transferBatchesResult=null;
try {
transferBatchesResult = transferService.transferBatches(transferBatchesRequest);
log.info("企业支付完成:[msg:{}]",transferBatchesResult);
} catch (WxPayException e) {
e.printStackTrace();
throw new CaptchaException("企业支付失败:"+e.getMessage());
}
}
/**
* 时间
* @return 时间戳
*/
private String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}

6.回调接口(第二点yaml文件要配置的那两个接口)

在这个项目里实际上只用到了支付回调接口,用于判断用户是否支付成功

@ApiOperation(value = "微信的付款回调:前端不需要使用", hidden = true)
@PostMapping("/pay/notify")
public String parseOrderNotifyResult(@RequestBody String xmlData) {
try {
final WxPayOrderNotifyResult notifyResult = this.wxPayService.parseOrderNotifyResult(xmlData);
if (SUCCESS.equals(notifyResult.getResultCode())) {
PayCallback payCallback = new PayCallback();
payCallback.setTotalFee(notifyResult.getTotalFee());
payCallback.setTradeNo(notifyResult.getOutTradeNo());
payCallback.setOpenId(notifyResult.getOpenid());
payCallback.setTime(LocalDateTime.now());
payCallback.setStatus(0);
payCallbackService.save(payCallback);
log.error("订单:" + notifyResult.getOutTradeNo() + "回调成功");
} else {
log.error("订单:" + notifyResult.getOutTradeNo() + "回调成功但支付失败");
}
return WxPayNotifyResponse.success("回调成功!");
} catch (WxPayException e) {
e.printStackTrace();
return WxPayNotifyResponse.fail("回调有误!");
}
}
/**
* 仅支持一次性退款,多次退款需要修改逻辑
*/
@ApiOperation(value = "微信的退款回调:前端不需要使用", hidden = true)
@PostMapping("/refund/notify")
public String refundNotify(@RequestBody String xmlData) {
WxPayRefundNotifyResult wxPayRefundNotifyResult;
try {
wxPayRefundNotifyResult = wxPayService.parseRefundNotifyResult(xmlData);
} catch (WxPayException e) {
log.error("退款失败,失败信息:{}", e);
return WxPayNotifyResponse.fail("退款失败");
}
//判断你返回状态信息是否正确
if (SUCCESS.equals(wxPayRefundNotifyResult.getReturnCode())) {
WxPayRefundNotifyResult.ReqInfo reqInfo = wxPayRefundNotifyResult.getReqInfo();
//判断退款状态
if (SUCCESS.equals(reqInfo.getRefundStatus())) {
try {
/**
* 一、可能会重复回调,需要做防重判断
* 二、处理我们系统内部业务,做修改订单状态,释放资源等!
*/
reqInfo.getOutTradeNo();
return WxPayNotifyResponse.success("退款成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
return WxPayNotifyResponse.fail("退款失败!");
}
posted @   J0kerLu  阅读(1565)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示