一 前期准备
1 公众号平台设置
- 地址:https://mp.weixin.qq.com 设置与开发->基本配置 获取开发者ID 密钥和设置白名单(获取本地公网出口IP命令: curl ip.sb)

2 微信支付平台设置
- 详细信息见:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml
二 开发过程
1 引入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
2 将密钥文件放到项目目录中

3 注入微信支付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.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.Properties;
@Configuration
@Data
@Slf4j
public class WxPayConfig {
@Value("${wechatPay.mch-id}")
private String mchId;
@Value("${wechatPay.mch-serial-no}")
private String mchSerialNo;
@Value("${wechatPay.private-key-path}")
private String privateKeyPath;
@Value("${wechatPay.api-v3-key}")
private String apiV3Key;
@Value("${wechatPay.appid}")
private String appid;
@Value("${wechatPay.domain}")
private String domain;
@Value("${wechatPay.notify-domain}")
private String notifyDomain;
@Value("${wechatPay.front-domain}")
private String frontDomain;
@Autowired
private ApplicationValues appValues;
private PrivateKey getPrivateKey(String filename) {
log.info("filename ==============> {}",filename);
log.info("this.getClass() ==============> {}",this.getClass().getClassLoader().getResourceAsStream(filename));
return PemUtil.loadPrivateKey(this.getClass().getClassLoader().getResourceAsStream(filename));
}
@Bean
public CloseableHttpClient getWxPayClient(Verifier verifier) {
RequestConfig requestConfig;
requestConfig = RequestConfig.custom()
.setSocketTimeout(appValues.getSocketTimeout())
.setConnectTimeout(appValues.getConnTimeOut())
.setConnectionRequestTimeout(appValues.getConnReqTimeOut())
.build();
PrivateKey privateKey = getPrivateKey(privateKeyPath);
HttpClientBuilder builder;
builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier))
.setDefaultRequestConfig(requestConfig);
CloseableHttpClient httpClient = builder
.build();
return httpClient;
}
@Bean
public Verifier getVerifier() {
PrivateKey privateKey = getPrivateKey(privateKeyPath);
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
CertificatesManager certificatesManager = CertificatesManager.getInstance();
try {
certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
} catch (IOException | GeneralSecurityException | HttpCodeException e) {
log.warn(e.getMessage());
throw new RuntimeException(e);
}
Verifier verifier = null;
try {
verifier = certificatesManager.getVerifier(mchId);
} catch (NotFoundException e) {
e.printStackTrace();
log.warn(e.getMessage());
}
return verifier;
}
}
4 Controller
import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonSyntaxException;
import com.test.weixin.common.CommonResult;
import com.test.weixin.jieshun.request.JhtDiscountReq;
import com.test.weixin.jieshun.service.JhtCommService;
import com.test.weixin.service.WechatPayService;
import com.test.weixin.utils.HttpUtils;
import com.test.weixin.utils.WechatPay2ValidatorForRequest;
import com.test.weixin.vo.WechatPayVo;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@RestController
@RequestMapping("/wechatPay")
@Api(tags = "网站微信支付API")
@Slf4j
public class WechatPayController {
@Resource
private WechatPayService wechatPayService;
@Resource
private Verifier verifier;
@PostMapping("/jsApiPay")
public CommonResult jsApiPay(@RequestBody WechatPayVo wechatPayVo) throws Exception {
log.info("发起支付请求");
return CommonResult.success(wechatPayService.createPayOrder(wechatPayVo));
}
@PostMapping("/notify")
public String jsApiNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
JSONObject res = new JSONObject();
try {
String body = HttpUtils.readData(request);
HashMap<String, Object> bodyMap = JSONObject.parseObject(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
log.info("支付通知的id ====> {}", requestId);
log.info("支付通知的完整数据 ====> {}", body);
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, body, requestId);
if (!wechatPay2ValidatorForRequest.validate(request)) {
log.info("通知验签失败");
response.setStatus(500);
res.put("code", "ERROR");
res.put("message", "通知验签失败");
return res.toString();
}
log.info("通知验签成功");
Boolean notify = wechatPayService.processPayNotify(bodyMap);
if (notify) {
response.setStatus(200);
res.put("code", "SUCCESS");
res.put("message", "成功");
return res.toString();
} else {
response.setStatus(500);
res.put("code", "ERROR");
res.put("message", "失败");
return res.toString();
}
} catch (JsonSyntaxException e) {
e.printStackTrace();
response.setStatus(500);
res.put("code", "ERROR");
res.put("message", "失败");
return res.toString();
}
}
}
5 Service and ServiceImpl
import com.alibaba.fastjson.JSONObject;
import com.test.weixin.vo.WechatPayVo;
import java.util.HashMap;
public interface WechatPayService {
JSONObject createPayOrder(WechatPayVo wechatPayVo) throws Exception ;
boolean processPayNotify(HashMap<String, Object> bodyMap) throws Exception;
}
package com.test.weixin.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.test.weixin.common.ParkException;
import com.test.weixin.common.ParkResult;
import com.test.weixin.common.WxPayConfig;
import com.test.weixin.constants.ErrorCode;
import com.test.weixin.constants.PayStatus;
import com.test.weixin.constants.wxpay.WxApiType;
import com.test.weixin.constants.wxpay.WxTradeState;
import com.test.weixin.jieshun.request.JhtPayNotifyReq;
import com.test.weixin.jieshun.service.JhtCommService;
import com.test.weixin.model.TWxPayResult;
import com.test.weixin.service.*;
import com.test.weixin.utils.TimeTransformUtil;
import com.test.weixin.utils.WXPayUtil;
import com.test.weixin.vo.WechatPayVo;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {
@Resource
private WxPayConfig wxPayConfig;
@Resource
@Qualifier("getWxPayClient")
private CloseableHttpClient wxPayClient;
@Resource
private RedissonClient redissonClient;
@Value("${lock.key.wechat_pay_change_status}")
private String wechatPayChangeStatus;
@Value("${wechatPay.private-key-path}")
private String privateKeyPath;
@Autowired
WxPayResultIService wxPayResultIService;
@Override
public JSONObject createPayOrder(WechatPayVo wechatPayVo) throws Exception {
log.info("WechatPayVo:"+wechatPayVo);
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType()));
JSONObject jsonParams = new JSONObject();
jsonParams.put("appid", wxPayConfig.getAppid());
jsonParams.put("mchid", wxPayConfig.getMchId());
jsonParams.put("description", wechatPayVo.getProduct());
jsonParams.put("out_trade_no", wechatPayVo.getOrderId());
jsonParams.put("time_expire", TimeTransformUtil.timeToRfc(wechatPayVo.getTimeExpire()));
jsonParams.put("notify_url", wxPayConfig.getNotifyDomain());
JSONObject amount = new JSONObject();
amount.put("total", wechatPayVo.getAmount().multiply(new BigDecimal("100")).intValue());
amount.put("currency", "CNY");
jsonParams.put("amount", amount);
JSONObject payer = new JSONObject();
payer.put("openid", wechatPayVo.getOpenId());
jsonParams.put("payer", payer);
log.info("请求参数:" + jsonParams);
String reqdata = jsonParams.toString();
StringEntity entity = new StringEntity(reqdata, "utf-8");
entity.setContentType("application/json");
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();
if (statusCode == 200) {
log.info("成功, 返回结果 = {}", bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
log.info("失败, 响应码 = {}, 返回结果 = {}", statusCode, bodyAsString);
throw new IOException(JSONObject.parseObject(bodyAsString).getString("message"));
}
JSONObject body = JSONObject.parseObject(bodyAsString);
String prepayId = (String) body.get("prepay_id");
long timestamp = WXPayUtil.getCurrentTimestamp();
String nonceStr = WXPayUtil.generateNonceStr();
String str = "%s\n%s\n%s\nprepay_id=%s\n";
str = String.format(str,wxPayConfig.getAppid(),timestamp,nonceStr,prepayId);
log.info("构造签名串:\n"+str);
String paySign = WXPayUtil.getSign(str,this.getClass().getClassLoader().getResourceAsStream(privateKeyPath));
JSONObject result = new JSONObject(new LinkedHashMap<>());
result.put("appId", wxPayConfig.getAppid());
result.put("timeStamp", timestamp);
result.put("nonceStr", nonceStr);
result.put("package", "prepay_id=".concat(prepayId));
result.put("signType", "RSA");
result.put("paySign", paySign);
return result;
} finally {
response.close();
}
}
@Override
public boolean processPayNotify(HashMap<String, Object> bodyMap) throws Exception {
log.info("订单处理");
String plainText = decryptFromResource(bodyMap);
log.info("返回明文数据:"+plainText);
HashMap plainTextMap = JSONObject.parseObject(plainText, HashMap.class);
String orderId = (String) plainTextMap.get("out_trade_no");
String transactionId = (String) plainTextMap.get("transaction_id");
String tradeState = (String) plainTextMap.get("trade_state");
String addLockKey = wechatPayChangeStatus + orderId;
RLock rLock = redissonClient.getLock(addLockKey);
try {
if (rLock.tryLock(10, 10, TimeUnit.SECONDS)) {
if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
return true;
}
}
} catch (Exception e) {
log.error("更改 id = {} 订单状态时 锁分配失败,10秒后再试", orderId);
}
return false;
}
private String decryptFromResource(HashMap<String, Object> bodyMap) throws GeneralSecurityException {
log.info("密文解密");
Map<String, String> resourceMap = (Map<String, String>) bodyMap.get("resource");
String ciphertext = resourceMap.get("ciphertext");
String nonce = resourceMap.get("nonce");
String associated_data = resourceMap.get("associated_data");
log.info("密文 ===> {}", ciphertext);
AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(
associated_data.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info("明文 ===> {}", plainText);
return plainText;
}
}
6 WXPayUtil
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
public static String getSign(String signatureStr, InputStream privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
7 备注
- 微信支付回调不一定能成功通知业务方 所以需要自己在项目中开定时任务扫描你的支付数据 定时去查询支付状态是否是未成功支付的订单
三 接口调用
