硬性条件
小程序必须为个体工商户或者企业账户,个人无法开通支付功能
小程序需要认证(300元认证费)
营业执照
准备工作
- 注册微信小程序。
-
?注册商户号,后期用户支付的钱会自动进入商户号中,并在次日打入注册商户号时所用的银行卡中。
小程序与商户号绑定
小程序界面中申请开通微信支付(如下图,我这边已开通)
小程序绑定商户号
商户号API证书申请及APIv3密钥设置
?证书申请需要下载微信官方的工具,具体操作如下:
?
?
?
证书生成完成后会得到一个压缩文件。解压后如下:
?具体代码
依赖:
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
<!--发送http请求-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
微信支付相关配置文件如下:(放在resources文件夹下)其中商户私钥文件即为商户号申请的API证书路径
?配置类,用于在springboot启动时加载配置文件中的内容
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址(支付结果通知地址)
private String notifyDomain01;
// APIv2密钥
// private String partnerKey;
/**
* 获取商户的私钥文件
*
* @param filename
* @return
*/
public PrivateKey getPrivateKey(String filename) {
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取签名验证器
*
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
log.info("获取签名验证器");
//获取商户私钥
PrivateKey privateKey = this.getPrivateKey(privateKeyPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
/**
* 获取http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = this.getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, mchSerialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
}
}
小程序拉起支付时调用的接口
/**
* 用户微信支付
*/
@PostMapping("/userPay")
public R userPay(@RequestBody UserPayDto userPayDto) {
try {
// JSAPI下单
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
// totalPrice = totalPrice.multiply(new BigDecimal(100));
rootNode.put("mchid", wxPayConfig.getMchId())//商户id
.put("appid", wxPayConfig.getAppid())//appid
.put("description", "微信支付测试")//商品描述
.put("notify_url", wxPayConfig.getNotifyDomain01())//回调地址
.put("out_trade_no", );//本地系统订单号
rootNode.putObject("amount")
.put("total",);//金额(单位:分)
rootNode.putObject("payer")//付款者openid
.put("openid", userPayDto.getUserOpenId());
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//调Api请求
CloseableHttpResponse response2 = wxPayClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response2.getEntity());
String[] split = bodyAsString.split("\"");
long startTs = System.currentTimeMillis();
String s = Long.toString(startTs);
String prepayId = "prepay_id=" + split[3];
String key = wxPayConfig.getAppid() + "\n" + s + "\n" +
userPayDto.getPwd() + "\n"
+ prepayId + "\n";
String sign = sign(key, wxPayConfig.getPrivateKeyPath());
return Objects.requireNonNull(Objects.requireNonNull(R.ok().put("prepayId", prepayId)).put("sign", sign)).put("timestamp", s).put("orderNo", orderNo);
} catch (Exception e) {
e.printStackTrace();
return R.error();
} finally {
log.error("保存预订单信息完成。结果:{}", b);
}
}
/**
* 支付通知
* 微信支付通过支付通知接口将用户支付成功消息通知给商户
*/
@PostMapping("/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
System.err.println(request);
System.err.println(request.getRequestURL());
Gson gson = new Gson();
Map<String, String> resultMap = new HashMap<>();
try {
String body = HttpUtils.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, Map.class);
String requestId = (String) bodyMap.get("id");
log.error("支付通知的id ===> {}", requestId);
//签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if (!wechatPay2ValidatorForRequest.validate(request)) {
log.error("通知验签失败");
//失败应答
response.setStatus(500);
resultMap.put("code", "ERROR");
resultMap.put("message", "通知验签失败");
return gson.toJson(resultMap);
}
log.error("通知验签成功");
response.setStatus(200);
//订单的业务逻辑处理
wxPayService.processOrder(bodyMap);
resultMap.put("code", "SUCCESS");
resultMap.put("message", "成功");
return gson.toJson(resultMap);
} catch (Exception e) {
e.printStackTrace();
log.error("通知验签失败");
//失败应答
response.setStatus(500);
resultMap.put("code", "ERROR");
resultMap.put("message", "失败");
return gson.toJson(resultMap);
}
}
支付结果通知的业务处理方法
/**
* 用户支付完成后的业务逻辑处理
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
log.error("处理订单");
String plainText = this.decryptFromResource(bodyMap);
//将明文转换成map
Gson gson = new Gson();
HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
System.err.println("plainTextMap:" + plainTextMap);
String orderNo = (String) plainTextMap.get("out_trade_no");
log.error("验证" + orderNo + "支付");
CtrlDeviceDto ctrlDeviceDto = new CtrlDeviceDto();
R r = new R();
QueryWrapper<WxOrderFlowEntity> wrapper = new QueryWrapper<WxOrderFlowEntity>().eq("order_num", orderNo);
//若 trade_state 参数为 SUCCESS ,则控制设备出酒
if ("SUCCESS".equals(plainTextMap.get("trade_state"))) {
log.error("用户支付完成。单号:{}", orderNo);
} else {
log.error("trade_state 参数 非SUCCESS。单号:{}", orderNo);
}
}
/**
* 对称解密
*/
private String decryptFromResource(Map<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 associatedData = resourceMap.get("associated_data");
log.info("密文 ===> {}", ciphertext);
AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info("明文 ===> {}", plainText);
return plainText;
}
小程序端代码
wxPay() {
var vm = this
vm.loadModal = true
var openId = uni.getStorageSync('userOpenId')
var pwd = this.get32Pwd()
wxPayRequest('/userPay', {
userOpenId: openId,
pwd: pwd,
}, 'POST').then((res) => {
vm.loadModal = false
if (res.code === 0) {
var that = this
var prepayId = res.prepayId;
var sign = res.sign;
var timestamp = res.timestamp;
wx.requestPayment({
timeStamp: timestamp, //时间戳,自1970年以来的秒数
nonceStr: pwd, //随机串
package: prepayId,
signType: "RSA", //微信签名方式:
paySign: sign,
success: function(res) {
vm.loadModalMakeOrder = true
console.log('用户付款完成');
},
fail: function(res) {},
complete: function(res) {},
});
} else {
uni.showToast({
title: '创建订单失败',
icon: 'error',
duration: 2000
});
}
}, (error) => {
vm.loadModal = false
console.log(error);
})
},
//获取32位随机串
get32Pwd() {
var $chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
var maxPos = $chars.length;
var pwd = "";
for (var i = 0; i < 32; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd
},
注意:微信支付结果的通知只能使用https协议接收,不可使用http,所以在测试的时候需要使用到内网穿透。推荐使用:ngrok
有问题欢迎评论区交流;转载请注明出处
|