微信小程序支付 JSAPI v3—下单、回调
一、接入前准备
1. 微信支付文档中心
@ 接入准备: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
@ 小程序支付API列表: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_3.shtml
2. pom.xml加入依赖
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.3.0</version>
</dependency>
3. application.yml
#微信支付参数相关
wx:
pay:
app_id: #微信公众号或者小程序等的appid
mch_id: #微信支付商户号
Api_V3_Key: # api v3支付秘钥 #微信支付商户密钥
# subAppId: #服务商模式下的子商户公众账号ID
# subMchId: #服务商模式下的子商户号
private_key_path: # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
private_cert_path: # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
notify_url: # 支付成功回调
二、编码
1. 编写配置WxPayProperties读取配置
@Component
@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/** * 设置微信公众号或者小程序等的appid */
private String appId;
/** * 微信支付商户号 */
private String mchId;
/** * 微信支付商户密钥 */
private String apiV3Key;
/** * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除 */
private String subAppId;
/** * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除 */
private String subMchId;
/** * apiclient_key.pem文件的绝对路径,或者如果放在项目中,请以classpath:开头指定 */
private String privateKeyPath;
/** * apiclient_key.pem文件的绝对路径,或者如果放在项目中,请以classpath:开头指定 */
private String privateCertPath;
/** * 支付成功回调地址:v3版本,必须是https */
private String notifyUrl;
}
2. 初始化微信支付相关配置参数
/** * 微信支付配置 */
@Configuration
@ConditionalOnClass(WxPayService.class)
@AllArgsConstructor
public class AlarmWxPayConfig {
@Autowired
private WxPayProperties properties;
/** * 初始化微信支付相关配置参数 * @return */
@Bean
@ConditionalOnMissingBean
public WxPayService wxService() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiV3Key()));
// 可以指定是否使用沙箱环境
payConfig.setUseSandboxEnv(false);
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
3. 下单
@ JSAPI下单
@ 请求参数(必填)
{
"appid": "", //【应用ID】必填
"mchid": "", //【直连商户号】必填
"description ": "", //【商品描述】必填
"out_trade_no": "", //【商户订单号】必填
"notify_url ": "", //【通知地址】必填
"amount": {
//【订单金额】必填
"total": "", //【总金额】必填
},
"payer": {
//【支付者】
"openid": "" //【用户标识】必填
},
"detail": {
//【优惠功能】
"invoice_id": "",
"goods_detail":{
//【单品列表】
"merchant_goods_id": "",//【商户侧商品编码】必填
"quantity": "", //【商品数量】必填
"unit_price": "" //【商品单价】必填
}
},
"scene_info": {
//【场景信息】
"payer_client_ip": "", //【用户终端IP】必填
"store_info": {
//【商户门店信息】
"id": "", //【门店编号】必填
}
},
}
@ JSAPI 下单—-生成预支付交易单—-返回小程序调起支付API—–必要参数
/** * * JSAPI 下单----生成预支付交易单----返回小程序调起支付API-----必要参数 * * 填入必填项 * * @return */
public WxPayUnifiedOrderV3Result.JsapiResult prePay("传入业务数据,填充") {
WxPayUnifiedOrderV3Request v3Request = new WxPayUnifiedOrderV3Request();
ArrayList<WxPayUnifiedOrderV3Request.GoodsDetail> goodsDetails = new ArrayList<>();
goodsDetails.add(new WxPayUnifiedOrderV3Request.GoodsDetail() {
}
.setMerchantGoodsId("")
.setUnitPrice(0).setQuantity(0));
v3Request.setAppid("")
.setMchid("")
.setNotifyUrl("")
.setDescription("")
.setOutTradeNo("")
.setAmount(new WxPayUnifiedOrderV3Request.Amount() {
}.setTotal(0))
.setPayer(new WxPayUnifiedOrderV3Request.Payer() {
}.setOpenid(""))
.setDetail(new WxPayUnifiedOrderV3Request.Discount() {
}.setInvoiceId("").setGoodsDetails(goodsDetails))
.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo() {
}
.setStoreInfo(new WxPayUnifiedOrderV3Request.StoreInfo() {
}.setId("")).setPayerClientIp(""));
JsapiResult jsapiResult = null;
try {
jsapiResult = this.wxPayService.createOrderV3(TradeTypeEnum.valueOf("JSAPI"), v3Request);
} catch (WxPayException e) {
e.printStackTrace();
log.info("JSAPI 下单:{}",e.getLocalizedMessage());
}
return jsapiResult;
}
4. 页面支付成功,回调
@ 页面支付成功,查询微信后台订单状态,更新商户平台订单状态
/** * 微信查询订单,参数两者使用其一 * * @param orderNo 商户订单号 * @param transactionId 微信交易流水号 * @return Result */
public Result<?> wxQueryPay(String orderNo, String transactionId) {
WxPayOrderQueryV3Result v3Result = null;
try {
v3Result = this.wxPayService.queryOrderV3(transactionId, orderNo);
} catch (WxPayException e) {
log.error("查询微信后台订单失败:{}", e.getMessage());
return Result.restResult(6001, e.getMessage(), null);
}
// 根据业务需要,更新商户平台订单状态
if(v3Result.getPayState.equels("SUCCES")){
// 业务需求
}
return Result.ok();
}
@ 页面支付成功回调问题
5. notify_url,回调
@ 解决,
- 页面支付成功调用API列表—–查询订单,更新
- 生成预订单时提供的notify_url,微信后台判定订单支付成功,主动发起访问商户平台
采用定时任务,轮询商户订单未支付订单,调用查询订单,更新(本文未提供)
/** * 获取回调请求头:签名相关 * * @param request HttpServletRequest * @return SignatureHeader */
public SignatureHeader getRequestHeader(HttpServletRequest request) {
// 获取通知签名
String signature = request.getHeader("wechatpay-signature");
String nonce = request.getHeader("wechatpay-nonce");
String serial = request.getHeader("wechatpay-serial");
String timestamp = request.getHeader("wechatpay-timestamp");
SignatureHeader signatureHeader = new SignatureHeader();
signatureHeader.setSignature(signature);
signatureHeader.setNonce(nonce);
signatureHeader.setSerial(serial);
signatureHeader.setTimeStamp(timestamp);
return signatureHeader;
}
/** * 微信支付回调 * * @param jsonData String * @param request HttpServletRequest * @param response HttpServletResponse * @return JSONObject */
@PostMapping("/wxNotifyUrl")
public JSONObject wxNotifyUrl(@RequestBody String jsonData, HttpServletRequest request, HttpServletResponse response) {
JSONObject wxPayResult = new JSONObject();
if (lock.tryLock()) {
// 支付成功结果通知
OriginNotifyResponse notifyResponse = JSONUtil.toBean(jsonData, OriginNotifyResponse.class);
WxPayOrderNotifyV3Result v3Result = null;
try {
v3Result=wxPayService.parseOrderNotifyV3Result(this.jsonStrSort(notifyResponse),this.getRequestHeader(request));
//解密后的数据
WxPayOrderNotifyV3Result.DecryptNotifyResult result = v3Result.getResult();
// 注意:微信会通知多次,因此需判断此订单
LambdaQueryWrapper<WxPayOrder> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(wxPayOrder::getOutTradeNo, result.getOutTradeNo());
WxPayOrder wxPayOrder = this.wxPayOrderMapper.selectOne(queryWrapper);
// 0:未支付,1:已支付
if(wxPayOrder.getPayState == 0){
//根据业务需要,更新商户平台订单状态
}
//通知应答:接收成功:HTTP应答状态码需返回200或204,无需返回应答报文。
response.setStatus(HttpServletResponse.SC_OK);
return null;
} catch (WxPayException e) {
e.printStackTrace();
log.error("支付回调失败:{}", e.getLocalizedMessage());
// 通知应答:HTTP应答状态码需返回5XX或4XX,同时需返回应答报文
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
wxPayResult.putOpt("code", "FAIL");
wxPayResult.putOpt("message", "失败");
return wxPayResult;
} finally {
lock.unlock();
}
}
// 通知应答码:HTTP应答状态码需返回5XX或4XX,同时需返回应答报文
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
wxPayResult.putOpt("code", "FAIL");
wxPayResult.putOpt("message", "失败");
return wxPayResult;
}
@ 从图上所说,有点模糊,翻译过来:就是需要按照API列表中所示的顺序,排序才能正确验签。,但实际body中接收到jsonData的顺序不是如下图所示,因此需重新排序。
@最简陋的方式排序。
/** * 请求报文:按官方接口示例键值 --- 排序(必须) * 官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml, * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml * 《微信支付API v3签名验证》 中注意:应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。 * * @param originNotifyResponse OriginNotifyResponse * @return String */
private String jsonStrSort(OriginNotifyResponse originNotifyResponse) {
Map<String, Object> jsonSort = new LinkedHashMap<>();
jsonSort.put("id", originNotifyResponse.getId());
jsonSort.put("create_time", originNotifyResponse.getCreateTime());
jsonSort.put("resource_type", originNotifyResponse.getResourceType());
jsonSort.put("event_type", originNotifyResponse.getEventType());
jsonSort.put("summary", originNotifyResponse.getSummary());
Map<String, Object> resource = new LinkedHashMap();
resource.put("original_type", originNotifyResponse.getResource().getOriginalType());
resource.put("algorithm", originNotifyResponse.getResource().getAlgorithm());
resource.put("ciphertext", originNotifyResponse.getResource().getCiphertext());
resource.put("associated_data", originNotifyResponse.getResource().getAssociatedData());
resource.put("nonce", originNotifyResponse.getResource().getNonce());
jsonSort.put("resource", resource);
return JSONUtil.toJsonStr(jsonSort);
}
三、随笔而已
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/39335.html