微信支付V3版本
关于微信支付V3版本和原来的微信支付有一定的区别,原来的微信支付采用的是MD5加密方式,并且可以使用微信支付证书,并限制了请求的服务器白名单(虽然可以手动设置请求服务器的ip地址)。
微信支付V3更改加密方式为RSA,不再限制请求服务器ip地址。对于数据采用加密和解密来实现安全。私钥在微信开放平台获取。
maven项目导入jar包
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.1</version>
</dependency>
自己封装的工具类,用于业务
WxPayment.java
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
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.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.*;
import org.apache.http.util.EntityUtils;
/**
* 微信支付 工具类,用于调用起微信支付
*/
@Slf4j
public class WxPayment {
private static final String APP_ID = "wxdd45ca77ce3d8c2f";//微信应用ID
private static final String MCH_ID = "1613773777";//商戶号
public final static String API_KEYV3 = "9";//apiV3秘钥,从商户中设置获得
public static final String NOTIFY_URL = "https:///callback/wxPayCallBack";//支付成功回调地址,要求必须是https
public static final String SERIAL_NO = "6D85E039C59CFE1E0CE615EF146873DDDA341A7D";//api证书序列号,从api证书点击查看进去可以看到序列号
public static final String PRIVATE_KEY = "";//证书私钥,32位设置的秘钥,从微信商户中设置后获得
/**
* 创建微信支付订单,不会直接支付,返回微信预支付ID
*
* @param orderNo 订单号
* @param title 支付标题
* @param money 支付金额,保留两位小数
* @return 返回微信预支付ID
*/
public static String createOrder(String orderNo, String title, BigDecimal money) {
try {
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
// 请求body参数
SortedMap<Object, Object> parameters = new TreeMap<>();
parameters.put("mchid", MCH_ID);
parameters.put("out_trade_no", orderNo);
parameters.put("appid", APP_ID);
parameters.put("description", title);
parameters.put("notify_url", NOTIFY_URL);
parameters.put("attach", orderNo);//附加数据
HashMap<String, Object> amount = new HashMap<>();
amount.put("total", money.multiply(new BigDecimal("100")).intValue());//需要将输入的金额*100变成分
amount.put("currency", "CNY");
parameters.put("amount", amount);
//将请求参数转换为json格式
String data = new GsonBuilder().create().toJson(parameters);
StringEntity entity = new StringEntity(data, StandardCharsets.UTF_8);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
// 获取私钥
PrivateKey privateKey = getPrivateKey(PRIVATE_KEY);
//加载平台证书
AutoUpdateCertificatesVerifier verifier = getCertificate(MCH_ID, SERIAL_NO, privateKey, API_KEYV3);
// 初始化httpClient
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(MCH_ID, SERIAL_NO, privateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
System.out.println("下单成功:" + EntityUtils.toString(response.getEntity()));
return new GsonBuilder().create().fromJson(EntityUtils.toString(response.getEntity()), Map.class).get("prepay_id").toString();
} else {
log.error("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
}
response.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 组装支付数据给app,用于发起微信支付,之所以要和下单接口分开,就是因为在支付过程中,有些已经有了预订单ID不需要再下单,直接传入预订单ID就可以发起支付了
*
* @param prepay_id 微信预支付ID
* @return 返回Map集合对象
*/
public static Map<String, Object> retPayStr(String prepay_id) {
try {
Map<String, Object> map = new HashMap<>();
map.put("appid", APP_ID);
map.put("partnerid", MCH_ID);
map.put("prepayid", prepay_id);
map.put("package", "Sign=WXPay");
map.put("noncestr", createNoncestr(32));
map.put("timestamp", Calendar.getInstance().getTimeInMillis());
map.put("sign", sign(APP_ID + "\n" + Calendar.getInstance().getTimeInMillis() + "\n" + createNoncestr(16) + "\n" + prepay_id + "\n"));
return map;
} catch (Exception e) {
log.error("组装微信支付数据失败!预支付ID为:{}", prepay_id);
e.printStackTrace();
}
return null;
}
/**
* 微信回调解密 --->实际应用时,可以将整个回调通知body传入,修改本方法内的前置处理
*
* @param associated_data 附加数据
* @param nonce 数据密文
* @param ciphertext 加密使用的随机串
* @return 返回解密后的Map参数集合
*/
public static Map callbackDecryption(String associated_data, String nonce, String ciphertext) {
try {
byte[] key = API_KEYV3.getBytes(StandardCharsets.UTF_8);
WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key);
String decryptToString = aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
return new Gson().fromJson(decryptToString, Map.class);
} catch (Exception e) {
e.printStackTrace();
log.error("微信回调解密失败!");
}
return null;
}
/**
* 查询订单是否支付成功,用于订单超时检测,返回SUCCESS则支付成功
*
* @param outTradeNo 订单号
* @return 返回交易状态 SUCCESS:支付成功 * REFUND:转入退款 * NOTPAY:未支付 * CLOSED:已关闭 * REVOKED:已撤销(仅付款码支付会返回) * USERPAYING:用户支付中(仅付款码支付会返回) * PAYERROR:支付失败(仅付款码支付会返回)
*/
public static String selectOrderPay(String outTradeNo) {
try {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "?mchid=" + MCH_ID);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
// 初始化httpClient
AutoUpdateCertificatesVerifier verifier = getCertificate(MCH_ID, SERIAL_NO, getPrivateKey(PRIVATE_KEY), API_KEYV3);
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(MCH_ID, SERIAL_NO, getPrivateKey(PRIVATE_KEY))
.withValidator(new WechatPay2Validator(verifier)).build();
CloseableHttpResponse response = httpClient.execute(httpGet);
return EntityUtils.toString(response.getEntity());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取随机字符串
*
* @param length 字符串长度
* @return 返回随机生成的字符串
*/
public static String createNoncestr(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(str.length());
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 获取平台证书
*
* @param micId 商户ID
* @param serialNo 证书序列号
* @param privateKey 私钥
* @param apiV3Key apiV3秘钥
* @return 返回证书信息
*/
public static AutoUpdateCertificatesVerifier getCertificate(String micId, String serialNo, PrivateKey privateKey, String apiV3Key) {
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
return new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(micId, new PrivateKeySigner(serialNo, privateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
}
/**
* 对输入内容进行微信私钥签名,这里其实也是调用的jar包里面的加密,只是因为里面没有对外公开,所以封装了一个方法
*
* @param message 签名内容
* @return 返回签名后的字符串
*/
public static String sign(String message) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
//初始化签名
signature.initSign(getPrivateKey(PRIVATE_KEY));
byte[] bytes = message.getBytes();
//将数据添加到签名
signature.update(bytes);
//计算签名
return Base64.getEncoder().encodeToString(signature.sign());
}
/**
* 获取私钥。
*
* @param privateKey 私钥字符串 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String privateKey) {
// 加载商户私钥(privateKey:私钥字符串)
return PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)));
}
}
截止微信支付就成功了,至于controller就不贴了,其实回调也很简单,只需要接收一个参数@RequestBody String body就可以了,得到的这个是字符串,需要手动json格式化,然后传入解密就ok了。
可能有部分类不可用,由于写这个文章时,在家,项目没有跑起来,有什么问题可以私聊:1126539036