jjzjj

微信小程序开通并对接微信支付教程及核心源码

青丝到无发 2023-10-25 原文

硬性条件

小程序必须为个体工商户或者企业账户,个人无法开通支付功能

小程序需要认证(300元认证费)

营业执照

准备工作

  1. 注册微信小程序。
  2.  注册商户号,后期用户支付的钱会自动进入商户号中,并在次日打入注册商户号时所用的银行卡中。

小程序与商户号绑定

小程序界面中申请开通微信支付(如下图,我这边已开通)

小程序绑定商户号

商户号API证书申请及APIv3密钥设置

 证书申请需要下载微信官方的工具,具体操作如下:

 

 

 

证书生成完成后会得到一个压缩文件。解压后如下:

 具体代码

依赖:

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.3.0</version>
        </dependency>
        <!--发送http请求-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

微信支付相关配置文件如下:(放在resources文件夹下)其中商户私钥文件即为商户号申请的API证书路径

 配置类,用于在springboot启动时加载配置文件中的内容

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址(支付结果通知地址)
    private String notifyDomain01;

    // APIv2密钥
//    private String partnerKey;

    /**
     * 获取商户的私钥文件
     *
     * @param filename
     * @return
     */
    public PrivateKey getPrivateKey(String filename) {
        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    /**
     * 获取签名验证器
     *
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {
        log.info("获取签名验证器");

        //获取商户私钥
        PrivateKey privateKey = this.getPrivateKey(privateKeyPath);

        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));

        return verifier;
    }


    /**
     * 获取http请求对象
     *
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
        log.info("获取httpClient");

        //获取商户私钥
        PrivateKey privateKey = this.getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient() {

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }

}

小程序拉起支付时调用的接口

 /**
     * 用户微信支付
     */
    @PostMapping("/userPay")
    public R userPay(@RequestBody UserPayDto userPayDto) {
        try {
            // JSAPI下单
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode rootNode = objectMapper.createObjectNode();
//            totalPrice = totalPrice.multiply(new BigDecimal(100));
            rootNode.put("mchid", wxPayConfig.getMchId())//商户id
                    .put("appid", wxPayConfig.getAppid())//appid
                    .put("description", "微信支付测试")//商品描述
                    .put("notify_url", wxPayConfig.getNotifyDomain01())//回调地址
                    .put("out_trade_no", );//本地系统订单号
            rootNode.putObject("amount")
                    .put("total",);//金额(单位:分)
            rootNode.putObject("payer")//付款者openid
                    .put("openid", userPayDto.getUserOpenId());
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            
            //调Api请求
            CloseableHttpResponse response2 = wxPayClient.execute(httpPost);
            String bodyAsString = EntityUtils.toString(response2.getEntity());
            
            String[] split = bodyAsString.split("\"");
            long startTs = System.currentTimeMillis();
            String s = Long.toString(startTs);
            String prepayId = "prepay_id=" + split[3];
            String key = wxPayConfig.getAppid() + "\n" + s + "\n" +
                    userPayDto.getPwd() + "\n"
                    + prepayId + "\n";
            String sign = sign(key, wxPayConfig.getPrivateKeyPath());
            return Objects.requireNonNull(Objects.requireNonNull(R.ok().put("prepayId", prepayId)).put("sign", sign)).put("timestamp", s).put("orderNo", orderNo);
        } catch (Exception e) {
            e.printStackTrace();
            return R.error();
        } finally {
            
            log.error("保存预订单信息完成。结果:{}", b);
        }

    }

    /**
     * 支付通知
     * 微信支付通过支付通知接口将用户支付成功消息通知给商户
     */
    @PostMapping("/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
        System.err.println(request);
        System.err.println(request.getRequestURL());
        Gson gson = new Gson();
        Map<String, String> resultMap = new HashMap<>();
        try {
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, Map.class);
            String requestId = (String) bodyMap.get("id");
            log.error("支付通知的id ===> {}", requestId);
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                    = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                log.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                resultMap.put("code", "ERROR");
                resultMap.put("message", "通知验签失败");
                return gson.toJson(resultMap);
            }
            log.error("通知验签成功");
            response.setStatus(200);
            //订单的业务逻辑处理
            wxPayService.processOrder(bodyMap);
            resultMap.put("code", "SUCCESS");
            resultMap.put("message", "成功");
            return gson.toJson(resultMap);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("通知验签失败");
            //失败应答
            response.setStatus(500);
            resultMap.put("code", "ERROR");
            resultMap.put("message", "失败");
            return gson.toJson(resultMap);
        }
    }

支付结果通知的业务处理方法

/**
     * 用户支付完成后的业务逻辑处理
     */
@Override
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.error("处理订单");
        String plainText = this.decryptFromResource(bodyMap);
        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        System.err.println("plainTextMap:" + plainTextMap);
        String orderNo = (String) plainTextMap.get("out_trade_no");
        log.error("验证" + orderNo + "支付");

        CtrlDeviceDto ctrlDeviceDto = new CtrlDeviceDto();
        R r = new R();
        QueryWrapper<WxOrderFlowEntity> wrapper = new QueryWrapper<WxOrderFlowEntity>().eq("order_num", orderNo);
        //若 trade_state 参数为 SUCCESS ,则控制设备出酒
        if ("SUCCESS".equals(plainTextMap.get("trade_state"))) {
            log.error("用户支付完成。单号:{}", orderNo);
        } else {
            log.error("trade_state 参数 非SUCCESS。单号:{}", orderNo);
        }
        
    }

/**
     * 对称解密
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("密文解密");
        // 通知数据
        Map<String, String> resourceMap = (Map<String, String>) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        String associatedData = resourceMap.get("associated_data");
        log.info("密文 ===> {}", ciphertext);

        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        log.info("明文 ===> {}", plainText);

        return plainText;
    }

小程序端代码

            wxPay() {
				var vm = this
				vm.loadModal = true
				var openId = uni.getStorageSync('userOpenId')
				var pwd = this.get32Pwd()
				wxPayRequest('/userPay', {
					userOpenId: openId,
					pwd: pwd,
					}, 'POST').then((res) => {
					vm.loadModal = false
					if (res.code === 0) {
						var that = this
						var prepayId = res.prepayId;
						var sign = res.sign;
						var timestamp = res.timestamp;
						wx.requestPayment({
							timeStamp: timestamp, //时间戳,自1970年以来的秒数
							nonceStr: pwd, //随机串
							package: prepayId,
							signType: "RSA", //微信签名方式:
							paySign: sign,
							success: function(res) {
								vm.loadModalMakeOrder = true
								console.log('用户付款完成');
							},
							fail: function(res) {},
							complete: function(res) {},
						});
					} else {
						uni.showToast({
							title: '创建订单失败',
							icon: 'error',
							duration: 2000
						});
					}
				}, (error) => {
					vm.loadModal = false
					console.log(error);
				})
			},
            //获取32位随机串
			get32Pwd() {
				var $chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
				var maxPos = $chars.length;
				var pwd = "";
				for (var i = 0; i < 32; i++) {
					pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
				}
				return pwd
			},

注意:微信支付结果的通知只能使用https协议接收,不可使用http,所以在测试的时候需要使用到内网穿透。推荐使用:ngrok

有问题欢迎评论区交流;转载请注明出处

有关微信小程序开通并对接微信支付教程及核心源码的更多相关文章

  1. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  2. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  3. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

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

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

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

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

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

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

  7. hadoop安装之保姆级教程(二)之YARN的配置 - 2

    1.1.1 YARN的介绍 为克服Hadoop1.0中HDFS和MapReduce存在的各种问题⽽提出的,针对Hadoop1.0中的MapReduce在扩展性和多框架⽀持⽅⾯的不⾜,提出了全新的资源管理框架YARN. ApacheYARN(YetanotherResourceNegotiator的缩写)是Hadoop集群的资源管理系统,负责为计算程序提供服务器计算资源,相当于⼀个分布式的操作系统平台,⽽MapReduce等计算程序则相当于运⾏于操作系统之上的应⽤程序。 YARN被引⼊Hadoop2,最初是为了改善MapReduce的实现,但是因为具有⾜够的通⽤性,同样可以⽀持其他的分布式计算模

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

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

  9. ruby - 在 RUBY 上的 PADRINO 框架上使用 RSPEC 进行测试的教程 - 2

    我是Ruby新手,并被要求在我们的新项目中使用它。我们还被要求使用Padrino(Sinatra)作为后端/框架。我们被要求使用Rspec进行测试。我一直在寻找可以指导在Padrino上使用RspecforRuby的教程。我得到的主要是引用RoR。但是,我需要RubyonPadrino。请在任何入门/指南/引用/讨论等方面指导我。如有不妥之处请指正。可能是我没有针对我的问题搜索正确的词/短语组合。我正在使用Ruby1.9.3和Padrinov.0.10.6。注意:我还提到了SOquestion,但它没有帮助。 最佳答案 我没用过Pa

  10. 区块链入门教程(6)--WeBASE-Front节点前置服务安装 - 2

    文章目录1.任务背景2.任务目标3.相关知识点4.任务实操4.1安装配置JDK4.2启动FISCOBCOS4.3下载解压WeBASE-Front4.4拷贝sdk证书文件4.5启动节点4.6访问节点4.7检查运行状态5.任务总结1.任务背景FISCOBCOS其实是有控制台管理工具,用来对区块链系统进行各种管理操作。但是对于初学者来说,还是可视化界面更友好,本节就来介绍WeBASE管理平台,这是一款微众银行开源的自研区块链中间件平台,可以降低区块链使用的门槛,大幅提高区块链应用的开发效率。微众银行是腾讯牵头设立的民营银行,在国内民营银行里还是比较出名的。微众银行参与FISCOBCOS生态建设,一定

随机推荐