目录
概述
本篇博客主要说明:从零开始使用SpringBoot和uni-app实现小程序的微信支付功能,后端服务部署到华为云云耀云服务器L实例上,文章末尾有完整的demo链接地址。
在我开始写这个demo之前,刚好遇到了华为云云服务器测评的活动,可免费领取云耀云服务器L实例30天。刚好可以用于我这一次后端的部署,并且可以帮忙测评一下这个服务器实例,如果大家感兴趣:活动地址。在最后我会写下关于使用此服务器的体验。
关于微信支付,我在始第一次写这个功能时,想要到网上找相关的文章来参考一下,但是找来找去其实感觉都不太全,总是漏了一些功能点没说清楚,也没有完整的demo可参考,官方的文档功能api说明的非常清楚,但是连续性还是差了点,这是对于我来说的。
因此,我决定写下这一篇博文,给大家介绍我从营业执照的申请开始到完成这个微信支付功能的过程,过程涉及到的东西比较多,如果您了解了这些过程只想看看demo,可以直接到文章末尾查看gitee地址,代码可用(20213-9-6)。
其实微信支付的流程并不复杂,大致为:
1.前端触发支付按钮2.调用后端提供的下单接口
3.后端向微信服务器发送请求创建一个预支付订单
4.后端获取下单返回的预支付订单号
5.封装预支付订单号在内的参数给前端
6.前端调用函数requestPayment唤起支付,输入密码完成支付。
博文和Demo说明
为了使大家更好的判断此文章对自己是否有用,避免浪费大家时间,在此说明博文内容和最后要实现的效果。
文章说明:
1.使用微信支付功能需要的前提。
2.商户号的注册
3.微信支付需要的定西说明(如商户号,api密匙,证书等)
4.后端的https协议说明
5.前后端的关键代码说明
6.测试
7.华为云耀云服务器L实例的使用体验
效果:
这是前端的页面,最后实现:当我们输入金额,点击【支付】后,可用输入密码实现支付功能。
后端写有查看支付订单和支付成功回调的逻辑接口,不在小程序中体现,如果您有需求,请到demo中查看。
营业执照说明
在第一次实现微信支付功能的时候,我不知道大家心里都在合计着什么,我说说我的所想:我想的比较多的不是关于代码的实现上面,而是各种认证,身份,商户这些东西。
因为我是个人开发者,并没有什么营业执照,所以我去百度查一查“主体为个人能使用微信支付功能吗?”,回答也很干脆 NO,好了不做了躺床睡觉~~那是不可能的。
所以,这个结果也很明显了, 我们要么去弄一个个体工商户要么是弄一个公司。
个体工商户的成本肯定更一些,可以线上申请并且申请下来不用花一毛钱。
至于线上申请的地址,每个地方的都不一样,可以自行百度一下"个体工商户在线申请"。
申请很快就下来了,我申请个工作室用了2天就申请下来了。
但是,请阁下想好了再申请。
营业执照作用(支付功能实现中)
1.申请可用支付功能的小程序
2.申请注册商户,获取商户号。
认证过的小程序(免去每年300认证费方案)
当我们有了营业执照后,我们也就摆脱了最大的限制,然后当我们开开心心的去注册小程序的时候,发现要微信认证,跟我们说" 300块拿来,小子!",或是去注册商户的时候,发现要填写认证过的小程序appid,就很痛苦。
然后心想,就想玩一玩微信支付,怎么就那么难,着认证的钱是不可能给的,国家给我发的营业执照还需要你300块认证?离谱!(破口大骂)好了不做了躺床睡觉~~那还是不可能的。
那么有没有方法能免去这每年的300块呢?那我告示你,是有的。
我们可以到"某宝"搜索"微信认证",ok新世界的大门打开了,这种方式认证可能会收你几块钱,但是这认证是永久的,不用每年都交300去认证,很香了吧。
那么这种方式安不安全,正不正规呢?
网上查的话,对于在这方式是安全的,大致是第三方服务商帮我们调用官方接口快速注册一个小程序,我们认证之后就可以使用小程序账号登录了,登录的小程序账号是认证过了的。这是我查到的不一定准确,如果您不放心的话可以交300去给微信认证一下子也是没问题的,此方式我已经验证过了可以用。
商户注册
在我们有了营业执照和一个认证过的微信小程序之后,那么我们就能够注册微信商户了,微信商户平台地址:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式 (qq.com)
注册过程就不演示了,填写一些表单信息而已,只要有认证过的小程序和营业执照那么就能够立即注册成功的,不需要等。
注册这个有什么用?
相信你在查看微信支付相关接口的时候,经常能看到一些词,例如:"商户号","证书","API密匙"等。那么这些东西就是只有成为微信商户之后,在商户平台去获取的。
还有,你的微信支付功能支付后的钱也是到这个商户里面去的,就是收钱的嘛。
获取支付支持参数(商户号,API密匙...)
我们注册成为了微信商户之后,我们就有了调用微信支付功能的能力了。
那么接下来我们就获取支付需要的一些参数然后就可以进行开发了。
商户号
APIv3密匙和API证书
证书的申请:证书的申请点击申请之后按照官方的提示照做就行,保存好发放的证书.pem文件和证书序列号。
证书序列号
(1)
(2)
至此,在商户平台获取的参数就ok,还有小程序的appid这就不多说了。
然后,就可以拿到这些参数去开发了。
后端开发
前置说明
在开始后端开发之前,我想要说明一些问题:
1.关于https协议,其实后端服务不用https协议目前是可以的(2023-9-7),但是官方还是推荐使用https协议,所以,如果您不知道后端如何开启https协议提供服务,那么我之前写过的一篇博客:http://t.csdn.cn/d306v 有相关的说明,很简单,下载证书部署一下就ok。
2.关于"支付成功的回调地址",什么是支付成功的回调呢?
就是当我们前端调用函数requestPayment唤起支付密码键盘,成功支付之后,那么微信服务器会往我们设置的这个"回调地址"去发送请求,此请求携带着支付成功的一些参数,比较重要的像"微信支付订单号"就在回调的请求里面。
所以,我们后端就需要写一个接口来接收支付成功回调中的参数。
注意:这个回调地址需要线上能够访问到,不然微信服务器就找不到你了,具体可以看:回调通知注意事项-最佳实践 | 微信支付商户平台文档中心 (qq.com)
前置准备
1.导入相关依赖
<!-- 微信支付SDK -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version>
</dependency>
<!-- 引入hutool,java的工具类库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.4</version>
</dependency>
2.部署API证书
将从商户平台获取的证书放到resources目录下。
目录可以自定义,代码中需要到工具类:WxPayJsApiHttpUtil中的getResourcesPath方法中修改一下就行。
3.修改配置文件,填写商户号等相关支付参数
application.yml文件中修改:
项目目录结构
1.constant:存放各种常量
2.controller:存放接口
3.dto:前后端数据联调对象R
4.model:VO和BO对象
5.工具类
主要功能代码
考虑到读者的阅读体验和阅读效率,在此我只展示两个接口(下单接口,回调接口)主要的功能代码。至于接口中方法之间的调用关系,请到完整demo中查看。
ps:完整demo中提供三个接口示例(下单接口,查单接口,支付成功回调接口)。
下单接口
接口主要作用:接收前端传入的下单金额,向微信服务器创建预支付订单并取回预支付订单号,封装预支付订单号,时间戳,随机字符串等参数后返回给前端,前端拿到封装好的参数对象调用函数:requestPayment 完成支付。
微信下单接口文档:JSAPI下单 - JSAPI支付 | 微信支付商户文档中心 (qq.com)
可以看看这个微信下单接口需要什么参数,再看下方代码会清楚很多。
业务层代码
/**
* 微信JS_API下单
*/
public WxPayJsApiPreVO jsApiV3(Integer total) throws Exception {
//请求URL
HttpPost httpPost = new HttpPost(WxPayJsApiConstant.JS_API_URL);
// 生成模拟系统内部订单号(yyyyMMddHHmmssSSS)
String out_trade_no = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN);
// 构造请求body参数
// 金额参数,WxPayJsApiAmountBO为自己的业务类
WxPayJsApiAmountBO amount = new WxPayJsApiAmountBO(); // 金额相关对象
amount.setTotal(total);
amount.setCurrency("CNY");
// 支付者参数,WxPayPayerBO为自己的业务类
WxPayPayerBO payer = new WxPayPayerBO();
payer.setOpenid("o_UuL6_KWNlz2Lve");
// 其他支付参数,WxPayJsApiOrderBO为自己的业务类
WxPayJsApiOrderBO request = new WxPayJsApiOrderBO();
request.setMchid(mchId); // 商户号
request.setOut_trade_no(out_trade_no); // 自己生成的订单号
request.setAttach("我的测试下单"); // 附件,不是必填参数
request.setAppid(appId); // 应用ID
request.setDescription("唔巴阁组件套餐"); // 商品的描述
request.setNotify_url(notifyUrl); // 支付成功回调的地址
request.setAmount(amount); // 金额对象
request.setPayer(payer); // 支付者信息对象
// 封装参数
StringEntity entity = new StringEntity(JSONUtil.toJsonStr(request), "utf-8");
entity.setContentType("application/json");
// 将请求参数和请求头设置上
httpPost.setEntity(entity);
httpPost.setHeader(WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT, WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT_VAL);
// 调用自定义方法,构建请求信息
CloseableHttpClient closeableHttpClient = initCloseableHttpClientV3();
// 完成请求,获取响应数据
CloseableHttpResponse response = closeableHttpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
// 关闭资源
response.close();
closeableHttpClient.close();
// 构造响应信息 (调用自定义方法,将预支付订单号传入,构造获取需要返回给前端的数据)-- 参数:prepay_id为预支付订单号
// 封装的方法convertWxPayJsApiPreInfo请到完整demo中查看
WxPayJsApiPreVO result = convertWxPayJsApiPreInfo((String) JSONUtil.parseObj(bodyAsString).get(WxPayJsApiOtherConstant.HTTP_OTHER_PREPAY_ID));
return result;
}
支付成功回调接口
/**
* 微信支付回调状态处理逻辑
*/
public String notifyV3(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 构建微信支付回调通知body数据(此处为加密数据),getVerifyNotifyBody为自定义方法
String body = getVerifyNotifyBody(request);
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
// 解密,获取解密后的数据,内涵微信支付订单号,verifyNotify为自定义方法
String plainText = verifyNotify(body, apiV3Key);
// 构建微信支付回调通知map数据
Map<String, String> map = new LinkedHashMap<>();
if (StrUtil.isNotEmpty(plainText)) {
response.setStatus(WxPayJsApiResponseConstant.RESPONSE_SUCCESS);
map.put(WxPayJsApiResponseConstant.RESPONSE_CODE, WxPayJsApiResponseConstant.RESPONSE_SUCCESS_MESSAGE);
map.put(WxPayJsApiResponseConstant.RESPONSE_MESSAGE, WxPayJsApiResponseConstant.RESPONSE_SUCCESS_MESSAGE);
logger.info("微信支付回调成功,处理成功 " + map);
} else {
response.setStatus(WxPayJsApiResponseConstant.RESPONSE_ERROR);
map.put(WxPayJsApiResponseConstant.RESPONSE_CODE, WxPayJsApiResponseConstant.RESPONSE_ERROR_MESSAGE);
map.put(WxPayJsApiResponseConstant.RESPONSE_MESSAGE, "签名错误");
logger.info("微信支付回调成功,处理失败 " + map);
}
// 构建响应信息
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
// 取出微信支付系统生成的订单号。
String wx_pay_num = (String) JSONUtil.parseObj(plainText).get("transaction_id");
// 后续处理,可以将此号存入数据库订单表记录中
return wx_pay_num;
}
ps:后端代码参考博文:http://t.csdn.cn/jHCG2 感谢这位老哥。
前端开发
前端做了一个简单的输入框和支付按钮,用于demo测试。
<template>
<view class="content">
<image class="logo" src="/static/wallet.png"></image>
<view class="my_input_box">
<input class="my_input" type="text" placeholder="请输入金额1" v-model="total">
</view>
<button class="my_button" @click="payHanle">支付</button>
</view>
<view class="info_box">
支付状态: <text style="font-weight: 800;">{{ payStatus }} </text>
</view>
</template>
<script setup>
import { ref,reactive } from "vue";
// 支付状态
let payStatus = ref("未支付");
// 支付金额
let total = ref(0.01);
// 支付处理器
function payHanle(){
// 调用后端接口,向微信服务器创建预支付订单
createPrePayOrder();
}
// 创建预支付订单
function createPrePayOrder(){
// 处理金额,前端单位为元,后端单位为分
let total_R = total.value * 100
uni.request({
url:`https://localhost/pay/preOrderCreate?total=${total_R}`,
success(res) {
// 获取后端返回的支付参数,调用自定义支付唤起函数
payActive(res.data.data);
}
})
}
// 唤起支付
function payActive(payParam){
console.log("唤起支付");
uni.requestPayment({
"nonceStr": payParam.wxNonceStr, // 随机字符串
"package": payParam.wxPackage, // 预支付订单号
"signType":payParam.wxSignType, // 签名类型
"timeStamp": payParam.wxTimeStamp, // 时间戳
"paySign": payParam.wxPaySign, // 签名,这里用的 RSA 签名
success(res) {
// 支付成功处理逻辑
console.log("支付成功!");
payStatus.value = "支付成功";
},
fail(e) {
// 失败回调
console.log("支付失败");
console.log(e);
}
})
}
</script>
<style>
.my_input_box{
display: flex;
flex-direction: row;
/* border: solid red 2px; */
}
.my_button{
background-color: #ff2658;
height: 41px;
width: 99px;
margin-top: 14px;
color: white;
}
.my_input{
border: solid #d22f7a 1px;
padding-left: 4px;
border-radius: 5px;
height: 26px;
line-height: 24px;
}
.info_box{
border: solid #2646ff 1px;
margin-top: 30px;
text-align: center;
font-size: 14px;
height: 25px;
line-height: 25px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
</style>
支付测试
我们的下单并支付的测试可以把后端服务跑在本地。
但是,如果你想要测试支付成功回调,那么你是一定需要把后端服务跑在服务器上的,不然微信服务器会找不到你填写的回调地址。
测试-下单并支付
我们点击【支付】按钮,由于是微信开发者工具上模拟,所以会出现支付二维码:
扫码-输入支付密码-支付成功-自定义修改事件触发
测试-回调接口的调用情况
打包部署:将我们的后端服务打包放到服务器上去跑,然后我们再重复支付一次,查看回调接口调用情况。
服务器上后端服务的运行
再次支付,查看服务器控制台
发现调用了我们的回调接口,成功的打印了微信支付单号,测试成功。
Demo的Gitee地址
WechatPayDemo: SpringBoot,uni-app环境,完整微信支付demo。 (gitee.com)
华为云耀云服务器使用体验
0.首先,我是领取了华为云耀云服务器L实例,使用CentOS7.6镜像,用于此项目demo的部署和线上测试,总体来说体验是很好的。如果价格合适会入手,就像通过这个评测活动进到华为云,看到新用户福利:竟然有香港地区的云服务器ecs,而且一年才99块钱,果断入手了(打广告嫌疑)。
1.关于控制台的界面,对比云服务器ecs的控制台界面相差也太多了。这云耀云服务器的界面风格看着太舒服了。给大家对比一下:
云服务器ECS界面
云耀云服务器界面
2.关于实例修改密码,我比较想吐槽一下
修改实例密码会提示需要到 远程登录页面 才能修改。
我看了这操作都是一样的,就是一个【操作确定】的模态框是个区别,其实在主页面这个【操作确定】模态框也是能弹出的吧。
目的是不是想让用户看到你们这个【远程登录】页面有变更优化呢?
因为现在我觉得大多数人都不太会使用官网提供的【远程登录】页面来操作服务器了吧。
个人拙见。
3.关于服务器的端口开放方面,最开始基本的80和443端口没有没开放,这很常用的端口我个人觉得如果默认开放这些常用的端口体验感会更顺畅,可能官方是为了安全。
4.我是需要部署一个java项目到云耀云服务器上的,所以需要java环境,安装的过程和环境变量的配置也很顺利。
5.还安装了nginx测试了一下,也是没什么大问题。
再次记录一下安装nginx需要安装的依赖(一键安装四个依赖环境)
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel** 以及需要重新执行./configure生成Makefile文件,然后再编译-安装。
最后启动,没问题。
总体来说体验是非常流畅的,华为云服务器印象分++
最后
回到微信支付功能,我认为比较需要"大处着眼 小处着手",也就是把微信支付整体的大致过程先记住,到了小的功能点再去花研究实现。有着全局思想开发起来思路也许会开放很多。
那么在复用一下博文开头,implements一下首尾呼应:
其实微信支付的流程并不复杂,大致为:
1.前端触发支付按钮2.调用后端提供的下单接口
3.后端向微信服务器发送请求创建一个预支付订单
4.后端获取下单返回的预支付订单号
5.封装预支付订单号在内的参数给前端
6.前端调用函数requestPayment唤起支付,输入密码完成支付。
最后,因为能力有限,如果博文有不足之处请各位读者指出,感谢。