微信小程序支付 JSAPI v3---下单、回调

微信小程序支付 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下单
微信小程序支付 JSAPI v3---下单、回调_第1张图片
@ 请求参数(必填)

{
	"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();
    }

@ 页面支付成功回调问题
微信小程序支付 JSAPI v3---下单、回调_第2张图片

5. notify_url,回调

@ 解决,

  1. 页面支付成功调用API列表-----查询订单,更新
  2. 生成预订单时提供的notify_url,微信后台判定订单支付成功,主动发起访问商户平台
  3. 采用定时任务,轮询商户订单未支付订单,调用查询订单,更新 (本文未提供)
    /**
     * 获取回调请求头:签名相关
     *
     * @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;
    }

微信小程序支付 JSAPI v3---下单、回调_第3张图片
@ 从图上所说,有点模糊,翻译过来:就是需要按照API列表中所示的顺序,排序才能正确验签。,但实际body中接收到jsonData的顺序不是如下图所示,因此需重新排序。
微信小程序支付 JSAPI v3---下单、回调_第4张图片
@最简陋的方式排序。

    /**
     * 请求报文:按官方接口示例键值 --- 排序(必须)
     * 官方文档: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);
    }

三、随笔而已

你可能感兴趣的:(小程序,小程序,微信小程序)