后端支付类
package com.ruoyi.coupon.payment;
import com.google.gson.Gson;
import com.ruoyi.coupon.payment.dto.PayParamJsapiDto;
import com.ruoyi.coupon.payment.dto.RefundParam;
import com.ruoyi.coupon.service.ICouponConfigService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.*;
import java.util.*;
@Service
public class WeChatJsapiPayment {
private static final String mchId = "165"; // 商户号
private static final String mchSerialNo = "12C5F95"; // 商户证书序列号
//商户私钥 //https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml
private static final String privateKey = "-----BEGIN PRIVATE KEY-----" +
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOo7rFC6cJF+kd" +
"7cPFkmrHlbdihgznK2u5v2CDoSClwrY1hGE81VJMHjHic03Ky0Hx1lCZh+hOCZXk" +
"4mnTnbeUlvq/6E6I6gexsM9ww6nz0YbPRZuNPPxBmP1rqKzlZO7LiLvIwg8x3cOr" +
"qB4uPCo5Eh0Ty3r1kygfEXDyWEHWRYx1JVesmcxBp2n8HkfIpNA3tyPFnTFJEI4W" +
"/7T4ScoSlRRUsYNrg0oox683HgFXWE9fAJloyt35EHPoBEx6gUGKC9Gm1luJNrSb" +
"9GAQCPLVgtr3hq0kQHvVKcKsgpzPJA5G20bTM1X9Yo/nr+rt+HFZpbaNvviuhXg7" +
"EvajkeprAgMBAAECggEAUXVTzBei2j/VwtmW4kys0U7gwvMLJRoF1mxt9JXRE+cl" +
"AwXTezKwxZblnVpM4VOtBFOy5EMiPZkjwN3MUAKNewLliD9grjJrpA2KSO6pEZeY" +
"aB8NiejvnEBeLlGJDsjyJcQaGrL9YHAGkaWteBZ5opPUaJg/OY2wNEcg2jgWhAui" +
"eTqocUEnbowDCFlpHEJmAJKxuaLaV39Wad1jKs9N9CiqYyVNfNoeZJ7LSHekrz6v" +
"SOVD8nz3y/1evf4LqkaIjLJwTS9UXVZuKjlyLcjsEpGf4X3cZ7hI/wJEqXDqKSL8" +
"cgbnkZTI6YI32ukAIne/d3AOsRkbYomR26g3cqpeqQKBgQD9bxwYm+Z73SrfCJte" +
"8Tj9ejrBWcAU1r8p1uu7kiL2gU0RVxwzBe0QNhLUybP5ciONzAtkS6/NPLkHU6HQ" +
"frb9JqoipC6LT7c2R4kR+4wcLH9lbiscljPUji04sEEMzUrLWjwcAwLjlCNbqZ4m" +
"UJ2y4skFFzVEa6a8uO7qX5qFpQKBgQDQu1Sgrhb2Yby/YuJyq2j8k5BRiHcjLmUI" +
"Pi1uIq0lUof4F61EkFuua+bsr+oaiQxhqKIkPbURnjdkEG2bC5K/n+f2BbOFOfSz" +
"14ygBoZaRGZvoS56xOETh9ao9sLCsQJgz1E3gwYpAIbh6DBvoU8w0prL/O0gbAC/" +
"CywSvL1SzwKBgHJ413X+JTNZiN3JI3TU/Grx5Mwk3/AJt7sMStokfgpeCROGB0S8" +
"roGeCw92NAa+GXUY7yRUU4oenWzDx9lHAxyBdGPFSQi/7v9jfHRU7MplBv4nru1w" +
"ouSle6OZaSiBKgGENpZofcuRxA7JJJgl3bVJXocgHn1TKrMfzTqsCD3JAoGBAK8S";
// 你的微信支付平台证书
private static final String apiV3Key = "f2f4667bfeab441";
//公众号appId
private static final String appid="wx29d5";
private static final String secret = "58be861a9f6";
public static Map<String,RSAAutoCertificateConfig> config=new HashMap<>();
@Autowired
private ICouponConfigService couponConfigService;
/**
* 获取jsapi服务类
* @return
*/
public JsapiService getJsapiService(){
RSAAutoCertificateConfig rsaAutoCertificateConfig = config.get(this.mchId);
if(rsaAutoCertificateConfig == null){
RSAAutoCertificateConfig build = new RSAAutoCertificateConfig.Builder()
.merchantId(this.mchId)
.privateKey(this.privateKey)
//.privateKeyFromPath(WxConfig.privateKeyPath)
.merchantSerialNumber(this.mchSerialNo)
.apiV3Key(this.apiV3Key).build();
config.put(this.mchId,build);
rsaAutoCertificateConfig = build;
}
JsapiService service = new JsapiService.Builder().config(rsaAutoCertificateConfig).build();
return service;
}
/**
* 进行支付
* @param payParam
* @return
*/
public String pay(PayParamJsapiDto payParam) {
String apiUrl = couponConfigService.getValue("online_domain");
JsapiService jsapiService = getJsapiService();
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal((int)(Double.parseDouble(payParam.getAmount().toPlainString())*100));
//amount.setTotal(1);
request.setAmount(amount);
request.setAppid(payParam.getAppid());
request.setMchid(payParam.getMerchantId());
request.setDescription(payParam.getDescription());
request.setNotifyUrl(apiUrl +"api" + "/payment/weChatOfficialAccounts/payment/notify");//这个回调url必须是https开头的
request.setOutTradeNo(payParam.getOutTradeNo());
Payer payer = new Payer();
payer.setOpenid(payParam.getOpenid());
request.setPayer(payer);
//设置回传参数
Gson gson=new Gson();
request.setAttach(gson.toJson(payParam.getParam()));
PrepayResponse prepay = jsapiService.prepay(request);
String prepayid=prepay.getPrepayId();
return prepayid;
}
/** 商户订单号查询订单 */
public Transaction queryOrderByOutTradeNo(String orderNum) {
JsapiService jsapiService = getJsapiService();
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setOutTradeNo(orderNum);
// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
// 调用接口
return jsapiService.queryOrderByOutTradeNo(request);
}
/**
* 支付成功验签
* @param request
* @return
*/
public Map paySuccessCheck(HttpServletRequest request) {
/*Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(dto.getWechatpayMchid())
.privateKey(dto.getWechatpayPrivatekey())
.merchantSerialNumber(dto.getWechatpayMchserialno())
.apiV3Key(dto.getWechatpayApiv3key())
.build();*/
RSAAutoCertificateConfig config1 = config.get(this.mchId);
if(config1 == null){
RSAAutoCertificateConfig build = new RSAAutoCertificateConfig.Builder()
.merchantId(this.mchId)
.privateKey(this.privateKey)
//.privateKeyFromPath(WxConfig.privateKeyPath)
.merchantSerialNumber(this.mchSerialNo)
.apiV3Key(this.apiV3Key).build();
config.put(this.mchId,build);
config1 = build;
}
// 从请求头中获取信息
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String singType = request.getHeader("Wechatpay-Signature-Type");
String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
String requestBody = getRequestBody(request);
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPayCertificateSerialNumber)
.nonce(nonce)
.signature(signature)
.signType(singType)
.timestamp(timestamp)
.body(requestBody)
.build();
// 初始化解析器 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) config1);
// 这个Transaction是微信包里面的
Transaction decryptObject = parser.parse(requestParam, Transaction.class);
TransactionAmount amount = decryptObject.getAmount();
String outTradeNo = decryptObject.getOutTradeNo();
String attach = decryptObject.getAttach();
Map map=new HashMap();
map.put("amount",amount.getTotal());
map.put("outTradeNo",outTradeNo);
map.put("attach",attach);
return map;
}
// 获取请求头里的数据
private String getRequestBody(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
try (
ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
System.out.println("读取数据流异常:"+e);
}
return sb.toString();
}
/**
* 组装支付参数
* @param prepayId
* @return
*/
public Map getPayParameter(String prepayId){
Map<String,String> map=new HashMap<>();
map.put("appId",this.appid);
map.put("timeStamp",String.valueOf(new Date().getTime()));
map.put("nonceStr", UUID.randomUUID().toString().replace("-",""));
map.put("package","prepay_id="+prepayId);
map.put("signType","RSA");
String s = map.get("appId") + "\n" + map.get("timeStamp") + "\n" + map.get("nonceStr") + "\n" + map.get("package") + "\n";
try {
PrivateKey privateKey1 = PemUtil.loadPrivateKeyFromString(this.privateKey);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey1);
sign.update(s.getBytes("utf-8"));
String string = Base64.getEncoder().encodeToString(sign.sign());
map.put("paySign",string);
return map;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
return null;
}
/**
* 公众号 code 换取 openid
* @param code
* @return
*/
public String getOpenId(String code) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + this.appid + "&secret=" + this.secret + "&code=" + code + "&grant_type=authorization_code")
.build();
try {
Response response = client.newCall(request).execute();
String string = response.body().string();
//System.out.println(string);
Gson gson = new Gson();
Map map = gson.fromJson(string, Map.class);
/**
* {
* "session_key": "G4YmjKXmPsqjDFdtcjp/9A==",
* "openid": "o27Z36yx16I4EF84dpTslNdVhsQY"
* }
*/
return (String) map.get("openid");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 申请退款
* @param refundParam
*/
public void refundOrder(RefundParam refundParam) {
String apiUrl = couponConfigService.getValue("online_domain");
BigDecimal bigDecimal = new BigDecimal(100);
int total = bigDecimal.multiply(new BigDecimal(refundParam.getTotal())).intValue();
int refund = bigDecimal.multiply(new BigDecimal(refundParam.getRefund())).intValue();
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(mchId)
.privateKey(privateKey)
.merchantSerialNumber(mchSerialNo)
.apiV3Key(apiV3Key)
.build();
RefundService service = new RefundService.Builder().config(config).build();
//生成随机退款订单号
String s = UUID.randomUUID().toString().replace("-","");
CreateRequest request = new CreateRequest();
//金额信息
AmountReq amount = new AmountReq();
amount.setCurrency("CNY");
amount.setTotal(Long.valueOf(total));
amount.setRefund(Long.valueOf(refund));
request.setAmount(amount);
request.setOutTradeNo(refundParam.getOutTradeNo());
request.setOutRefundNo(s);
//request.setNotifyUrl("http://egevdr.natappfree.cc" + "/payment/weChatPay/refund/notify");
request.setNotifyUrl(apiUrl +"api" + "/payment/weChatOfficialAccounts/refund/notify");
//发送请求
Refund refund1 = service.create(request);
}
public Map refundSuccessCheck(HttpServletRequest request) {
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(mchId)
.privateKey(privateKey)
.merchantSerialNumber(mchSerialNo)
.apiV3Key(apiV3Key)
.build();
// 从请求头中获取信息
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String singType = request.getHeader("Wechatpay-Signature-Type");
String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
String requestBody = getRequestBody(request);
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPayCertificateSerialNumber)
.nonce(nonce)
.signature(signature)
.signType(singType)
.timestamp(timestamp)
.body(requestBody)
.build();
// 初始化解析器 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) config);
// 这个Transaction是微信包里面的
RefundNotification decryptObject = parser.parse(requestParam, RefundNotification.class);
com.wechat.pay.java.service.refund.model.Amount amount = decryptObject.getAmount();
String outTradeNo = decryptObject.getOutTradeNo();
Map map=new HashMap();
map.put("amount",amount.getTotal());
map.put("outTradeNo",outTradeNo);
return map;
}
}
后端下单
String openId=weChatJsapiPayment.getOpenId(code);
PayParamJsapiDto payParam = new PayParamJsapiDto();
//payParam.setSubject(StringUtils.isBlank(byId2.getGoodsName())?byId2.getGoodsSuffixName():byId2.getGoodsName());
payParam.setDescription(StringUtils.isBlank(byId2.getGoodsName())?byId2.getGoodsSuffixName():byId2.getGoodsName());
payParam.setOutTradeNo(couponOrder.getOrderNum());
//payParam.setAmount(new BigDecimal("0.01"));
payParam.setAmount(new BigDecimal(price.toString()));
payParam.setAppid("wx29d50");
payParam.setMerchantId("1651");
payParam.setOpenid(openId);
String pay = weChatJsapiPayment.pay(payParam);
Map payParameter = weChatJsapiPayment.getPayParameter(pay);
后端退款
RefundParam param=new RefundParam();
param.setOutTradeNo(byId.getOrderNum());
param.setBody("卡券权益退款");
param.setRefund(byId.getPayMoney().toString());
param.setTotal(byId.getPayMoney().toString());
//微信退款
weChatJsapiPayment.refundOrder(param);
下单和退款的dto
package com.ruoyi.coupon.payment.dto;
import lombok.Data;
/**
* 退款参数封装
*/
@Data
public class RefundParam {
//订单号
private String outTradeNo;
//本次退款金额金额
private String refund;
//订单总金额
private String total;
//备注
private String body;
}
package com.ruoyi.coupon.payment.dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class PayParamJsapiDto {
private String appid;
private String merchantId;
//支付描述
private String description;
private String openid;
private String outTradeNo;
//需支付金额
private BigDecimal amount;
private PassbackParamsJsapiDto param;
}
package com.ruoyi.coupon.payment.dto;
import lombok.Data;
@Data
public class PassbackParamsJsapiDto {
}
后端支付回调类
package com.ruoyi.api.controller.coupon.payment.rest;
import com.ruoyi.coupon.payment.WeChatJsapiPayment;
import com.ruoyi.coupon.payment.WeChatPayment;
import com.ruoyi.coupon.payment.event.PaymentEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 商城支付项微信支付的回调接口
*/
@Slf4j
@RestController
@RequestMapping("/payment/weChatOfficialAccounts")
public class WeChatOfficialAccountsNotify {
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private WeChatJsapiPayment weChatJsapiPayment;
/**
* 微信支付/充值回调
*/
@PostMapping("/payment/notify")
public ResponseEntity.BodyBuilder renotify(HttpServletRequest request) {
Map map = weChatJsapiPayment.paySuccessCheck(request);
Integer amount = (Integer)map.get("amount");
String outTradeNo = (String)map.get("outTradeNo");
String attach = (String)map.get("attach");
Map<String, Object> map1 = new HashMap<>();
//将附加信息 attach 解析到 map1 中,因为现在没有传递任何参数,所以没有解析
BigDecimal divide = new BigDecimal(amount).divide(new BigDecimal("100"));
publisher.publishEvent(new PaymentEvent(this, outTradeNo,0,map1,divide.toString()));
return ResponseEntity.status(HttpStatus.OK);
}
/**
* 微信退款回调
*/
@PostMapping("/refund/notify")
public ResponseEntity.BodyBuilder parseRefundNotifyResult(HttpServletRequest request) {
Map map = weChatJsapiPayment.refundSuccessCheck(request);
Long amount = (Long)map.get("amount");
String outTradeNo = (String)map.get("outTradeNo");
BigDecimal divide = new BigDecimal(amount).divide(new BigDecimal("100"));
publisher.publishEvent(new PaymentEvent(this, outTradeNo,1,null,divide.toString()));
return ResponseEntity.status(HttpStatus.OK);
}
}
回调参数封装
package com.ruoyi.coupon.payment.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.core.annotation.Order;
import java.util.Map;
/**
* 商城支付项的支付成功和退款成功事件
*/
@Order(500)
public class PaymentEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private final String orderId;
private final Integer type;//0:支付成功 1:退款成功
private final Map passbackParams; //回传参数
private final String actionMoney; //支付或退款金额
public PaymentEvent(Object source, String orderId, Integer type, Map passbackParams,String actionMoney) {
super(source);
this.orderId=orderId;
this.type=type;
this.passbackParams=passbackParams;
this.actionMoney=actionMoney;
}
public String getOrderId() {
return this.orderId;
}
public Integer getType() {
return this.type;
}
public Map getPassbackParams() {
return this.passbackParams;
}
public String getActionMoney() {
return this.actionMoney;
}
}
uniapp端获取公众号code
//获取code
uni.navigateTo({
url:"/pages/WeChatOfficialAccounts/getCode"
})
<template>
<view>
获取信息中...
</view>
</template>
<script>
export default {
data() {
return {
code:''
}
},
onLoad(){
let appid = 'wx29d501';
let redirect_uri='http://qy.gsjf.cc/h5/#/pages/WeChatOfficialAccounts/getCode'
redirect_uri = encodeURIComponent(redirect_uri)
//console.log('redirect_uri', redirect_uri);
let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_base&state=STATE&connect_redirect=1#wechat_redirect`;
this.code = this.getUrlCode().code;
if (this.code == null || this.code === '' ) {//
window.location.href = url;
}else{
//获取到code后的逻辑
console.log('code', this.code);
/*let pages = getCurrentPages(); // 当前页页⾯实例
console.log('pages',pages);
//let nowPage = pages[pages.length -1]; //当前页⾯实例
let prevPage = pages[pages.length -2]; // 上一页面实例
let object ={
code: this.code
};
prevPage.$vm.getCodeBack(object);*/
/* uni.navigateBack({ //uni.navigateTo跳转的返回,默认1为返回上一级
delta: 2
}); */
localStorage.setItem('weChatOfficialAccountsCode', this.code);
window.history.go(-2);
}
},
methods: {
getUrlCode() { // 截取url中的code方法
var url = location.search
var theRequest = new Object()
if (url.indexOf("?") != -1) {
var str = url.substr(1)
var strs = str.split("&");
for (var i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1])
}
}
// console.log(theRequest)
console.log(theRequest)
return theRequest
},
}
}
</script>
<style>
</style>
获取code后,返回跳转前页面,在跳转前页面获取code
onLoad() {
const data = localStorage.getItem('weChatOfficialAccountsCode');
if(data != null){
this.getCodeBack(data);
}else{
//获取code
uni.navigateTo({
url:"/pages/WeChatOfficialAccounts/getCode"
})
}
},
onShow(){
const data = localStorage.getItem('weChatOfficialAccountsCode');
if(data != null){
this.getCodeBack(data);
}else{
//获取code
uni.navigateTo({
url:"/pages/WeChatOfficialAccounts/getCode"
})
}
},
onUnload(){
localStorage.removeItem('weChatOfficialAccountsCode');
this.weChatOfficialAccountsCode=null;
},
前端调起支付接口
npm install jweixin-module
localStorage.removeItem('weChatOfficialAccountsCode');
this.weChatOfficialAccountsCode=null;
let dataV=JSON.parse(res.data.payStrand)
jweixin.config({
debug: false,
appId: dataV.appId,
timestamp: dataV.timeStamp,
nonceStr: dataV.nonceStr,
signature: dataV.signature,
jsApiList: ['chooseWXPay']
});
jweixin.ready(function() {
jweixin.chooseWXPay({
timestamp: dataV.timeStamp,
nonceStr: dataV.nonceStr,
package: dataV.package,
signType: dataV.signType,
paySign: dataV.paySign,
success: function() {
uni.showToast({
title: '支付成功',
icon: 'none'
});
},
cancel: function(res) {
uni.showToast({
title: '支付失败',
icon: 'none'
});
},
complete: function() {
}
});
});
getCodeBack(code){
this.weChatOfficialAccountsCode=code;
console.log("父页面获取到值",this.weChatOfficialAccountsCode);
setTimeout(()=>{
uni.showToast({
title: '获取信息成功',
icon: 'none', //success 等
duration: 5000
});
},500)
}
判断是否是微信公众号环境
var isWeixin = navigator.userAgent.indexOf("MicroMessenger") > 0;