当前位置:网站首页>对接微信支付(二)统一下单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站上观看尚硅谷的视屏进行学习的,退款和下单都可以正常进行,但是验证签名一直失败??
边栏推荐
- QT writing IOT management platform 47 general database settings
- Mysql45 speak in simple terms index
- Rocbossphp free open source light community system
- 又一开源神器,值得收藏学习!
- 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
- Day110.尚医通:Gateway集成、医院排班管理:科室列表、根据日期统计数据、排班详情
- Realize channel routing based on policy mode
- 金仓数据库 KingbaseES SQL 语言参考手册 (8. 函数(十一))
- SSH Remote Management
- Development projects get twice the result with half the effort, a large collection of open source STM32 driver Libraries
猜你喜欢

Mongondb API usage

为什么LPDDR不能完全代替DDR?
![[paper notes] anti packet loss joint coding for network speech steganography](/img/ca/95476b6d4b5765f5fde82cbeda577e.png)
[paper notes] anti packet loss joint coding for network speech steganography

Dynamic memory management and flexible array

Interview questions for software testing is a collection of interview questions for senior test engineers, which is exclusive to the whole network
![[personal summary] end of July 24, 2022](/img/9e/dfc37c2684aa8849291817782c947b.png)
[personal summary] end of July 24, 2022

Use latex to typeset multiple-choice test paper

Benji Bananas 开启第二季边玩边赚奖励活动,支持使用多张 Benji 通行证!

DOM operation -- operation node

MongoDB 常用命令
随机推荐
Redis persistence RDB
DOM operation -- operation node
[cloud native] record of feign custom configuration of microservices
Introduction to Chinese text error correction task
Dynamic memory management and flexible array
FTP experiment and overview
The refurbishment and counterfeiting of chips have made people feel numb
Use of feign (Part 2)
[论文笔记] 面向网络语音隐写的抗分组丢失联合编码
ES Cluster in Red status: what about write & delete operations?
Interview questions for software testing is a collection of interview questions for senior test engineers, which is exclusive to the whole network
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
Redis持久化-RDB
[personal summary] end of July 24, 2022
满二叉树 / 真二叉树 / 完全二叉树 ~
leetcode-Array
金仓数据库 KingbaseES SQL 语言参考手册 (8. 函数(十))
Who is responsible for the problems of virtual idol endorsement products? And listen to the lawyer's analysis
程序员如何改善精神内耗?
[paper notes] anti packet loss joint coding for network speech steganography