admin管理员组文章数量:1559105
一、前言
微信支付账号类型分为商户平台和j9九游会老哥俱乐部交流区的合作伙伴平台,今天主要是梳理商品平台微信支付流程。
商品平台文档地址,(在接入前建议仔细阅读这份文档,会少走很多弯路!!!)
小程序下单 - 小程序支付 | 微信支付商户文档中心
二、接入流程
以下是我springboot项目接入微信支付v3流程,如果还有更好的集成方式,欢迎留言探讨。
2.1、pom.xml导入微信支付sdk
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.3.0
2.2、application.yaml配置微信支付相关配置
# 微信支付相关参数
wxpay:
# 商户号
mch-id: 1888888888
# 商户api证书序列号
mch-serial-no: 188d8d888888888a476b7deb5f820082570ca4ba
# apiv3密钥
api-v3-key: 88888888
# appid
appid: wx88888ff5ee888f88
# 微信服务器地址
domain: https://api.mch.weixin.qq
# 微信支付回调地址 https和外网可以访问的地址
notify-domain: https://35p3811d81.yicp.fun
# 商户私钥 此处注意换行,回车等不可见字符
private-key: biievgibadanbgkqhkig9w0baqefaascbkgwggskageaaoibaqdv9hl2qvkpvaafbih4scv6dfbws/rem hhfwq8mv1xtt7kzumasebrfoe3nshx5oi zpnr/nnhsh9vtyubbh0lce7ad oltlnvtszxlorupoegcxsokanhxd9gfgpkx/i8rkdnnoktesbxgf8tgrqnibycwwggjmuangveajqkvn3pc 4wrlfm/x1gwuegovxnukuayu7vpgn ukauc1buadwm6eoka
2.3、wxpaycontroller.java(回调通知接口一定要从权限拦截器中剔除)
/**
* 微信支付api
*/
@postmapping("/save")
@resubmit
public apiresponse savewxpayorder(@requestbody orderwxpayappreq orderwxpayappreq) throws exception {
orderwxpayappreq.setuserid(getuserid());
return apiresponse.ok(orderwxpayservice.savewxpayorder(orderwxpayappreq));
}
/**
* 微信退款api
*/
@postmapping("/refunds/apply")
@resubmit
public apiresponse refunds(@requestbody orderwxpayappreq orderwxpayappreq) throws exception {
log.info("申请退款");
orderwxpayappreq.setuserid(getuserid());
return apiresponse.ok(orderwxpayservice.wxrefund(orderwxpayappreq));
}
/**
* 微信支付回调通知
*/
@postmapping("/callback/pay/notify")
public apiresponse paycallback(httpservletrequest request) {
gson gson = new gson();
try {
//处理通知参数
string body = httputils.readdata(request);
map bodymap = gson.fromjson(body, hashmap.class);
string requestid = (string) bodymap.get("id");
//签名的验证
wechatpay2validatorforrequest wechatpay2validatorforrequest
= new wechatpay2validatorforrequest(verifier, requestid, body);
if (!wechatpay2validatorforrequest.validate(request)) {
//失败应答
return apiresponse.error("通知验签失败");
}
//处理订单
orderwxpayservice.processorder(bodymap);
//成功应答
return apiresponse.ok("成功");
} catch (exception e) {
e.printstacktrace();
//失败应答
return apiresponse.error("失败");
}
}
/**
* 微信退款结果通知
*/
@postmapping("/callback/refunds/notify")
public apiresponse refundsnotify(httpservletrequest request) {
log.info("退款通知执行");
try {
//处理通知参数
string body = httputils.readdata(request);
gson gson = new gson();
map bodymap = gson.fromjson(body, hashmap.class);
string requestid = (string) bodymap.get("id");
//签名的验证
wechatpay2validatorforrequest wechatpay2validatorforrequest
= new wechatpay2validatorforrequest(verifier, requestid, body);
if (!wechatpay2validatorforrequest.validate(request)) {
return apiresponse.error("通知验签失败");
}
//处理退款单
orderwxpayservice.processrefund(bodymap);
return apiresponse.ok("成功");
} catch (exception e) {
e.printstacktrace();
//失败应答
return apiresponse.error("失败");
}
}
2.4、wxpayservice.java
@override
public map savewxpayorder(orderwxpayappreq orderwxpayappreq) throws ioexception {
log.info("调用统一下单api");
list orderidlist = orderwxpayappreq.getorderidlist();
if (collutil.isempty(orderidlist)) {
throw new baseruntimeexception(ordererrorcodeenums.base_003);
}
//查询订单
lambdaquerywrapper orderquerywrapper = new lambdaquerywrapper()
.in(orderinfo::getorderid, orderidlist)
.eq(orderinfo::getdeleted, boolean.false);
list orderinfolist = orderinfomapper.selectlist(orderquerywrapper);
if (collutil.isempty(orderinfolist)) {
throw new baseruntimeexception(ordererrorcodeenums.order_001);
}
atomicreference totalpaymoney = new atomicreference<>(0l);
string orderidstr = orderinfolist.stream().map(o -> {
totalpaymoney.updateandget(v -> v o.getpaymoney());
return string.valueof(o.getorderid());
}).collect(collectors.joining(","));
//调用统一下单api
httppost httppost = new httppost(wxpayconfig.getdomain().concat(wxapitype.jsapi_pay.gettype()));
string paymentkey = ordernoutils.createorderno(ordernoutils.payment_serial_number_prefix);
// key值有效期为1小时
redisutil.set(rediskeys.orderkeys.order_payment_key paymentkey, orderidstr, 3600);
// 请求body参数
gson gson = new gson();
map paramsmap = new hashmap<>(commonconstants.default_map_capacity);
paramsmap.put("appid", wxpayconfig.getappid());
paramsmap.put("mchid", wxpayconfig.getmchid());
paramsmap.put("description", orderidstr);
paramsmap.put("out_trade_no", paymentkey);
paramsmap.put("notify_url", wxpayconfig.getnotifydomain().concat(wxnotifytype.jsapi_notify.gettype()));
map amountmap = new hashmap<>(commonconstants.default_map_capacity);
long totalmoney = totalpaymoney.get();
boolean enablevirtualpay = optional.ofnullable(enabled.getenablevirtualpay()).orelse(boolean.true);
if (enablevirtualpay) {
totalmoney = 1l;
}
amountmap.put("total", totalmoney);
amountmap.put("currency", "cny");
paramsmap.put("amount", amountmap);
map payermap = new hashmap<>(commonconstants.default_map_capacity);
final userthirdinfoapivo userthirdinfoapivo = userfeignapi.getuserthirdinforsp(orderwxpayappreq.getuserid(), "wx", wxpayconfig.getmanagerappid());
payermap.put("openid", userthirdinfoapivo.getopenid());
paramsmap.put("payer", payermap);
//将参数转换成json字符串
string jsonparams = gson.tojson(paramsmap);
stringentity entity = new stringentity(jsonparams, "utf-8");
entity.setcontenttype("application/json");
httppost.setentity(entity);
httppost.setheader("accept", "application/json");
//完成签名并执行请求
closeablehttpresponse response = wxpayclient.execute(httppost);
//返回二维码
map map = new hashmap<>(commonconstants.default_map_capacity);
try {
//响应体
string bodyasstring = entityutils.tostring(response.getentity());
log.info("成功, 支付接口返回结果:{} ", bodyasstring);
//响应状态码
int statuscode = response.getstatusline().getstatuscode();
//响应结果
map resultmap = gson.fromjson(bodyasstring, hashmap.class);
//处理成功
if (statuscode != 200 && statuscode != 204) {
throw new baseruntimeexception(ordererrorcodeenums.order_002.code, resultmap.get("message"));
}
//预支付交易会话标识
string prepayid = resultmap.get("prepay_id");
string prepayidstr = "prepay_id=" prepayid;
long epochsecond = localdatetime.now().toepochsecond(zoneoffset.of(" 8"));
string timestamp = string.valueof(epochsecond);
string noncestr = nonceutil.createnonce(32);
//关联的公众号的appid
map.put("appid", wxpayconfig.getappid());
//时间戳
map.put("timestamp", timestamp);
//生成随机字符串
map.put("noncestr", noncestr);
map.put("package", prepayidstr);
map.put("signtype", "rsa");
map.put("paysign", wxpayconfig.getpaysign(timestamp, noncestr, prepayidstr));
} finally {
response.close();
}
return map;
}
@transactional(rollbackfor = exception.class)
@override
public boolean processorder(map bodymap) throws generalsecurityexception {
//将明文转换成map
hashmap plaintextmap = new gson().fromjson(decryptfromresource(bodymap), hashmap.class);
string paymentkey = string.valueof(plaintextmap.get("out_trade_no"));
object orderidobj = redisutil.get(rediskeys.orderkeys.order_payment_key paymentkey);
if (objectutil.isnull(orderidobj)) {
return boolean.false;
}
// 处理支付成功后业务数据
... ...
return boolean.true;
}
/**
* 退款
*
* @param orderwxpayappreq
* @return
*/
@transactional(rollbackfor = exception.class)
@override
public boolean wxrefund(orderwxpayappreq orderwxpayappreq) throws ioexception {
if (objectutil.isempty(orderwxpayappreq.getafterorderid())) {
throw new baseruntimeexception(ordererrorcodeenums.base_003.code, ordererrorcodeenums.base_003.desc);
}
orderafter orderafter = orderaftermapper.selectbyid(orderwxpayappreq.getafterorderid());
orderinfo orderinfo = orderinfomapper.selectbyid(orderafter.getorderid());
if (objectutil.isnull(orderinfo.getpayserialnumber())) {
return boolean.false;
}
// 请求body参数
gson gson = new gson();
map paramsmap = new hashmap<>(commonconstants.default_map_capacity);
paramsmap.put("out_trade_no", orderinfo.getpayserialnumber());
paramsmap.put("out_refund_no", string.valueof(orderafter.getafterorderid()));
paramsmap.put("reason", orderwxpayappreq.getrefundreason());
paramsmap.put("notify_url", wxpayconfig.getnotifydomain().concat(wxnotifytype.refund_notify.gettype()));
map amountmap = new hashmap<>(commonconstants.default_map_capacity);
final boolean enablevirtualpay = optional.ofnullable(enabled.getenablevirtualpay()).orelse(boolean.true);
long refund = orderafter.getapplyrefundamount();
long total = orderinfo.getpaymoney();
if (enablevirtualpay) {
refund = 1l;
total = 1l;
}
amountmap.put("refund", refund);
amountmap.put("total", total);
amountmap.put("currency", "cny");
paramsmap.put("amount", amountmap);
//将参数转换成json字符串
string jsonparams = gson.tojson(paramsmap);
stringentity entity = new stringentity(jsonparams, "utf-8");
entity.setcontenttype("application/json");
//调用统一下单api
string url = wxpayconfig.getdomain().concat(wxapitype.domestic_refunds.gettype());
httppost httppost = new httppost(url);
httppost.setentity(entity);
httppost.setheader("accept", "application/json");
//完成签名并执行请求,并完成验签
closeablehttpresponse response = wxpayclient.execute(httppost);
try {
//解析响应结果
string bodyasstring = entityutils.tostring(response.getentity());
int statuscode = response.getstatusline().getstatuscode();
map bodymap = gson.fromjson(bodyasstring, hashmap.class);
if (statuscode != 200 && statuscode != 204) {
throw new baseruntimeexception(ordererrorcodeenums.base_003.getcode(), string.valueof(bodymap.get("message")));
}
//保存退款日志
return saverefundpaymentbillinfo(bodymap);
} finally {
response.close();
}
}
@override
@transactional(rollbackfor = exception.class)
public boolean processrefund(map bodymap) throws generalsecurityexception, interruptedexception {
//解密报文
string plaintext = decryptfromresource(bodymap);
//将明文转换成map
hashmap plaintextmap = new gson().fromjson(plaintext, hashmap.class);
string payserialnumber = (string) plaintextmap.get("out_trade_no");
string outrefundno = (string) plaintextmap.get("out_refund_no");
// 处理退款后的业务逻辑
... ...
return boolean.true;
}
/**
* 对称解密
*
* @param bodymap
* @return
*/
private string decryptfromresource(map bodymap) throws generalsecurityexception {
log.info("密文解密");
//通知数据
map resourcemap = (map) bodymap.get("resource");
//数据密文
string ciphertext = resourcemap.get("ciphertext");
//随机串
string nonce = resourcemap.get("nonce");
//附加数据
string associateddata = resourcemap.get("associated_data");
aesutil aesutil = new aesutil(wxpayconfig.getapiv3key().getbytes(standardcharsets.utf_8));
string plaintext = aesutil.decrypttostring(associateddata.getbytes(standardcharsets.utf_8),
nonce.getbytes(standardcharsets.utf_8),
ciphertext);
return plaintext;
}
2.5、wxapitype.java
@allargsconstructor
@getter
public enum wxapitype {
/**
* jsapi下单
* https://api.mch.weixin.qq/v3/pay/transactions/jsapi
*/
jsapi_pay("/v3/pay/transactions/jsapi"),
/**
* 申请退款
* https://api.mch.weixin.qq/v3/refund/domestic/refunds
*/
domestic_refunds("/v3/refund/domestic/refunds");
/**
* 类型
*/
private final string type;
}
2.6、wxpayconfig.java
/**
* 微信支付配置类
*/
@configuration
@configurationproperties(prefix = "wxpay")
@data
@slf4j
public class wxpayconfig {
/**
* 商户号
*/
private string mchid;
/**
* 商户api证书序列号
*/
private string mchserialno;
/**
* apiv3密钥
*/
private string apiv3key;
/**
* appid
*/
private string appid;
/**
* 微信服务器地址
*/
private string domain;
/**
* 微信回调通知地址
*/
private string notifydomain;
/**
* 商户密钥
*/
private string privatekey;
/**
* 获取商户的私钥
*
* @return
*/
public privatekey getprivatekey(string privatekey) {
return pemutil.loadprivatekey(privatekey);
}
/**
* 获取签名验证器
*
* @return
*/
@bean
public scheduledupdatecertificatesverifier getverifier() {
log.info("获取签名验证器");
//获取商户私钥
privatekey privatekeyobj = getprivatekey(privatekey);
//私钥签名对象
privatekeysigner privatekeysigner = new privatekeysigner(mchserialno, privatekeyobj);
//身份认证对象
wechatpay2credentials wechatpay2credentials = new wechatpay2credentials(mchid, privatekeysigner);
// 使用定时更新的签名验证器,不需要传入证书
scheduledupdatecertificatesverifier verifier = new scheduledupdatecertificatesverifier(
wechatpay2credentials,
apiv3key.getbytes(standardcharsets.utf_8));
return verifier;
}
/**
* 获取http请求对象
*
*/
@bean(name = "wxpayclient")
public closeablehttpclient getwxpayclient(scheduledupdatecertificatesverifier verifier) throws filenotfoundexception {
log.info("获取httpclient");
//获取商户私钥
privatekey privatekeyobj = getprivatekey(privatekey);
wechatpayhttpclientbuilder builder = wechatpayhttpclientbuilder.create()
.withmerchant(mchid, mchserialno, privatekeyobj)
.withvalidator(new wechatpay2validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的httpclient
// 通过wechatpayhttpclientbuilder构造的httpclient,会自动的处理签名和验签,并进行证书自动更新
closeablehttpclient httpclient = builder.build();
return httpclient;
}
public string getpaysign(string timestamp, string noncestr, string prepayidstr) {
string paysign = "";
try {
signature sign = signature.getinstance("sha256withrsa");
sign.initsign(getprivatekey(privatekey));
string message = appid "\n" timestamp "\n" noncestr "\n" prepayidstr "\n";
sign.update(message.getbytes(standardcharsets.utf_8));
paysign = base64.getencoder().encodetostring(sign.sign());
} catch (nosuchalgorithmexception | invalidkeyexception | signatureexception e) {
e.printstacktrace();
}
return paysign;
}
public static void main(string[] args) throws signatureexception, nosuchalgorithmexception, invalidkeyexception {
wxpayconfig wxpayconfig = new wxpayconfig();
long timestamp = 1414561699l;
string noncestr = "5k8264iltkch16cq2502si8znmtm67vs";
string packageval = "prepay_id=wx201410272009395522657a690389285100";
string appid = "wx8b888b888e888d8a";
string sign = wxpayconfig.getpaysign(string.valueof(timestamp), noncestr, packageval);
system.out.println(sign);
}
}
2.7、wxnotifytype.java
/**
* 微信支付,退款回调地址
* 1.此处注意接口路径地址
* 2.在权限控制拦截器中放开接口限制
*/
@allargsconstructor
@getter
public enum wxnotifytype {
/**
* 支付通知
*/
jsapi_notify("/app/order/wx/pay/callback/pay/notify"),
/**
* 退款结果通知
*/
refund_notify("/app/order/wx/pay/callback/refunds/notify");
/**
* 类型
*/
private final string type;
}
三、参考资料
产品介绍 - jsapi支付 | 微信支付商户文档中心
本文标签: 版本springboot
j9九游会老哥俱乐部交流区的版权声明:本文标题:springboot集成微信支付v3版本流程(商户平台篇) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dongtai/1727371836a1111267.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。