本篇文章教给大家如何快速上手微信支付,没有太多的理论讲解。希望大家动手敲一遍代码,你会发现基于Native支付的微信对接没你想象的那么困难!
付款码支付:用户展示微信钱包内的“付款码”给商家,商家扫描后直接完成支付,适用于线下面对面收银的场景。
JSAPI支付:1.线下场所:商户展示一个支付二维码,用户使用微信扫描二维码后,输入需要支付的金额,完成支付。 2.公众号场景:用户在微信内进入商家公众号,打开某个页面,选择某个产品,完成支付。3.PC网站场景:在网站中展示二维码,用户使用微信扫描二维码,输入需要支付的金额,完成支付。特点:用户在客户端输入支付金额。
小程序支付:在微信小程序平台内实现支付的功能。
Native支付:Native支付是指商户展示支付二维码,用户再用微信“扫一扫”完成支付的模式。这种方式适用于PC网站。特点:商家预先指定支付金额。
APP支付:商户通过在移动端独立的APP应用程序中集成微信支付模块,完成支付。
刷脸支付:用户在刷脸设备前通过摄像头刷脸、识别身份后进行的一种支付方式。
上面的多种支付产品进行对接时,都需要相同的参数。这些参数是需要我们在微信上申请的,申请商户时,需要公司的营业执照,所以个人是无法申请或得到对接微信支付的参数。这里我给大家一份,大家后面学习时,用我给的这份参数即可(我这个也是网上找的,如果大家是通过公司申请的,请保护好个人隐私,不要对外公开!)
mch-id(商户号):1558950191
mch-serial-no(商户API证书序列号):34345964330B66427E0D3D28826C4993C77E631F
api-v3-key(APIv3密钥):UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
appid(APPID):wx74862e0dfcf69954
private-key-path(商户私钥文件):apiclient_key.pem
domain(微信服务器地址):https://api.mch.weixin.qq.com
notify-domain(接收结果通知地址):这个需要自己本地进行内网穿透,微信回调才能打到本地,下面我会讲解。
注意:这里的apiclient_key.pem是一个文件,请大家下载到自己本地。链接: https://pan.baidu.com/s/1PoMVGLbNX7bXz7PN19Bbcg 提取码: pmma
访问ngrok官网:https://ngrok.com/
注册账号,然后去QQ邮箱验证。
点击QQ邮箱中链接跳转到ngrok官网
下载安装包到本地 这里直接下载我的。链接: https://pan.baidu.com/s/1dozNDO5wlvPoxEukQ7LOxQ 提取码: avsr
下载完成后,直接解压到当前文件夹。打开ngrok.exe,输入复制的命令。
回调接口参数 notify-domain(接收结果通知地址):https://573c-59-174-56-67.ngrok-free.app
注意:这里每个人的都不同,请大家自行配置。在微信支付对接中,该回调地址参数是必填的。你可以填一个错误的,但是订单支付或退款成功,你是无法感知的。所以大家还是要配一个,订单支付或退款成功,微信回调我们的接口,告知我们订单状态,同时,我们可以异步的对订单表进行其他的逻辑操作。比如修改订单状态,统计订单数据等等。
测试配置是否成功:
server:
port: 9091 #这里的端口号与内网穿透配置的一样
package com.weige.javaskillpoint.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ngrok")
public class TestController {
@GetMapping("/test")
public String test(){
return "内网穿透成功";
}
}
启动项目:
微信官方文档链接:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_4.shtml
项目目录:
pom.xml文件
<?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.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.weige</groupId>
<artifactId>java-skill-point</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>java-skill-point</name>
<description>java-skill-point</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--Springboot项目自带 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Springboot Web项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</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>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 9091 #这里的端口号与内网穿透配置的一样
wxpay.properties
wxpay.mch-id=1558950191 #商户号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F #商户API证书序列号
wxpay.private-key-path=apiclient_key.pem #商户私钥文件
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B #APIv3密钥
wxpay.appid=wx74862e0dfcf69954 #APPID
wxpay.domain=https://api.mch.weixin.qq.com #微信服务器地址
wxpay.notify-domain=https://573c-59-174-56-67.ngrok-free.app/ #接收结果通知地址
apiclient_key.pem
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDnSAKI8sea8p+d
OBVPWlZmxqJfPbdhzZxdI5Kx1j5SJNZwXWtr43/giw38pwzSlBI+bubBcYlkFTI0
guigMZO/yueb1mZChaY/JG1vsT02Ubj0xkVvBwKNbYS48NEpZhK61Mia09R4n1iH
1vip9kt8J6Zrx+xIqwmuCNWigyivGrvY9AdevCNlNSVdHVOZUJiJ6UGtvVmgZb0u
RTwBzfkjnwTgEcsrZMmF15nFubFsyJLyF/zY4NhrISc8H/rbjgleqa8ybYL26iTS
gfPCXe4U9f8fNFF2bSA06GTiB2R93q2B0zHeUYrpgF4XOGlIAqH+Ea4Vn+aOj6I0
pduh03idAgMBAAECggEBAJ+4SB/hYd1szrPZhkXtwhtp87pIObtuLhzYMzdjGFjM
HdctfMDeNHKSNU+U4bMPFOZO2kcfLF2Ukb5X5WSzuDBMZNRnJOmtuJiEhJsM0JQR
reREhLDfK3EWAAFkNV4corSpu/vIbEP87zuoRsPBVnHgQ/rM7y1kCORKL5bycwcw
5BI4xhULKAu14LEcDL3+xDJo39w+WCFlxuP+6Bs7+vIeavs+AC3TJkA4kg2nyWd3
W07xPjHl64f17icqsFhuFZ+VuSf5CAgQGWDbC7BHqRkDStUDSiiUiFushouKCLdK
MpA0x4ogb2ZwfZDRhZHiLNAGe4QovYCcXWBydzuT0WECgYEA828Bo1JAHE5kdnsO
E9+enH/yMcOKTRnuYPiXsFXNvqofc5tZiXJmVE/+EKv7LFmtUA6qqKC7FDek8TpP
SkfXmSDAgfM6AdzT0YoHH23FRVewnFMEYumtogXsXJTyI5siBSJp16s9Rn/YwESt
JqjW5+9Ck1dkU+UJCZ4lOw4HeGkCgYEA8zho2BKQTh3P/xcFcoTcunVZpRayVkHM
g8Ef6RGGo4vM1oshQLvXyPqCmhAIf6j71I9WPqUwjmeGyaR7Hir0dbgTCm2fJPFW
lxAvgbCISxEPz10RYBcR2umMSlJLfZfhqv1CyfU4vfCTbdOimgsz2039E3oLTbzg
eDe/mdzu2BUCgYEAleKjf4wFLWiXMtxRrqrhXjrpRPrBDPgKbmqh+1DZfawB8YyV
dKublg4qwNkjrgsJS2G8cleE2M3qIR1l9LaHaSFhZqH79WmigkIaYJ+V9zwm4hm7
eaun3TsIbXjIHmRGbiLiSIiHEgFl0/x1IHiU2fnXZCFLBNzg06ssAVCCCQECgYA1
4BfxTONkOlxZgAr33BBcySPLWuS0EK0xvjTIVtaBIbWFDJqYEUPyQ/NsFwMa7B6k
bf/HrqW71ZjYz7Np8k/mR5kIJVIsR71Lhw1O6AC4yBW9dDsmEtYkrLkjuWj5cAxP
6PvDaqtf/4tYt5l8D+Ezwem+R7l7RcxfNNIfTf4mJQKBgE57dnRx+Ijx7VHjJvjl
X2jB/VSVGpK5OADykmmZ/wvHPlQcyzd+5kAIoJhSuY48CFeI1DOogR2p01LEFQEL
j4AI5FqOOQwRJvNmfoKcKwO36tSxSEGSM8POKOsa21PG/gvDpJjVFo2hn5QcMHWn
z5SjsgA/1YbXejubdLxT/3pl
-----END PRIVATE KEY-----
WxPayConfig文件 微信支付对接参数类
package com.weige.javaskillpoint.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.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() {
log.info("获取签名验证器");
//获取商户私钥
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) {
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
log.info("无需进行应答签名验证,获取httpClient");
//获取商户私钥
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();
return httpClient;
}
}
WxPayController文件 订单流程操作类
package com.weige.javaskillpoint.controller;
import com.google.gson.Gson;
import com.weige.javaskillpoint.config.WxPayConfig;
import com.weige.javaskillpoint.enums.wxpay.WxApiType;
import com.weige.javaskillpoint.enums.wxpay.WxNotifyType;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private CloseableHttpClient wxPayClient;
@Resource
//无需应答签名
private CloseableHttpClient wxPayNoSignClient;
@Resource
private WxPayConfig wxPayConfig;
/**
* Native下单
*/
@PostMapping("/native")
public void nativePay() throws Exception {
log.info("发起支付请求 v3");
log.info("调用统一下单API");
//调用统一下单API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数
Gson gson = new Gson();
HashMap<String, Object> paramsMap = new HashMap<String, Object>();
paramsMap.put("appid", wxPayConfig.getAppid());// APPID
paramsMap.put("mchid", wxPayConfig.getMchId());// 商户id
paramsMap.put("description", "魏凯的小商铺"); // 订单描述
paramsMap.put("out_trade_no", "123456789987"); // 订单号
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));// 二维码扫描支付成功后进行回调
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("total", 1); // 金额 以分为单位 这里写1分钱 订单号123456789987对应的金额为1分
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
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");
//完成签名并执行请求(这个地方就是mchId与商户证书密钥进行校验)
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
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 resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码
String codeUrl = (String) resultMap.get("code_url");
log.info("二维码为: " + codeUrl);
}
}
/**
* 查询订单
*/
@GetMapping("/query/{orderNo}")
public void queryOrder(@PathVariable String orderNo) throws Exception {
log.info("查询订单");
log.info("查单接口调用 ===> {}", orderNo);
// 拼接url
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
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("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
}
}
/**
* 用户取消订单
*/
@PostMapping("/cancel/{orderNo}")
public void cancel(@PathVariable String orderNo) throws Exception {
log.info("关单接口的调用,订单号 ===> {}", orderNo);
//创建远程请求对象
String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url);
HttpPost httpPost = new HttpPost(url);
//组装json请求体
Gson gson = new Gson();
Map<String, String> paramsMap = new HashMap<>();
paramsMap.put("mchid", wxPayConfig.getMchId());
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");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功200");
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功204");
} else {
log.info("Native下单失败,响应码 = " + statusCode);
throw new IOException("request failed");
}
}
}
/**
* 用户申请退款
*/
@PostMapping("/refunds/{orderNo}")
public void refunds(@PathVariable String orderNo) throws Exception {
log.info("申请退款");
log.info("调用退款API");
//调用统一下单API
String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
HttpPost httpPost = new HttpPost(url);
// 请求body参数
Gson gson = new Gson();
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("out_trade_no", orderNo);//订单编号
paramsMap.put("out_refund_no", "123456");//退款单编号 随便填
paramsMap.put("reason", "随便填一个");//退款原因
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款成功通知地址
Map<String, java.io.Serializable> amountMap = new HashMap<String, java.io.Serializable>();
amountMap.put("refund", 1);//退款金额 这里的金额应该根据订单id查询出来
amountMap.put("total", 1);//原订单金额 这里的金额应该根据订单id查询出来
amountMap.put("currency", "CNY");//退款币种
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
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");//设置响应报文格式
//完成签名并执行请求,并完成验签
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
//解析响应结果
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
}
}
}
/**
* 查询退款
*/
@GetMapping("/query-refund/{refundNo}")
public void queryRefund(@PathVariable String refundNo) throws Exception {
log.info("查询退款");
log.info("查询退款接口调用 ===> {}", refundNo);
String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
url = wxPayConfig.getDomain().concat(url);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 查询退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("查询退款异常, 响应码 = " + statusCode + ", 查询退款返回结果 = " + bodyAsString);
}
}
}
/**
* 获取账单url 这个url无法在浏览器打开
*/
@GetMapping("/querybill/{billDate}/{type}")
public void queryTradeBill(@PathVariable String billDate, @PathVariable String type) throws Exception {
log.info("获取账单url");
log.warn("申请账单接口调用 {}", billDate);
String url = "";
if ("tradebill".equals(type)) {
url = WxApiType.TRADE_BILLS.getType();
} else if ("fundflowbill".equals(type)) {
url = WxApiType.FUND_FLOW_BILLS.getType();
} else {
throw new RuntimeException("不支持的账单类型");
}
url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 申请账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("申请账单异常, 响应码 = " + statusCode + ", 申请账单返回结果 = " + bodyAsString);
}
//获取账单下载地址
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
log.info("账单链接;" + resultMap.get("download_url"));
}
}
/**
* 下载账单
*/
@GetMapping("/downloadbill/{billDate}/{type}")
public void downloadBill(@PathVariable String billDate, @PathVariable String type) throws Exception {
log.info("下载账单");
log.warn("下载账单接口调用 {}, {}", billDate, type);
//获取账单url地址
String downloadUrl = this.queryBill(billDate, type);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 下载账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + bodyAsString);
}
}
}
/**
* 申请账单
*/
public String queryBill(String billDate, String type) throws Exception {
log.warn("申请账单接口调用 {}", billDate);
String url = "";
if ("tradebill".equals(type)) {
url = WxApiType.TRADE_BILLS.getType();
} else if ("fundflowbill".equals(type)) {
url = WxApiType.FUND_FLOW_BILLS.getType();
} else {
throw new RuntimeException("不支持的账单类型");
}
url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 申请账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("申请账单异常, 响应码 = " + statusCode + ", 申请账单返回结果 = " + bodyAsString);
}
//获取账单下载地址
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap.get("download_url");
}
}
}
WxPayNotifyController文件 支付成功或者退款成功回调类
package com.weige.javaskillpoint.controller;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.weige.javaskillpoint.util.HttpUtils;
import com.weige.javaskillpoint.util.WechatPay2ValidatorForRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/wx-pay/notify")
@Slf4j
public class WxPayNotifyController {
@Resource
private Verifier verifier;
/**
* 订单支付成功回调
*/
@PostMapping("/native")
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 ===> {}", requestId);
//签名的验证
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("通知验签成功");
//处理订单 这里可以对订单进行处理 比如订单支付成功 修改数据库订单表订单状态为已支付
// 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);
}
}
@PostMapping("/refunds")
public String refundsNotify(HttpServletRequest request, HttpServletResponse response){
log.info("退款通知执行");
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 ===> {}", requestId);
//签名的验证
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("通知验签成功");
//处理退款单 订单退款成功 修改订单表中订单状态为已退款
// processRefund(bodyMap);
//成功应答
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);
}
}
}
WxApiType文件 对接微信支付接口后缀
package com.weige.javaskillpoint.enums.wxpay;
import lombok.AllArgsConstructor;
import lombok.Getter;
@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;
}
WxNotifyType文件 接口回调地址后缀
package com.weige.javaskillpoint.enums.wxpay;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum WxNotifyType {
/**
* 支付通知
*/
NATIVE_NOTIFY("/api/wx-pay/notify/native"),
/**
* 退款结果通知
*/
REFUND_NOTIFY("/api/wx-pay/notify/refunds");
/**
* 类型
*/
private final String type;
}
HttpUtils文件 工具类(将通知参数转化为字符串)
package com.weige.javaskillpoint.util;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
/**
* @author weikai
*/
public class HttpUtils {
/**
* 将通知参数转化为字符串
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
WechatPay2ValidatorForRequest文件 工具类
package com.weige.javaskillpoint.util;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
public class WechatPay2ValidatorForRequest {
protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
/**
* 应答超时时间,单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}
JavaSkillPointApplication 启动类
package com.weige.javaskillpoint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JavaSkillPointApplication {
public static void main(String[] args) {
SpringApplication.run(JavaSkillPointApplication.class, args);
}
}
项目搭建完成,接下来讲解订单从创建到结束的整个流程:
要想与微信进行支付对接,首先第一步我们需要获取签名验证器和HttpClient,如果这个成功不了,则无法与微信对接
证书密钥使用说明:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay3_0.shtml
我把获取签名验证器的代码写到WxPayConfig类中,这个类在项目启动时,会执行下面几步:
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
根据读取配置文件的微信对接参数,获取签名验证器与http请求对象
/**
* 获取签名验证器
*
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
log.info("获取签名验证器");
//获取商户私钥
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) {
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
微信支付对接参数不正确,则微信签名失败(我们这里将wxpay.mch-id修改为1558950192):
微信支付对接参数不正确,则微信签名失败(我们这里将wxpay.mch-serial-no修改为34345964330B66427E0D3D28826C4993C77E631F1):
代码:
package com.weige.javaskillpoint.controller;
import com.google.gson.Gson;
import com.weige.javaskillpoint.config.WxPayConfig;
import com.weige.javaskillpoint.enums.wxpay.WxApiType;
import com.weige.javaskillpoint.enums.wxpay.WxNotifyType;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private CloseableHttpClient wxPayClient; // 对应WxPayConfig类中的@Bean(name = "wxPayClient") 获取http请求对象
@Resource
private WxPayConfig wxPayConfig;
/**
* Native下单
*/
@PostMapping("/native")
public void nativePay() throws Exception {
log.info("发起支付请求 v3");
log.info("调用统一下单API");
//调用统一下单API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数
Gson gson = new Gson();
HashMap<String, Object> paramsMap = new HashMap<String, Object>();
paramsMap.put("appid", wxPayConfig.getAppid());// APPID
paramsMap.put("mchid", wxPayConfig.getMchId());// 商户id
paramsMap.put("description", "魏凯的小商铺"); // 订单描述
paramsMap.put("out_trade_no", "123456789988"); // 订单号
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));// 二维码扫描支付成功后进行回调
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("total", 1); // 金额 以分为单位 这里写1分钱 订单号123456789987对应的金额为1分
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
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");
//完成签名并执行请求(这个地方就是mchId与商户证书密钥进行校验)
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
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 resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码
String codeUrl = (String) resultMap.get("code_url");
log.info("二维码为: " + codeUrl);
}
}
}
请求微信下单接口:wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()) -> https://api.mch.weixin.qq.com/v3/pay/transactions/native
扫码收款码支付成功回调:wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()) -> https://573c-59-174-56-67.ngrok-free.app/api/wx-pay/notify/native
订单金额以分为单位,可以前端传入。这里我统一写为1分。请求微信下单接口参数按我代码封装即可。
参数详情可以查看微信文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
收款码:weixin://wxpay/bizpayurl?pr=Y4vn02Lzz 根据url生成收款码
扫描图中二维码进行微信付款,付款成功后,微信会回调我们的支付成功接口
代码:
package com.weige.javaskillpoint.controller;
import com.google.gson.Gson;
import com.weige.javaskillpoint.config.WxPayConfig;
import com.weige.javaskillpoint.enums.wxpay.WxApiType;
import com.weige.javaskillpoint.enums.wxpay.WxNotifyType;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private CloseableHttpClient wxPayClient;
@Resource
//无需应答签名
private CloseableHttpClient wxPayNoSignClient;
@Resource
private WxPayConfig wxPayConfig;
/**
* 查询订单
*/
@GetMapping("/query/{orderNo}")
public void queryOrder(@PathVariable String orderNo) throws Exception {
log.info("查询订单");
log.info("查单接口调用 ===> {}", orderNo);
// 拼接url
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
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("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
}
}
}
查询刚刚支付的订单号:123456789988
代码:
package com.weige.javaskillpoint.controller;
import com.google.gson.Gson;
import com.weige.javaskillpoint.config.WxPayConfig;
import com.weige.javaskillpoint.enums.wxpay.WxApiType;
import com.weige.javaskillpoint.enums.wxpay.WxNotifyType;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private CloseableHttpClient wxPayClient;
@Resource
//无需应答签名
private CloseableHttpClient wxPayNoSignClient;
@Resource
private WxPayConfig wxPayConfig;
@PostMapping("/refunds/{orderNo}")
public void refunds(@PathVariable String orderNo) throws Exception {
log.info("申请退款");
log.info("调用退款API");
//调用统一下单API
String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
HttpPost httpPost = new HttpPost(url);
// 请求body参数
Gson gson = new Gson();
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("out_trade_no", orderNo);//订单编号
paramsMap.put("out_refund_no", "123456");//退款单编号 随便填
paramsMap.put("reason", "随便填一个");//退款原因
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款成功通知地址
Map<String, java.io.Serializable> amountMap = new HashMap<String, java.io.Serializable>();
amountMap.put("refund", 1);//退款金额 这里的金额应该根据订单id查询出来
amountMap.put("total", 1);//原订单金额 这里的金额应该根据订单id查询出来
amountMap.put("currency", "CNY");//退款币种
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
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");//设置响应报文格式
//完成签名并执行请求,并完成验签
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
//解析响应结果
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
}
}
}
}
请求微信退款接口:wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()) -> https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
微信退款成功回调:wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()) -> https://573c-59-174-56-67.ngrok-free.app/api/wx-pay/notify/refunds
代码:
package com.weige.javaskillpoint.controller;
import com.google.gson.Gson;
import com.weige.javaskillpoint.config.WxPayConfig;
import com.weige.javaskillpoint.enums.wxpay.WxApiType;
import com.weige.javaskillpoint.enums.wxpay.WxNotifyType;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private CloseableHttpClient wxPayClient;
@Resource
//无需应答签名
private CloseableHttpClient wxPayNoSignClient;
@Resource
private WxPayConfig wxPayConfig;
/**
* 下载账单
*/
@GetMapping("/downloadbill/{billDate}/{type}")
public void downloadBill(@PathVariable String billDate, @PathVariable String type) throws Exception {
log.info("下载账单");
log.warn("下载账单接口调用 {}, {}", billDate, type);
//获取账单url地址
String downloadUrl = this.queryBill(billDate, type);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 下载账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + bodyAsString);
}
}
}
/**
* 申请账单
*/
public String queryBill(String billDate, String type) throws Exception {
log.warn("申请账单接口调用 {}", billDate);
String url = "";
if ("tradebill".equals(type)) {
url = WxApiType.TRADE_BILLS.getType();
} else if ("fundflowbill".equals(type)) {
url = WxApiType.FUND_FLOW_BILLS.getType();
} else {
throw new RuntimeException("不支持的账单类型");
}
url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 申请账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("申请账单异常, 响应码 = " + statusCode + ", 申请账单返回结果 = " + bodyAsString);
}
//获取账单下载地址
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap.get("download_url");
}
}
}
这里的账单有两种类型。一种是申请交易账单,接口后缀为/v3/bill/tradebill;一种是申请资金账单,接口后缀为/v3/bill/fundflowbill
这里我分别演示两种不同账单,并打印到控制台中:
第一种交易账单:
大家在学习微信对接或准备微信对接时,一定要多看微信官方文档。
官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
项目代码: https://pan.baidu.com/s/1FRGfYmujzdpUtl4RwSlylA 提取码: 1999