微信小程序完整实现微信支付功能(SpringBoot和小程序)

1.前言

不久前给公司实现支付功能,折腾了一阵子,终于实现了,微信支付对于小白来说真的很困难,特别是没有接触过企业级别开发的大学生更不用说,因此尝试写一篇我如何从小白实现微信小程序支付功能的吧,使用的后端是SpringBoot

2.准备工作

首先,要实现支付功能的条件:

(1)小程序是企业级别

(2)拥有微信支付商户号

(3)小程序绑定商户号

(4)拥有域名,并且有SSL证书(也就是HTTPS)

满足以上条件即可开始配置支付功能,这里我实现的是JSAPI支付(也就是小程序直接提供数字金额支付),还有Native支付(也就是弹出二维码进行扫码支付)

3.后端实现

先讲后端,因为后端需要准备的东西比较多,后端差不多就如下图三个类

不过要先准备如下东西,这些都需要去微信支付网页登录得到如下图登录,具体去看其他教程

申请证书,然后可以和我一样把证书放在项目的resources文件夹,如下

导入微信支付的pom.xml相关包依赖


        <!-- 微信支付坐标 start-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-pay</artifactId>
            <version>4.2.5.B</version>
        </dependency>
        <!-- 退款用 -->
        <dependency>
            <groupId>org.jodd</groupId>
            <artifactId>jodd-http</artifactId>
            <version>6.0.8</version>
        </dependency>
        <!-- 微信支付坐标 end-->

微信支付在yml文件的相关配置信息,没有的信息就登录商户号申请得到,接下来如果你是小白的话建议直接复制粘贴我的代码。

# 微信pay相关
wxpay:
  # appId
  appId: wx23d3df1350a9xxxx #小程序appId
  # 商户id
  mchId:  164919xxxx #商户Id
  # 商户秘钥
  mchKey: xxxxxxxxxxx #商户密钥,登录商户号自定义
  # p12证书文件的绝对路径或者以classpath:开头的类路径.
  keyPath: classpath:/wxpay_cert/apiclient_cert.p12 #证书路径,我放在项目resources目录下
  privateKeyPath: classpath:/wxpay_cert/apiclient_key.pem #这个也是和上面一样
  privateCertPath: classpath:/wxpay_cert/apiclient_cert.pem #这个也是一样
  # 微信支付的异步通知接口
  notifyUrl: https://www.xxxx.com/wechat/pay/notify #这个是回调函数就是前端要来访问支付的路由,可以自己写,域名写自己的
  # 退款回调地址
  refundNotifyUrl: https://www.xxxx.com/wechat/pay/refund_notify #退款的也一样

接下来就是获取上面配置信息的Java代码,WechatPayConfig类,注意这里变量名和yml文件的变量名要一样


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
    private String appId;
    private String mchId;
    private String mchKey;
    private String keyPath;
    private String privateKeyPath;
    private String privateCertPath;
    private String notifyUrl;
    private String refundNotifyUrl;

}

接下来就是人们说的创建支付统一订单,BizWechatPayServic类,直接复制粘贴


import com.example.mengchuangyuan.common.wechat.wxpay.config.WechatPayConfig;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;

import java.net.InetAddress;

/**
 * 微信支付
 */
@Slf4j
@Service
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WechatPayConfig.class)
@AllArgsConstructor
public class BizWechatPayService {

    private WechatPayConfig wechatPayConfig;

    public WxPayService wxPayService() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(wechatPayConfig.getAppId());
        payConfig.setMchId(wechatPayConfig.getMchId());
        payConfig.setMchKey(wechatPayConfig.getMchKey());
        payConfig.setKeyPath(wechatPayConfig.getKeyPath());
        payConfig.setPrivateKeyPath(wechatPayConfig.getPrivateKeyPath());
        payConfig.setPrivateCertPath(wechatPayConfig.getPrivateCertPath());
        // 可以指定是否使用沙箱环境
        payConfig.setUseSandboxEnv(false);
        payConfig.setSignType("MD5");

        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }

    /**
     * 创建微信支付订单
     *
     * @param productTitle 商品标题
     * @param outTradeNo   订单号
     * @param totalFee     总价
     * @param openId     openId
     * @return
     */
    public Object createOrder(String productTitle, String outTradeNo, Integer totalFee, String openId) {
        log.info(openId);
        log.info(wechatPayConfig.toString());
        try {
            WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
            // 支付描述
            request.setBody(productTitle);
            // 订单号
            request.setOutTradeNo(outTradeNo);
            // 请按照分填写
            request.setTotalFee(totalFee);
            // 小程序需要传入 openid
            request.setOpenid(openId);
            // 回调链接
            request.setNotifyUrl(wechatPayConfig.getNotifyUrl());
            // 终端IP.
            request.setSpbillCreateIp(InetAddress.getLocalHost().getHostAddress());
            // 设置类型为JSAPI
            request.setTradeType(WxPayConstants.TradeType.JSAPI);
            // 一定要用 createOrder 不然得自己做二次校验
            Object order = wxPayService().createOrder(request);
            return order;
        } catch (Exception e) {
            return "未知错误!";
        }

    }

    /**
     * 退款
     *
     * @param tradeNo 订单号
     * @param totalFee 总价
     * @return
     */
    public WxPayRefundResult refund(String tradeNo, Integer totalFee) {
        WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
        wxPayRefundRequest.setTransactionId(tradeNo);
        wxPayRefundRequest.setOutRefundNo(String.valueOf(System.currentTimeMillis()));
        wxPayRefundRequest.setTotalFee(totalFee);
        wxPayRefundRequest.setRefundFee(totalFee);
        wxPayRefundRequest.setNotifyUrl(wechatPayConfig.getRefundNotifyUrl());
        try {
            WxPayRefundResult refund = wxPayService().refundV2(wxPayRefundRequest);
            if (refund.getReturnCode().equals("SUCCESS") && refund.getResultCode().equals("SUCCESS")) {
                return refund;
            }

        } catch (WxPayException e) {
            e.printStackTrace();
        }
        return null;
    }
}

然后到提供前端调用支付路由的类,WechatController类,注意我这里路由拼接的有/wechat/pay/notify,这个要和之前配置yml文件的支付回调函数一样,要不然不行。这里也可以看到需要用户的openid,获取openid我就不多说了,有疑问的同学直接私信我或者评论区

package com.example.mengchuangyuan.common.wechat.wxpay.controller;

import com.example.mengchuangyuan.common.wechat.wxpay.service.BizWechatPayService;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/wechat/pay")
@AllArgsConstructor
public class WechatController {

    BizWechatPayService wechatPayService;

    private static Logger logger = LoggerFactory.getLogger(WechatController.class);

    /**
     * 创建微信订单
     *
     * @return 统一下单参数
     */
//    @GetMapping("/unified/request")
//    public Object appPayUnifiedRequest(@RequestParam("openid") String openid,@RequestParam("totalFee") Integer totalFee) {
//        log.info(openid);
//        // totalFee 必须要以分为单位
//
//        Object createOrderResult = wechatPayService.createOrder("报名支付", String.valueOf(System.currentTimeMillis()), totalFee*100, openid);
//        log.info("统一下单的生成的参数:{}", createOrderResult);
//        return createOrderResult;
//    }
    @GetMapping("/unified/request")
    public Object appPayUnifiedRequest(@RequestParam("openid") String openid,@RequestParam("totalFee") Integer totalFee) {
        log.info(openid);
        // totalFee 必须要以分为单位

        Object createOrderResult = wechatPayService.createOrder("报名支付", String.valueOf(System.currentTimeMillis()), totalFee*100, openid);
        log.info("统一下单的生成的参数:{}", createOrderResult);
        return createOrderResult;
    }

    @PostMapping("/notify")
    public String notify(@RequestBody String xmlData) {
        try {
            WxPayOrderNotifyResult result = wechatPayService.wxPayService().parseOrderNotifyResult(xmlData);
            // 支付返回信息
            if ("SUCCESS".equals(result.getReturnCode())) {
                // 可以实现自己的业务逻辑
                logger.info("来自微信支付的回调:{}", result);
            }

            return WxPayNotifyResponse.success("成功");
        } catch (WxPayException e) {
            logger.error(e.getMessage());
            return WxPayNotifyResponse.fail("失败");
        }
    }

    /**
     * 退款
     *
     * @param transaction_id
     */
    @PostMapping("/refund")
    public void refund(@RequestBody String transaction_id) {
        // totalFee 必须要以分为单位,退款的价格可以这里只做的全部退款
        WxPayRefundResult refund = wechatPayService.refund(transaction_id, 1);
        // 实现自己的逻辑
        logger.info("退款本地回调:{}", refund);
    }

    /**
     * 退款回调
     *
     * @param xmlData
     * @return
     */
    @PostMapping("/refund_notify")
    public String refundNotify(@RequestBody String xmlData) {
        // 实现自己的逻辑
        logger.info("退款远程回调:{}", xmlData);
        // 必须要返回 SUCCESS 不过有 WxPayNotifyResponse 给整合成了 xml了
        return WxPayNotifyResponse.success("成功");
    }

}

到此后端完成,说明一下,我只实现了支付功能,接下来前端没有退款功能

4.前端(小程序端)

前端相对简单,就是小程序给后端金额并且向后端发起支付请求弹出支付界面输入密码就OK,就一个方法。

goPay(e) {
        var that = this
        //用户openid,我之前缓存有的,这里根据你是如何获取用户openid
        var openid = app.globalData.userInfo.openid 
        sendRequest({
            url: "/wechat/pay/unified/request", //后端支付请求路由
            method: 'GET',
            data: {
                openid: openid,
                totalFee: that.data.money //支付金额,注意是以分为单位,要么在前端处理乘100,要么在后端,在我后端代码中,可以看出我选择的是后端处理
                // totalFee: 1

            },
        }).then(res => {
            // console.log(res);
            let obj = {  //这里的数据就是之前后端生成的统一订单
                appid: res.data.appId,
                noncestr: res.data.nonceStr,
                package: res.data.packageValue,
                // partnerid: res.data.partnerId,
                prepayid: res.data.prepayId,
                timestamp: res.data.timeStamp,
                signType: res.data.signType,
                paySign: res.data.paySign,
            };
            // console.log(obj);这里就是小程序弹出支付界面了
            wx.requestPayment({ //下面参数为必传
                provider: 'wxpay', //支付类型
                appId: obj.appid, //小程序Appid
                timeStamp: obj.timestamp, //创建订单时间戳
                nonceStr: obj.noncestr, // 随机字符串,长度为32个字符以下。
                package: obj.package, // 统一下单接口返回的 prepay_id 参数值,格式如“prepay_id=*”
                signType: obj.signType, // 加密方式统一'MD5'
                paySign: obj.paySign, // 后台支付签名返回
                success: function (res) {
                    // 支付成功后的回调函数, res.errMsg = 'requestPayment:ok'
                    // console.log(res);
                    if (res.errMsg === 'requestPayment:ok') {
                        that.signStart(e)
                    }
                },
                fail: function (res) {
                    // console.log(res);
                    // 支付失败或取消支付后的回调函数, res.errMsg = 'requestPayment:fail cancel' 取消支付;res.errMsg = 'requestPayment:fail (detail error message)'
                }
            })

        }).catch(err => {
            wx.showModal({
                content: "支付失败,请重试!",
                showCancel: false
            })
        })

    },

sendRequest函数包装在request.js如下

request.js代码内容如下

const baseUrl = 'https://www.xxxx.com'


const sendRequest = (params) => {
    //   console.log(params); 
    return new Promise((resolve, reject) => {
        wx.request({
            url: baseUrl + params.url,
            method: params.method || 'GET',
            header: {
            'Content-Type': 'application/json' // 设置请求的Content-Type
            },
            data: params.data || '',
            success: res => {
                // console.log(res);
                resolve(res)
            },
            fail: err => {
                reject(err)
            }
        })
    })
}

export default sendRequest

效果如下,这里因为我的手机不能截图支付页面,所以用的开发者工具支付的效果,都是一样的。

到此为止,微信小程序支付功能就完成了,看似简单,但是应该还有一些细节要处理,希望大家有耐心处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/183823.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

rabbitMQ发布确认-交换机不存在或者无法抵达队列的缓存处理

rabbitMQ在发送消息时&#xff0c;会出现交换机不存在&#xff08;交换机名字写错等消息&#xff09;&#xff0c;这种情况如何会退给生产者重新处理&#xff1f;【交换机层】 生产者发送消息时&#xff0c;消息未送达到指定的队列&#xff0c;如何消息回退&#xff1f; 核心&…

便携式工业RFID读写器怎么选?

便携式工业RFID读写器在物流、零售、制造等行业都有着极为广泛的应用。企业利用RFID手持终端设备&#xff0c;可以将采集到的物品信息自动传输到中央信息系统&#xff0c;实现数据的实时交换和共享。目前市面上RFID手持终端品牌、型号众多&#xff0c;ANDEAWELL作为国内物联网产…

数十亿美元商机!英国数字基础设施公司Equinix与法国量子计算公司Alice Bob 合作

​&#xff08;图片来源&#xff1a;网络&#xff09; 近日&#xff0c;全球数字基础设施公司Equinix宣布与全球领先的法国量子计算公司Alice & Bob合作&#xff0c;旨在共同开发市场上最为可靠的量子处理器之一。此次合作将使Equinix公司的客户通过使用Equinix Metal和Eq…

2023软件应用类下载系统平台源码/手机软件应用、新闻资讯下载站/软件库网站源码

源码简介&#xff1a; 这个是最新软件应用类平台源码、手机应用下载系统源码、软件应用市场下载站源码、新闻资讯软件下载。2023软件应用类平台源码/手机软件应用、新闻资讯下载站&#xff0c;它是软件库网站源码。 最新软件应用类平台源码 手机应用下载系统源码 软件应用市场…

Vue基础入门(二):Vue3的创建与分析

Vue3的创建 ​ vue3 是基于 es6 的一些新特性的支持而从 vue2 升级上来的版本&#xff0c;但是 vue3 是兼容 vue2 的。 一、Vue的使用 1.1 通过CDN使用Vue ​ 你可以借助 script 标签直接通过 CDN 来使用 Vue&#xff1a; <script src"https://unpkg.com/vue3/dist…

用友BIP与用友BIP对接集成销售出库列表查询连通销售出库单个保存((红字)销售出库审核-v)

用友BIP与用友BIP对接集成销售出库列表查询连通销售出库单个保存(&#xff08;红字&#xff09;销售出库审核-v) 源系统:用友BIP 面向数智化市场&#xff0c;用友倾力打造了全球领先的数智商业创新平台——用友BIP&#xff0c;定位为数智商业的应用级基础设施、企业服务产业的共…

2023-11-23 LeetCode每日一题(HTML 实体解析器)

2023-11-23每日一题 一、题目编号 1410. HTML 实体解析器二、题目链接 点击跳转到题目位置 三、题目描述 「HTML 实体解析器」 是一种特殊的解析器&#xff0c;它将 HTML 代码作为输入&#xff0c;并用字符本身替换掉所有这些特殊的字符实体。 HTML 里这些特殊字符和它们…

新苹果手机如何导入旧手机数据?解决方案来了,记得收藏!

为了保持其竞争优势&#xff0c;苹果公司不断推出新的产品和服务&#xff0c;因此苹果手机的更新换代速度是比较快的。正巧最近刚出了iPhone15&#xff0c;相信很多小伙伴已经换上了期待已久的新手机。 更换新手机后&#xff0c;大家都会面临一个问题&#xff1a;新苹果手机如…

深入了解接口测试:方法、工具和关键考虑因素(一)

接口测试是软件测试中的一项重要工作&#xff0c;它涉及到系统与系统之间的交互点。接口可以是外部接口&#xff0c;也可以是内部接口&#xff0c;包括上层服务与下层服务接口以及同级接口。在接口测试中&#xff0c;我们需要确保接口能够按照预期的方式进行通信和交互&#xf…

BMS实战: BMS产品介绍,电池外观分析,电芯种类分析,焊接方式分析,充电方式,电压平台,电芯型号分析。

快速入门的办法就是了解产品,了解现在市面上正在流通的成熟产品方案。光看基础知识是没有效果的。 首先我们找到了一张市面上正在出售的电池pack包。 图片来源网上,侵权删 电池外观分析 外壳: 一般是金属外壳,大部分都是铁壳加喷漆,特殊材质可以定制。 提手 一般是…

内衣专用洗衣机怎么样?口碑最好的小型洗衣机

随着人们的生活水平的提升&#xff0c;越来越多小伙伴来开始追求更高的生活水平&#xff0c;一些智能化的小家电就被发明出来&#xff0c;而且内衣洗衣机是其中一个。现在通过内衣裤感染到细菌真的是越来越多&#xff0c;所以我们对内衣裤的清洗频次会高于普通衣服&#xff0c;…

案例018:基于微信小程序的实习记录系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

iview table 默认排序字段不高亮解决办法

iview treeSelect 组件封装 1、表格增加排序时触发的方法2、定义三个变量&#xff0c;sortColumnDefaultStyle存放默认的样式&#xff0c;定义页面默认的列以及顺序3、显示的列加上 sortable, 和样式4、使用下面这块代表默认选中5、点击时清除掉默认的排序6、把排序的字段查询时…

为什么,word文件在只读模式下,仍然能编辑?

Word文档设置了只读模式&#xff0c;是可以编辑的&#xff0c;但是当我们进行保存的时候就会发现&#xff0c;word提示需要重命名并选择新路径才能够保存。 这种操作&#xff0c;即使可以编辑文字&#xff0c;但是原文件是不会受到影响的&#xff0c;编辑之后的word文件会保存到…

uniApp微信支付实现

后端&#xff1a;小程序下单 - 小程序支付 | 微信支付商户文档中心 服务端需要请求&#xff1a;https://api.mch.weixin.qq.com该地址获取微信支付Api接口需要的参数。 服务端请求接口需要的Body参数&#xff1a; 客户端&#xff08;前端&#xff09;需要调用&#xff1a;wx.…

22款奔驰S400L升级原厂360全景影像 高清环绕 无死角

360全景影像影像系统提升行车时的便利&#xff0c;不管是新手或是老司机都将是一个不错的配置&#xff0c;无论是在倒车&#xff0c;挪车以及拐弯转角的时候都能及时关注车辆所处的环境状况&#xff0c;避免盲区事故发生&#xff0c;提升行车出入安全性。 360全景影像包含&…

VINS-MONO代码解读----vins_estimator(重点部分)

1. 代码目录如下&#xff0c;重点和难点是factor部分&#xff0c;是关于IMU部分的&#xff0c;有较多关于IMU预积分公式的推导。 1. 条件变量con.wait读取测量值&#xff1a;getMeasurements() 读取buf中IMU和IMG的数据&#xff0c;并进行align&#xff0c;最后的结果是这样…

Linux-Ubuntu环境下搭建SVN服务器

Linux-Ubuntu环境下搭建SVN服务器 一、背景二、前置工作2.1确定IP地址保持不变2.2关闭防火墙 三、安装SVN服务器四、修改SVN服务器版本库目录五、调整SVN配置5.1查看需要修改的配置文件5.2修改svnserve.conf文件5.3修改passwd文件&#xff0c;添加账号和密码&#xff08;window…

【Unity】 UGUI的PhysicsRaycaster (物理射线检测)组件的介绍及使用

1. 什么是PhysicsRaycaster组件&#xff1f; PhysicsRaycaster是Unity UGUI中的一个组件&#xff0c;用于在UI元素上进行物理射线检测。它可以检测鼠标或触摸事件是否发生在UI元素上&#xff0c;并将事件传递给相应的UI元素。 2. PhysicsRaycaster的工作原理 PhysicsRaycast…

Java零基础——Linux篇

1.【熟悉】认识Linux 1.1 什么是操作系统 1.2 现实生活中的操作系统 1.2.1 Win10 1.2.2 Mac 1.2.3 Android(Linux) 1.2.4 iOS(Unix) 1.3 操作系统的发展史 1.3.1 Unix 1965年之前的时候&#xff0c;电脑并不像现在一样普遍&#xff0c;它可不是一般人能碰的起的&#xff0c…