微信官方文档–小程序支付:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
微信官方文档–支付接入前准备https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
下载平台证书报错处理:https://blog.csdn.net/aiguo94/article/details/124037688
使用Apache HttpClient处理HTTP的Java开发库(封装了签名生成、签名验证、敏感信息加/解密、媒体文件上传等基础功能):https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
maven导包
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.5</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
</dependency>
实体类
wxpay_v3.properties(微信支付配置,有appId,商户id,API密钥,证书地址,回调地址,该文件在resource根目录)
WxPayV3Bean (微信支付配置对象,获取wxpay_v3.properties中的配置的参数值)
PayOrderDTO (微信下单对象)
WxPayVO (微信下单后返回给前端的对象)
WxQueryVO(微信查单返回的对象)
WxPayStateEnum (微信支付订单状态枚举)
wxpay_v3.properties(其中,apiclient_key.pem,apiclient_cert.pem,apiclient_cert.p12是通过接入前准备,在微信支付商户平台配置后获取,platformCert.pem的获取参考https://blog.csdn.net/aiguo94/article/details/124037688)
v3.appId=wx10xxxxxxxxxxxx
v3.mchId=16xxxxxxxxx
v3.mchSerialNo=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
v3.apiKey3=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
v3.keyPath=/cert/apiclient_key.pem
v3.certPath=/cert/apiclient_cert.pem
v3.certP12Path=/cert/apiclient_cert.p12
v3.platformCertPath=/cert/platformCert.pem
v3.notifyUrl=https:/xxxxxxxxxxxxxxxxx/app/pay/payNotify
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 配置对象
* @Author smilehan
*/
@Component
@ConfigurationProperties(prefix = "v3")
@Data
@ToString
public class WxPayV3Bean {
private String appId;
private String mchId;
private String mchSerialNo;
private String apiKey3;
private String keyPath;
private String certPath;
private String certP12Path;
private String platformCertPath;
private String notifyUrl;
}
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付下单
* @Author smilehan
*/
@Data
@Accessors(chain = true)
public class PayOrderDTO {
private Long userId;
private Integer totalPrice;
private String goodsName;
private String openId;
private String orderSn;
}
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 微信下单返回
* @Author smilehan
*/
@Data
@Accessors(chain = true)
public class WxPayVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 预支付交易会话标识小程序下单接口返回的prepay_id参数值
*/
private String prepayId;
/**
* 随机字符串
*/
private String nonceStr;
/**
* 时间戳
*/
private String timeStamp;
/**
* 签名
*/
private String paySign;
}
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 微信订单查询
* @Author smilehan
*/
@Data
@Accessors(chain = true)
public class WxQueryVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单号
*/
private String outTradeNo;
/**
* 交易状态(0:支付成功 1:转入退款 2:未支付 3:已关闭 4:已撤销(付款码支付)" +
* " 5:用户支付中(付款码支付) 6:支付失败(其他原因,如银行返回失败) 7:已接收,等待扣款 8: 订单不存在)
*/
private Integer tradeState;
/**
* 交易状态描述
*/
private String tradeStateDesc;
/**
* 支付单号
*/
private String transactionId;
}
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 微信支付状态值
* @Author smilehan
*/
@Getter
@AllArgsConstructor
public enum WxPayStateEnum {
SUCCESS(0, "SUCCESS", "支付成功"),
REFUND(1, "REFUND", "转入退款"),
NOTPAY(2, "NOTPAY", "未支付"),
CLOSED(3, "CLOSED", "已关闭"),
REVOKED(4, "REVOKED", "已撤销(付款码支付)"),
USERPAYING(5, "USERPAYING", "用户支付中(付款码支付)"),
PAYERROR(6, "PAYERROR", "支付失败(其他原因,如银行返回失败)"),
ACCEPT(7, "ACCEPT", "已接收,等待扣款"),
ABSENCE(8, "ABSENCE", "订单不存在"),
OK(9, "OK", "OK"),
PROCESS(10, "PROCESSING", "PROCESSING")
;
private Integer code;
private String name;
private String description;
public static WxPayStateEnum getByName(String name) {
for (WxPayStateEnum wxPayStateEnum : WxPayStateEnum.values()) {
if (wxPayStateEnum.getName().equals(name)) {
return wxPayStateEnum;
}
}
return null;
}
}
工具类
WxPayUtil(支付util,初始化httpClient,生成签名,支付时间转换)
import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import com.ijpay.core.kit.PayKit;
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.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.entity.pay.WxPayV3Bean;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
/**
* 支付相关
* @Author smilehan
*/
@Component
@Slf4j
@Data
public class WxPayUtil {
@Resource
private WxPayV3Bean wxPayV3Bean;
private CloseableHttpClient httpClient;
private Verifier verifier;
@PostConstruct
public void initHttpClient(){
log.info("微信支付httpClient初始化");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(FileUtil.getInputStream(wxPayV3Bean.getKeyPath()));
X509Certificate wechatPayCertificate = PemUtil.loadCertificate(FileUtil.getInputStream(wxPayV3Bean.getPlatformCertPath()));
ArrayList<X509Certificate> listCertificates = new ArrayList<>();
listCertificates.add(wechatPayCertificate);
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(wxPayV3Bean.getMchId(), wxPayV3Bean.getMchSerialNo(), merchantPrivateKey)
.withWechatPay(listCertificates)
.build();
}
@PostConstruct
public void initVerifier() throws Exception{
log.info("微信支付Verifier初始化");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(FileUtil.getInputStream(wxPayV3Bean.getKeyPath()));
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(wxPayV3Bean.getMchId(), new WechatPay2Credentials(wxPayV3Bean.getMchId(),
new PrivateKeySigner(wxPayV3Bean.getMchSerialNo(), merchantPrivateKey)), wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
verifier = certificatesManager.getVerifier(wxPayV3Bean.getMchId());
}
/**
* 生成签名
* @param appId
* @param timestamp
* @param nonceStr
* @param prepayId
* @return
* @throws Exception
*/
public String getSign(String appId, String timestamp, String nonceStr, String prepayId) throws Exception{
String message=appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ prepayId + "\n";
String sign = PayKit.createSign(message, wxPayV3Bean.getKeyPath());
return sign;
}
/**
* 回调的支付时间转成date
* @param dateTimeString
* @return
*/
public Date dateTimeToDate(String dateTimeString){
DateTime dateTime = new DateTime(dateTimeString);
long timeInMillis = dateTime.toCalendar(Locale.getDefault()).getTimeInMillis();
return new Date(timeInMillis);
}
}
service
WxPayService (生成本系统自有订单后调用,微信下单,生成预支付交易会话等信息WxPayVO返回给小程序)
import com.web.controller.dto.pay.PayOrderDTO;
import com.web.controller.vo.pay.WxPayVO;
import com.web.controller.vo.pay.WxQueryVO;
public interface WxPayService {
/**
* 微信下单
* @param dto
* @return
*/
WxPayVO createOrder(PayOrderDTO dto) throws Exception;
/**
* 查询订单。queryType。1,根据支付订单查询。2,根据系统自有订单号查询
* @return
*/
WxQueryVO queryOrder(String payNumber,Integer queryType);
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ijpay.core.kit.WxPayKit;
import com.base.execption.RRException;
import com.common.enums.pay.WxPayStateEnum;
import com.common.utils.WxPayUtil;
import com.entity.pay.WxPayV3Bean;
import com.service.pay.WxPayService;
import com.web.controller.dto.pay.PayOrderDTO;
import com.web.controller.vo.pay.WxPayVO;
import com.web.controller.vo.pay.WxQueryVO;
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 org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
/**
* 微信下单/查单
* @Author smilehan
*/
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WxPayV3Bean wxPayV3Bean;
@Autowired
private WxPayUtil wxPayUtil;
@Override
public WxPayVO createOrder(PayOrderDTO orderDetailEntity) throws Exception {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", wxPayV3Bean.getMchId())
.put("appid", wxPayV3Bean.getAppId())
.put("description", orderDetailEntity.getGoodsName())
.put("notify_url", wxPayV3Bean.getNotifyUrl())
.put("out_trade_no", orderDetailEntity.getOrderSn())
.put("attach", orderDetailEntity.getUserId().toString());
rootNode.putObject("amount")
.put("total", orderDetailEntity.getTotalPrice());
rootNode.putObject("payer")
.put("openid", orderDetailEntity.getOpenId());
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpClient httpClient = wxPayUtil.getHttpClient();
CloseableHttpResponse response = httpClient.execute(httpPost);
WxPayVO wxPayVO = new WxPayVO();
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
wxPayVO.setNonceStr(WxPayKit.generateStr());
wxPayVO.setTimeStamp(System.currentTimeMillis() / 1000 + "");
JSONObject jsonObject = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
String prepay_id = jsonObject.getString("prepay_id");
wxPayVO.setPrepayId("prepay_id=" + prepay_id);
wxPayVO.setPaySign(wxPayUtil.getSign(wxPayV3Bean.getAppId(), wxPayVO.getTimeStamp(), wxPayVO.getNonceStr(), wxPayVO.getPrepayId()));
log.info("200success,return body:{} ", EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
log.info("204success");
} else {
log.info("failed,resp code:{} ,return body:{} ", statusCode, EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception("系统错误");
}
return wxPayVO;
}
/**
* 订单查询
*
* @return
*/
@Override
public WxQueryVO queryOrder(String payNumber, Integer queryType) {
WxQueryVO vo = new WxQueryVO();
try {
String url = "";
if (queryType.equals(1)) {
//根据支付订单查询
url = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + payNumber;
} else {
//根据系统自有订单号查询
url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + payNumber;
}
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.setParameter("mchid", wxPayV3Bean.getMchId());
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
CloseableHttpClient httpClient = wxPayUtil.getHttpClient();
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject data = JSON.parseObject(bodyAsString);
log.info("微信订单查询结果:{}", data);
if (StringUtils.hasText(data.getString("out_trade_no"))
&& StringUtils.hasText(data.getString("trade_state"))) {
vo.setOutTradeNo(data.getString("out_trade_no"));
vo.setTradeState(WxPayStateEnum.getByName(data.getString("trade_state")).getCode());
vo.setTradeStateDesc(data.getString("trade_state_desc"));
vo.setTransactionId(data.getString("transaction_id"));
} else {
vo.setTradeState(WxPayStateEnum.ABSENCE.getCode());
vo.setTradeStateDesc(data.getString("message"));
}
} catch (Exception e) {
e.printStackTrace();
}
return vo;
}
}
controller
WxPayV3Controller (用于支付成功后的回调处理)
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.common.enums.pay.WxPayStateEnum;
import com.common.utils.WxPayUtil;
import com.entity.pay.WxPayV3Bean;
import com.web.controller.dto.pay.PayNotifyDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.wildfly.common.Assert;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
/**
* 微信支付回调
* @Author smilehan
*/
@RestController
@RequestMapping("/app/pay")
@Slf4j
public class WxPayV3Controller {
@Autowired
private WxPayV3Bean wxPayV3Bean;
@Autowired
private WxPayUtil wxPayUtil;
/**
* 微信支付回调通知
*/
@PostMapping(value = "/payNotify")
public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("微信回调开始");
//读取请求体的信息
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
//读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
// 构建request,传入必要参数
NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNo)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(s1)
.build();
NotificationHandler handler = new NotificationHandler(wxPayUtil.getVerifier(), wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(notificationRequest);
Assert.assertNotNull(notification);
log.info("回调结果:{}",notification);
JSONObject jsonObject = JSONObject.parseObject(notification.getDecryptData());
String tradeState = jsonObject.getString("trade_state");
String paySn = jsonObject.getString("transaction_id");
String successTime = jsonObject.getString("success_time");
String attach = jsonObject.getString("attach");
JSONObject amount = JSONObject.parseObject(jsonObject.getString("amount"));
String payerTotal = amount.getString("payer_total");
String orderSn = jsonObject.getString("out_trade_no");
try {
PayNotifyDTO payNotifyDTO = new PayNotifyDTO().setPaySn(paySn)
.setPayPrice(new BigDecimal(payerTotal).divide(new BigDecimal("100")))
.setPayTime(wxPayUtil.dateTimeToDate(successTime))
.setAttach(attach)
.setOrderSn(orderSn);
if (StringUtils.hasText(tradeState) && WxPayStateEnum.SUCCESS.getName().equals(tradeState)) {
//用payNotifyDTO处理相关逻辑(比如:更改订单状态为已支付,将微信支付单号-支付金额-支付时间存到订单表等等)
}
response.setStatus(200);
}catch (Exception e){
Map<String, String> map = new HashMap<>(12);
response.setStatus(500);
map.put("code", "FAIL");
map.put("message", "失败");
response.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
}finally {
response.flushBuffer();
}
}
}
小程序,确定订单信息后——>确认支付——>调用后端的生成订单接口——>后端生成系统自有订单——>后端通过自有订单号、金额、openid等去请求微信下单接口,微信返回预支付交易会话标识prepay_id——>后端给appid、timestamp、nonceStr、prepayId签名,并将签名、timestamp、nonceStr、prepay_id返回给小程序——>小程序调用wx.requestPayment拉起微信支付——>用户付完钱后,微信根据下单时候填的回调地址,请求自有系统的回调接口,自有系统处理相应的逻辑