一、🌈前沿
在淘宝、抖音、拼多多、京东等不同的电商平台快速发展的时期,可以说支付业务显得非常重要,站在支付新风口下,支付新模式必然出现,尤其是新零售时代,支付模式变革大势所趋,出现了线上和线下完全不同的支付模式。做商业项目必然要具备支付模块,而且随着新零售的普及,支付的方式也越来越多样。线上支付有小程序支付、APP支付、Native支付,JSAPI支付。线下的支付有付款码支付、收款码支付等等。作为一个合格新零售电商系统来说,必须要支持这么多种支付方式。
二、微信支付各模式介绍✔
1️⃣付款码支付
付款码支付是用户展示微信钱包的 s刷卡条码/二维码 给商户系统扫描后直接完成支付的模式,主要应用线下面对面的收银场景。
2️⃣Native支付
Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式,该模式使用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
3️⃣JSAPI支付
JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微支付提供的JSAPI接口调起微信支付模块完成支付,应该场景主要有:
- 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付。
- 用户的好友在朋友圈、聊天窗口等分享商家页面链接,用户点击链接打开商家页面,完成支付。
- 将商家页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付。
JSAPI支付接口使用的场景,用户必须是在 微信内置的浏览器里面 下单支付。作为电商网站来说,我们可以编写JavaScript代码,检测浏览器环境。如果用户下单的时候,JavaScript代码检测出网页是运行在微信里面,这时候电商网站就开始调用JSAPI接口,为用户提供支付功能。
4️⃣APP支付
APP支付又称移动端支付,是商户通过在移动端应用APP集中开放SDK调起微信支付模块完成支付的模式。
5️⃣H5支付
H5支付主要是在手机、ipad等移动设备中通过浏览器来唤醒微信支付的产品。用户在微信里边打开电商网站下单支付,要使用JSAPI支付接口。如果用户是在 手机内置的浏览器里 边打开电商网站,选好商品下单支付,这个时候呢,电商网站就得调用H5支付接口,为用户提供微信支付功能。
6️⃣小程序支付
小程序支付是专门被定义使用在小程序中的支付产品,目前小程序中能且仅能使用小程序支付来唤醒微信支付。
三、JSAPI支付介绍
JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,商户的支付场景是在微信内置浏览器打开调起支付完成收款。
四、JSAPI支付应用场景
- JSAPI支付适用于线下场所、公众号场景和PC网站场景支付。
- 商户已有H5商城网站,用户通过消息或者扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。具体操作流程如下:
如图2.1,商户下发图文消息或者通过自定义菜单吸引用户点击进入商户网页。
步骤二 如图2.2,进入商户网页,用户选择购买,完成选购流程。
步骤三 如图2.3,调起微信支付控件,用户开始输入支付密码。
图2.1 商户网页下单 | 图2.2 请求微信支付 | 图2.3 用户确认支付,输入密码 |
---|
步骤四 如图2.4,密码验证通过,支付成功。商户后台得到支付成功的通知。
步骤五 如图2.5,返回商户页面,显示购买成功。该页面由商户自定义。
步骤六 如图2.6,微信支付公众号下发支付凭证。
图2.4 用户支付成功提示 | 图2.5 返回商户页面 | 图2.6 用户收到微信通知 |
---|
五、业务流程图
重点步骤说明:
- 步骤3: 用户下单发起支付,商户可通过JSAPI下单创建支付订单。
- 步骤8:商户可在微信浏览器内通过JSAPI调起支付API调起微信支付,发起支付请求。
- 步骤15:用户支付成功后,商户可接收到微信支付支付结果通知支付结果通知API。
- 步骤20:商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。
六、接入前准备(V3版本)
1️⃣选择接入模式
商户/服务商在接入前首先需要判断自己公司注册区域适应的接入模式,微信支付目前提供两种接入方式:直连模式和服务商模式。
- 直连模式
直连模式是指商户自行开发系统来对接微信支付进行交易,微信支付将资金直接结算到商户的结算账户,商户给用户提供支付服务。该模式要求商户具备系统开发能力,商户可自行前往商户平台完成入驻。
该模式下数据流与资金流如图所示:
该模式下常用参数说明如下:
参数名称 | 参数说明 |
---|---|
AppID | 商户应用载体的AppID,可以是公众号,小程序或App |
mchid | 商户在微信侧申请入驻的收款账号 |
API v3密钥 | 商户在商户平台设置的API v3密钥,主要用于对敏感字段信息的加密或解密,具体设置流程请参考各产品接入前准备说明 |
商户API证书 | 商户在商户平台下载的证书,主要用于API请求的签名生成及验证,具体下载操作说明请参考各产品接入前准备说明 |
OpenId | 用户在直连商户应用下的用户标示 OpenId获取详见 |
所以一般我们有开发能力的公司系统需要接入微信支付基本上都是采用的是 直连模式。
- 服务商模式
服务商模式是指针对市面上一些中小型且没有开发能力的商户,由已在微信支付官方注册入驻的系统开发商或解决方案提供商协助这些商户完成入驻,开发及日常运营工作的模式。服务商可前往 服务商平台完成注册入驻。
该模式下数据流与资金流如图所示:
该模式下常用参数说明如下:
参数名称 | 参数说明 |
---|---|
AppID | 服务商应用载体的AppID,可以是公众号或小程序 |
mchid | 服务商在微信侧申请入驻的收款账号(注意:服务商收款账号并不具备收款能力) |
API v3密钥 | 服务商在服务商平台设置的API v3密钥,主要用于对敏感字段信息的加密或解密,具体设置流程请参考各产品接入前准备说明 |
证书 | 服务商在服务商平台下载的证书,主要用于API请求的签名生成及验证,具体下载操作说明请参考各产品接入前准备说明 |
OpenId | 用户在直连商户应用下的用户标示 |
sp_openid | 用户在服务商应用下的用户标示 |
sub_appid | 子商户应用载体的AppID,可以是公众号,小程序或App |
sub_mchid | 子商户在服务商下开通的微信支付收款账户 |
sub_openid | 用户在子商户sub_appid下的OpenId |
2️⃣参数申请
商户自行申请入驻微信支付,无服务商协助。(商户平台申请)成为直连商户
①申请APPID -> ②申请商户ID(mchId)->③绑定APPID及mchId
直连模式下,APPID与mchid之间的关系为多对多,即一个APPID下可以绑定多个mchid,而一个mchid也可以绑定多个APPID。
3️⃣配置API key
API v3密钥主要用于平台证书解密、回调信息解密。
4️⃣下载并配置商户证书
商户API证书具体使用说明可参见接口规则文档中私钥和证书章节
商户可登录微信商户平台,在【账户中心】->【API安全】目录下载证书。
以下为具体的下载步骤:
登录微信商户平台|账户中心|API安全|点击申请证书
在弹出窗口中点击“确定”。
在弹出窗口内点击“下载证书工具”按钮下载证书工具。
安装证书工具并打开,选择证书需要存储的路径后点击“申请证书”。
在证书工具中,将复制的商户信息粘贴并点击“下一步”。
获取请求串
生成证书串
- 步骤1 在【商户平台】-“复制证书串”环节,点击“复制证书串”按钮后;
- 步骤2 在【证书工具】-“复制请求串”环节,点击“下一步”按钮进入“粘贴证书串”环节;
- 步骤3 在【证书工具】-“粘贴证书串”环节,点击“粘贴”按钮后;
- 步骤4 点击“下一步”按钮,进入【证书工具】-“生成证书”环节
在【证书工具】-“生成证书”环节,已完成申请证书流程,点击“查看证书文件夹”,查看已生成的证书文件,即可完成证书下载。
5️⃣配置应用
设置支付授权目录
七、JSAPI开发指引(旧版本)
7.1 JSAPI调起支付
在微信浏览器里面打开H5网页中执行JS调起支付,网页端接口请求参数列表 (参数需要重新进行签名计算,参与签名的参数为:appId、timeStamp、nonceStr、package、signType,参数区分大小写。)
名称 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
公众号id | appId | 是 | String(16) | wx8888888888888888 | appId为当前服务商号绑定的appid |
时间戳 | timeStamp | 是 | String(32) | 1414561699 | 当前的时间,其他详见时间戳规则 |
随机字符串 | nonceStr | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
订单详情扩展字符串 | package | 是 | String(128) | prepay_id=123456789 | 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** |
签名方式 | signType | 是 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 |
签名 | paySign | 是 | String(64) | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名生成算法 |
7.2 商户系统和微信支付服务平台交互流程
- 商户server调用统一下单接口请求订单,api参见公共api【统一下单API】
- 商户server可通过【JSAPI调起支付API】调起微信支付,发起支付请求。
- 商户server接收支付通知,api参见公共api【支付结果通知API】
- 商户server查询支付结果,api参见公共api【查询订单API】(查单实现可参考:支付回调和查单实现指引)
7.3 接口API调用实现
1️⃣【服务端】JSAPI下单
说明:该下单操作步骤主要是在用户选择相关商户购买时,商户系统(可以理解为:后端服务)先调用该接口在微信支付服务后台生成预支付交易单。
代码示例:
WXPay wxPay = new WXPay(haloPaymentConfig);
Map<String, String> params = new HashMap<>();
params.put(WxConstant.BODY, "订单备注");
params.put(WxConstant.NONCE_STR, WXPayUtil.generateNonceStr()); //随机字符串
params.put(WxConstant.OUT_TRADE_NO, order.getCode()); //商户系统内部订单号(这里也就是订单编号)
params.put(WxConstant.TOTAL_FEE, amount); //订单总金额,单位为分 (字符串类型)
params.put(WxConstant.SPBILL_CREATE_IP, "127.0.0.1"); //终端IP
params.put(WxConstant.NOTIFY_URL, "http://127.0.0.1/app/pay/notify"); //异步接收微信支付结果通知的回调地址
params.put(WxConstant.TRADE_TYPE, TradeTypeEnum.JSAPI.getTradeType()); //交易类型
params.put(WxConstant.OPENID, user.getOpenId()); //用户授权唯一标识
//统一下单接口,商户系统需要先调用该接口在微信支付后台生成预支付交易单
Map<String, String> response = wxPay.unifiedOrder(params);
String flag = response.get("return_code");
String prepayId = response.get("prepay_id");
if (!WxConstant.FAIL.equals(flag) && StringUtils.isNotBlank(prepayId)) {
log.info("prepay_id: {}", prepayId);
// TODO 返回下单接口返回的信息给到客户端(前端)
}
重点参数说明:
参数 | 说明 |
---|---|
out_trade_no | 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
description | 商品描述 |
notify_url | 支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串 |
total | 商订单总金额,单位为分 |
openid | openid是微信用户在appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户。openid获取方式请参考以下文档小程序获取openid、公众号获取openid、APP获取openid |
2️⃣ JSAPI调起支付(客户端)
说明:第一步后端通过JSAPI下单API成功获取 预支付交易会话标识(prepay_id),前端拿到prepay_id参数后,需要通过JSAPI调起支付API来调起微信支付收银台。
此API需要将请求参数进行签名(参与签名的参数为:appId、timeStamp、nonceStr、package,参数区分大小写)
重要入参说明:
参数 | 说明 |
---|---|
package | JSAPI下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** |
signType | 该接口V3版本仅支持RSA |
paySign | 签名 |
paySign生成规则、响应详情及错误码请参见 JSAPI调起支付接口文档,一般的话paySign可以由服务端生成返回给前端直接使用。
服务端生成签名代码示例:
{
// 生成数字签名等信息返回给前端
Map<String, String> data = new HashMap<>(16);
String timeStamp = System.currentTimeMillis() + "";
String nonceStr = WXPayUtil.generateNonceStr();
data.put(WxConstant.APPID, appId); //appId
data.put(WxConstant.TIMESTAMP, timeStamp); //时间戳
data.put(WxConstant.NONCE_STR, WXPayUtil.generateNonceStr()); //随机字符串
data.put(WxConstant.PACKAGE, prepayId); //预支付交易会话标识
data.put(WxConstant.SIGN_TYPE, SignTypeEnum.MD5.getType()); //数字签名方式,默认为MD5
//生成数字签名
String paySign = WXPayUtil.generateSignature(data, mchKey);
//返回前端data信息
return Result.success(data);
}
3️⃣【服务端】接收支付结果通知
说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答。
注意:
- 微信支付回调通知的结果是xml的格式,开发者需要解析对应的xml(body),这个操作微信支付sdk已经有对应的类来实现xml解析。
- 商户系统接收到异步通知之后,需要返回对应的信息给到微信平台(也是xml格式)。
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<attach><![CDATA[支付测试]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
<time_end><![CDATA[20140903131540]]></time_end>
<total_fee>1</total_fee>
<coupon_fee><![CDATA[10]]></coupon_fee>
<coupon_count><![CDATA[1]]></coupon_count>
<coupon_type><![CDATA[CASH]]></coupon_type>
<coupon_id><![CDATA[10000]]></coupon_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
</xml>
返回参数
商户处理后同步返回给微信参数:
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
返回状态码 | return_code | 是 | String(16) | SUCCESS | SUCCESS/FAILSUCCESS表示商户接收通知成功并校验成功 |
返回信息 | return_msg | 否 | String(128) | OK | 返回信息,如非空,为错误原因:签名失败参数格式校验错误 |
响应举例如下:
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
- 支付结果通知是以
POST
方法访问商户设置的通知url,通知的数据以JSON
格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情。 - 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头
Wechatpay-Signature。
商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考 《微信支付API v3签名验证》。 - 支付通知http应答码为
200
或204
才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500
,或者4xx
- 商户成功接收到回调通知后应返回成功的http应答码为
200
或204
。 - 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
- 对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)。
👨🎓💬:当我们收到回到通知的时候,如果支付成功,我们可以在回调API(http://xxx/pay/notify)加上我们的业务逻辑,比如修改订单的支付状态等等。
异步回调代码示例
package io.halo.payment.modules.app.ctrl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import io.halo.payment.common.utils.R;
import io.halo.payment.constants.WxPayConst;
import io.halo.payment.github.wxpay.sdk.WXPayUtil;
import io.halo.payment.modules.app.model.HaloPaymentOrder;
import io.halo.payment.modules.app.service.HaloPaymentOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Writer;
import java.util.Map;
/**
* 订单
*
* @author: austin
* @since: 2022/12/30 22:09
*/
@Slf4j
@RestController
@RequestMapping("/app/pay")
@Api("订单业务相关接口")
public class HaloPaymentNoticeController {
@Autowired
private HaloPaymentOrderService haloPaymentOrderService;
@PostMapping(value = "/notice")
@ApiOperation(value = "订单支付回调")
public R notice(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setCharacterEncoding("UTF-8");
BufferedReader reader = request.getReader();
String line = reader.readLine();
StringBuffer temp = new StringBuffer();
while (StringUtils.isNotBlank(line)) {
temp.append(line);
line = reader.readLine();
}
reader.close();
Map<String, String> map = WXPayUtil.xmlToMap(temp.toString());
//提取响应码和响应信息
String returnCode = map.get("return_code");
String returnMsg = map.get("return_msg");
if (WxPayConst.SUCCESS.equals(returnCode) && WxPayConst.OK.equals(returnMsg)) {
// 获取订单流水号、订单ID,修改订单状态
String outTradeNo = map.get("out_trade_no");
UpdateWrapper<HaloPaymentOrder> wrapper = new UpdateWrapper<>();
wrapper.eq("code", outTradeNo);
wrapper.set("status", 2);
haloPaymentOrderService.update(wrapper);
//给微信平台一个响应
response.setCharacterEncoding("UTF-8");
response.setContentType("application/xml");
Writer writer = response.getWriter();
BufferedWriter bufferedWriter = new BufferedWriter(writer);
bufferedWriter.write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
bufferedWriter.close();
writer.close();
}
return R.ok("订单支付回调成功!");
}
}
八、JSAPI开发指引(API v3版本)
最新微信平台为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付APIv3接口。该版本API的具体规则请参考“APIv3接口规则”
之前,博主也有发布过一篇文章主要是针对《全新微信支付API v3》展开说明,有兴趣的可以了解一下。
开发者需要详细了解签名生成、签名验证、敏感信息加/解密、媒体文件上传等常用方法的具体代码实现,可阅读下面的详细说明:
以上便是该篇文章的所有内容,主要是讲解了微信支付的JSAPI支付的整体流程和代码案例实践,我是👨🎓austin流川枫,如文章对您有帮助,感谢点赞+关注+收藏,谢谢,我们下期见😉~