😊 @ 作者: 一恍过去
🎉 @ 主题: 微信支付统一支付接口(H5、JSAPI、H5、App、小程序)
?? @ 创作时间: 2022年07月10日
前言
对微信支付的H5、JSAPI、H5、App、小程序支付方式进行统一,此封装接口适用于普通商户模式支付,如果要进行服务商模式支付可以结合服务商官方API进行参数修改(未验证可行性)。
1、引入POM
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
2、配置Yaml
wxpay:
appId: xxxx
mchId: xxx
apiKey: xxxx
apiV3Key: xxx
baseUrl: https://api.mch.weixin.qq.com/v3
notifyUrl: http://pjm6m9.natappfree.cc/pay/payNotify
refundNotifyUrl: http://pjm6m9.natappfree.cc/pay/refundNotify
keyPemPath: apiclient_key.pem
serialNo: xxxxx
3、配置密钥文件
在商户/服务商平台的”账户中心" => “API安全” 进行API证书、密钥的设置,API证书主要用于获取“商户证书序列号”以及“p12”、“key.pem”、”cert.pem“证书文件,j将获取的apiclient_key.pem 文件放在项目的resources 目录下。
4、配置PayConfig
WechatPayConfig:
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 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.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
private String appId;
private String mchId;
private String slMchId;
private String apiKey;
private String apiV3Key;
private String notifyUrl;
private String refundNotifyUrl;
private String keyPemPath;
private String serialNo;
private String baseUrl;
public PrivateKey getPrivateKey(String keyPemPath){
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if(inputStream==null){
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
PrivateKey privateKey = getPrivateKey(keyPemPath);
PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(mchId);
}
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
return builder.build();
}
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator((response) -> true);
return builder.build();
}
}
5、定义统一枚举
WechatPayUrlEnum:
@AllArgsConstructor
@Getter
public enum WechatPayUrlEnum {
NATIVE("native"),
APP("app"),
H5("h5"),
JSAPI("jsapi"),
SUB_JSAPI("sub_jsapi"),
PAY_TRANSACTIONS("/pay/transactions/"),
NATIVE_PAY_V2("/pay/unifiedorder"),
ORDER_QUERY_BY_NO("/pay/transactions/out-trade-no/"),
CLOSE_ORDER_BY_NO("/pay/transactions/out-trade-no/%s/close"),
DOMESTIC_REFUNDS("/refund/domestic/refunds"),
DOMESTIC_REFUNDS_QUERY("/refund/domestic/refunds/"),
TRADE_BILLS("/bill/tradebill"),
FUND_FLOW_BILLS("/bill/fundflowbill");
private final String type;
}
6、封装统一请求处理
WechatPayRequest:
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
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.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
@Component
@Slf4j
public class WechatPayRequest {
@Resource
private CloseableHttpClient wxPayClient;
public String wechatHttpGet(String url) {
try {
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpGet);
return getResponseBody(response);
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
}
public String wechatHttpPost(String url,String paramsStr) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(paramsStr, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
return getResponseBody(response);
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
}
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
String body = entity==null?"":EntityUtils.toString(entity);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
log.info("成功, 返回结果 = " + body);
} else {
String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
log.error(msg);
throw new RuntimeException(msg);
}
return body;
}
}
7、封装统一代码
7.1、统一下单处理
PayController:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.lhz.demo.pay.WechatPayConfig;
import com.lhz.demo.model.enums.WechatPayUrlEnum;
import com.lhz.demo.pay.WechatPayRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Api(tags = "支付接口(API3)")
@RestController
@RequestMapping("/test")
@Slf4j
public class PayController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WechatPayRequest wechatPayRequest;
@Resource
private CloseableHttpClient wxPayNoSignClient;
@ApiOperation(value = "统一下单-统一接口", notes = "统一下单-统一接口")
@ApiOperationSupport(order = 10)
@GetMapping("/transactions")
public Map<String,Object> transactions(String type) {
log.info("统一下单API,支付方式:{}",type);
Map<String, Object> params = new HashMap<>(8);
params.put("appid", wechatPayConfig.getAppId());
params.put("mchid", wechatPayConfig.getMchId());
params.put("description", "测试商品");
int outTradeNo = new Random().nextInt(999999999);
params.put("out_trade_no", outTradeNo + "");
params.put("notify_url", wechatPayConfig.getNotifyUrl());
Map<String, Object> amountMap = new HashMap<>(4);
amountMap.put("total", 1);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
Map<String, Object> sceneInfoMap = new HashMap<>(4);
sceneInfoMap.put("payer_client_ip", "127.0.0.1");
sceneInfoMap.put("device_id", "127.0.0.1");
if (type.equals(WechatPayUrlEnum.H5.getType())) {
Map<String, Object> h5InfoMap = new HashMap<>(4);
h5InfoMap.put("type", "IOS");
sceneInfoMap.put("h5_info", h5InfoMap);
} else if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
Map<String, Object> payerMap = new HashMap<>(4);
payerMap.put("openid", "123123123");
params.put("payer", payerMap);
}
params.put("scene_info", sceneInfoMap);
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
String[] split = type.split("_");
String newType = split[split.length - 1];
String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType)), paramsStr);
Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>(){});
Map<String, Object> signMap = paySignMsg(resMap, type);
resMap.put("type",type);
resMap.put("signMap",signMap);
return resMap;
}
private Map<String, Object> paySignMsg(Map<String, Object> map,String type){
if(type.equals(WechatPayUrlEnum.H5.getType()) || type.equals(WechatPayUrlEnum.NATIVE.getType()) ){
return null;
}
long timeMillis = System.currentTimeMillis();
String appId = wechatPayConfig.getAppId();
String timeStamp = timeMillis/1000+"";
String nonceStr = timeMillis+"";
String prepayId = map.get("prepay_id").toString();
String packageStr = "prepay_id="+prepayId;
Map<String, Object> resMap = new HashMap<>();
resMap.put("nonceStr",nonceStr);
resMap.put("timeStamp",timeStamp);
if(type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType()) ) {
resMap.put("appId",appId);
resMap.put("package", packageStr);
String paySign = createSign(resMap);
resMap.put("paySign", paySign);
resMap.put("signType", "HMAC-SHA256");
}
if(type.equals(WechatPayUrlEnum.APP.getType())) {
resMap.put("appid",appId);
resMap.put("prepayid", prepayId);
String sign = createSign(resMap);
resMap.put("package", "Sign=WXPay");
resMap.put("partnerid", wechatPayConfig.getMchId());
resMap.put("sign", sign);
resMap.put("signType", "HMAC-SHA256");
}
return resMap;
}
private String createSign(Map<String, Object> params){
try {
Map<String, Object> treeMap = new TreeMap<>(params);
List<String> signList = new ArrayList<>(5);
for (Map.Entry<String, Object> entry : treeMap.entrySet())
{
signList.add(entry.getKey() + "=" + entry.getValue());
}
String signStr = String.join("&", signList);
signStr = signStr+"&key="+wechatPayConfig.getApiV3Key();
System.out.println(signStr);
Mac sha = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(wechatPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha.init(secretKey);
byte[] array = sha.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
signStr = sb.toString().toUpperCase();
System.out.println(signStr);
return signStr;
}catch (Exception e){
throw new RuntimeException("加密失败!");
}
}
}
7.2 、其他接口处理(退款、查询、取消订单等)
PayController:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.lhz.demo.pay.WechatPayConfig;
import com.lhz.demo.model.enums.WechatPayUrlEnum;
import com.lhz.demo.pay.WechatPayRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Api(tags = "支付接口(API3)")
@RestController
@RequestMapping("/test")
@Slf4j
public class PayController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WechatPayRequest wechatPayRequest;
@Resource
private CloseableHttpClient wxPayNoSignClient;
@ApiOperation(value = "根据订单号查询订单-统一接口", notes = "根据订单号查询订单-统一接口")
@ApiOperationSupport(order = 15)
@GetMapping("/transactions/{orderNo}")
public Map<String, Object> transactionsByOrderNo(@PathVariable("orderNo") String orderNo) {
log.info("根据订单号查询订单,订单号: {}", orderNo);
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.ORDER_QUERY_BY_NO.getType().concat(orderNo))
.concat("?mchid=").concat(wechatPayConfig.getMchId());
String res = wechatPayRequest.wechatHttpGet(url);
log.info("查询订单结果:{}",res);
Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});
String outTradeNo = resMap.get("out_trade_no").toString();
String appId = resMap.get("appid").toString();
String mchId = resMap.get("mchid").toString();
String tradeState = resMap.get("trade_state").toString();
log.info("outTradeNo:"+outTradeNo);
log.info("appId:"+appId);
log.info("mchId:"+mchId);
log.info("tradeState:"+tradeState);
return resMap;
}
@ApiOperation(value = "关闭(取消)订单-统一接口", notes = "关闭(取消)订单-统一接口")
@ApiOperationSupport(order = 20)
@PostMapping("/closeOrder/{orderNo}")
public void closeOrder(@PathVariable("orderNo") String orderNo) {
log.info("根据订单号取消订单,订单号: {}", orderNo);
String url = String.format(WechatPayUrlEnum.CLOSE_ORDER_BY_NO.getType(), orderNo);
url = wechatPayConfig.getBaseUrl().concat(url);
Map<String, String> params = new HashMap<>(2);
params.put("mchid", wechatPayConfig.getMchId());
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
String res = wechatPayRequest.wechatHttpPost(url,paramsStr);
}
@ApiOperation(value = "申请退款-统一接口", notes = "申请退款-统一接口")
@ApiOperationSupport(order = 25)
@PostMapping("/refundOrder/{orderNo}")
public void refundOrder(@PathVariable("orderNo") String orderNo) {
log.info("根据订单号申请退款,订单号: {}", orderNo);
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS.getType());
Map<String, Object> params = new HashMap<>(2);
params.put("out_trade_no", orderNo);
int outRefundNo = new Random().nextInt(999999999);
log.info("退款申请号:{}",outRefundNo);
params.put("out_refund_no",outRefundNo+"");
params.put("reason","申请退款");
params.put("notify_url", wechatPayConfig.getRefundNotifyUrl());
Map<String, Object> amountMap =new HashMap<>();
amountMap.put("refund", 1);
amountMap.put("total", 1);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
String res = wechatPayRequest.wechatHttpPost(url,paramsStr);
log.info("退款结果:{}",res);
}
@ApiOperation(value = "查询单笔退款信息-统一接口", notes = "查询单笔退款信息-统一接口")
@ApiOperationSupport(order = 30)
@GetMapping("/queryRefundOrder/{refundNo}")
public Map<String, Object> queryRefundOrder(@PathVariable("refundNo") String refundNo) {
log.info("根据订单号查询退款订单,订单号: {}", refundNo);
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo));
String res = wechatPayRequest.wechatHttpGet(url);
log.info("查询退款订单结果:{}",res);
Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});
String successTime = resMap.get("success_time").toString();
String refundId = resMap.get("refund_id").toString();
String status = resMap.get("status").toString();
String channel = resMap.get("channel").toString();
log.info("successTime:"+successTime);
log.info("channel:"+channel);
log.info("refundId:"+refundId);
log.info("status:"+status);
return resMap;
}
@ApiOperation(value = "申请交易账单-统一接口", notes = "申请交易账单-统一接口")
@ApiOperationSupport(order = 35)
@GetMapping("/tradeBill")
public String tradeBill(@RequestParam("billDate") String billDate, @RequestParam("billType") String billType) {
log.info("申请交易账单,billDate:{},billType:{}", billDate,billType);
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.TRADE_BILLS.getType())
.concat("?bill_date=").concat(billDate).concat("&bill_type=").concat(billType);
String res = wechatPayRequest.wechatHttpGet(url);
log.info("查询退款订单结果:{}",res);
Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});
String downloadUrl = resMap.get("download_url").toString();
return downloadUrl;
}
@ApiOperation(value = "申请资金账单-统一接口", notes = "申请资金账单-统一接口")
@ApiOperationSupport(order = 40)
@GetMapping("/fundFlowBill")
public String fundFlowBill(@RequestParam("billDate") String billDate, @RequestParam("accountType") String accountType) {
log.info("申请交易账单,billDate:{},accountType:{}", billDate,accountType);
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.FUND_FLOW_BILLS.getType())
.concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType);
String res = wechatPayRequest.wechatHttpGet(url);
log.info("查询退款订单结果:{}",res);
Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});
String downloadUrl = resMap.get("download_url").toString();
return downloadUrl;
}
@ApiOperation(value = "下载账单-统一接口", notes = "下载账单-统一接口")
@ApiOperationSupport(order = 45)
@GetMapping("/downloadBill")
public void downloadBill(String downloadUrl) {
log.info("下载账单,下载地址:{}",downloadUrl);
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response =null;
try {
response = wxPayNoSignClient.execute(httpGet);
String body = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200 || statusCode == 204) {
log.info("下载账单,返回结果 = " + body);
} else {
throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + body);
}
writeStringToFile(body);
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
finally {
if(response!=null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void writeStringToFile(String body) {
FileWriter fw = null;
try {
String filePath = "C:\\Users\\lhz12\\Desktop\\wxPay.txt";
fw = new FileWriter(filePath, true);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(body);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(fw!=null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|