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

news2024/12/23 15:51:00

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能医疗越发周到!新的机器人系统评估中风后的活动能力

原创 | 文 BFT机器人 中风是在医疗界上最难的解决的病例之一&#xff0c;全球每年有超过1500万人中风&#xff0c;四分之三的中风患者的手臂和手部会出现损伤、虚弱和瘫痪。 许多中风患者日常生活是依靠他们强壮的手臂来完成的&#xff0c;从拿一些小东西到梳头&#xff0c;即…

python 如何利用everything的能力快速搜索兴趣文档

演示代码 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/23 17:10 file: python 如何通过everything搜索兴趣文档.py desc: xxxxxx """# region 引入必要的依赖 import os模块名 DebugInfo try:from Debu…

一些好用的前端小插件(转自知乎)

一些好用的前端小插件&#xff08;2&#xff09; 1. cropper.js Cropper.js 2.0 是一系列用于图像裁剪的 Web 组件。 官网地址&#xff1a;https://fengyuanchen.github.io/cropperjs/v2/zh/ 2. Vditor Vditor是一款浏览器端的 Markdown 编辑器&#xff0c;支持所见即所得、…

【C++】标准模板库STL作业(其二)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

了解销售管理系统,看这篇就够了

在充满活力的现代商业环境中&#xff0c;高效的销售管理是成功的关键。 本文将全面介绍销售管理系统 及其对销售流程的变革性影响。 从潜在客户开发和机会管理到分析驱动的决策&#xff0c;销售管理系统旨在为销售团队提供提高生产力和绩效的工具。 本文分为销售管理系统的概念…

在springboot中实现WebSocket协议通信

前面介绍了使用netty实现websocket通信&#xff0c;有些时候&#xff0c;如果我们的服务并不复杂或者连接数并不高&#xff0c;单独搭建一个websocket服务端有些浪费资源&#xff0c;这时候我们就可以在web服务内提供简单的websocket连接支持。其实springboot已经支持了websock…

通俗理解词向量模型,预训练模型,Transfomer,Bert和GPT的发展脉络和如何实践

最近研究GPT&#xff0c;深入的从transfomer的原理和代码看来一下&#xff0c;现在把学习的资料和自己的理解整理一下。 这个文章写的很通俗易懂&#xff0c;把transformer的来龙去脉&#xff0c;还举例了很多不错的例子。 Transformer通俗笔记&#xff1a;从Word2Vec、Seq2S…

解决PDF预览时,电子签章、日期等不显示问题

文章目录 问题描述问题排查问题解决 问题描述 在预览PDF时&#xff0c;部分签章或控件没有显示。如下图&#xff1a; 正确应该要这样&#xff1a; 问题排查 根据网上搜索&#xff0c;排查&#xff0c;我先看看&#xff0c;pdf.worker.js 里的这三行代码&#xff0c;是否已经注…

MySQL-02-InnoDB存储引擎

实际的业务系统开发中&#xff0c;使用MySQL数据库&#xff0c;我们使用最多的当然是支持事务并发的InnoDB存储引擎的这种表结构&#xff0c;下面我们介绍下InnoDB存储引擎相关的知识点。 1-Innodb体系架构 InnoDB存储引擎有多个内存块&#xff0c;可以认为这些内存块组成了一…

error: ‘for‘ loop initial declarations are only allowed in C99 or C11 mode

在使用for循环时&#xff0c;在循环内定义变量&#xff0c;出现如下错误 [Error] ‘for’ loop initial declarations are only allowed in C99 or C11 mode [Note] use option -stdc99&#xff0c;-stdgnu99&#xff0c;-stdc11 or-stdgnu11 to compile your code 出现这个错误…

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

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

2015年8月19日 Go生态洞察:Go 1.5版本发布

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【C++】内存管理(new与delete)

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 本篇文章我们一起来学习C的内存管理方式&…

App 设计工具

目录 说明 打开 App 设计工具 示例 创建 App 创建自定义 UI 组件 打开现有 App 文件 打包和共享 App 本文主要讲述以交互方式创建 App。 说明 App 设计工具是一个交互式开发环境&#xff0c;用于设计 App 布局并对其行为进行编程。 可以使用 App 设计工具&#xff1a…

Leetcode:622. 设计循环队列 题解【具详细】

目录 一、题目&#xff1a; 二、思路详解&#xff1a; 1.循环队列的存储定义 2.循环队列的创建 3.循环队列的判空与判断情况 (1) 循环队列的判空: (2) 循环队列的判满 4.循环队列元素的插入 5.循环队列元素的删除 6.获取队头元素 7.获取队尾元素 8.循环队列释放 三…

手写工作流设计模式,针对常见的工作流步骤流转,减少过多的if/else,提升编程思维

需求 这一年下来&#xff0c;写两次工作流流转&#xff0c;总结下经验。 第一次写的时候&#xff0c;只找到用模版设计模式包裹一下&#xff0c;每个方法都做隔离&#xff0c;但是在具体分支实现的时候&#xff0c;if/else 满屏分&#xff0c;而且因为要针对不同情况&#xff…

【TensorRT部署】pytorch模型(pt/pth)转onnx,onnx转engine(tensorRT)

1. 单帧处理 1. pt2onnx import torch import numpy as np from parameters import get_parameters as get_parameters from models._model_builder import build_model TORCH_WEIGHT_PATH ./checkpoints/model.pth ONNX_MODEL_PATH ./checkpoints/model.onnx torch.set_de…

毛里塔尼亚市场开发攻略,收藏一篇就够了

毛里塔尼亚是非洲西北部的一个国家&#xff0c;也是中国长期援建的一个国家&#xff0c;也是一带一路上的国家。毛里塔尼亚生产生活资料依赖进口&#xff0c;长期依赖跟我们国家的贸易关系也是比较紧密的&#xff0c;今天就来给大家介绍一下毛里塔尼亚的市场开发公路。文章略长…

“关爱零距离.情暖老人心”主题活动

为提高社区老年人的生活质量&#xff0c;促进邻里间的互动与友谊&#xff0c;以及弘扬尊老爱幼的社区精神&#xff0c;11月21日山东省潍坊市金阳公益服务中心、重庆市潼南区同悦社会工作服务中心在潼南区桂林街道东风社区共同在潼南区桂林街道东风社区举办了“关爱零距离.情暖老…

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

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