一、对接前准备
最开始需要在微信支付的官网注册一个商户;
在管理页面中申请关联小程序,通过小程序的 appid 进行关联;商户号和appid之间是多对多的关系
进入微信公众平台,功能-微信支付中确认关联
具体流程请浏览官方文档:接入前准备-小程序支付 | 微信支付商户平台文档中心
流程走完之后,需要获取以下参数:
1,商户 id: mchId,
2,小程序id:appId
3,商户证书序列号: 这个商户证书序列号在申请完证书之后就可以看到
4, 商户APIV3密钥, 我对接的是v3 接口 所以用APIV3密钥
当你按照文档下载商户证书zip,解压得到4个文件,一定要保存好。不能泄露
二、开始写代码
1.pom引入微信库
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency><dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
2.yml文件写入配置
wxpay:
#应用编号
appId: xxxxxxxxxxxxx
#商户号
mchId: xxxxxxxxxxxxx
# APIv3密钥
apiV3Key: xxxxxxxxxxxxx
# 支付通知回调, 本地测试内网穿透地址
notifyUrl: http://405c3382p5.goho.co:25325/wenbo-pay/notify/payNotify
# 退款通知回调, 本地测试内网穿透地址
refundNotifyUrl: http://405c3382p5.goho.co:25325/wenbo-pay/notify/refundNotify
# 密钥路径,resources根目录下
keyPemPath: apiclient_key.pem
# 商户证书序列号
serialNo: xxxxxxxxxxxxx
# 小程序密钥
appSecret: xxxxxxxxxxxxx
3.商户API私钥 放入resources根目录下 能获取到就行
编写配置类获取yml配置
package com.example.pay.config;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
/**
* @ClassName: WechatPayConfig
* @author: tang
* @createTime 2023-08-10
* 小程序支付配置类
*/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
/**
* 应用编号
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知回调地址
*/
private String notifyUrl;
/**
* 退款回调地址
*/
private String refundNotifyUrl;
/**
* API 证书中的 key.pem
*/
private String keyPemPath;
/**
* 商户序列号
*/
private String serialNo;
/**
* 获取商户的私钥文件
*
* @param keyPemPath
* @return
*/
public PrivateKey getPrivateKey(String keyPemPath) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if (inputStream == null) {
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取证书管理器实例
*
* @return
*/
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(mchId);
}
/**
* 获取支付http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
}
封装 支付-退款 请求API
package com.example.pay.config;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: WechatPayRequest
* @author: tang
* @createTime 2023-08-10
* 封装公共的请求API,用于在业务请求时的统一处理。
*/
@Component
@Slf4j
public class WechatPayRequest {
@Resource
private CloseableHttpClient wxPayClient;
/**
* 支付请求
*
* @param url
* @param paramsStr
* @return
*/
public String wechatHttpOrderPost(String url, String paramsStr) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(paramsStr, "utf-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
//响应体
HttpEntity entity = response.getEntity();
String body = entity == null ? "" : EntityUtils.toString(entity);
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//处理成功,204是,关闭订单时微信返回的正常状态码
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
log.info("成功, 返回结果 = " + body);
} else {
String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
log.info("支付模块-生成订单 = " + msg);
throw new RuntimeException(msg);
}
return body;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 退款请求
*
* @param url
* @param paramsStr
* @return
*/
public String wechatHttpPost(String url, String paramsStr) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(paramsStr, "utf-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
//响应体
HttpEntity entity = response.getEntity();
String body = entity == null ? "" : EntityUtils.toString(entity);
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//处理成功,204是,关闭订单时微信返回的正常状态码
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
log.info("成功, 返回结果 = " + body);
// 请求成功或已处理成功,返回成功的响应
return "退款处理中";
} else if (statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
// 请求参数错误或系统错误,返回失败的响应
JSONObject json = JSONObject.parseObject(body);
return json.getString("message");
} else if (statusCode == HttpStatus.SC_FORBIDDEN) {
// 权限问题,没有退款权限
return "没有退款权限";
} else if (statusCode == HttpStatus.SC_NOT_FOUND) {
// 订单号不存在
return "订单号不存在";
} else if (statusCode == 429) {
// 频率限制
return "退款请求频率过高,请稍后重试";
} else if (statusCode == HttpStatus.SC_PAYMENT_REQUIRED) {
// 余额不足
return "商户余额不足,请充值后重试";
} else {
// 其他状态码,返回通用的失败响应
return "退款失败,请稍后重试";
}
} catch (Exception e) {
log.info("支付模块-退款失败 = " + e.getMessage());
JSONObject json = JSONObject.parseObject(e.getMessage());
return json.getString("message");
}
}
}
支付回调解密处理
package com.example.pay.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
/**
* @ClassName: WechatPayValidator
* @author: tang
* @createTime 2023-08-10
* 回调校验器
* 用于对微信支付成功后的回调数据进行签名验证,保证数据的安全性与真实性。
*/
@Slf4j
public class WechatPayValidator {
/**
* 应答超时时间,单位为分钟
*/
private static final long RESPONSE_EXPIRED_MINUTES = 5;
private final Verifier verifier;
private final String requestId;
private final String body;
public WechatPayValidator(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
private void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
private String buildMessage(HttpServletRequest request) {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
/**
* 对称解密,异步通知的加密数据
*
* @param resource 加密数据
* @param apiV3Key apiV3密钥
* @param type 1-支付,2-退款
* @return
*/
public static Map<String, Object> decryptFromResource(String resource, String apiV3Key, Integer type) {
String msg = type == 1 ? "支付成功" : "退款成功";
log.info(msg + ",回调通知,密文解密");
try {
//通知数据
Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {
});
//数据密文
String ciphertext = resourceMap.get("ciphertext");
//随机串
String nonce = resourceMap.get("nonce");
//附加数据
String associatedData = resourceMap.get("associated_data");
log.info("密文: {}", ciphertext);
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info(msg + ",回调通知,解密结果 : {}", resourceStr);
return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {
});
} catch (Exception e) {
throw new RuntimeException("回调参数,解密失败!");
}
}
/**
* 将通知参数转化为字符串
*
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
编写下单 退款 controller 这里注意 微信用户openid
package com.example.pay.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.example.pay.config.WechatPayConfig;
import com.example.pay.config.WechatPayRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* @ClassName: pay
* @author: tang
* @createTime 2023-08-16
*/
@Slf4j
@RestController
@RequestMapping("/pay")
public class PayController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WechatPayRequest wechatPayRequest;
/**
* 预支付订单生成入口
*/
@GetMapping("/transactions")
public Map<String, Object> transactions() {
// 统一参数封装
Map<String, Object> params = new HashMap<>(10);
// 1,appid:公众号或移动应用的唯一标识符。
params.put("appid", wechatPayConfig.getAppId());
// 2,mch_id:商户号,由微信支付分配。
params.put("mchid", wechatPayConfig.getMchId());
// 3.description body:商品描述。
params.put("description", "奥迪a4l 2023-限量款");
// 4.out_trade_no:商户订单号,由商户自定义。
params.put("out_trade_no", "we56f45waf4w6a5fwa");
// 5.notify_url:接收微信支付异步通知回调地址。
params.put("notify_url", wechatPayConfig.getNotifyUrl());
// 6.total_fee:订单总金额,单位为分。
Map<String, Object> amountMap = new HashMap<>(4);
// 金额单位为分
amountMap.put("total", 999999);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 7.openid:用户在商户appid下的唯一标识。
Map<String, Object> payerMap = new HashMap<>(4);
// openid 需要前端小程序通过用户code 请求微信接口获取用户唯一openid 不懂的看官方文档:https://developers.weixin.qq.com/doc/aispeech/miniprogram/quickuse.html
payerMap.put("openid", "xxxxxxxxxxxxxxxxxxxx");
params.put("payer", payerMap);
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
// 微信预支付下单接口路径
String payUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
// 获取支付 prepay_id参数
String resStr = wechatPayRequest.wechatHttpOrderPost(payUrl, paramsStr);
Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
});
Object prepayId = resMap.get("prepay_id");
// 得到当前系统时间搓
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
// 获取签名
String paySign;
try {
StringBuilder sb = new StringBuilder();
// 应用id
sb.append(wechatPayConfig.getAppId()).append("\n");
// 支付签名时间戳
sb.append(timeStamp).append("\n");
// 随机字符串
sb.append("5w7er7wa4fwa5e").append("\n");
// 预支付交易会话ID 这个要注意 key = "prepay_id=xxxxxx"
sb.append("prepay_id=").append(prepayId).append("\n");
// 签名
Signature sign = Signature.getInstance("SHA256withRSA");
// 获取商户私钥并进行签名
PrivateKey privateKey = wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath());
sign.initSign(privateKey);
sign.update(sb.toString().getBytes(StandardCharsets.UTF_8));
// 得到签名
paySign = Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
log.error("支付模块_生成交易签名失败!" + e);
return new HashMap<>();
}
// 将签名时数据和签名一起返回前端用于前端吊起支付
Map<String, Object> map = new HashMap<>();
// 小程序id
map.put("appId", wechatPayConfig.getAppId());
// 时间戳
map.put("timeStamp", timeStamp);
// 随机字符串
map.put("nonceStr", "56523268632356");
// 预支付交易会话ID
map.put("package", "prepay_id=" + prepayId);
// 签名方式
map.put("signType", "RSA");
// 签名
map.put("paySign", paySign);
return map;
}
/**
* 申请退款
*/
@GetMapping("/refundOrder")
public String refundOrder() {
log.info("根据订单号申请退款,订单号: {}", "要退款的订单号 这里写死");
// 退款请求路径
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
// 设置参数
Map<String, Object> params = new HashMap<>(2);
// 要退款的订单编号订单编号
params.put("out_trade_no", "57984wera64");
// 商户自定义退款记录单号 用于退款记录的单号 跟退款订单号不是一样的
int outRefundNo = new Random().nextInt(999999999);
log.info("退款申请号:{}", outRefundNo);
params.put("out_refund_no", outRefundNo + "");
// 退款原因
params.put("reason", "申请退款");
// 退款通知回调地址
params.put("notify_url", wechatPayConfig.getRefundNotifyUrl());
Map<String, Object> amountMap = new HashMap<>();
//退款金额,单位:分
amountMap.put("refund", 999999);
//原订单金额,单位:分
amountMap.put("total", 99999);
//退款币种
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
String paramsStr = JSON.toJSONString(params);
// todo 插入一条退款记录到数据库
log.info("请求参数 ===> {}" + paramsStr);
String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
log.info("退款结果:{}", res);
return res;
}
}
支付 退款 回调controller
package com.example.pay.controller;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.example.pay.config.WechatPayConfig;
import com.example.pay.config.WechatPayValidator;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* @ClassName: NotifyController
* @author: tang
* @createTime 2023-08-10
*/
@RestController
@Slf4j
@RequestMapping("/callback")
public class CallbackController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private Verifier verifier;
private final ReentrantLock lock = new ReentrantLock();
/**
* 支付回调处理
*
* @param request
* @param response
* @return
*/
@PostMapping("/payNotify")
public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("支付回调");
// 处理通知参数
Map<String, Object> bodyMap = getNotifyBody(request);
if (bodyMap == null) {
return falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
String orderNo = resourceMap.get("out_trade_no").toString();
// String transactionId = resourceMap.get("transaction_id").toString();
// 更改状态 获取订单号 修改订单状态为已支付
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return trueMsg(response);
}
/**
* 退款回调处理
*
* @param request
* @param response
* @return
*/
@PostMapping("/refundNotify")
public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("退款回调");
// 处理通知参数
Map<String, Object> bodyMap = getNotifyBody(request);
if (bodyMap == null) {
return falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
String orderNo = resourceMap.get("out_trade_no").toString();
// String transactionId = resourceMap.get("transaction_id").toString();
log.info("退款所有参数" + resourceMap);
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
// 更改订单状态为已退款
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return trueMsg(response);
}
private Map<String, Object> getNotifyBody(HttpServletRequest request) {
//处理通知参数
String body = WechatPayValidator.readData(request);
log.info("退款回调参数:{}", body);
// 转换为Map
Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
});
// 微信的通知ID(通知的唯一ID)
String notifyId = bodyMap.get("id").toString();
// 验证签名信息
WechatPayValidator wechatPayValidator
= new WechatPayValidator(verifier, notifyId, body);
if (!wechatPayValidator.validate(request)) {
log.error("通知验签失败");
return null;
}
log.info("通知验签成功");
return bodyMap;
}
private Map<String, String> falseMsg(HttpServletResponse response) {
Map<String, String> resMap = new HashMap<>(8);
//失败应答
response.setStatus(500);
resMap.put("code", "ERROR");
resMap.put("message", "通知验签失败");
return resMap;
}
private Map<String, String> trueMsg(HttpServletResponse response) {
Map<String, String> resMap = new HashMap<>(8);
//成功应答
response.setStatus(200);
resMap.put("code", "SUCCESS");
resMap.put("message", "成功");
return resMap;
}
}
整体项目结构
请求预支付订单 得到预支付订单参数 小程序拿着这些参数拉起支付页面进行支付
支付完成后 微信会回调我们预留的回调接口
这里使用的是花生壳内网穿透进行回调测试的
退款业务就不一一截图了 图片太多显得文章拉胯文章来源:https://www.toymoban.com/news/detail-740585.html
文章来源地址https://www.toymoban.com/news/detail-740585.html
总结
到了这里,关于【微信支付】java-微信小程序支付-V3接口的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!