前言:最近对接了微信JSAPI接口(V3版本),在此记录一下。以便之后可以快速定位
接入之前的准备事项,可以先观看官方文档。把需要准备的一些信息都准备好。
传送门:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml
1.选择接入模式
直连模式
2.参数申请如下:
2.1 申请APPID
2.2 申请mchid
2.3 登录微信小程序后端,绑定APPID及mchid
3.配置API key
API v3密钥主要用于平台证书解密、回调信息解密,具体使用方式可参见接口规则文档中证书和回调报文解密章节。
请根据以下步骤配置API key:
-
1登录微信商户平台,进入【账户中心 > API安全 > API安全】目录,点击【设置密钥】。
-
2在弹出窗口中点击“已沟通”。
-
3输入API密钥,内容为32位字符,包括数字及大小写字母。点击获取短信验证码。
-
4输入短信验证码,点击“确认”即设置成功。
4.下载并配置商户证书
商户API证书具体使用说明可参见接口规则文档中私钥和证书章节
商户可登录微信商户平台,在【账户中心】->【API安全】->【API证书】目录下载证书
以下为具体下载步骤:
-
1从2018年底开始,微信支付新入驻机构及商户都将使用CA签发证书,在证书申请页面上点击“下载证书”。
-
2在弹出窗口内点击“下载证书工具”按钮下载证书工具。
-
3安装证书工具并打开,选择证书需要存储的路径后点击“申请证书”。
-
4在证书工具中,将复制的商户信息粘贴并点击“下一步”。
-
5获取请求串
-
6生成证书串
步骤1 在【商户平台】-“复制证书串”环节,点击“复制证书串”按钮后;
步骤2 在【证书工具】-“复制请求串”环节,点击“下一步”按钮进入“粘贴证书串”环节;
步骤3 在【证书工具】-“粘贴证书串”环节,点击“粘贴”按钮后;
步骤4 点击“下一步”按钮,进入【证书工具】-“生成证书”环节
-
7在【证书工具】-“生成证书”环节,已完成申请证书流程,点击“查看证书文件夹”,查看已生成的证书文件。
5.配置应用
设置支付目录
支付授权目录说明
1)商户最后请求拉起微信支付收银台的页面地址我们称之为“支付目录”,例如:https://www.weixin.com/pay.php。
2)商户实际的支付目录必须和在微信支付商户平台设置的一致,否则会报错“当前页面的URL未注册:”
支付授权目录设置说明
登录【微信支付商户平台—>产品中心—>开发配置】,设置后一般5分钟内生效。
支付授权目录校验规则说明
1)如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;
2)如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/
设置授权域名
开发JSAPI支付时,在JSAPI下单接口中要求必传用户openid,而获取openid则需要您在微信公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体界面如图所示:
**开通流程:**在入驻时选择线下场所,公众号场景,PC网站场景的商户系统默认开通此功能,其他商户如有需要,可以在入驻后前往商户平台-产品中心-JSAPI支付-申请开通。
温馨提示:准备里的东西都要弄好,不然无法正常对接。(大神请忽略)
以上的参数都准备好之后,开始正式操作。
需要把证书都下载下来。放到resource文件夹中。
配置参数model,我是通过ConfigurationProperties注解进行注入。也可以用别的方法。
WeChatConfig.java
@Component
@ConfigurationProperties(prefix = "xxxxx")
public class WeChatConfig {
/**
* 微信APPID
*/
public static String appID;
/**
* 微信APP密码
*/
public static String appSecret ;
/**
* 商户号
*/
public static String mchId;
/**
* 商户KEY
*/
public static String mchKey;
/**
* 通知地址
*/
public static String notifyUrl;
/**
* 证书序列号
*/
public static String serialNo;
/**
* apiV3密钥
*/
public static String apiV3Key;
public void setSerialNo(String serialNo) {
this.serialNo = serialNo;
}
public void setApiV3Key(String apiV3Key) {
this.apiV3Key = apiV3Key;
}
public void setAppID(String appID) {
this.appID = appID;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public void setMchKey(String mchKey) {
this.mchKey = mchKey;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
}
配置工具类
payUtil.java(工具类可以通过下载官方demo获得里面的方法)
public class PayUtil {
// 统一下单(JSAPI)
public static final String ORDER_PAY_JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
/**
* 创建支付随机字符串
*
* @return
*/
public static String getNonceStr() {
return IdUtils.fastSimpleUUID().replaceAll("-", "");
}
/**
* V3 SHA256withRSA 签名.
*
* @param appId 请求方法 GET POST PUT DELETE 等
* @param timestamp 当前时间戳 因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
* @param nonceStr 随机字符串 要和TOKEN中的保持一致
* @return the string
*/
public static String sign(String appId, long timestamp, String nonceStr,String packageKey)
throws Exception {
String signatureStr = buildMessage(appId,timestamp,nonceStr,packageKey);
Signature sign = null;
PrivateKey privateKey = getPrivateKey("xxxxxxxxxx.pem");
sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(signatureStr.getBytes("utf-8"));
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 配置请求头的签名
*/
public static CloseableHttpClient wxClient() throws IOException {
//excel模板路径
ClassPathResource cpr = new ClassPathResource("xxxxxxxxxx.pem");
// IO流
InputStream ioStream = cpr.getInputStream();
byte[] getData = readInputStream(ioStream);
ioStream.read(getData);
String content = new String(getData);
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(
content.getBytes(StandardCharsets.UTF_8)));;
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(WeChatConfig.mchId,
new PrivateKeySigner(WeChatConfig.serialNo,
merchantPrivateKey)), WeChatConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
// 初始化httpClient
return WechatPayHttpClientBuilder.create()
.withMerchant(WeChatConfig.mchId, WeChatConfig.serialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
}
/**
* 获取私钥。
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
//excel模板路径
ClassPathResource cpr = new ClassPathResource(filename);
// IO流
InputStream ioStream = cpr.getInputStream();
byte[] getData = readInputStream(ioStream);
ioStream.read(getData);
String content = new String(getData);
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
public static byte[] readInputStream(InputStream inputStream) throws IOException {
byte[] buffer = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
return bos.toByteArray();
}
private static String buildMessage(String appId, long timestamp, String nonceStr, String packageKey) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ packageKey + "\n";
}
}
调用JSAPI统一下单接口获取preparey_id
Map<String, Object> param2 = new LinkedHashMap<String, Object>();
// 应用ID
param2.put("appid", WeChatConfig.appID);
// 直连商户号
param2.put("mchid", WeChatConfig.mchId);
// 通知地址
param2.put("description", "xxxxx");
// 通知地址
param2.put("notify_url", WeChatConfig.notifyUrl);
// 商户订单号
param2.put("out_trade_no", uuid);
// 订单金额
Map<String,Object> moneyMap = new LinkedHashMap<String, Object>();
moneyMap.put("total",amounts); // 金额
param2.put("amount",moneyMap);
// 支付者
Map<String,Object> payerMap = new LinkedHashMap<String, Object>();
payerMap.put("openid",openid);
param2.put("payer",payerMap);
// 请求微信获取预支付信息
String json = JSON.toJSONString(param2);
HttpPost httpPost = new HttpPost(PayUtil.ORDER_PAY_JSAPI);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(json, StandardCharsets.UTF_8));
/*打开wx链接,配置请求头签名*/
CloseableHttpClient client = PayUtil.wxClient();
CloseableHttpResponse response = client.execute(httpPost);
/*返回数据*/
String bodyAsString = EntityUtils.toString(response.getEntity());
client.close();
/*请求状态code*/
int statusCode = response.getStatusLine().getStatusCode();
response.close();
if (statusCode == 200) {
JSONObject jsonObject = JSON.parseObject(bodyAsString);
/*获取主要信息*/
String preperId = jsonObject.getString("prepay_id");
// 返回参数示例
Map<String,Object> returnParams = new LinkedHashMap<>();
// 参数
String appId = WeChatConfig.appID;
Long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = PayUtil.getNonceStr();
// 返回前台参数
returnParams.put("package","prepay_id="+preperId);
returnParams.put("appId",appId);
returnParams.put("billId",bill.getId());
returnParams.put("timeStamp",timestamp);
returnParams.put("nonceStr",nonceStr);
returnParams.put("signType","RSA");
returnParams.put("paySign",PayUtil.sign(appId,timestamp,nonceStr,String.valueOf(returnParams.get("package"))));
// 返回请求参数
return AjaxResult.success(returnParams);
else{
return AjaxResult.error("请求微信获取预支付信息 不正确");
}
如果获得了正确的preparey_id,把所需的参数返回前端。前端根据参数调起支付收银台即可。我用的是uniapp。uniapp有封装。所以我前段是这么调用的。
// 仅作为示例,非真实参数信息。
uni.requestPayment({
provider: 'wxpay',
timeStamp: 返回的timeStamp,
nonceStr: '返回的nonceStr',
package: '返回的package',
signType: '返回的signType',
paySign: '',
success: function (res) {
console.log('success:' + JSON.stringify(res));
},
fail: function (err) {
console.log('fail:' + JSON.stringify(err));
}
});
完接撒花!!!!!!!!!!!!!!!!
有一个小坑的地方。如果出现下面的问题。是因为JRE本身中自带的“local_policy.jar ”和“US_export_policy.jar”只支持128位密钥的加密算法
解决办法:下载 Java Security(JCE)
下载地址:
链接: https://pan.baidu.com/s/1MP8i8TsOaXKMqBpdR89utw 提取码: 5nyg
使用方式
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
4.2 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件 (可以先备份原先的)
4.3 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件