对接微信新版本的微信V3支付,本文章只对接JSAPI支付的相关代码,其他的方式都差不多;
提示:以下是本篇文章正文内容,下面案例可供参考
<!--微信V3支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
封装的实体相关信息,可以自行修改代码如下(示例):
/**
* @author 孤巷.
* @description 微信支付参数模型-POST
* @date 2023/8/1 下午 3:45
*/
@Data
public class WxPayOutModel {
/**
* 微信apiV3密钥
*/
private String apiV3Key;
/**
* 商户号
*/
private String mchId;
/**
* 微信API证书序列号
*/
private String serialNo;
/**
* 微信请求接口地址-也就是你要调用的微信接口地址
*/
private String url;
/**
* 微信请求接口入参
*/
private String value;
private String method;
private String body;
/**
* 机构编码
*/
private String orgName;
}
代码如下(示例):
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.whxx.base.core.constant.WeChatPayConstant;
import com.whxx.base.core.model.WxPayOutModel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Slf4j
public class WxPayUtil {
private KeyStore store;
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
private final Object lock = new Object();
/**
* 初始化微信支付配置参数
*
* @return HttpClient
*/
public HttpClient iniWeChatV3Configuration(WxPayOutModel wxPayOutModel) {
//获取商户密钥
PrivateKey privateKey = getPrivateKey(null);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(wxPayOutModel.getSerialNo(), privateKey);
//用来实现省份认证
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(wxPayOutModel.getMchId(), privateKeySigner);
// 获取证书管理器实例
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
wxPayOutModel.getApiV3Key().getBytes(StandardCharsets.UTF_8)
);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wxPayOutModel.getMchId(), wxPayOutModel.getSerialNo(), getPrivateKey(null))
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
/**
* Post请求 调用微信支付接口
*
* @param wxPayOutModel 接口入参
* @return JSON
*/
public JSONObject httpPostException(WxPayOutModel wxPayOutModel) {
HttpClient httpClient = iniWeChatV3Configuration(wxPayOutModel);
HttpPost httpPost = new HttpPost(wxPayOutModel.getUrl());
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
String bodyAsString = null;
try {
objectMapper.writeValue(bos, JSON.parseObject(wxPayOutModel.getValue()));
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
bodyAsString = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
log.error("微信支付异常 == {}", e.getMessage());
e.printStackTrace();
}
return JSON.parseObject(bodyAsString);
}
/**
* 获取证书私钥-把微信证书私钥放在项目resources文件夹下即可;
* @param filename 私钥证书地址
* @return 私钥
*/
private PrivateKey getPrivateKey(String filename){
try {
InputStream systemResourceAsStream = this.getClass().getResourceAsStream("/apiclient_key.pem");
return PemUtil.loadPrivateKey(systemResourceAsStream);
} catch (Exception e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
@SneakyThrows
public String awakenPaySign(String appid, long timestamp, String nonceStr, String packages) {
String signatureStr = Stream.of(appid, String.valueOf(timestamp), nonceStr, packages)
.collect(Collectors.joining("\n", "", "\n"));
log.info("签名的拼接参数 == {}",signatureStr);
Signature sign = Signature.getInstance("SHA256withRSA");
log.info("signatureStr == {}", signatureStr);
sign.initSign(getPrivateKey(null));
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
就是用到的常量,你也可以把他写死;代码如下(示例):
/**
* @author 孤巷.
* @description 微信API接口常量
* @date 2023/8/1 下午 3:27
*/
public class WeChatPayConstant {
/**
* ISV模式jsapi支付
*/
public static final String ISV_JSAPI_URL = "v3/pay/partner/transactions/jsapi";
/**
* ISV模式Native支付
*/
public static final String ISV_NATIVE_URL = "v3/pay/partner/transactions/native";
/**
* JSAPI支付
*/
public static final String JSAPI_URL = "v3/pay/transactions/jsapi";
/**
* Native支付
*/
public static final String NATIVE_URL = "v3/pay/transactions/native";
/**
* 币种:CNY人民币
*/
public static final String CNY = "CNY";
/**
* 退款币种
*/
public static final String CURRENCY = "currency";
/**
* 退款金额
*/
public static final String REFUND = "refund";
/**
* 订单总金额
*/
public static final String TOTAL = "total";
/**
* 商户订单号
*/
public static final String OUT_TRADE_NO = "out_trade_no";
/**
* 商户退款订单号
*/
public static final String OUT_REFUND_NO = "out_refund_no";
/**
* 退款原因
*/
public static final String REASON = "reason";
/**
* 唯一用户标识
*/
public static final String OPENID = "openid";
/**
* 服务商应用ID
*/
public static final String ISV_APPID = "sp_appid";
/**
* 服务商户号
*/
public static final String ISV_MCHID = "sp_mchid";
/**
* 子商户应用ID
*/
public static final String SUB_APPID = "sub_appid";
/**
* 子商户号
*/
public static final String SUB_MCHID = "sub_mchid";
/**
* 直连商户的商户号,由微信支付生成并下发
*/
public static final String WX_MCH_ID = "mchid";
public static final String WX_APP_ID = "appid";
/**
* 商品描述
*/
public static final String DESCRIPTION = "description";
/**
* 通知地址
*/
public static final String NOTIFY_URL = "notify_url";
/**
* 订单失效时间
*/
public static final String TIME_EXPIRE = "time_expire";
/**
* 支付者信息
*/
public static final String PAYER = "payer";
/**
* 用户在子商户appid下的唯一标识
*/
public static final String SUB_OPENID = "sub_openid";
/**
* 预支付会话标识
*/
public static final String PREPAY_ID = "prepay_id";
/**
* 平台应用编码
*/
public static final String APPID = "appId";
/**
* 时间类型
*/
public static final String TIMESTAMP = "timeStamp";
public static final String NONCESTR = "nonceStr";
/**
* 包
*/
public static final String PACKAGE = "package";
public static final String PREPAY_ID_EQUAL = "prepay_id=";
/**
* 签名类型
*/
public static final String SIGNTYPE = "signType";
/**
* 加密方式RSA
*/
public static final String RSA = "RSA";
public static final String PAYSIGN = "paySign";
public static final String FIELD_SIGN = "sign";
public enum SignType {
MD5, HMACSHA256
}
public static final String SUCCESS = "SUCCESS";
public static final String PROCESSING = "PROCESSING";
public static final String WECHAT_REFUND_CODE = "return_code";
public static final String RETURN_MSG = "return_msg";
public static final String RESULT_CODE = "result_code";
public static final String ERROR_CODE_DES = "err_code_des";
public static final String MICRO_PAY_SUCCESS = "MICROPAY";
public static final String TRADE_TYPE = "trade_type";
public static final String CLOSE = "CLOSE";
/**
* 微信支付订单号
*/
public static final String TRANSACTION_ID = "transaction_id";
/**
* 退款成功时间/支付完成时间
*/
public static final String SUCCESS_TIME = "success_time";
/**
* 金额信息
*/
public static final String AMOUNT = "amount";
/**
* 交易状态
*/
public static final String TRADE_STATE = "trade_state";
/**
* 微信签名校验参数
*/
public static final String WECHATPAY_NONCE = "Wechatpay-Nonce";
public static final String WECHATPAY_TIMESTAMP = "Wechatpay-Timestamp";
public static final String WECHATPAY_SIGNATURE = "Wechatpay-Signature";
public static final String SHA256WITHRSA = "SHA256withRSA";
/**
* 状态码
*/
public static final String CODE = "code";
public static final String FAIL = "FAIL";
public static final String MESSAGE = "message";
public static final String SUCCESS_MSG = "操作成功";
public static final String FAIL_MSG = "操作失败";
}
ResultUtil我就不多介绍了,参考我的统一出入参文章即可;代码如下(示例):
/**
* 微信JSAPI支付-调起预付单窗口-V3版本接口
* @return 成功则返回前端所需调用预付单窗口参数
*/
public JSONObject jsApi(UnifyPayOrder payOrder, String loginHospitalId, String loginSoftId, String orgName) {
//封装JSAPI微信入参
WxPayOutModel wxPayOutModel = builderWxPayOutModel(payOrder,loginHospitalId,loginSoftId,orgName);
log.info("微信JSAPI支付-封装入参 == {}",wxPayOutModel);
//调用微信JSAPI接口,获取回参
WxPayUtil wxPayUtil = new WxPayUtil();
JSONObject prePayIdResult = wxPayUtil.httpPostException(wxPayOutModel);
if(prePayIdResult.isEmpty()){
return ResultUtil.fail("微信请求失败:"+prePayIdResult);
}
if(prePayIdResult.getString(WeChatPayConstant.PREPAY_ID) == null){
throw new WeChatException("微信获取prePayId失败:"+prePayIdResult);
}
String prePayId = prePayIdResult.getString(WeChatPayConstant.PREPAY_ID);
JSONObject result = new JSONObject();
result.put(WeChatPayConstant.APPID, configRedisService
.getConfigStringValue(loginHospitalId,loginSoftId,PayConfigConstant.GZH_APPID));
result.put(WeChatPayConstant.TIMESTAMP, System.currentTimeMillis() / 1000);
result.put(WeChatPayConstant.NONCESTR, WxPayUtil.generateNonceStr());
result.put(WeChatPayConstant.PACKAGE, WeChatPayConstant.PREPAY_ID_EQUAL + prePayId);
result.put(WeChatPayConstant.SIGNTYPE, WeChatPayConstant.RSA);
String sign = wxPayUtil.awakenPaySign(result.getString(WeChatPayConstant.APPID), result.getLong(WeChatPayConstant.TIMESTAMP),result.getString(WeChatPayConstant.NONCESTR),result.getString(WeChatPayConstant.PACKAGE));
result.put(WeChatPayConstant.PAYSIGN, sign);
return ResultUtil.success(result);
}
/**
* 微信JSAPI请求入参封装
* @param payOrder 充值订单实体类
* @return WxPayOutModel
*/
private WxPayOutModel builderWxPayOutModel(UnifyPayOrder payOrder,String loginHospitalId,String loginSoftId,String orgName) {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
String url;
//这个就是获取系统配置,判断是不是服务商模式
boolean isIsv = configRedisService.getConfigBoolValue(loginHospitalId,loginSoftId,PayConfigConstant.IS_ISV);
if (isIsv) {
//服务商模式
rootNode.put(WeChatPayConstant.ISV_APPID, configRedisService
.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.ISV_APPID));
rootNode.put(WeChatPayConstant.ISV_MCHID, configRedisService
.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.ISV_MCHID));
rootNode.put(WeChatPayConstant.SUB_APPID, configRedisService
.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.GZH_APPID));
rootNode.put(WeChatPayConstant.SUB_MCHID, configRedisService
.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.MCH_ID));
//下面实际就是根据支付渠道编码判断下具体是哪种支付,如果只有JSAPI判断可以直接去掉就可以
//jsapi
if (payOrder.getPayChannelCode().equals(WeChatPayConstant.JS_API_CODE)) {
rootNode.putObject(WeChatPayConstant.PAYER).put(WeChatPayConstant.SUB_OPENID, payOrder.getOpenId());
url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.ISV_JSAPI_URL;
//Native
} else {
url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.ISV_NATIVE_URL;
}
} else {
rootNode.put(WeChatPayConstant.WX_APP_ID
, configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.GZH_APPID));
rootNode.put(WeChatPayConstant.WX_MCH_ID
, configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.MCH_ID));
//jsapi
if (payOrder.getPayChannelCode().equals(WeChatPayConstant.JS_API_CODE)) {
rootNode.putObject(WeChatPayConstant.PAYER).put(WeChatPayConstant.OPENID, payOrder.getOpenId());
url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.JSAPI_URL;
//Native
} else {
url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.NATIVE_URL;
}
}
rootNode.put(WeChatPayConstant.TIME_EXPIRE, getExpiredTime());
rootNode.put(WeChatPayConstant.DESCRIPTION, createPayOrderSubject(payOrder.getCardNo()));
rootNode.put(WeChatPayConstant.OUT_TRADE_NO, payOrder.getPlatformNo());
String notifyUrl = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
, PayConfigConstant.RECHARGE_RETURN_URL);
rootNode.put(WeChatPayConstant.NOTIFY_URL, getNotifyUrl(notifyUrl, loginHospitalId,loginSoftId));
rootNode.putObject(WeChatPayConstant.AMOUNT)
.put(WeChatPayConstant.TOTAL, BigDecimalUtil.yuanToFen(payOrder.getMoney()));
//调用封装函数获取微信返回数据
return buildWxPayOutModel(orgName,url,loginHospitalId,loginSoftId,rootNode,isIsv);
}
/**
* 封装调用微信支付接口入参
*
* @param url 微信请求地址
* @param rootNode 微信请求入参
* @return weChatOutApiModel
*/
private WxPayOutModel buildWxPayOutModel(String orgName,String url,String loginHospitalId,String loginSoftId, Object rootNode,boolean isIsv) {
WxPayOutModel wxPayOutModel = new WxPayOutModel();
wxPayOutModel.setApiV3Key(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId,PayConfigConstant.TRADE_KEY));
wxPayOutModel.setCertAddress(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId,PayConfigConstant.WECHAT_REFUND_FILE_URL));
if (isIsv) {
//服务商模式则使用服务商的商户id
wxPayOutModel.setMchId(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.ISV_MCHID));
} else {
wxPayOutModel.setMchId(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.MCH_ID));
}
wxPayOutModel.setValue(String.valueOf(rootNode));
wxPayOutModel.setUrl(url);
wxPayOutModel.setSerialNo(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.WECHAT_CERT_SERIAL_NO));
wxPayOutModel.setOrgName(orgName);
return wxPayOutModel;
}
有相关疑问欢迎大家下方评论,我会第一时间回复!!