当前位置:网站首页>H5网站接入微信支付(H5支付+JSAPI支付)
H5网站接入微信支付(H5支付+JSAPI支付)
2022-07-21 09:14:00 【HackShendi】
Hello, I’m Shendi
最近开发 H5 项目,需要接入微信支付,这里记录一下
场景
项目是 H5 项目,这里踩坑了,以为接入 H5 支付就可以了,后面发现 H5 支付只能在微信外调用,所以后面连忙加入 JSAPI 支付
H5支付
H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。
说明:要求商户已有H5商城网站,并且已经过ICP备案,即可申请接入。
申请开通需要3-5天
JSAPI支付
JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,商户的支付场景是在微信内置浏览器打开调起支付完成收款。
好像默认就开通,开通好像是秒开
接入前准备
微信支付官方的文档已经比较详细了,可以先从指引文档,基础支付入手
https://pay.weixin.qq.com/wiki/doc/apiv3_partner/index.shtml
需要用到 appid,具体参考文档,有个公众号就可以了
然后就是关于商户的一些信息及证书(参考文档)
对于 JSAPI 接入,需要用到用户的 openid,所以需要在公众号内配置
公众号 -> 设置与开发 -> 公众号设置 -> 功能设置 -> 网页授权域名
JSAPI是在 js 内调起支付,所以这个地方域名填写前端域名就可以了(基本上配置域名配置前端域名就可以了),配置的时候要将txt文件放到前端服务器的根目录才可以确认
需要注意的是,目前这个域名配置只能配置两个,网上的解决办法是搭个反向代理服务器
选择 SDK
在微信官方文档的指引文档中的开发指引,选择对应的 SDK
我使用的 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.7</version>
</dependency>
初始化
首先需要加载商户私钥,平台证书,初始化 httpClient …
关于证书,大概有三个文件
- apiclient_cert.p12
- apiclient_cert.pem
- apiclient_key.pem
首先要安装证书,windows下直接双击 apiclient_cert.p12 即可,剩下的自己看需求操作,Linux自行百度
其中 apiclient_key.pem 是商户私钥
获取私钥的代码封装
/** * 获取私钥。 * * @param filename 私钥文件路径 (required) * @return 私钥对象 */
public static PrivateKey getPrivateKey(String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
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("无效的密钥格式");
}
}
初始化代码如下
/** 平台证书管理器 */
private static CertificatesManager certificatesManager;
/** 商户私钥 */
private static PrivateKey privateKey;
/** 如果你是使用Apache HttpClient的商户开发者,可以使用它构造HttpClient。得到的HttpClient在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。 */
private static CloseableHttpClient httpClient = null;
//--- 此处参数根据自己的内容赋值
/** 商户id */
private String mchId;
/** 商户序列号 */
private String mchSerialNo;
/** api_v3_key */
private String apiV3Key;
public static void init() {
// 加载商户私钥(privateKey:私钥字符串)
privateKey = getPrivateKey("apiclient_key.pem文件地址"));
// 获取证书管理器实例
certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, privateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(mchId))).build();
}
在项目启动的时候执行 init() 初始化就可以了
不同的地方
对于后端来说,H5支付和JSAPI支付不同的地方差不多只有下单部分
下单部分有些参数不一样,以及返回不一样,还有 url 不一样,以及前端调起支付的方式不一样
查询订单,退款,支付通知这些一模一样,可以通用
下单
H5支付或者JSAPI支付,第一步是下单,下单在指引文档中都有代码示例,这里不过多阐述。文档内使用的是字符串追加形式拼接的参数,我们可以改为JSONObject
H5 下单比较简单,服务端下单后,响应一串 url,将这串 url 给前端,让前端跳转到此 url 即可完成调用微信支付,付款
JSAPI 则需要先获取到用户 openid,然后带上 openid 下单
公司需求是两者都要有,所以对于前端,判断浏览器是否为微信内置浏览器代码如下
function isWeixin(){
return navigator.userAgent.indexOf("MicroMessenger")>0;
}
if (isWeixin()) {
// jsapi...
} else {
// h5...
}
是微信则调用 JSAPI下单
不是则h5下单
在下单完成,一般将订单存入数据库,状态为待付款即可
JSAPI 下单需要 openid 的获取方式
这里主要记录下 JSAPI 的 openid 获取方式
文档 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
公司有公众号,于是appid用的公众号的,还需要公众号的 secret
文档里面有以下几步
我们只需要获取 openid,所以只需要第一,第二步即可,第一步参考文档让用户跳转组装好的url就可以了,其中如果提示 redirect_uri 错误之类的则是公众号内未配置此域名
用户跳转后会跳到 redirect_uri,并携带参数 code
踩坑,回调后,code参数是在最外层框架上,即使在iframe内跳转,所以需要使用 parent.location 等去获取 code
获取到code第一步就完成了
第二步是前端拿着code去请求后端,后端要有个接口,内容大致如下
// https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
StringBuilder url = new StringBuilder();
url.append("https://api.weixin.qq.com/sns/oauth2/access_token")
.append("?appid=").append("公众号appid")
.append("&secret=").append("公众号secret")
.append("&code=").append("第一步获取到的code")
.append("&grant_type=").append("authorization_code");
HttpsURLConnection huc = null;
try {
huc = (HttpsURLConnection) new URL(url.toString()).openConnection();
huc.setRequestMethod("GET");
huc.setDoInput(true);
huc.setDoOutput(true);
// readAllBytes 是读取所有字节, Java9及以上才有
String data = new String(huc.getInputStream().readAllBytes());
JSONObject result = (JSONObject) JSONObject.parse(data);
if (isError(result)) {
// 有错误
} else {
// 拿到了 openid, 响应给用户即可
String openid = result.get("openid");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (huc != null) huc.disconnect();
}
其中判断结果是否有误的函数如下(可通用)
/** * 判断返回的数据是否为错误数据. * @param json 返回的数据 * @return true为错误数据,false为正确数据 */
public static boolean isError(JSONObject json) {
return json.getString("errcode") != null && !json.getString("errcode").equals("0");
}
前端拿到 openid 后调用下单接口即可
JSAPI下单
下单不复杂,但是前端调起需要的参数麻烦,这里就格外说一下
下单和h5差不多,响应一串字符串(h5是url),但是和h5不一样,不需要将这一串字符串响应给前端,而是需要自己组装参数发给前端
可以看JSAPI开发指引的这一部分
timeStamp 是当前时间戳(毫秒) / 1000 即可(秒)
nonceStr 是需要自己生成,随机字符串
… 具体参考文档,签名生成方式如下
// 签名
StringBuilder paySignBuild = new StringBuilder();
paySignBuild.append(appid).append("\n")
.append(timeStamp).append("\n")
.append(nonceStr).append("\n")
.append(packageStr).append("\n");
// 签名方式
Signature sign = Signature.getInstance("SHA256withRSA");
// 商户私钥
sign.initSign(privateKey);
sign.update(paySignBuild.toString().getBytes());
String paySign = Base64.getEncoder().encodeToString(sign.sign());
将这些信息都发给前端即可
前端调起
H5的话后端下单完,响应的是一个url,将url给前端,前端跳转即可
跳转完,不管支付成功与否,都会退回到当前页面,在有些设备内会导致刷新,所以需要自己保存状态
JSAPI 则是直接调起,支付完不会刷新页面,调起代码在开发指引文档上有,参数改成后端返回的参数即可
踩坑,WeixinJSBridge内置对象在其他浏览器中无效,在 iframe 内必须在最顶层框架才有此对象,例如 parent.WeixinJSBridge
参数中 timeStamp 必须是字符串类型,例如 timeStamp + “”,否则在 IOS 中会出错
支付完成后显示是否支付完成提示框,当用户点击我已完成支付就去查询订单状态即可
支付回调
下单有一个必须携带的参数 notify_url,当支付完成后,微信会请求此参数对应的接口,要确保这个接口不需要任何验证即可访问(必须https,且请求类型为POST)
我的需求只需要知道商户订单号即可
我直接用的 Servlet,代码如下,其中 PayUtil.getLog().log() 为日志,供参考
public class PayCallBackServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
byte[] data = req.getInputStream().readAllBytes();
if (data == null) {
PayUtil.getLog().log("支付回调,获取数据为空, ip=%s", IPUtil.get(req));
return;
}
String body = new String(data);
System.out.println(body);
try {
// 构建request,传入必要参数
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(PayUtil.getPlatformC())
.withNonce(req.getHeader("Wechatpay-Nonce"))
.withTimestamp(req.getHeader("Wechatpay-Timestamp"))
.withSignature(req.getHeader("Wechatpay-Signature"))
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(certificatesManager.getVerifier(mchId), apiV3Key.getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(request);
// 获取商户订单号,根据订单号查询结果
PayUtil.getLog().log("接收到支付回调内容, ip=%s, data=%s", IPUtil.get(req), notification.toString());
JSONObject dDataObj = JSONObject.parseObject(notification.getDecryptData());
String tradeId = dDataObj.getString("out_trade_no");
// tradeId 为商户id, 进行操作 ...
// 这里最好是再通过商户id去查询订单状态,状态和数据库内状态不一致在改变/完成,否则有可能造成资金损失
PayUtil.getLog().log("支付回调完成, trade=%s, ip=%s, info=%s", tradeId, IPUtil.get(req), result);
} catch (Exception e) {
e.printStackTrace();
PayUtil.getLog().log("处理回调通知出错: error=%s, ip=%s", e.getMessage(), IPUtil.get(req));
}
}
}
查询订单
分为通过微信订单号查询,和通过商户号查询
参考文档中的开发指引即可
查询订单,判断数据库内订单状态是否一致,不一致则再处理
关闭订单
同查询订单
参考文档中的开发指引即可
边栏推荐
- Number game: n people count off, those who report a multiple of 3 leave, and the rest continue
- The sum of the last three numbers
- MySQL之binlog用法及介绍
- R语言ggplot2可视化:ggplot2可视化分组箱图,将可视化图像的图例(legend)放置在图像底部居中、其中图例信息水平平铺 (position legend bottom center)
- Compare the market Taobao short video tools / software, and analyze the future trend of Taobao short video
- Sentinel使用代码实现流控熔断降级规则
- 童年的回忆小游戏来了——贪吃蛇,快来自己动手写一个属于自己的小游戏吧
- QT hodgepodge (total)
- Chapter 3 business function development (to display the main page of clues and query the data of each drop-down box of the form)
- Hcip day 10 notes
猜你喜欢
mac M1安装的mysql,开启binlog
Neural network plus attention mechanism, accuracy does not rise but fall?
Matlab simulation analysis of spectrum switching mechanism of cognitive radio based on relay assistance
This article enables you to master 22 neural network training skills
Pytorch common code snippet collection
weirdo A company's interview questions asked about toilet habits, eating time, sleeping time, etc
Gateway路由服务的创建
Sentinel容错规则持久化
07.02 Huffman code
【学术相关】陈天奇、王威廉等人推荐:ACL最佳论文奖得主给新入行研究者的一点建议...
随机推荐
解决Error L6218E Undefined symbol XXX....问题
浅谈牛顿迭代
07.02 哈夫曼编码
Communication excerpt from "happy when you smell defects" (this book can be downloaded for free)
R语言ggplot2可视化:ggplot2可视化分组箱图,将可视化图像的图例(legend)放置在图像底部居中、其中图例信息水平平铺 (position legend bottom center)
Microsoft will slow recruitment in security software and cloud business units
LeetCode 560和为 K 的子数组(有负数、一次遍历前缀和)、LeetCode 438找到字符串中所有字母异位词(优化滑动窗口)、LeetCode 141环形链表I(快慢指针)、142II
Gateway路由服务的创建
Pix2Seq:谷歌大脑提出 CV 任务统一接口!
【御芯微WIoTa自组网协议开发套件试用体验】rt-thread bsp软件包制作
基于三维GIS的场数据模型研究与实践
Rigid body of unity physical component
一文彻底理解BIO、NIO、AIO
PageHelper分页插件学习
Unity物理组件之刚体Rigidbody
如何使用订单流分析工具(上)
解决GD32F207串口可以接收但发送00的问题
PageHelper paging plug-in learning
浏览器本地存储webStroage
import torch_ geometric. NN error /lib64/libm so. 6: version `GLIBC_ 2.27‘ not found