jjzjj

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

「已注销」 2023-04-07 原文
微信小程序支付 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,回调

@ 解决,

  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;
    }


@ 从图上所说,有点模糊,翻译过来:就是需要按照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);
    }

三、随笔而已

有关微信小程序支付 JSAPI v3---下单、回调的更多相关文章

  1. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  2. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  3. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  4. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  5. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  6. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  7. ruby-on-rails - 将保存回调添加到单个 ActiveRecord 实例,可以吗? - 2

    是否可以为单个ActiveRecord实例添加回调?作为进一步的限制,这是继续使用库,所以我无法控制该类(除了对其进行猴子修补)。这或多或少是我想做的:defdo_something_creazymessage=Message.newmessage.on_save_call:do_even_more_crazy_stuffenddefdo_even_more_crazy_stuff(message)puts"Message#{message}hasbeensaved!Hallelujah!"end 最佳答案 你可以通过在创建对象后立

  8. ruby-on-rails - Ruby method_added 回调不触发包括模块 - 2

    我想写一点“Deprecate-It”库并经常使用“method_added”回调。但是现在我注意到在包含模块时不会触发此回调。是否有任何回调或变通方法,以便在某些内容包含到自身时通知类“Foobar”?用于演示的小Demo:#IncludingModulswon'ttriggermethod_addedcallbackmoduleInvisibleMethoddefinvisible"Youwon'tgetacallbackfromme"endendclassFoobardefself.method_added(m)puts"InstanceMethod:'#{m}'addedto'

  9. ruby-on-rails - 使用 before_save 回调或自定义验证器添加验证错误? - 2

    我有一个模型Listingbelongs_to:user。或者,Userhas_many:listings。每个列表都有一个对其进行分类的类别字段(狗、猫等)。User还有一个名为is_premium的bool字段。这是我验证类别的方式...validates_format_of:category,:with=>/(dogs|cats|birds|tigers|lions|rhinos)/,:message=>'isincorrect'假设我只想让高级用户能够添加老虎、狮子和犀牛。我该怎么做?最好在before_save方法中执行此操作吗?before_save:premium_che

  10. Ubuntu20.04系统WineHQ7.0安装微信 - 2

    提供3种Ubuntu系统安装微信的方法,在Ubuntu20.04上验证都ok。1.WineHQ7.0安装微信:ubuntu20.04安装最新版微信--可以支持微信最新版,但是适配的不是特别好;比如WeChartOCR.exe报错。2.原生微信安装:linux系统下的微信安装(ubuntu20.04)--微信适配的最好,反应最快,但是微信版本只到2.1.1,版本太老,很多功能都没有。3.深度deepin-wine6安装微信:ubuntu20.04+系统deepin-wine6安装新版微信--综合比较好,当前个人使用此种方法1个月,微信版本3.4;没什么大问题,尚可。一、WineHQ7.0安装微信

随机推荐