因为微信支付回调需要一个外网可访问的地址,我本地调试是采用的内网穿透的方式进行调试的。
Cpolar是一款内网穿透工具,它支持http/https/tcp协议,不限制流量,操作简单,无需公网IP,也无需路由器,可以轻松把服务暴露到公网访问。
1 下载安装cpolar内网穿透
访问cpolar官网,注册一个账号,并下载安装cpolar客户端。详细可以参考文档教程进行下载安装。
2 创建隧道
cpolar安装成功后,我们在浏览器上访问本地9200端口,登录Cpolar的web ui界面:http://localhost:9200。
点击左侧仪表盘的隧道管理——创建隧道,由于tomcat中配置的是82端口,因此我们要来创建一条http隧道,指向82端口:
隧道名称:可自定义,注意不要与已有隧道名称重复
协议:http协议
本地地址:82
域名类型:免费选择随机域名
地区:选择China top
点击左侧仪表盘的状态——在线隧道列表,可以看到刚刚创建的隧道已经有生成了相应的公网地址,一个http协议,一个https协议(免去配置ssl证书的繁琐步骤),将其复制想下来
接下来我们就直接上代码了:
一、引入官方提供的pom依赖
<!-- 微信支付 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.2</version>
</dependency>
二、配置微信支付必要的参数
weixin:
appid: # appid
mch-serial-no: # 证书序列号
private-key-path: apiclient_key.pem # 证书路径 我这边保存在resource文件夹下读取文件时通过ClassPathResource去读取的
mch-id: # 商户号
key: # api秘钥
domain: https://api.mch.weixin.qq.com # 微信服务器地址
notify-domain: http://303fe2d4.r5.cpolar.top/v3/wechat/callback # 回调,自己的回调地址需要外网可访问
三、编写微信支付配置类
import com.qz.common.exception.CommonException;
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.Verifier;
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.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
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 org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
/**
* @author lihao
* @create 2023-03-22 17:24
* @desc
**/
@Configuration
@PropertySource("classpath:application.yml") //读取配置文件
@ConfigurationProperties(prefix = "weixin") //读取weixin节点
@Data //使用set方法将weixin节点中的值填充到当前类的属性中
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
/**
* 获取商户的私钥文件
*
* @param filename 证书地址
* @return 私钥文件
*/
public PrivateKey getPrivateKey(String filename) {
try {
ClassPathResource classPathResource = new ClassPathResource(filename);
return PemUtil.loadPrivateKey(classPathResource.getInputStream());
} catch (IOException e) {
throw new CommonException("私钥文件不存在");
}
}
/**
* 获取签名验证器
*/
@Bean
public Verifier getVerifier() {
// 获取商户私钥
final PrivateKey privateKey = getPrivateKey(privateKeyPath);
// 私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
// 身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
try {
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, wechatPay2Credentials, key.getBytes(StandardCharsets.UTF_8));
} catch (IOException | GeneralSecurityException | HttpCodeException e) {
e.printStackTrace();
}
try {
return certificatesManager.getVerifier(mchId);
} catch (NotFoundException e) {
e.printStackTrace();
throw new CommonException("获取签名验证器失败");
}
}
/**
* 获取微信支付的远程请求对象
*
* @return Http请求对象
*/
@Bean
public CloseableHttpClient getWxPayClient() {
// 获取签名验证器
Verifier verifier = getVerifier();
// 获取商户私钥
final PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
return builder.build();
}
}
四、微信支付枚举类
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author lihao
* @create 2023-03-22 17:21
* @desc 微信支付枚举类
**/
@AllArgsConstructor
@Getter
public enum WxApiType {
/**
* Native下单
*/
NATIVE_PAY("/v3/pay/transactions/native"),
/**
* jsapi下单
*/
JSAPI_PAY("/v3/pay/transactions/jsapi"),
/**
* jsapi下单
*/
H5_PAY("/v3/pay/transactions/h5"),
/**
* APP下单
*/
APP_PAY("/v3/pay/transactions/app"),
/**
* 查询订单
*/
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s/?mchid=%s"),
/**
* 查询订单
*/
ORDER_QUERY_BY_ID("/v3/pay/transactions/id/%s?mchid=%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"),
/**
* 发起商家转账
*/
TRANSFER_BATCHES("/v3/transfer/batches");
/**
* 类型
*/
private final String type;
}
五、微信支付工具类(这边以native扫码支付为例)
(1)创建native扫码支付工具类 后面需要用到时传入参数直接调用即可
/**
* @author lihao
* @create 2023-03-23 9:58
* @desc Native扫码支付工具类
**/
@Slf4j
public class WxPayNativeUtils {
public static Map<String, Object> nativePay(WxPayConfig wxPayConfig, WeChatPayNativeParam param, CloseableHttpClient wxPayClient) throws Exception {
Gson gson = new Gson();
// 创建POST请求对象,里面输入的是地址,也就是那个https://api.wxpayxxxxx 那个,只不过我们直接去配置文件里面读取然后拼接出来了,懒得每次都输入一遍
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
Map paramsMap =new HashMap();
paramsMap.put("appid",wxPayConfig.getAppid()); // 我们的APPID
paramsMap.put("mchid",wxPayConfig.getMchId()); // 我们的商户ID
paramsMap.put("description",param.getDescription()); // 扫码之后显示的标题
paramsMap.put("out_trade_no",param.getOutTradeNo()); // 商户订单号 我们自己生成的那个
// 这里就是微信响应给我们系统的那个地址 必须是能外网访问的
paramsMap.put("notify_url",wxPayConfig.getNotifyDomain());
Map amountMap = new HashMap();
amountMap.put("total",param.getTotal()); // 支付金额
amountMap.put("currency","CNY"); // 交易货币的类型
paramsMap.put("amount",amountMap);
paramsMap.put("attach",param.getDeviceInfo());
// 将这个json对象转换成字符串,用于后面进行网络传输
String jsonParams = gson.toJson(paramsMap);
log.info("请求微信支付V3 Native扫码支付接口参数 =========> " + 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);
HashMap<String, Object> returnMap = new HashMap<>();
try {
String body = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功 有返回就会进入这里
log.info("请求微信支付V3 Native扫码支付接口成功,返回结果 =========> " + body);
} else if (statusCode == 204) { //处理成功,无返回就会进入到这里
System.out.println("success");
} else { // 接口调用失败的话就会进入这里
log.info("Native下单失败,响应码 =====> " + statusCode+ ",返回结果= " + body);
HashMap<String,String> resultMap = gson.fromJson(body, HashMap.class);
returnMap.put("err_code_des",resultMap.get("message"));
}
// 相微信那边返回的结果 我们将json返回转成一个Map对象
HashMap<String,String> resultMap = gson.fromJson(body, HashMap.class);
// 然后从这个Map里面拿到code_url,这个是微信定义的,我们拿这个结果生成二维码
String code_url = resultMap.get("code_url");
returnMap.put("code_url",code_url);
return returnMap;
} finally {
response.close();
}
}
}
(2)native支付成功回调工具类
/**
* @author lihao
* @create 2023-03-23 11:40
* @desc
**/
@Slf4j
public class WxPayCallbackUtil {
/**
* 微信支付创建订单回调方法
* @param verifier 证书
* @param wxPayConfig 微信配置
* @param businessCallback 回调方法,用于处理业务逻辑
* @return json格式的string数据,直接返回给微信
*/
public static String wxPaySuccessCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, Consumer<WxchatCallbackSuccessResult> businessCallback) {
Gson gson = new Gson();
// 1.处理通知参数
final String body = HttpUtils.readData(request);
HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
// 2.签名验证
WechatPayValidatorForRequest wechatForRequest = new WechatPayValidatorForRequest(verifier, body, (String) bodyMap.get("id"));
try {
if (!wechatForRequest.validate(request)) {
// 通知验签失败
response.setStatus(500);
final HashMap<String, Object> map = new HashMap<>();
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
} catch (IOException e) {
e.printStackTrace();
}
// 3.获取明文数据
String plainText = decryptFromResource(bodyMap,wxPayConfig);
HashMap<String,Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
log.info("plainTextMap:{}",plainTextMap);
// 4.封装微信返回的数据
WxchatCallbackSuccessResult callbackData = new WxchatCallbackSuccessResult();
callbackData.setSuccessTime(String.valueOf(plainTextMap.get("success_time")));
callbackData.setOrderId(String.valueOf(plainTextMap.get("out_trade_no")));
callbackData.setTransactionId(String.valueOf(plainTextMap.get("transaction_id")));
callbackData.setTradestate(String.valueOf(plainTextMap.get("trade_state")));
callbackData.setTradetype(String.valueOf(plainTextMap.get("trade_type")));
callbackData.setAttach(String.valueOf(plainTextMap.get("attach")));
String amount = String.valueOf(plainTextMap.get("amount"));
HashMap<String,Object> amountMap = gson.fromJson(amount, HashMap.class);
String total = String.valueOf(amountMap.get("total"));
callbackData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
log.info("callbackData:{}",callbackData);
if ("SUCCESS".equals(callbackData.getTradestate())) {
// 执行业务逻辑
businessCallback.accept(callbackData);
}
// 5.成功应答
response.setStatus(200);
final HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("code", "SUCCESS");
resultMap.put("message", "成功");
return gson.toJson(resultMap);
}
/**
* 对称解密
*/
private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig) {
// 通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 数据密文
String ciphertext = resourceMap.get("ciphertext");
// 随机串
String nonce = resourceMap.get("nonce");
// 附加数据
String associateData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes(StandardCharsets.UTF_8));
try {
return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
} catch (GeneralSecurityException e) {
e.printStackTrace();
throw new CommonException("解密失败");
}
}
}
(3)请求参数
/**
* @author lihao
* @create 2023-03-23 10:02
* @desc 微信Native支付参数
**/
@Data
public class WeChatPayNativeParam {
/**
* 扫码之后显示的标题
*/
private String description;
/**
* 商户订单号 我们自己生成的那个
*/
private String outTradeNo;
/**
* 支付金额
*/
private BigDecimal total;
/**
* 自定义参数 具体看业务要求 无要求可不填
*/
private String deviceInfo;
}
(4)controller中调用native扫码支付工具类
@Autowired
private WxPayConfig wxPayConfig;
@Autowired
private CloseableHttpClient wxPayClient;
@ApiOperation("微信支付v3版返回链接")
@SaPlatCheckLogin
@PostMapping("/nativePay")
public CommonResult<WeChatCodeResult> nativePay(@RequestBody WeChatPayParam payParam) throws Exception {
WeChatPayNativeParam param = new WeChatPayNativeParam();
param.setDescription(payParam.getDescription());
//这里注意要保持商户订单号唯一
param.setOutTradeNo(payParam.getOrderId()+ RandomUtil.randomNumbers(4));
param.setDeviceInfo(payParam.getOrderId());
BigDecimal money = new BigDecimal(0.01);
param.setTotal((money.multiply(new BigDecimal(100))).setScale(0,BigDecimal.ROUND_HALF_DOWN));
Map<String, Object> map = WxPayNativeUtils.nativePay(wxPayConfig,param,wxPayClient);
if(ObjectUtil.isEmpty(map) || ObjectUtil.isNotEmpty(map.get("err_code_des"))){
return CommonResult.data(new WeChatCodeResult(null,"FAIL",map.get("err_code_des").toString()));
}
if(ObjectUtil.isEmpty(map.get("code_url"))){
throw new CommonException("生成支付二维码失败!");
}
return CommonResult.data(new WeChatCodeResult(map.get("code_url").toString(),"SUCCESS",null));
}
@ApiOperation("微信支付回调接口")
@PostMapping("/callback")
public String courseNative(HttpServletRequest request, HttpServletResponse response) {
return WxPayCallbackUtil.wxPaySuccessCallback(request, response, verifier, wxPayConfig, callbackData -> {
log.info("微信支付返回的信息:{}", callbackData);
//这里处理自己的业务
weChatPayService.payOrder(callbackData.getAttach(),callbackData.getTotalMoney().toString(), callbackData.getTransactionId());
});
}
六、附下其他几种支付方式
(1)微信退款工具类
/**
* @author lihao
* @create 2023-03-23 11:18
* @desc 微信退款工具类
**/
@Slf4j
public class WxPayRefundUtil {
/**
* 发起微信退款申请
*
* @param wxPayConfig 微信配置信息
* @param param 微信支付申请退款请求参数
* @param wxPayClient 微信请求客户端
* @return
*/
public static String refundPay(WxPayConfig wxPayConfig, WeChatRefundParam param, CloseableHttpClient wxPayClient) {
// 1.获取请求参数的Map格式
Map<String, Object> paramsMap = getRefundParams(param);
// 2.获取请求对象
HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.DOMESTIC_REFUNDS, paramsMap);
// 3.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 4.解析response对象
HashMap<String, String> resultMap = resolverResponse(response);
log.info("============>发起微信退款参数:{}",resultMap);
if (resultMap != null) {
// 返回微信支付退款单号
return resultMap.get("out_refund_no");
}
return null;
}
/**
* 查询单笔退款
* @param wxPayConfig 微信配置信息
* @param outRefundNo 商户退款单号
* @param wxPayClient 微信请求客户端
* @return
*/
public static Map<String,String> refundQuery(WxPayConfig wxPayConfig, String outRefundNo, CloseableHttpClient wxPayClient) {
// 1.请求路径和对象
String url = String.format(wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS_QUERY.getType()),outRefundNo);
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
// 2.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpGet);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 3.解析返回的数据
Map<String,String> map = resolverResponse(response);
log.info("微信支付查询单笔退款信息========>{}",map);
return map;
}
/**
* 解析响应数据
*
* @param response 发送请求成功后,返回的数据
* @return 微信返回的参数
*/
private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
try {
// 1.获取请求码
int statusCode = response.getStatusLine().getStatusCode();
// 2.获取返回值 String 格式
final String bodyAsString = EntityUtils.toString(response.getEntity());
Gson gson = new Gson();
if (statusCode == 200) {
// 3.如果请求成功则解析成Map对象返回
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap;
} else {
if (StringUtils.isNoneBlank(bodyAsString)) {
log.error("微信支付请求失败,提示信息:{}", bodyAsString);
// 4.请求码显示失败,则尝试获取提示信息
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
throw new CommonException(resultMap.get("message"));
}
log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
// 其他异常,微信也没有返回数据,这就需要具体排查了
throw new IOException("request failed");
}
} catch (Exception e) {
e.printStackTrace();
throw new CommonException(e.getMessage());
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取请求对象(Post请求)
*
* @param wxPayConfig 微信配置类
* @param apiType 接口请求地址
* @param paramsMap 请求参数
* @return Post请求对象
*/
private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
// 1.设置请求地址
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));
// 2.设置请求数据
Gson gson = new Gson();
String jsonParams = gson.toJson(paramsMap);
// 3.设置请求信息
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
return httpPost;
}
/**
* 封装微信支付申请退款请求参数
*
* @param param 微信支付申请退款请求参数
* @return 封装后的map微信支付申请退款请求参数对象
*/
private static Map<String, Object> getRefundParams(WeChatRefundParam param) {
Map<String, Object> paramsMap = new HashMap<>();
if (StringUtils.isNoneBlank(param.getTransactionId())) {
paramsMap.put("transaction_id", param.getTransactionId());
} else if (StringUtils.isNoneBlank(param.getOrderId())) {
paramsMap.put("out_trade_no", param.getOrderId());
} else {
throw new CommonException("微信支付订单号和商户订单号必须填写一个");
}
paramsMap.put("out_refund_no", param.getRefundOrderId());
if (StringUtils.isNoneBlank(param.getReason())) {
paramsMap.put("reason", param.getReason());
}
//paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(param.getNotify().getType()));
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("refund", param.getRefundMoney().multiply(new BigDecimal(100)).setScale(0,BigDecimal.ROUND_HALF_DOWN));
amountMap.put("total", param.getTotalMoney().multiply(new BigDecimal(100)).setScale(0,BigDecimal.ROUND_HALF_DOWN));
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
return paramsMap;
}
}
(2)订单查询工具类
/**
* @author lihao
* @create 2023-03-23 15:49
* @desc 微信支付查询订单信息工具类
**/
@Slf4j
public class WxPaySearchOrderUtil {
/**
* 根据微信支付系统生成的订单号查询订单详情
* @param wxPayConfig 微信支付配置信息
* @param transactionId 微信支付系统生成的订单号
* @param wxPayClient 微信支付客户端请求对象
* @return 微信订单对象
*/
public static WxchatCallbackSuccessResult searchByTransactionId(WxPayConfig wxPayConfig, String transactionId, CloseableHttpClient wxPayClient) {
// 1.请求路径和对象
String url = String.format(wxPayConfig.getDomain().concat(WxApiType.ORDER_QUERY_BY_ID.getType()),transactionId,wxPayConfig.getMchId());;
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
// 2.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpGet);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 3.解析返回的数据
WxchatCallbackSuccessResult callbackData = resolverResponse(response);
log.info("微信支付根据订单号查询信息========>callbackData:{}",callbackData);
return callbackData;
}
/**
* 根据微信支付系统生成的订单号查询订单详情
* @param wxPayConfig 微信支付配置信息
* @param orderId 我们自己的订单id
* @param wxPayClient 微信支付客户端请求对象
* @return 微信订单对象
*/
public static WxchatCallbackSuccessResult searchByOrderId(WxPayConfig wxPayConfig, String orderId, CloseableHttpClient wxPayClient) {
// 1.请求路径和对象
String url = String.format(wxPayConfig.getDomain().concat(WxApiType.ORDER_QUERY_BY_NO.getType()),orderId,wxPayConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
// 2.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpGet);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 3.解析返回的数据
WxchatCallbackSuccessResult callbackData = resolverResponse(response);
log.info("微信支付根据商户订单号查询信息========>callbackData:{}",callbackData);
return callbackData;
}
/**
* 解析响应数据
* @param response 发送请求成功后,返回的数据
* @return 微信返回的参数
*/
private static WxchatCallbackSuccessResult resolverResponse(CloseableHttpResponse response) {
try {
// 1.获取请求码
int statusCode = response.getStatusLine().getStatusCode();
// 2.获取返回值 String 格式
final String bodyAsString = EntityUtils.toString(response.getEntity());
Gson gson = new Gson();
if (statusCode == 200) {
// 3.如果请求成功则解析成Map对象返回
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
// 4.封装成我们需要的数据
WxchatCallbackSuccessResult callbackData = new WxchatCallbackSuccessResult();
callbackData.setSuccessTime(String.valueOf(resultMap.get("success_time")));
callbackData.setOrderId(String.valueOf(resultMap.get("out_trade_no")));
callbackData.setTransactionId(String.valueOf(resultMap.get("transaction_id")));
callbackData.setTradestate(String.valueOf(resultMap.get("trade_state")));
callbackData.setTradetype(String.valueOf(resultMap.get("trade_type")));
callbackData.setAttach(String.valueOf(resultMap.get("attach")));
String amount = String.valueOf(resultMap.get("amount"));
HashMap<String,Object> amountMap = gson.fromJson(amount, HashMap.class);
String total = String.valueOf(amountMap.get("total"));
callbackData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
return callbackData;
} else {
if (StringUtils.isNoneBlank(bodyAsString)) {
log.error("微信支付请求失败,提示信息:{}", bodyAsString);
// 4.请求码显示失败,则尝试获取提示信息
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
throw new CommonException(resultMap.get("message"));
}
log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
// 其他异常,微信也没有返回数据,这就需要具体排查了
throw new IOException("request failed");
}
} catch (Exception e) {
e.printStackTrace();
throw new CommonException(e.getMessage());
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3)商家发起转账工具类
/**
* @author lihao
* @create 2023-03-22 17:21
* @desc 发起商家转账工具类
**/
@Slf4j
public class WxPayTransferBatchesUtils {
/**
* 发起商家转账,支持批量转账
*
* @param wxPayConfig 微信配置信息
* @param param 转账请求参数
* @param wxPayClient 微信请求客户端()
* @return 微信支付二维码地址
*/
public static String transferBatches(WxPayConfig wxPayConfig, WechatTransferBatchesParam param, CloseableHttpClient wxPayClient) {
// 1.获取请求参数的Map格式
Map<String, Object> paramsMap = getParams(wxPayConfig, param);
// 2.获取请求对象,WxApiType.TRANSFER_BATCHES="/v3/transfer/batches"
HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.TRANSFER_BATCHES , paramsMap);
// 3.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("商家转账请求失败");
}
// 4.解析response对象
HashMap<String, String> resultMap = resolverResponse(response);
if (resultMap != null) {
// batch_id微信批次单号,微信商家转账系统返回的唯一标识
return resultMap.get("batch_id");
}
return null;
}
/**
* 解析响应数据
* @param response 发送请求成功后,返回的数据
* @return 微信返回的参数
*/
private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
try {
// 1.获取请求码
int statusCode = response.getStatusLine().getStatusCode();
// 2.获取返回值 String 格式
final String bodyAsString = EntityUtils.toString(response.getEntity());
Gson gson = new Gson();
if (statusCode == 200) {
// 3.如果请求成功则解析成Map对象返回
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap;
} else {
if (StringUtils.isNoneBlank(bodyAsString)) {
log.error("商户转账请求失败,提示信息:{}", bodyAsString);
// 4.请求码显示失败,则尝试获取提示信息
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
throw new CommonException(resultMap.get("message"));
}
log.error("商户转账请求失败,未查询到原因,提示信息:{}", response);
// 其他异常,微信也没有返回数据,这就需要具体排查了
throw new IOException("request failed");
}
} catch (Exception e) {
e.printStackTrace();
throw new CommonException(e.getMessage());
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取请求对象(Post请求)
*
* @param wxPayConfig 微信配置类
* @param apiType 接口请求地址
* @param paramsMap 请求参数
* @return Post请求对象
*/
private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
// 1.设置请求地址
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));
// 2.设置请求数据
Gson gson = new Gson();
String jsonParams = gson.toJson(paramsMap);
// 3.设置请求信息
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
return httpPost;
}
/**
* 封装转账请求参数
*
* @param wxPayConfig 微信的配置文件
* @param param 批量转账请求数据
* @return 封装后的map对象
*/
private static Map<String, Object> getParams(WxPayConfig wxPayConfig, WechatTransferBatchesParam param) {
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("out_batch_no", param.getBatchId());
paramsMap.put("batch_name", param.getTitle());
paramsMap.put("batch_remark", param.getRemark());
paramsMap.put("total_amount", param.getTotalMoney().multiply(new BigDecimal("100")).intValue());
paramsMap.put("total_num", param.getTransferDetailList().size());
// 存储转账明细,一次最多三千笔
if (param.getTransferDetailList().size() > 3000) {
throw new CommonException("发起批量转账一次最多三千笔");
}
List<Map<String, Object>> detailList = new ArrayList<>();
for (WechatTransferBatchesParam.transferDetail detail : param.getTransferDetailList()) {
Map<String, Object> detailMap = new HashMap<>();
detailMap.put("out_detail_no",detail.getBatchId());
detailMap.put("transfer_amount",detail.getTotalDetailMoney().multiply(new BigDecimal("100")).intValue());
detailMap.put("transfer_remark",detail.getDetailRemark());
detailMap.put("openid",detail.getOpenid());
detailList.add(detailMap);
}
paramsMap.put("transfer_detail_list", detailList);
return paramsMap;
}
}
本人针对支付方式做了一个简单的通用类(包含native方式、jsapi方式、app方式),如果用不到可不采用
支付通用参数
/**
* @author lihao
* @create 2023-03-22 17:48
* @desc 微信支付通用参数
**/
@Data
public class WeChatBasePayData {
/**
* 商品描述
*/
private String title;
/**
* 商家订单号,对应 out_trade_no
*/
private String orderId;
/**
* 订单金额
*/
private BigDecimal price;
/**
* 回调地址
*/
private String notify;
}
微信支付通用配置
/**
* @author lihao
* @create 2023-03-22 17:45
* @desc 微信通用配置
**/
@Slf4j
public class WxPayCommon {
/**
* 创建微信支付订单-Native方式
*
* @param wxPayConfig 微信配置信息
* @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
* @param wxPayClient 微信请求客户端
* @return 微信支付二维码地址
*/
public static String wxNativePay(WxPayConfig wxPayConfig, WeChatBasePayData basePayData, CloseableHttpClient wxPayClient) {
// 1.获取请求参数的Map格式
Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);
// 2.获取请求对象
HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.NATIVE_PAY, paramsMap);
// 3.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 4.解析response对象
HashMap<String, String> resultMap = resolverResponse(response);
if (resultMap != null) {
// native请求返回的是二维码链接,前端将链接转换成二维码即可
return resultMap.get("code_url");
}
return null;
}
/**
* 创建微信支付订单-jsapi方式
*
* @param wxPayConfig 微信配置信息
* @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
* @param openId 通过微信小程序或者公众号获取到用户的openId
* @param wxPayClient 微信请求客户端
* @return 微信支付二维码地址
*/
public static String wxJsApiPay(WxPayConfig wxPayConfig, WeChatBasePayData basePayData, String openId,CloseableHttpClient wxPayClient) {
// 1.获取请求参数的Map格式
Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);
// 1.1 添加支付者信息
Map<String,String> payerMap = new HashMap();
payerMap.put("openid",openId);
paramsMap.put("payer",payerMap);
// 2.获取请求对象
HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.JSAPI_PAY, paramsMap);
// 3.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 4.解析response对象
HashMap<String, String> resultMap = resolverResponse(response);
if (resultMap != null) {
// native请求返回的是二维码链接,前端将链接转换成二维码即可
return resultMap.get("prepay_id");
}
return null;
}
/**
* 创建微信支付订单-APP方式
*
* @param wxPayConfig 微信配置信息
* @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
* @param wxPayClient 微信请求客户端
* @return 微信支付二维码地址
*/
public static String wxAppPay(WxPayConfig wxPayConfig, WeChatBasePayData basePayData, CloseableHttpClient wxPayClient) {
// 1.获取请求参数的Map格式
Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);
// 2.获取请求对象
HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.APP_PAY, paramsMap);
// 3.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new CommonException("微信支付请求失败");
}
// 4.解析response对象
HashMap<String, String> resultMap = resolverResponse(response);
if (resultMap != null) {
// native请求返回的是二维码链接,前端将链接转换成二维码即可
return resultMap.get("prepay_id");
}
return null;
}
/**
* 解析响应数据
* @param response 发送请求成功后,返回的数据
* @return 微信返回的参数
*/
private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
try {
// 1.获取请求码
int statusCode = response.getStatusLine().getStatusCode();
// 2.获取返回值 String 格式
final String bodyAsString = EntityUtils.toString(response.getEntity());
Gson gson = new Gson();
if (statusCode == 200) {
// 3.如果请求成功则解析成Map对象返回
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap;
} else {
if (StringUtils.isNoneBlank(bodyAsString)) {
log.error("微信支付请求失败,提示信息:{}", bodyAsString);
// 4.请求码显示失败,则尝试获取提示信息
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
throw new CommonException(resultMap.get("message"));
}
log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
// 其他异常,微信也没有返回数据,这就需要具体排查了
throw new IOException("request failed");
}
} catch (Exception e) {
e.printStackTrace();
throw new CommonException(e.getMessage());
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取请求对象(Post请求)
*
* @param wxPayConfig 微信配置类
* @param apiType 接口请求地址
* @param paramsMap 请求参数
* @return Post请求对象
*/
private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
// 1.设置请求地址
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));
// 2.设置请求数据
Gson gson = new Gson();
String jsonParams = gson.toJson(paramsMap);
// 3.设置请求信息
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
return httpPost;
}
/**
* 封装基础通用请求参数
*
* @param wxPayConfig 微信的配置文件
* @param basePayData 微信支付基础请求数据
* @return 封装后的map对象
*/
private static Map<String, Object> getBasePayParams(WxPayConfig wxPayConfig, WeChatBasePayData basePayData) {
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("mchid", wxPayConfig.getMchId());
// 如果商品名称过长则截取
String title = basePayData.getTitle().length() > 62 ? basePayData.getTitle().substring(0, 62) : basePayData.getTitle();
paramsMap.put("description", title);
paramsMap.put("out_trade_no", basePayData.getOrderId());
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain());
Map<String, Integer> amountMap = new HashMap<>();
amountMap.put("total", basePayData.getPrice().multiply(new BigDecimal("100")).intValue());
paramsMap.put("amount", amountMap);
return paramsMap;
}
}