一.微信小程序接入微信支付流程图:
重点步骤说明:
步骤4:用户下单发起支付,商户可通过[JSAPI下单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml)创建支付订单。
步骤9:商户小程序内使用[小程序调起支付API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml)(wx.requestPayment)发起微信支付,详见小程序API文档
步骤16:用户支付成功后,商户可接收到微信支付支付结果通知[支付通知API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml)。
步骤21:商户在没有接收到微信支付结果通知的情况下需要主动调用[查询订单API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_2.shtml)查询支付结果。
二.准备工作:
官方文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
1. 小程序开通微信支付
首先需要在微信支付的官网注册一个商户;
在管理页面中申请关联小程序,通过小程序的 appid 进行关联;
进入微信公众平台,功能-微信支付中确认关联(如果服务商和小程序的注册主体不一样,还要经过微信的审核)
2. 获取各种证书、密钥文件-基于微信支付 V3 的版本:
商户 id:这个可以在小程序微信公众平台-功能-微信支付 页面中的已关联商户号中得到
商户密钥:这个需要在微信支付的管理后台中申请获取
证书编号: 同样在微信支付的管理后台中申请证书,申请证书后就会看到证书编号
证书私钥:上一步申请证书的时候同时也会获取到证书的公钥、私钥文件。开发中只需要私钥文件
三.后端代码
> 1.引入微信支付官方的pom依赖:
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version>
</dependency>
> 2.微信支付配置信息:
wx-pay:
v3:
#微信关联的小程序的appid
appId: xxxxx
#微信支付商户号
mchId: xxxxxx
#微信支付证书序列号
serialnumber: xxxxxx
#微信支付apiv3的密钥
apiKey3: xxxxx
#微信支付证书
certKeyPath: /cert/xxxxx.pem
#微信支付回调商户线上地址api
notifyUrl: https://xxxxx/pay/callback
#微信支付关闭订单api
closeOrderUrl: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close
#微信支付小程序预下单api
jsApiUrl: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
#微信支付订单查询api
queryOrder: https://api.mch.weixin.qq.com/v3/pay/transactions/id/
@Data
@Component
@ConfigurationProperties(prefix="wx-pay.v3")
public class WxPayConfig {
//appId
private String appId;
//商户id
private String mchId;
//证书序列号
private String serialnumber;
//商户秘钥
private String apiKey3;
//商户的key【API密匙】存放路径
private String certKeyPath;
//回调通知地址
private String notifyUrl;
//jsapi预支付url
private String jsApiUrl;
//关闭订单接口
private String closeOrderUrl;
//查询订单地址
private String queryOrder;
}
3.初始化微信支付的参数
@Service
@Transactional
public class SysPayServiceImpl implements SysPayService {
private static final Logger log = LoggerFactory.getLogger(SysPayServiceImpl.class);
@Autowired
private WxPayConfig wxPayConfig;
//微信专业httpClient
private static CloseableHttpClient httpClient;
//生成签名用
private static Sign sign;
//证书管理器
private static CertificatesManager certificatesManager;
@Autowired
private SysOrderDao sysOrderDao;
//初始化微信支付的参数
@PostConstruct
public void init() throws Exception {
//获取商户私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(getFileInputStream(wxPayConfig.getCertKeyPath()));
// 获取证书管理器实例
certificatesManager = CertificatesManager.getInstance();
sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, merchantPrivateKey.getEncoded(), null);
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(wxPayConfig.getMchId(), new WechatPay2Credentials(wxPayConfig.getMchId(),
new PrivateKeySigner(wxPayConfig.getSerialnumber(), merchantPrivateKey)), wxPayConfig.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(wxPayConfig.getMchId());
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wxPayConfig.getMchId(), wxPayConfig.getSerialnumber(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
httpClient = builder.build();
}
/**
* 使用getResourceAsStream直接从resources根路径下获取文件流
* @param path
*/
private InputStream getFileInputStream(String path) {
InputStream in = this.getClass().getResourceAsStream(path);
return in;
}
}
4.预订单生成返回给小程序请求参数
//预订单生成返回给小程序请求参数
@Override
public AjaxResult prePay(WxPayDto dto) {
log.info("微信小程序支付JSAPI预下单开始------");
AjaxResult ajaxResult = AjaxResult.success();
JSONObject obj = new JSONObject();
//应用ID
obj.put("appid", wxPayConfig.getAppId());
//直连商户号
obj.put("mchid", wxPayConfig.getMchId());
//商品描述
obj.put("description", dto.getDescription());
//商户订单号
obj.put("out_trade_no", dto.getOrderCode());
//通知地址
obj.put("notify_url", wxPayConfig.getNotifyUrl());
//附加数据 查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段。
Map<String, String> attach = new HashMap<>();
attach.put("orderCode", dto.getOrderCode());
obj.put("attach", JSON.toJSONString(attach));
//订单金额
JSONObject amount = new JSONObject();
amount.put("total", dto.getPrice());
obj.put("amount", amount);
//支付者
JSONObject payer = new JSONObject();
payer.put("openid", dto.getOpenId());
obj.put("payer", payer);
log.info("微信小程序支付JSAPI预下单请求参数:{}" + obj.toJSONString());
HttpPost httpPost = new HttpPost(wxPayConfig.getJsApiUrl());
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(obj.toJSONString(), "UTF-8"));
String bodyAsString ;
try {
if(httpClient == null){
log.info("微信小程序支付JSAPI预下单请求失败");
return AjaxResult.error("预下单失败,请重试,无法连接微信支付服务器!" );
}
//执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
bodyAsString = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
log.info("微信小程序支付JSAPI预下单请求失败{}", e.getMessage());
return AjaxResult.error("预下单失败,请重试!" + e.getMessage());
}
String prePayId = JSONObject.parseObject(bodyAsString).getString("prepay_id");
if (prePayId == null){
String message = JSONObject.parseObject(bodyAsString).getString("message");
log.info("微信小程序支付JSAPI预下单请求失败{}", message);
return AjaxResult.error("预下单失败,请重试!" + message);
}
//准备小程序端的请求参数
ajaxResult.put("appId", wxPayConfig.getAppId());
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
ajaxResult.put("timeStamp", timeStamp);
String nonceStr = IdUtil.fastSimpleUUID();
ajaxResult.put("nonceStr", nonceStr);
String packageStr = "prepay_id=" + prePayId;
ajaxResult.put("package", packageStr);
ajaxResult.put("signType", "RSA");
String signString = wxPayConfig.getAppId() + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n";
ajaxResult.put("paySign", Base64.encode(sign.sign(signString.getBytes())));
return ajaxResult;
}
> 5.微信支付回调通知
//微信支付回调通知
@Override
public void callback(HttpServletRequest servletRequest, HttpServletResponse response) {
log.info("----------->微信支付回调开始");
Map<String, String> map = new HashMap<>(12);
String timeStamp = servletRequest.getHeader("Wechatpay-Timestamp");
String nonce = servletRequest.getHeader("Wechatpay-Nonce");
String signature = servletRequest.getHeader("Wechatpay-Signature");
String certSn = servletRequest.getHeader("Wechatpay-Serial");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(servletRequest.getInputStream()))) {
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String obj = stringBuilder.toString();
log.info("微信支付回调请求参数:{},{},{},{},{}", obj, timeStamp, nonce, signature, certSn);
Verifier verifier = certificatesManager.getVerifier(wxPayConfig.getMchId());
String sn = verifier.getValidCertificate().getSerialNumber().toString(16).toUpperCase(Locale.ROOT);
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(sn)
.withNonce(nonce)
.withTimestamp(timeStamp)
.withSignature(signature)
.withBody(obj)
.build();
NotificationHandler handler = new NotificationHandler(verifier, wxPayConfig.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(request);
JSONObject res = JSON.parseObject(notification.getDecryptData());
log.info("微信支付回调响应参数:{}", res.toJSONString());
//做一些操作
if (res != null) {
//如果支付成功
String tradeState = res.getString("trade_state");
if("SUCCESS".equals(tradeState)){
//拿到商户订单号
String outTradeNo = res.getString("out_trade_no");
JSONObject amountJson = res.getJSONObject("amount");
Integer payerTotal = amountJson.getInteger("payer_total");
SysOrder sysOrder = sysOrderDao.queryByOrderCode(outTradeNo);
if(sysOrder != null){
if(sysOrder.getPayStatus() == 1){
//如果支付状态为1 说明订单已经支付成功了,直接响应微信服务器返回成功
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
//验证用户支付的金额和订单金额是否一致
if(payerTotal.equals(sysOrder.getOrderPrice())){
//修改订单状态
String successTime = res.getString("success_time");
sysOrder.setPayStatus(1);
sysOrderDao.update(sysOrder);
//响应微信服务器
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}
}
}
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
log.error("微信支付回调失败", e);
}
}
> 6.当用户10分钟内未支付,系统自动关闭微信支付订单并删除
//当用户10分钟内未支付,系统自动关闭微信支付订单并删除
@Override
public void deleteClosePayOrder(String orderCode) {
JSONObject obj = new JSONObject();
//直连商户号
obj.put("mchid", wxPayConfig.getMchId());
String ret2 = wxPayConfig.getCloseOrderUrl().replace("{out_trade_no}", orderCode);
log.info("关闭和删除微信订单--请求地址{}" , ret2);
log.info("关闭和删除微信订单--请求参数{}" , obj.toJSONString());
HttpPost httpPost = new HttpPost(ret2);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(obj.toJSONString(), "UTF-8"));
String bodyAsString ;
try {
if(httpClient == null){
log.info("关闭和删除微信订单--关闭订单失败,请重试,无法连接微信支付服务器!");
}
//执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
//状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 204) {
//关闭订单成功!
//删除订单
log.info("关闭和删除微信订单--关闭订单成功orderCode:{}!", orderCode);
sysOrderDao.deleteByOrderCode(orderCode);
}else if(statusCode == 202){
//用户支付中,需要输入密码
log.info("关闭和删除微信订单--用户支付中,需要输入密码,暂时不做处理!");
}else{
log.info("关闭和删除微信订单--关闭支付订单失败,出现未知原因{}", EntityUtils.toString(response.getEntity()));
}
} catch (IOException e) {
log.info("关闭和删除微信订单--关闭订单失败{}", e.getMessage());
}
}
> 7.查询订单支付结果接口
//查询订单支付结果
@Override
public AjaxResult queryPayResult(String payId){
//请求URL
URIBuilder uriBuilder = null;
try {
uriBuilder = new URIBuilder(wxPayConfig.getQueryOrder() + payId);
uriBuilder.setParameter("mchid", wxPayConfig.getMchId());
if(httpClient == null){
return AjaxResult.error("查询订单失败,请重试,无法连接微信支付服务器!" );
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200 || statusCode == 204) {
String ss = EntityUtils.toString(response.getEntity());
if(StringUtils.isNotEmpty(ss)){
JSONObject res = JSON.parseObject(ss);
log.info("success,return body = " + ss);
//拿到商户订单号
String outTradeNo = res.getString("out_trade_no");
JSONObject amountJson = res.getJSONObject("amount");
Integer payerTotal = amountJson.getInteger("payer_total");
SysOrder sysOrder = sysOrderDao.queryByOrderCode(outTradeNo);
if(sysOrder != null){
if(sysOrder.getPayStatus() == 1){
//如果支付状态为1 说明订单已经支付成功了,直接响应成功
return AjaxResult.success("查询支付成功!" );
}
//验证用户支付的金额和订单金额是否一致
if(payerTotal.equals(sysOrder.getOrderPrice())){
//修改订单状态
String successTime = res.getString("success_time");
sysOrder.setPayStatus(1);
sysOrderDao.update(sysOrder);
return AjaxResult.success("查询支付成功!");
}
}
}
}
return AjaxResult.error("查询支付失败:" + "failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
return AjaxResult.error("查询支付失败:" + e.getMessage());
}
}
### 四.前端小程序代码
>微信小程序支付需要用到官方的支付API:wx.requestPayment(Object object)
>官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestPayment.html
wx.requestPayment({
timeStamp: 'xxx', // 时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
nonceStr: 'xxxxxx', // 随机字符串,长度为32个字符以下
package: 'xxxxxxxxxxxx', // 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signType: 'xxx', // 签名算法,应与后台下单时的值一致
paySign: 'xxxxxxxxxx', // 签名,具体见微信支付文档
success (res) { // 成功的回调
wx.showToast({title: '付款成功'})
},
fail (res) { // 失败的回调
wx.showToast({title: '支付失败',icon: 'none'})
}
})
})