SpringBoot + 微信支付"/>
02、SpringBoot + 微信支付
目录
- SpringBoot + 微信支付 -->基础支付API V3 -->网页弹出二维码支付功能 并能 真实支付成功
- 基础支付API V3
- 1、引入支付参数
- 参数解释
- 1-1、商户号:
- 1-2、商户API证书序列号:
- 1-3、商户私钥文件
- 1-4、APIv3密钥
- 1-5、APPID
- 1-6、微信服务器地址
- 1-7、接收结果通知地址
- 2、读取支付参数
- 2-1、测试支付参数的获取
- 3、配置 Annotation Processor
- 3-1、原因:
- 3-2、方法:
- 4、加载商户私钥
- 4-1、复制商户私钥
- 4-2、引入SDK
- 4-3、获取商户私钥
- 4-4、测试商户私钥的获取
- 5、获取签名验证器和HttpClient
- 5-1、APIv3证书与密钥使用说明
- 5-2、获取签名验证器
- 5-3、获取HttpClient对象
- 5-4、WxPayConfig 代码(签名和验签)
- 6、API字典和相关工具
- 6-1、API 列表
- 6-2、接口规则
- 6-3、定义枚举
- 6-4、添加工具类
- 7、调用Native下单API
- 7-1、需求:
- 7-2、native 支付流程图:
- 7-3、Native下单
- 7-3-1、需求流程分析:
- 7-3-2、代码:
- 7-3-2-1、接口相关数据
- 7-3-2-2、controller
- 7-3-2-3、Service
- 7-3-2-4、Impl
- 7-3-2-5、测试结果:
- 7-3-2-6、成功真实支付
- 7-2-3、完整代码
- WxPayController
- WxPayService
- WxPayServiceImpl
SpringBoot + 微信支付 -->基础支付API V3 -->网页弹出二维码支付功能 并能 真实支付成功
需求:
如图,实现流程:选择一个课程,点击支付,然后弹出一个支付二维码,用户扫描二维码进行支付。
基础支付API V3
1、引入支付参数
这些支付参数是视频制作者提供的
这是个配置文件
如图:这个 wxpay.private-key-path=apiclient_key.pem 是商户私钥文件路径,可以通过这个路径获取私钥文件
参数解释
1-1、商户号:
就是商家的账号
属于学习视频的截图:
1-2、商户API证书序列号:
在商户平台申请的API证书,有私钥和证书,证书里面封装了公钥
1-3、商户私钥文件
商户的私钥文件加载到应用程序当中的目的主要是为了做签名,用私钥将请求进行签名,然后发请求的信息发送给微信的服务器端,微信的服务器端就会根据发来的请求中的 商户API证书的序列号 这个参数找到对应的证书,然后再从这个证书中解密出我们的公钥,然后用这个公钥对这个发来的加密的请求进行验签。
(公钥就存放在加密了的证书中)
这就是一个请求发送和接收的过程,对应的是一个签名和验签的过程。
签名:就是将请求加密
验签:就是用来判断这个请求有没有被篡改过,验签通过就是没被篡改过
现在这个是商户私钥的路径,通过这个路径获取商户私钥文件
1-4、APIv3密钥
这个密钥是一个对称加密的密钥
1-5、APPID
在申请商户号的时候,同时申请的微信公众号,这个就是微信公众号的id
1-6、微信服务器地址
远程向这个地址发起调用
向微信发起请求
1-7、接收结果通知地址
微信向商户端发起请求
每个人的地址都是不一样的,记得修改
内网穿透用的
2、读取支付参数
创建一个配置文件来读取支付参数配置文件里面的数据
这个方法可以读取到配置文件的值。
也可以通过这种方式来获取配置文件中的属性值,这个是其他的代码,仅作记录
2-1、测试支付参数的获取
能成功拿到配置文件的里面的属性值
3、配置 Annotation Processor
可以帮助我们生成自定义配置的元数据信息,让配置文件和Java代码之间的对应参数可以自动定位,方
便开发。
3-1、原因:
如图,在idea 中,没有把这个wxpay 看成是一个spring的配置文件,虽然不影响程序运行,但是却少了很多功能,比如自动定位,就是点击配置文件的属性名,能自动定位到 WxPayConfig 配置文件的对应字段去。
3-2、方法:
实现配置文件能够自动定位的操作
4、加载商户私钥
4-1、复制商户私钥
把商户私钥复制到项目根目录下:
4-2、引入SDK
.shtml
我们可以使用官方提供的 SDK,帮助我们完成开发。实现了请求签名的生成和应答签名的验证
微信支付的SDK 依赖
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-apache-httpclient</artifactId><version>0.3.0</version>
</dependency>
4-3、获取商户私钥
因为我们的私钥是存在一个文件的(apiclient_key.pem),所以用第一个示例方法
在 WxPayConfig 中写一个获取商户私钥文件的方法
4-4、测试商户私钥的获取
在 PaymentDemoApplicationTests 测试类中添加如下方法,测试私钥对象是否能够获取出来。
(将前面的方法改成public的再进行测试)
在测试类里面测试
5、获取签名验证器和HttpClient
5-1、APIv3证书与密钥使用说明
.shtml
5-2、获取签名验证器
(定时更新平台证书功能)
平台证书:平台证书封装了微信的公钥,商户可以使用平台证书中的公钥进行验签。
签名验证器:帮助我们进行验签工作,我们单独将它定义出来,方便后面的开发。
下图是学习视频的访问该网址的代码,但是现在打开该网址的代码已经改变了。
5-3、获取HttpClient对象
(定时更新平台证书功能)
HttpClient 对象:是建立远程连接的基础,我们通过SDK创建这个对象
**HttpClient对象作用: **通过 WechatPayHttpClientBuilder 构造的 HttpClient,会自动的处理签名和验签,并进行证书自动更新。
5-4、WxPayConfig 代码(签名和验签)
package cn.ljh.paymentdemo.config;import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
public class WxPayConfig
{// 商户号private String mchId;// 商户API证书序列号private String mchSerialNo;// 商户私钥文件的路径private String privateKeyPath;// APIv3密钥private String apiV3Key;// APPIDprivate String appid;// 微信服务器地址private String domain;// 接收结果通知地址private String notifyDomain;/*** 获取商户的私钥文件** @param fileName 私钥文件的路径* @return PrivateKey*/public PrivateKey getPrivateKey(String fileName){try{return PemUtil.loadPrivateKey(new FileInputStream(fileName));} catch (FileNotFoundException e){throw new RuntimeException("私钥文件不存在", e);}}/*** 获取签名验证器** @return 签名验证器*/@Beanpublic ScheduledUpdateCertificatesVerifier getVerifer(){//获取商户私钥PrivateKey privateKey = 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;}/*** 获取HttpClient 请求对象:是建立远程连接的基础,我们通过SDK创建这个对象* 通过 WechatPayHttpClientBuilder 构造的 HttpClient,会自动的处理签名和验签,并进行证书自动更新** @param verifier 签名验证器* @return HttpClient 请求对象,会自动的处理签名和验签,并进行证书自动更新*/@Beanpublic CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);//用于构造HttpClientWechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey).withValidator(new WechatPay2Validator(verifier));// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient// 通过 WechatPayHttpClientBuilder 构造的 HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient httpClient = builder.build();return httpClient;}}
6、API字典和相关工具
6-1、API 列表
.html
我们的项目中要实现以下所有API的功能。
.shtml
Native支付API列表
6-2、接口规则
.shtml
微信支付 APIv3 使用 JSON 作为消息体的数据交换格式。
如上图,因为使用JSON作为数据交互的格式,不再使用XML , 所以在项目里面添加这个json处理的依赖
6-3、定义枚举
为了开发方便,我们预先在项目中定义一些枚举。枚举中定义的内容包括接口地址,支付状态等信息。
就是我们去调用支付的一些 商户订单号查询订单、关闭订单、退款申请 的接口,这些都是微信支付平台提供的接口,因为会经常调用,所以把这些接口地址、状态等直接封装成枚举,方便调用。
6-4、添加工具类
将资料文件夹中的 util 目录复制到源码目录中,我们将会使用这些辅助工具简化项目的开发
7、调用Native下单API
7-1、需求:
如图,选择一个课程,点击支付,然后弹出一个支付二维码,用户扫描二维码进行支付。
7-2、native 支付流程图:
完整流程
.shtml
7-3、Native下单
7-3-1、需求流程分析:
生成订单–>调用统一下单的API,生成支付二维码–>生成预支付交易–>返回预支付交易链接
下单的接口说明
.html
7-3-2、代码:
7-3-2-1、接口相关数据
由官方指定的一些要求和示例
【服务端】Native下单 的官方示例代码
.shtml
native下单的接口说明
.html
要封装哪些请求参数,在这里看
7-3-2-2、controller
7-3-2-3、Service
7-3-2-4、Impl
创建订单,调用native支付接口
7-3-2-5、测试结果:
7-3-2-6、成功真实支付
7-2-3、完整代码
一些配置文件、枚举类就没列出来了,太多了
WxPayController
package cn.ljh.paymentdemo.controller;import cn.ljh.paymentdemo.service.WxPayService;
import cn.ljh.paymentdemo.vo.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.Map;@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{@Resourceprivate WxPayService wxPayService;//调用统一下单API,生成支付二维码的链接和订单号//swagger注解@ApiOperation("调用统一下单API,生成支付二维码")@PostMapping("/native/{productId}")public R nativePay(@PathVariable Long productId) throws Exception{log.info("发起支付请求");//返回支付二维码的链接和订单号Map<String,Object> map = wxPayService.nativePay(productId);return R.ok().setData(map);}}
WxPayService
package cn.ljh.paymentdemo.service;import java.util.Map;public interface WxPayService
{//调用统一下单API,生成支付二维码的链接和订单号Map<String, Object> nativePay(Long productId) throws Exception;}
WxPayServiceImpl
package cn.ljh.paymentdemo.service.impl;import cn.ljh.paymentdemo.config.WxPayConfig;
import cn.ljh.paymentdemo.entity.OrderInfo;
import cn.ljh.paymentdemo.enums.OrderStatus;
import cn.ljh.paymentdemo.enums.wxpay.WxApiType;
import cn.ljh.paymentdemo.enums.wxpay.WxNotifyType;
import cn.ljh.paymentdemo.service.WxPayService;
import cn.ljh.paymentdemo.util.OrderNoUtils;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;//创建订单,调用 Native 支付接口
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService
{@Resourceprivate WxPayConfig wxPayConfig;/* 原本应该注 入WxPayConfig 这个类,然后调用 getWxPayClient() 方法获取 HttpClient请求对象* 但是因为 getWxPayClient() 方法加了@Bean注解,交给了spring容器管理,所以项目启动的时候就会执行这个方法,* 就会存在返回值为 CloseableHttpClient 类型的 HttpClient请求对象* 所以这里可以直接注入这个 CloseableHttpClient 对象*/@Resourceprivate CloseableHttpClient wxPayClient;/*** 创建订单,调用 Native 支付接口** @param productId 商品id* @return code_url 和 订单号* @throws Exception*///调用统一下单API,生成支付二维码的链接和订单号@Overridepublic Map<String, Object> nativePay(Long productId) throws Exception{log.info("生成订单.....");//生成订单OrderInfo orderInfo = new OrderInfo();orderInfo.setTitle("test"); //订单标题orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //生成订单号orderInfo.setProductId(productId); //商品idorderInfo.setTotalFee(1); //单位是:分orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType()); //支付状态//TODO: 需要将这个订单存到数据库/** 官方提供的 Native下单 接口* 支持商户:【普通商户】* 请求方式:【POST】/v3/pay/transactions/native* 请求域名:【主域名】* "" 改成* wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType())*/log.info("调用统一下单API.....");//调用统一下单API---拷贝官网的实例代码进行修改---统一下单的接口地址HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));// 请求body参数---------调用接口需要的参数Gson gson = new Gson();//数据类型不固定,所以就不写泛型了Map paramsMap = new HashMap();//设置参数 --- 根据官网要求设置对应的参数paramsMap.put("appid", wxPayConfig.getAppid()); //公众号IDparamsMap.put("mchid", wxPayConfig.getMchId()); //直连商户号paramsMap.put("description", orderInfo.getTitle()); // 商品描述paramsMap.put("out_trade_no", orderInfo.getOrderNo()); //商户订单号paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType())); //通知地址//订单金额有两个参数--嵌套数据Map amountMap = new HashMap();amountMap.put("total", orderInfo.getTotalFee()); //总金额amountMap.put("currency", "CNY"); //货币类型paramsMap.put("amount", amountMap); // 订单金额//将参数转成字符串String jsonParams = gson.toJson(paramsMap);log.info("支付的请求参数:" + jsonParams);//把参数设置到请求体当中StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);//希望得到的响应类型httpPost.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = wxPayClient.execute(httpPost);//这些就是对调用下单方法的响应结果的处理了try{//字符串形式的响应体String bodyAsString = EntityUtils.toString(response.getEntity());//响应状态码int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200){ //处理成功System.out.println("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204){ //处理成功,无返回BodySystem.out.println("成功");} else{System.out.println("下单失败, 响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);throw new IOException("请求失败 request failed");}//如果下单成功,获取响应结果, gson.fromJson()用于将 JSON 字符串转换为 Java 对象Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);//从返回的结果中获取二维码的url, 从官网看出 code_url 是 二维码的keyString codeUrl = resultMap.get("code_url");//创建一个url和订单号的返回值Map<String, Object> map = new HashMap<>();map.put("codeUrl", codeUrl);map.put("orderNo", orderInfo.getOrderNo());return map;} finally{response.close();}}
}
更多推荐
02、SpringBoot + 微信支付
发布评论