统一下单
本节,主要来以分析小程序统一下单的案例,来说明sdk的使用方式,以及商户证书和平台证书的在调用下单接口时是如何使用的。当然,下单接口涉及的安全验证过程也会详细说明。
在这之前,我们来说说,统一下单的接口意义何在?为何不直接发起微信支付接口的调用,而在调用支付之前都要先调用统一下单接口。
纵观各类的支付api,都是要先调用统一下单的流程,那么其真正的意义大家有考虑过么?
1、中心化管理
中心化管理又称为统一管理,在整个支付系统中,需要面对很多个商户,通过统一下单接口,支付系统会记录所有商户、所有订单的记录。所有通过统一下单接口创建的订单数据都会被支付系统记录并保存。
此外,通过中心化管理,电商平台可定期或实时同步订单数据,确保自身系统的数据与微信支付系统的数据保持一致。这种同步机制有助于电商平台进行数据分析和财务对账。
再者,通过中心化管理,电商平台可以可以实时监控所有订单的状态,确保订单数据的一致性和准确性。
2、预处理与检查
在调用实际的支付API之前,统一下单接口允许开发者对订单进行预处理和检查。例如,开发者可以检查订单的金额、商品描述、用户身份等信息是否有效和正确。这有助于减少因错误或无效的订单信息导致的支付失败。
接口实现
通过上面的分析,我们知道,为什么要使用统一下单接口,下面来看看具体的接入及实现过程,并分析此api是如何封装签名及验签、加密、解密逻辑的。
在调用小程序下单接口之前,首先要创建订单数据,调用下单接口时将订单数据传过去,比如订单金额、商品id、订单编号等。微信收到订单后会返回预付订单id以及微信平台返回的微信订单信息,下面来看看具体的调用代码,下面是小程序下单的代码块。
if (config == null){
config = WxInitUtils.getInstance(wxPayConfig);
}
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
// 填充预下单参数
PrepayRequest request = new PrepayRequest();
//appid
request.setAppid(wxPayConfig.getAppid());
//商户id
request.setMchid(wxPayConfig.getMchId());
//产品描述
assert orderInfo != null;
request.setDescription(orderInfo.getOrderTitle());
//商户订单号
request.setOutTradeNo(orderInfo.getOrderNo());
//通知url
request.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
//金额信息
Amount amount = new Amount();
amount.setTotal(orderInfo.getTotalFee());
amount.setCurrency("CNY");
request.setAmount(amount);
//用户信息
Payer payer = new Payer();
payer.setOpenid(openId.toString());
request.setPayer(payer);
log.info("请求参数 ===> {}" + request.toString());
PrepayWithRequestPaymentResponse response = null;
try {
// response包含了调起支付所需的所有参数,可直接用于前端调起支付
response = service.prepayWithRequestPayment(request);
} catch (Exception e) {
log.error("请求下单失败,错误信息" + e.getMessage());
throw new RuntimeException("请求下单失败,错误信息" + e.getMessage());
}
//处理返回值
assert response != null;
String packageVal = response.getPackageVal();
String prepayId = packageVal;
上述代码中,首先拿到配置信息config,然后调用JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
这个是sdk提供的api,接下来组装好参数之间向微信发起请求,请求的代码段response = service.prepayWithRequestPayment(request);
在prepayWithRequestPayment方法中调用了String prepayId = this.jsapiService.prepay(request).getPrepayId(); 在prepay方法中才是真正的发起请求,重点来看下请求的代码段:
HttpRequest httpRequest = (new HttpRequest.Builder()).httpMethod(HttpMethod.POST).url(requestPath).headers(headers).body(this.createRequestBody(request)).build();
HttpResponse<PrepayResponse> httpResponse = this.httpClient.execute(httpRequest, PrepayResponse.class);
在execute方法中又调用了如下代码,和下载证书的接口调用是一样的。
HttpRequest innerRequest =
new Builder()
.url(httpRequest.getUrl())
.httpMethod(httpRequest.getHttpMethod())
.headers(httpRequest.getHeaders())
.addHeader(AUTHORIZATION, getAuthorization(httpRequest))
.addHeader(USER_AGENT, getUserAgent())
.body(httpRequest.getBody())
.build();
OriginalResponse originalResponse = innerExecute(innerRequest);
validateResponse(originalResponse);
重点来关注Authorization, 的组成如下:
Authorization: 认证类型 签名信息
具体组成为:
认证类型: 目前为WECHATPAY2-SHA256-RSA2048
签名信息
发起请求的商户(包括直连商户、服务商或渠道商)的商户号mchid
商户API证书序列号serial_no,用于声明所使用的证书
请求随机串nonce_str
时间戳timestamp
签名值signature
示例如下:
‘Authorization: WECHATPAY2-SHA256-RSA2048 mchid=”1900009191”,nonce_str=”593BEC0C930BF1AFEB40B4A08C8FB242”,signature=”uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==”,timestamp=”1554208460”,serial_no=”1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C”‘
以上就是签名的过程,使用商户私钥签名,签名后之间发送到了微信平台,所以小程序统一下单,并没有加密的操作,只有签名的操作。同样微信同步响应后,也需要在商户侧进行验签。
sdk中验签的代码段如下:
String timestamp = responseHeaders.getHeader(WECHAT_PAY_TIMESTAMP);
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes()
>= RESPONSE_EXPIRED_MINUTES) {
throw new IllegalArgumentException(
String.format(
"Validate http response,timestamp[%s] of httpResponse is expires, "
+ "request-id[%s]",
timestamp, responseHeaders.getHeader(REQUEST_ID)));
}
} catch (DateTimeException | NumberFormatException e) {
throw new IllegalArgumentException(
String.format(
"Validate http response,timestamp[%s] of httpResponse is invalid, request-id[%s]",
timestamp, responseHeaders.getHeader(REQUEST_ID)));
}
String message =
timestamp
+ "n"
+ responseHeaders.getHeader(WECHAT_PAY_NONCE)
+ "n"
+ (responseBody == null ? "" : responseBody)
+ "n";
logger.debug("Message for verifying signatures is[{}]", message);
String serialNumber = responseHeaders.getHeader(WECHAT_PAY_SERIAL);
logger.debug("SerialNumber for verifying signatures is[{}]", serialNumber);
String signature = responseHeaders.getHeader(WECHAT_PAY_SIGNATURE);
logger.debug("Signature for verifying signatures is[{}]", signature);
return verifier.verify(serialNumber, message, signature);
验证签名要经过以下几个步骤:
(1)校验微信响应的时间戳是否过期,有效时间为5分钟,// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes()
>= RESPONSE_EXPIRED_MINUTES)目的是防止重放攻击,[重放攻击]是指攻击者截取报文及其签名,并以恶意或欺诈目的重新传输数据的一种攻击手段,为了降低此类攻击的风险,微信支付在 HTTP头 Wechatpay-Timestamp 中提供了生成签名的时间戳。若微信支付需要重新发送某个通知回调,我们也会重新生成相应的时间戳和签名。在验证签名之前,商户系统应检查时间戳是否已过期。我们建议商户系统允许最多5分钟的时间偏差。如果时间戳与当前时间的偏差超过5分钟,您应拒绝处理当前的响应或回调通知。
构造验签名串由3个部分组成 ,应答或通知回调中获取以下信息:
HTTP 头 Wechatpay-Timestamp 中的应答时间戳
HTTP 头 Wechatpay-Nonce 中的应答随机串
应答报文主体(Response Body),请使用原始报文主体执行验签。如果您使用了某个框架,要确保它不会篡改报文主体。对报文主体的任何篡改都会导致验证失败。
检查 HTTP 头 Wechatpay-Serial 的内容是否跟商户当前所持有的微信支付平台证书的序列号一致。若不一致,请重新获取证书。否则,签名的私钥和证书不匹配,将验证失败。重点代码如下:根据头部返回的证书序列号查询下载时存放的证书是否一致。
/**
* 根据证书序列号获取证书
*
* @param serialNumber 微信支付平台证书序列号
* @return X.509证书实例
*/
@Override
public X509Certificate getCertificate(String serialNumber) {
return AutoCertificateService.getCertificate(merchantId, ALGORITHM_TYPE, serialNumber);
}
原文始发于微信公众号(小核桃编程):统一下单接口意义何在?透过小程序统一下单接口看微信支付API的本质。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/215986.html