微信支付
2023年4月18日大约 12 分钟
纯手写微信支付工具
本节所有代码,均根据微信支付官方文档编写,可直接用于生产环境。点击下载本节源码
准备
将下载的商户私钥 apiclient_key.pem 放在项目资源文件夹中 即 resources/static/wxApiBook/apiclient_key.pem
- application.yml
# 微信支付 参数配置
weixin:
appId: xxx
appSecret: xxx
mchId: xxx #商户号
apiV3key: xxx
serialNo: xxx #证书序列号
apiClientKey: static/wxApiBook/apiclient_key.pem #商户私钥
notifyUrl: xxx #支付成功通知
refundNotifyUrl: xxx #退款成功通知地址- 配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "weixin")
/**
* @author ZJX
* @date 2021/03/19
**/
public class WxConfig {
//小程序appid
private String appId;
//小程序secret
private String appSecret;
//直连商户号
private String mchId;
//商户平台上设置的APIv3密钥
private String apiV3key;
//证书序列号
private String serialNo;
//商户私钥文件路径
private String apiClientKey;
// 通用的支付回调地址
private String notifyUrl;
// 退款成功通知地址
private String refundNotifyUrl;
}- 实体类
| 实体类 | 描述 |
|---|---|
| PayEnum | 支付类型(包含各种场景) |
| Amount | 金额信息 |
| Attach | 提交订单附加数据【附加数据,在查询 API 和支付通知中原样返回】 |
| Detail | 优惠、商品列表 |
| GoodsDetail | 商品 |
| H5Info | H5 场景信息 |
| Payer | 支付者 |
| SceneInfo | 支付场景(H5 场景信息是其属性) |
| NotifyResource | 通知数据, 加密数据包含在此处 |
| NotifySuccess | 支付成功/退款成功, 微信回调 |
获取商户私钥并生成签名
- 私钥工具类
import cn.hutool.core.io.FileUtil;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
/**
* @author ZJX
* @description 获取微信商户私钥
**/
public class WXPrivateKey {
/**
* 获取私钥jsapi
*
* @param filename 私钥文件【apiclient_key.pem】路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKeyForWechat(String filename) throws Exception {
File file = new ClassPathResource(filename).getFile();
String content = new String(FileUtil.readBytes(file), StandardCharsets.UTF_8);
FileUtils.forceDeleteOnExit(file);
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
}- 使用私钥工具获取签名
import cn.hutool.core.util.IdUtil;
import com.sensor.wood.exception.BusinessException;
import com.sensor.wood.util.weixin.WxConfig;
import okhttp3.HttpUrl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.Signature;
import java.util.Base64;
import java.util.Locale;
/**
* @author ZJX
* @description 微信支付生成签名
* @date 2021/3/30 16:52
**/
@Component
public class CreateSign {
//微信认证类型
public static final String SCHEMA = "WECHATPAY2-SHA256-RSA2048";
@Resource
WxConfig config;
/**
* @param method url请求方法
* @param url URL地址对象
* @param body 请求体
* @return 签名串
* @description 根据得到的签名值,拼接请求微信接口的请求头
*/
public String getSign(String method, HttpUrl url, String body){
String nonceStr = IdUtil.fastSimpleUUID().toUpperCase(Locale.ROOT);
long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(method, url, timestamp, nonceStr, body);
String signature = signForWechat(message.getBytes(StandardCharsets.UTF_8));
return SCHEMA + " " + "mchid=\"" + config.getMchId() + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + config.getSerialNo() + "\","
+ "signature=\"" + signature + "\"";
}
/**
* @param message 待签名串
* @return 签名值
* @description 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值
*/
public String signForWechat(byte[] message){
String signStr;
try{
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(WXPrivateKey.getPrivateKeyForWechat(config.getApiClientKey()));
sign.update(message);
signStr = Base64.getEncoder().encodeToString(sign.sign());
}catch (Exception e){
throw new BusinessException(e.getMessage());
}
return signStr;
}
/**
* @param method 请求方法(GET,POST,PUT等)
* @param url 请求的绝对URL(去除域名部分;请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串)
* @param timestamp 格林威治时间至现在的总秒数
* @param nonceStr 请求随机串
* @param body 请求体(post请求传body,get传"")
* @return 待签名串
* @description 构建待签名串
*/
String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
}携带签名访问微信支付 url
- 携带签名的 http 工具类
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
/**
* @author ZJX
* @date 2021/3/22 0:38
**/
public class SimpleHttpUtils {
/**
* @param url 请求地址
* @param data 请求body
* @param sign 签名
* @return JSONObject
* @description 支付相关,携带加密签名的请求,用于post方式访问微信端url
*/
public static JSONObject sendPostWithSign(String url, String data, String sign) {
String result = HttpUtil.createPost(url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", sign)
.body(data)
.execute()
.charset("UTF8")
.body();
//如果为空,说明是调用微信端关闭订单接口
if (StrUtil.isBlank(result)) {
return new JSONObject();
}
return JSONUtil.parseObj(result);
}
/**
* @param url 请求地址
* @param sign 签名
* @return JSONObject
* @description 支付相关,携带加密签名的请求,用于Get方式访问微信端url
*/
public static JSONObject sendGetWithSign(String url, String sign) {
String result = HttpUtil
.createGet(url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", sign)
.execute()
.charset("UTF8")
.body();
return JSONUtil.parseObj(result);
}
}- 统一下单工具类
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.sensor.wood.util.weixin.SimpleHttpUtils;
import com.sensor.wood.util.weixin.WxConfig;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Amount;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Attach;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Detail;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Payer;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.SceneInfo;
import com.sensor.wood.util.weixin.secretUtil.CreateSign;
import okhttp3.HttpUrl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
/**
* @author ZJX
* @description 微信端统一下单工具类
* @date 2021/3/22 0:28
**/
@Component
public class PrepaidOrderService {
@Resource
WxConfig config;
@Resource
CreateSign createSign;
/**
* @param description 商品描述
* @param attach 附加数据,在查询API和支付通知中原样返回
* @param amount 订单金额信息
* @param openId 支付者
* @return prepay_id 预支付交易会话标识,用于后续接口调用中使用
* @description JSAPI方式调用
*/
public Map<String,Object> prepaidOrderForJSAPI(String description, String outTradeNo, Attach attach,
Integer amount, String openId, Detail detail) {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
JSONObject dataBody = new JSONObject();
dataBody.accumulate("appid", config.getAppId()).accumulate("mchid", config.getMchId()).accumulate("description", description)
.accumulate("out_trade_no", outTradeNo).accumulate("attach", attach)
.accumulate("notify_url", config.getNotifyUrl()).accumulate("amount", new Amount(amount)).accumulate("payer", new Payer(openId));
if (detail != null) dataBody.accumulate("detail", detail);
return this.getSign(url,dataBody.toString());
}
/**
* @param description 商品描述
* @param amount 订单金额信息
* @param attach 附加数据,在查询API和支付通知中原样返回
* @return prepay_id 预支付交易会话标识,用于后续接口调用中使用
* @description APP方式调用
*/
public Map<String,Object> prepaidOrderForAPP( String description, String outTradeNo, String attach,
Integer amount) throws Exception {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
JSONObject dataBody= new JSONObject();
dataBody.accumulate("appid", config.getAppId()).accumulate("mchid", config.getMchId()).accumulate("description", description)
.accumulate("out_trade_no", outTradeNo).accumulate("notify_url", config.getNotifyUrl()).accumulate("amount", new Amount(amount))
.accumulate("attach", attach);
return this.getSign(url,dataBody.toString());
}
/**
* @param description 商品描述
* @param attach 附加数据,在查询API和支付通知中原样返回
* @param amount 订单金额信息
* @param sceneInfo 支付场景(客户端ip,支付场景类型【iOS, Android, Wap】)
* @return h5_url 支付跳转链接
* @description H5方式调用
*/
public JSONObject prepaidOrderForH5(String description, String outTradeNo, Attach attach,
Integer amount,String clientIp ,String sceneInfo, Detail detail) throws Exception {
JSONObject dataBody = new JSONObject();
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/h5";
dataBody.accumulate("appid", config.getAppId()).accumulate("mchid", config.getMchId()).accumulate("description", description)
.accumulate("out_trade_no", outTradeNo).accumulate("attach", attach)
.accumulate("notify_url", config.getNotifyUrl()).accumulate("amount", new Amount(amount)).accumulate("scene_info", new SceneInfo(clientIp,sceneInfo));
if (detail != null) dataBody.accumulate("detail", detail);
//获取签名
String body = dataBody.toString();
String sign = createSign.getSign("POST", HttpUrl.parse(url), body);
JSONObject h5_url = SimpleHttpUtils.sendPostWithSign(url, body, sign);
return h5_url;
}
// 追加前端调起支付需要的数据
public Map<String,Object> getSign(String url,String dataBody){
//获取签名
String sign = createSign.getSign("POST", HttpUrl.parse(url), dataBody);
JSONObject prepayData = SimpleHttpUtils.sendPostWithSign(url, dataBody, sign);
//appId
String str = config.getAppId();
//timeStamp
String timeStamp = String.valueOf(new Date().getTime() / 1000);
//nonceStr
String nonceStr = IdUtil.fastSimpleUUID().toUpperCase(Locale.ROOT);
//package
if (ObjectUtil.isEmpty(prepayData.get("prepay_id"))) {
return prepayData.set("error", prepayData.get("message"));
}
String prepayId = prepayData.get("prepay_id").toString();
String packages = "prepay_id=" + prepayId;
//signType
str = str + "\n" + timeStamp + "\n" + nonceStr + "\n" + packages + "\n";
String encode = createSign.signForWechat(str.getBytes(StandardCharsets.UTF_8));
prepayData.set("appId", config.getAppId());
prepayData.set("partnerid", config.getMchId()); // 【商户号 partnerid 填写商户号mchid对应的值,APP端调起支付会用到】
prepayData.set("timeStamp", timeStamp);
prepayData.set("nonceStr", nonceStr);
prepayData.set("package", packages);
prepayData.set("signType", "RSA");
prepayData.set("paySign", encode); // paySign jsApi调起支付会用到
prepayData.set("sign", encode); // sign app端调起支付会用到
return prepayData;
}
}- 订单管理工具类
import cn.hutool.json.JSONObject;
import com.sensor.wood.util.weixin.SimpleHttpUtils;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Amount;
import com.sensor.wood.util.weixin.secretUtil.CreateSign;
import okhttp3.HttpUrl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author ZJX
* @description 公用的微信端订单管理工具类
* @date 2021/3/22 10:23
**/
@Component
public class CommonOrderUtilsService {
@Resource
CreateSign createSign;
//订单查询
public JSONObject search(String transactionId, String mchId) throws Exception {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + transactionId + "?mchid=" + mchId;
String sign = createSign.getSign("GET", HttpUrl.parse(url), "");
return SimpleHttpUtils.sendGetWithSign(url, sign);
}
//关闭订单,返回204,处理成功
public JSONObject close(String outTradeNo, String mchId) throws Exception {
String data = new JSONObject().accumulate("mchid", mchId).toString();
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close";
String sign = createSign.getSign("POST", HttpUrl.parse(url), data);
return SimpleHttpUtils.sendPostWithSign(url, data, sign);
}
/**
* @param outTradeNo 商户订单号
* @param amount 退款金额信息
* @return Json 退款结果
* @throws Exception
* @description 申请退款
*/
public JSONObject refund(String outTradeNo, String outRefundNo, Integer amount) throws Exception {
String data = new JSONObject().accumulate("out_trade_no", outTradeNo)
.accumulate("out_refund_no", outRefundNo).accumulate("amount", new Amount(amount)).toString();
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
String sign = createSign.getSign("POST", HttpUrl.parse(url), data);
return SimpleHttpUtils.sendPostWithSign(url, data, sign);
}
/**
* @param outRefundNo 商户退款单号
* @return Json
* @throws Exception
* @description 退款查询
*/
public JSONObject searchRefund(String outRefundNo) throws Exception {
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + outRefundNo;
String sign = createSign.getSign("GET", HttpUrl.parse(url), "");
return SimpleHttpUtils.sendGetWithSign(url, sign);
}
}订单号工具
- 订单号生成工具
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author ZJX
* @description 生成订单号
* @date 2021/3/22 1:41
**/
public class CreateOrderNumber {
// public static void main(String[] args) throws InterruptedException {
// System.out.println(CreateOrderNumber.getInstance().getOrderNumber());
// }
//定义锁对象
private final static ReentrantLock lock = new ReentrantLock();
// 创建一个空实例对象,类需要用的时候才赋值
private static CreateOrderNumber instance = null;
// 全局自增数
private static int count = 1;
// 记录上一次的时间,用来判断是否需要递增全局数
private static String now = null;
// 使用单例模式,不允许直接创建实例
private CreateOrderNumber() {
}
// 单例模式--懒汉模式
public static synchronized CreateOrderNumber getInstance() {
if (instance == null) {
instance = new CreateOrderNumber();
}
return instance;
}
// 获取当前时间年月日时分秒毫秒字符串
private static String getNowDateStr() {
return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
}
//调用的方法
public String getOrderNumber() {
String Newnumber = null;
String dateStr = getNowDateStr();
lock.lock();//加锁
//判断是时间是否相同
if (dateStr.equals(now)) {
try {
if (count >= 10000) {
count = 1;
}
if (count < 10) {
Newnumber = "N" + getNowDateStr() + "000" + count;
} else if (count < 100) {
Newnumber = "N" + getNowDateStr() + "00" + count;
} else if (count < 1000) {
Newnumber = "N" + getNowDateStr() + "0" + count;
} else {
Newnumber = "N" + getNowDateStr() + count;
}
count++;
} catch (Exception e) {
} finally {
lock.unlock();
}
} else {
count = 1;
now = getNowDateStr();
try {
if (count >= 10000) {
count = 1;
}
if (count < 10) {
Newnumber = "N" + getNowDateStr() + "000" + count;
} else if (count < 100) {
Newnumber = "N" + getNowDateStr() + "00" + count;
} else if (count < 1000) {
Newnumber = "N" + getNowDateStr() + "0" + count;
} else {
Newnumber = "N" + getNowDateStr() + count;
}
count++;
} catch (Exception e) {
} finally {
lock.unlock();
}
}
return Newnumber;//返回的值
}
}通知工具
- 回调通知解密工具
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.sensor.wood.util.weixin.entity.NotifyBody;
import com.sensor.wood.util.weixin.paymentUtils.AdviceOfPaymentUtil;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Attach;
import com.sensor.wood.util.weixin.paymentUtils.payEnum.PayEnum;
import com.sensor.wood.util.weixin.paymentUtils.thirdResposeBody.NotifyResource;
import com.sensor.wood.util.weixin.paymentUtils.thirdResposeBody.NotifySuccess;
import com.sensor.wood.util.weixin.secretUtil.CreateSign;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author ZJX
* @description 通用的微信工具
* @date 2021/10/14 10:21
**/
@Component
@Slf4j
public class WeiXinCommonUtil {
@Resource
WxConfig wxConfig;
@Resource
CreateSign createSign;
/**
* @author ZJX
* @description 根据微信返回的prepayId得到前端需要调起支付的数据【用于提交订单但未完成支付】
* @date 2021/10/14 10:21
**/
public Map<String, Object> getPayDataByPrepayId(String prepayId, PayEnum scene) throws Exception {
Map<String, Object> map = new HashMap<>();
if (PayEnum.SCENE_H5.equals(scene)) {
map.put("h5url", prepayId);
}
if(PayEnum.SCENE_JSAPI.equals(scene)) {
//appId
String appId = wxConfig.getAppId();
//timeStamp
String timeStamp = String.valueOf(new Date().getTime() / 1000);
//nonceStr
String nonceStr = IdUtil.fastSimpleUUID().toUpperCase(Locale.ROOT);
//package
String packages = "prepay_id=" + prepayId;
//signType
String encode = createSign.signForWechat((appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packages + "\n").getBytes(StandardCharsets.UTF_8));
map.put("appId", appId);
map.put("timeStamp", timeStamp);
map.put("nonceStr", nonceStr);
map.put("package", packages);
map.put("signType", "RSA");
map.put("paySign", encode);
}
return map;
}
/**
* @author ZJX
* @description 解密微信回调数据,并返回解密实体数据
* @date 2021/10/14 10:21
**/
public NotifyBody getNotifyBody(NotifySuccess notifySuccess) throws Exception {
log.info("微信支付回调开始:++++++++++++++++++++++");
// 解密,得到订单和用户信息
NotifyResource resource = notifySuccess.getResource();
JSONObject jsonObject = JSONUtil.parseObj(new AdviceOfPaymentUtil(wxConfig.getApiV3key().getBytes())
.decryptToString(resource.getAssociated_data().getBytes(), resource.getNonce().getBytes(), resource.getCiphertext()));
log.info("解密数据为:++++++++++++++:{}", jsonObject);
String outTradeNo = jsonObject.getStr("out_trade_no");
String transactionId = jsonObject.getStr("transaction_id");
Integer amount = Integer.valueOf(JSONUtil.parseObj(jsonObject.getStr("amount")).get("total").toString());
String openID = "";
// 若是退款通知,不会返回openID
String payer = jsonObject.getStr("payer");
if (StringUtils.isNotBlank(payer)){
openID = JSONUtil.parseObj(payer).getStr("openid");
}
// 自定义的附加数据
Attach attach = JSONUtil.toBean(jsonObject.getStr("attach"), Attach.class);
return new NotifyBody(outTradeNo,transactionId,amount,openID,attach);
}
}- 调起支付、使用回调解密获取订单信息工具
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.sensor.wood.util.weixin.entity.NotifyBody;
import com.sensor.wood.util.weixin.paymentUtils.AdviceOfPaymentUtil;
import com.sensor.wood.util.weixin.paymentUtils.ThirdRequestBody.Attach;
import com.sensor.wood.util.weixin.paymentUtils.payEnum.PayEnum;
import com.sensor.wood.util.weixin.paymentUtils.thirdResposeBody.NotifyResource;
import com.sensor.wood.util.weixin.paymentUtils.thirdResposeBody.NotifySuccess;
import com.sensor.wood.util.weixin.secretUtil.CreateSign;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author ZJX
* @description 通用的微信工具
* @date 2021/10/14 10:21
**/
@Component
@Slf4j
public class WeiXinCommonUtil {
@Resource
WxConfig wxConfig;
@Resource
CreateSign createSign;
/**
* @author ZJX
* @description 根据微信返回的prepayId得到前端需要调起支付的数据【用于提交订单但未完成支付】
* @date 2021/10/14 10:21
**/
public Map<String, Object> getPayDataByPrepayId(String prepayId, PayEnum scene) throws Exception {
Map<String, Object> map = new HashMap<>();
if (PayEnum.SCENE_H5.equals(scene)) {
map.put("h5url", prepayId);
}
if(PayEnum.SCENE_JSAPI.equals(scene)) {
//appId
String appId = wxConfig.getAppId();
//timeStamp
String timeStamp = String.valueOf(new Date().getTime() / 1000);
//nonceStr
String nonceStr = IdUtil.fastSimpleUUID().toUpperCase(Locale.ROOT);
//package
String packages = "prepay_id=" + prepayId;
//signType
String encode = createSign.signForWechat((appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packages + "\n").getBytes(StandardCharsets.UTF_8));
map.put("appId", appId);
map.put("timeStamp", timeStamp);
map.put("nonceStr", nonceStr);
map.put("package", packages);
map.put("signType", "RSA");
map.put("paySign", encode);
}
return map;
}
/**
* @author ZJX
* @description 解密微信回调数据,并返回解密实体数据
* @date 2021/10/14 10:21
**/
public NotifyBody getNotifyBody(NotifySuccess notifySuccess) throws Exception {
log.info("微信支付回调开始:++++++++++++++++++++++");
// 解密,得到订单和用户信息
NotifyResource resource = notifySuccess.getResource();
JSONObject jsonObject = JSONUtil.parseObj(new AdviceOfPaymentUtil(wxConfig.getApiV3key().getBytes())
.decryptToString(resource.getAssociated_data().getBytes(), resource.getNonce().getBytes(), resource.getCiphertext()));
log.info("解密数据为:++++++++++++++:{}", jsonObject);
String outTradeNo = jsonObject.getStr("out_trade_no");
String transactionId = jsonObject.getStr("transaction_id");
Integer amount = Integer.valueOf(JSONUtil.parseObj(jsonObject.getStr("amount")).get("total").toString());
String openID = "";
// 若是退款通知,不会返回openID
String payer = jsonObject.getStr("payer");
if (StringUtils.isNotBlank(payer)){
openID = JSONUtil.parseObj(payer).getStr("openid");
}
// 自定义的附加数据
Attach attach = JSONUtil.toBean(jsonObject.getStr("attach"), Attach.class);
return new NotifyBody(outTradeNo,transactionId,amount,openID,attach);
}
}最终测试
@RestController
@RequestMapping("/api/test/v3")
@Slf4j
public class TestController {
@Resource
WxConfig config;
@Resource
CommonOrderUtilsService orderUtilsService;
@Resource
PrepaidOrderService prepaidOrderService;
@Resource
WeiXinCommonUtil weiXinCommonUtil;
/**
* h5方式统一下单(测试用的api)
* @return
* @throws Exception
*/
@PostMapping(path = "/pay/H5/unifiedOrder")
public BaseResult standardUnifiedOrderH5() throws Exception {
String outTradeNo = CreateOrderNumber.getInstance().getOrderNumber(); //生成商户订单号
JSONObject jsonObject = prepaidOrderService.prepaidOrderForH5(
"核酸检测支付下单", outTradeNo, new Attach(PayEnum.TYPE_STANDARD_PAY.name()), 1,
"127.0.0.1", "Android",null);
return BaseResult.success("h5统一下单调用成功!", jsonObject);
}
/**
* @return prepay_id 预支付交易会话标识,该值有效期为2小时
* @description JSAPI方式统一下单
*/
@PostMapping(path = "/pay/JSAPI/unifiedOrder")
public BaseResult standardUnifiedOrderJSAPI() throws Exception {
String outTradeNo = CreateOrderNumber.getInstance().getOrderNumber(); //生成商户订单号
Map<String,Object> data = prepaidOrderService.prepaidOrderForJSAPI(
"xxxx下单", outTradeNo, new Attach(PayEnum.TYPE_STANDARD_PAY.name()),
1,"xxx_OpenId", null);
return BaseResult.success("统一下单调用成功!", data);
}
/**
* @param notifySuccess 支付成功/退款成功,微信端返回的加密回调数据
* @return 商户返回给微信的应答数据
* @description 微信回调请求地址;
*/
@PostMapping(path = "/notify")
public String advicePayment(@RequestBody NotifySuccess notifySuccess) throws Exception {
//解密,得到订单和用户信息
NotifyBody notifyBody = weiXinCommonUtil.getNotifyBody(notifySuccess);
log.info("支付/退款成功,解密数据为:" + notifyBody);
//保存微信支付数据到订单表,根据具体业务完善
return new JSONObject().accumulate("code", "SUCCESS").accumulate("message", "成功").toString();
}
/**
* 订单查询(微信端)
* transactionId 微信提供的支付订单号
*/
@GetMapping("/searchOrder/{transactionId}")
public BaseResult searchOrder(@PathVariable String transactionId) throws Exception {
JSONObject data = orderUtilsService.search(transactionId, config.getMchId());
return BaseResult.success("订单查询成功", data);
}
/**
* 关闭订单
* outTradeNo 商户订单号
*/
@GetMapping("/closeOrder/{outTradeNo}")
public BaseResult closeOrder(@PathVariable String outTradeNo) throws Exception {
JSONObject data = orderUtilsService.close(outTradeNo, config.getMchId());
//这里要测试看看data是个什么东西
if (null != data) {
return BaseResult.success("关闭订单成功", data);
}
return BaseResult.error("关闭订单失败");
}
/**
* 订单申请退款
*/
@PostMapping("/refundMoney/{outTradeNo}")
public BaseResult refundMoney(@PathVariable String outTradeNo) throws Exception {
String outRefundNo = CreateOrderNumber.getInstance().getOrderNumber();
JSONObject jsonObject = orderUtilsService.refund(outTradeNo, outRefundNo, 1);
return BaseResult.success("申请退款成功,稍后金额会退还至本账户", jsonObject);
}
/**
* 单笔退款详情查询
*/
@GetMapping("/search/refundMoney/{outRefundNo}")
public BaseResult searchRefundMoney(@PathVariable String outRefundNo) throws Exception {
JSONObject jsonObject = orderUtilsService.searchRefund(outRefundNo);
return BaseResult.success( "退款查询成功", jsonObject);
}
}第三方微信支付工具
WxJava是一个 Java 编写的 SDK 工具,包括微信支付、开放平台、小程序、企业微信、视频号、公众号等功能。他们被分为不同模块,按需引入到项目中即可,这里只介绍微信支付模块。
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId> <!-- 微信支付模块 -->
<version>xxx</version>
</dependency>- 微信小程序:weixin-java-miniapp
- 微信支付:weixin-java-pay
- 微信开放平台:weixin-java-open
- 公众号(包括订阅号和服务号):weixin-java-mp
- 企业号/企业微信:weixin-java-cp

