当前位置:网站首页>对接微信支付(二)统一下单API
对接微信支付(二)统一下单API
2022-07-26 05:49:00 【IT盛夏的果实】
大家可以先想一下:大家平时在PC端发起的支付都需要什么,是不是你选好商品之后,点击支付,然后PC端弹出来一个二维码,你扫码付款,付款完成之后就OK了。当然这只是针对我们用户来说的,对于我们的一个后台应该是如何来实现的呢?

用户扫码后:1)后台生成订单 2)调用统一下单API 3)返回微信支付链接code_url 4)将链接生成的二维码展示给用户。5)用户扫码后提交扫码链接6)微信验证链接有效性7)返回需要用户支付授权7)用户输入支付密码,提交支付授权8)验证授权,完成支付交易。
相关依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.liubujun</groupId>
<artifactId>payment-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>payment-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!--Swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--生成自定义配置的元数据信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--微信支付的sdk-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
<!--json处理器-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
<build>
<!--项目打包时会将java目录中的*.xml文件进行打包-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
枚举类:
@AllArgsConstructor
@Getter
public enum WxApiType {
/**
* Native下单
*/
NATIVE_PAY("/v3/pay/transactions/native"),
/**
* 查询订单
*/
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
/**
* 关闭订单
*/
CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
/**
* 申请退款
*/
DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),
/**
* 查询单笔退款
*/
DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),
/**
* 申请交易账单
*/
TRADE_BILLS("/v3/bill/tradebill"),
/**
* 申请资金账单
*/
FUND_FLOW_BILLS("/v3/bill/fundflowbill");
/**
* 类型
*/
private final String type;
}@AllArgsConstructor
@Getter
public enum WxNotifyType {
/**
* 支付通知
*/
NATIVE_NOTIFY("/api/wx-pay/native/notify"),
/**
* 退款结果通知
*/
REFUND_NOTIFY("/api/wx-pay/refunds/notify");
/**
* 类型
*/
private final String type;
}主要配置类:WxPayConfig
package com.atguigu.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 lombok.extern.slf4j.Slf4j;
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.File;
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节点中的值填充到当前类的属性中
@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 notifyDomain;
/**
* 获取商户的私钥文件
* @param filename
* @return
*/
private PrivateKey getPrivateKey(String filename){
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在",e);
}
}
/**
* 获取签名验证器
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier(){
//获取商户私钥
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;
}
/**
* 获取http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
//通过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;
}
}
下单controller:
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API")
@Slf4j
public class WxPayController {
@Resource
private WxPayService wxPayService;
@Resource
private Verifier verifier;
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("/native/{productId}")
public R nativePay(@PathVariable Long productId) throws Exception {
//返回支付二维码链接和订单号
Map<String, Object> map = wxPayService.nativePay(productId);
return R.ok().setData(map);
}
/**
* 接收微信服务器发来的请求
* @param request
* @param response
* @return
*/
@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response){
Gson gson = new Gson();
//应答对象
Map<String, String> map = new HashMap<>();
try {
//处理通知参数
String body = HttpUtils.readData(request);
Map<String,Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String)bodyMap.get("id");
log.info("支付通知的id====》{}",bodyMap.get("id"));
// log.info("支付通知的完整数据====》",body);
// int a = 9 / 0;
//TODO 签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId,body);
if(!wechatPay2ValidatorForRequest.validate(request)){
log.error("通知验签失败");
response.setStatus(500);
map.put("code","ERROR");
map.put("message","通知验签失败");
return gson.toJson(map);
}
log.info("通知验签成功");
//TODO 处理订单
wxPayService.processOrder(bodyMap);
//模拟接收微信端的重复通知
TimeUnit.SECONDS.sleep(5);
//成功应答
response.setStatus(200);
map.put("code","SUCCESS");
map.put("message","成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code","ERROR");
map.put("message","失败");
return gson.toJson(map);
}
}下单service:
@Override
public Map<String, Object> nativePay(Long productId) throws Exception {
log.info("生成订单");
//生成订单
OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
String codeUrl = orderInfo.getCodeUrl();
if (!StringUtils.isEmpty(codeUrl)){
log.info("二维码订单已经存在");
//返回二维码
Map<String, Object> map = new HashMap<>();
map.put("codeUrl",codeUrl);
map.put("orderNo",orderInfo.getOrderNo());
return map;
}
log.info("调用统一下单API");
//调用统一下单API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数
Gson gson = new Gson();
Map paramMap = new HashMap<>();
paramMap.put("appid",wxPayConfig.getAppid());
paramMap.put("mchid",wxPayConfig.getMchId());
paramMap.put("description",orderInfo.getTitle());
paramMap.put("out_trade_no",orderInfo.getOrderNo());
//微信支付成功之后,向该地址发送通知
paramMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
Map amountMap = new HashMap<>();
amountMap.put("total",orderInfo.getTotalFee());
amountMap.put("currency","CNY");
paramMap.put("amount",amountMap);
//将参数转为json字符串
String jsonParams = gson.toJson(paramMap);
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) { //处理成功
log.info("成功 返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功");
} else {
log.info("Native 下单失败,响应码 " + statusCode+ ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
//响应结果
HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码
codeUrl = resultMap.get("code_url");
//保存二维码
String orderNo = orderInfo.getOrderNo();
orderInfoService.saveCodeUrl(orderNo,codeUrl);
//返回二维码
Map<String, Object> map = new HashMap<>();
map.put("codeUrl",codeUrl);
map.put("orderNo",orderInfo.getOrderNo());
return map;
} finally {
response.close();
}
}这是在B站上观看尚硅谷的视屏进行学习的,退款和下单都可以正常进行,但是验证签名一直失败??
边栏推荐
- 2022 National latest fire-fighting facility operator (Senior fire-fighting facility operator) simulation test questions and answers
- leetcode-Array
- Lamp architecture
- ES Cluster in Red status: what about write & delete operations?
- Usage and common problems of SIP softphone registered with SIP account
- 金仓数据库 KingbaseES SQL 语言参考手册 (9. 常见DDL子句)
- C language explanation series - understanding of functions (4) declaration and definition of functions, simple exercises
- ERROR: Could not open requirements file: [Errno 2] No such file or directory: ‘requirments.txt’
- Hack the box -sql injection fundamentals module detailed Chinese tutorial
- 日志收集分析平台搭建-1-环境准备
猜你喜欢

MongonDB API使用

为什么LPDDR不能完全代替DDR?

高频电子线路复习考试题及答案
![[论文笔记] 面向网络语音隐写的抗分组丢失联合编码](/img/ca/95476b6d4b5765f5fde82cbeda577e.png)
[论文笔记] 面向网络语音隐写的抗分组丢失联合编码

Solution to slow download speed of vagrant

2022 National latest fire-fighting facility operator (Senior fire-fighting facility operator) simulation test questions and answers

中文文本纠错任务简介

Mba-day29 arithmetic - preliminary understanding of absolute value

Introduction to Chinese text error correction task

高分子物理知识点
随机推荐
柠檬班自动化学习毕竟
leetcode-Array
Hack the box -sql injection fundamentals module detailed Chinese tutorial
No EGL display error resolution
How can red star Macalline design cloud upgrade the traditional home furnishing industry in ten minutes to produce film and television level interior design effects
How are masters refined?
二叉树的性质 ~
leetcode-aboutString
Two auxiliary functions of integral Mall for business user operation
Redis publish subscription
Nn.moudle module - details of creating neural network structure
Efficient, reliable and safe open source solution for serial communication
Mongodb tutorial Chapter 08 comparison operators
调试利器!一款轻量级日志库 log.c
Detailed explanation of the whole process of coding's pressure measurement operation
Rocbossphp free open source light community system
Qu Weihai, chairman and CEO of Xinyi interactive, adheres to mutual benefit and win-win results, and Qu Weihai promotes enterprise development
vagrant下载速度慢的解决方法
FTP experiment and overview
Redis master-slave replication