要使用Java集成API V3微信支付,你需要按照以下步骤操作:
-
准备工作
APPID:绑定支付的APPID(必须配置)
小程序和JSAPI支付需要公众号的APPID
APP支付需要微信支付绑定的APP的APPID
MCHID:商户号(必须配置)
KEY:商户支付密钥,参考开户邮件设置(必须配置)
APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置) -
引入相关依赖:在项目的pom.xml文件中添加以下依赖项:
3.完整代码
1、引入wxPay SDK
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
2、微信支付统一入口
@Data
public class wechatPayGoodsDto {
/**
* 币种 CNY
*/
private String currency;
/**
* 订单描述
*/
private String description;
/**
* 回调通知接口
*/
private String notifyUrl;
/**
* 支付方式
*/
private String type;
/**
* 支付金额
*/
private Integer money;
/**
* 场景类型:IOS, Android, Wap
*/
private String mobileType;
/**
* 订单号
*/
private String orderCode;
/**
* openId
*/
private String openId;
/**
* unionId
*/
private String unionId;
}
/**
* @Author:
* @Description:
**/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/pay")
@Tag(name = "支付统一管理表")
public class WechatPayCommonController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WechatPayRequest wechatPayRequest;
/**
* 无需应答签名
*/
@Resource
private CloseableHttpClient wxPayNoSignClient;
/**
* type:h5、jsapi、app、native、sub_jsapi
*
* @param wechatPayGoodsDto
* @return
*/
@Operation(summary = "统一下单-统一接口")
@PostMapping("/transactions")
public R<Map<String, Object>> transactions(@RequestBody WechatPayGoodsDto wechatPayGoodsDto, HttpServletRequest request) {
String type = wechatPayGoodsDto.getType().toLowerCase();
// 统一参数封装
Map<String, Object> params = new HashMap<>(8);
params.put("appid", type.equals(WechatPayUrlEnum.APP.getType())
? wechatPayConfig.getAppAppId() : wechatPayConfig.getAppId());
params.put("mchid", wechatPayConfig.getMchId());
params.put("description", wechatPayGoodsDto.getDescription());
params.put("out_trade_no", wechatPayGoodsDto.getOrderCode());
params.put("notify_url", wechatPayGoodsDto.getNotifyUrl());
Map<String, Object> amountMap = new HashMap<>(4);
amountMap.put("total", wechatPayGoodsDto.getMoney());// 金额单位为分
amountMap.put("currency", wechatPayGoodsDto.getCurrency());
params.put("amount", amountMap);
// 场景信息
Map<String, Object> sceneInfoMap = new HashMap<>(4);
// 客户端IP
sceneInfoMap.put("payer_client_ip", IpUtilV2.getIp(request));
// 除H5与JSAPI有特殊参数外,其他的支付方式都一样
if (type.equals(WechatPayUrlEnum.H5.getType())) {
sceneInfoMap.put("h5_info", MapBuilder.create().put("type", wechatPayGoodsDto.getMobileType()).build());
} else if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
params.put("payer", MapBuilder.create().put("openid", wechatPayGoodsDto.getOpenId()).build());
}
params.put("scene_info", sceneInfoMap);
String paramsStr = JSON.toJSONString(params);
// 重写type值,因为小程序会多一个下划线(sub_type)
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 R.ok(resMap);
}
private Map<String, Object> paySignMsg(Map<String, Object> map, String type) {
// 设置签名信息,Native与H5不需要
if (type.equals(WechatPayUrlEnum.H5.getType()) || type.equals(WechatPayUrlEnum.NATIVE.getType())) {
return null;
}
long timeMillis = System.currentTimeMillis();
String appId = type.equals(WechatPayUrlEnum.APP.getType()) ? wechatPayConfig.getAppAppId() : 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<>();
// JSAPI、SUB_JSAPI(小程序)
if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
resMap.put("nonceStr", nonceStr);
resMap.put("timeStamp", timeStamp);
resMap.put("appId", appId);
resMap.put("package", packageStr);
// 使用字段appId、timeStamp、nonceStr、package进行签名
String paySign = createSign(resMap);
resMap.put("paySign", paySign);
resMap.put("signType", "HMAC-SHA256");
// resMap.put("signType", "RSA");
}
// APP
if (type.equals(WechatPayUrlEnum.APP.getType())) {
return prepayWithRequestPayment(prepayId);
}
return resMap;
}
public String getSign(String message) {
byte[] sign;
try {
Signature signature = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = wechatPayConfig.getPrivateKey();
signature.initSign(privateKey);
signature.update(message.getBytes(StandardCharsets.UTF_8));
sign = signature.sign();
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
return Base64.getEncoder().encodeToString(sign);
}
public Map<String, Object> prepayWithRequestPayment(String prepayId) {
long timestamp = Instant.now().getEpochSecond();
String nonceStr = RandomUtil.randomNumbers(32);
String appId = wechatPayConfig.getAppAppId();
String message = appId + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayId + "\n";
Map<String, Object> resMap = new HashMap<>();
String sign = getSign(message);
resMap.put("appid", appId);
resMap.put("timeStamp", timestamp);
resMap.put("nonceStr", nonceStr);
resMap.put("prepayid", prepayId);
resMap.put("package", "Sign=WXPay");
resMap.put("partnerid", wechatPayConfig.getMchId());
resMap.put("signType", "HMAC-SHA256");
resMap.put("paySign", sign);
resMap.put("sign", sign);
System.out.println("resMap: " + resMap);
return resMap;
}
/**
* 获取加密数据
*/
private String createSign(Map<String, Object> params) {
try {
List<String> signList = new ArrayList<>(5);
for (Map.Entry<String, Object> entry : params.entrySet()) {
signList.add(entry.getKey() + "=" + entry.getValue());
}
String signStr = String.join("&", signList);
signStr = signStr + "&key=" + wechatPayConfig.getApiV3Key();
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("加密失败!");
}
}
/**
* TODO 如果是扫码支付时,该接口就很有必要,应该前端通过轮询的方式请求该接口查询订单是否支付成功
*
* @param orderNo
* @return
*/
@Operation(summary = "根据订单号查询订单-统一接口")
@GetMapping("/transactions/{orderNo}")
public R<Map<String, Object>> transactionsByOrderNo(@PathVariable("orderNo") String orderNo) {
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.ORDER_QUERY_BY_NO.getType().concat(orderNo))
.concat("?mchid=").concat(wechatPayConfig.getMchId());
String res = wechatPayRequest.wechatHttpGet(url);
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 = StrUtil.toString(resMap.get("trade_state"));
return R.ok(resMap);
}
/**
* 关闭(取消)订单
* TODO 用于在客户下单后,不进行支付,取消订单的场景
*
* @param orderNo
* @return
*/
@Operation(summary = "关闭(取消)订单-统一接口")
@GetMapping("/closeOrder/{orderNo}")
public R<String> closeOrder(@PathVariable("orderNo") String 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);
String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
return R.ok(res);
}
/**
* 申请退款
*
* @param wechatReturnMoneyDto
*/
@Operation(summary = "申请退款-统一接口")
@PostMapping("/refundOrder")
public R<String> refundOrder(@RequestBody WechatReturnMoneyDto wechatReturnMoneyDto) {
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS.getType());
Map<String, Object> params = new HashMap<>(2);
params.put("out_trade_no", wechatReturnMoneyDto.getOrderCode()); // 订单编号
params.put("out_refund_no", wechatReturnMoneyDto.getReturnCode()); // 退款单编号 - 自定义
params.put("reason", wechatReturnMoneyDto.getReason());// 退款原因
params.put("notify_url", wechatReturnMoneyDto.getRefundNotifyUrl()); // 退款通知回调地址
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("refund", wechatReturnMoneyDto.getRefund()); //退款金额,单位:分
amountMap.put("total", wechatReturnMoneyDto.getTotal()); //原订单金额,单位:分
amountMap.put("currency", wechatReturnMoneyDto.getCurrency()); //退款币种
params.put("amount", amountMap);
String paramsStr = JSON.toJSONString(params);
String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
return R.ok(res);
}
/**
* 查询单笔退款信息
*
* @param refundNo
* @return
*/
@Operation(summary = "查询单笔退款信息-统一接口")
@GetMapping("/queryRefundOrder/{refundNo}")
public Map<String, Object> queryRefundOrder(@PathVariable("refundNo") String refundNo) {
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo));
String res = wechatPayRequest.wechatHttpGet(url);
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();
/**
* 款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
* 枚举值:
* SUCCESS:退款成功
* CLOSED:退款关闭
* PROCESSING:退款处理中
* ABNORMAL:退款异常
*/
String status = resMap.get("status").toString();
/**
* 枚举值:
* ORIGINAL:原路退款
* BALANCE:退回到余额
* OTHER_BALANCE:原账户异常退到其他余额账户
* OTHER_BANKCARD:原银行卡异常退到其他银行卡
*/
String channel = resMap.get("channel").toString();
// TODO 在查询单笔退款信息时,可以再去查询一次订单的状态,保证该订单已经退款完毕了
return resMap;
}
/**
* 申请交易账单
*
* @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请 ,如果传入日期未为当天则会出错
* @param billType 分为:ALL、SUCCESS、REFUND
* ALL:返回当日所有订单信息(不含充值退款订单)
* SUCCESS:返回当日成功支付的订单(不含充值退款订单)
* REFUND:返回当日退款订单(不含充值退款订单)
* @return
*/
@Operation(summary = "申请交易账单-统一接口")
@GetMapping("/tradeBill")
public String tradeBill(@RequestParam("billDate") String billDate, @RequestParam("billType") String 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);
Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
});
String downloadUrl = resMap.get("download_url").toString();
return downloadUrl;
}
/**
* @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
* @param accountType 分为:BASIC、OPERATION、FEES
* BASIC:基本账户
* OPERATION:运营账户
* FEES:手续费账户
* @return
*/
@Operation(summary = "申请资金账单-统一接口")
@GetMapping("/fundFlowBill")
public String fundFlowBill(@RequestParam("billDate") String billDate, @RequestParam("accountType") String 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);
Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
});
String downloadUrl = resMap.get("download_url").toString();
return downloadUrl;
}
@Operation(summary = "下载账单-统一接口")
@GetMapping("/downloadBill")
public void downloadBill(String downloadUrl) {
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = null;
try {
//使用wxPayClient发送请求得到响应
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);
}
// TODO 将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 = "D:\\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();
}
}
}
/回调通知
private final ReentrantLock lock = new ReentrantLock();
@Resource
private WechatPayCommons wechatPayCommons;
/**
* 支付回调应该写在各个服务中,进行对应的处理,回调地址,通过服务传递到pay中
*
* @param request
* @param response
* @return
*/
@Operation(summary = "支付回调")
@PostMapping("/payNotify")
public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("支付回调");
// 处理通知参数
String body = HttpUtils.readData(request);
Map<String, String> headers = HttpUtils.getHeaders(request);
Map<String, Object> bodyMap = wechatPayCommons.getNotifyBody(body, headers);
if (bodyMap == null) {
return ResponseCommon.falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
String orderNo = resourceMap.get("out_trade_no").toString();
String transactionId = resourceMap.get("transaction_id").toString();
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return ResponseCommon.trueMsg(response);
}
@Operation(summary = "退款回调")
@PostMapping("/refundNotify")
public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("退款回调");
// 处理通知参数
String body = HttpUtils.readData(request);
Map<String, String> headers = HttpUtils.getHeaders(request);
Map<String, Object> bodyMap = wechatPayCommons.getNotifyBody(body, headers);
if (bodyMap == null) {
return ResponseCommon.falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
String orderNo = resourceMap.get("out_trade_no").toString();
String transactionId = resourceMap.get("transaction_id").toString();
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return ResponseCommon.trueMsg(response);
}
/**
* 微信回调信息解密
*
* @param resource 加密的回调信息
* @return R
*/
@Operation(summary = "解密微信回调信息")
@GetMapping("/decryptFromResource")
public R<Map<String, Object>> decryptFromResource(@RequestParam String resource) {
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
return R.ok(resourceMap);
}
/**
* 解析微信支付回调信息
*
* @param body
* @param headers
* @return
*/
@Operation(summary = "解密微信回调信息")
@GetMapping("/getNotifyBody")
R<Map<String, Object>> getNotifyBody(@RequestParam(name = "body") String body, @RequestParam(name = "headers") String headers) {
Map<String, String> headerMap = (Map<String, String>) JSONObject.parse(headers);
Map<String, Object> bodyMap = wechatPayCommons.getNotifyBody(body, headerMap);
return R.ok(bodyMap);
}
@Operation(summary = "验签获取微信返回内容")
@GetMapping("/validNotifyBody")
R<Map<String, Object>> validNotifyBody(@RequestParam(name = "body") String body, @RequestParam(name = "headers") String headers) {
Map<String, String> headerMap = (Map<String, String>) JSONObject.parse(headers);
Map<String, Object> bodyMap = wechatPayCommons.validNotifyBody(body, headerMap);
return R.ok(bodyMap);
}
}
2、相关的配置类
/**
* @Author:
* @Description:
**/
@Slf4j
public class WechatPayValidator {
/**
* 应答超时时间,单位为分钟
*/
private static final long RESPONSE_EXPIRED_MINUTES = 5;
private final Verifier verifier;
private final String requestId;
private final String body;
public WechatPayValidator(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(Map<String, String> headers) {
try {
//处理请求参数
validateParameters(headers);
//构造验签名串
String message = buildMessage(headers);
String serial = headers.get(WechatPayHttpHeaders.WECHAT_PAY_SERIAL);
String signature = headers.get(WechatPayHttpHeaders.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;
}
private void validateParameters(Map<String, String> headerMap) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WechatPayHttpHeaders.WECHAT_PAY_SERIAL, WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE, WechatPayHttpHeaders.WECHAT_PAY_NONCE, WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = headerMap.get(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);
}
}
private String buildMessage(Map<String, String> headers) {
String timestamp = headers.get(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP);
String nonce = headers.get(WechatPayHttpHeaders.WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
public static Map<String, Object> decryptFromResource(Map encrypt, String apiV3Key) {
try {
if(!encrypt.containsKey("resource")){
throw new ValidateCodeException("未包含待解密数据");
}
Map<String, String> resource = (Map) encrypt.get("resource");
String ciphertext = resource.get("ciphertext");
String nonce = resource.get("nonce");
String associatedData = resource.get("associated_data");
log.info("密文: {}", ciphertext);
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {});
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 对称解密,异步通知的加密数据
*
* @param resource 加密数据
* @param apiV3Key apiV3密钥
* @param type 1-支付,2-退款
* @return
*/
public static Map<String, Object> decryptFromResource(String resource, String apiV3Key, Integer type) {
String msg = type == 1 ? "支付成功" : "退款成功";
try {
Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {
});
String ciphertext = resourceMap.get("ciphertext");
String nonce = resourceMap.get("nonce");
String associatedData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {
});
} catch (Exception e) {
throw new RuntimeException("回调参数,解密失败!");
}
}
}
/**
* @Author:
* @Description:
**/
@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();
//处理成功,204是,关闭订单时微信返回的正常状态码
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;
}
}
/**
* @Author:
* @Description:
**/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
/**
* 应用编号
*/
// @Value("${wxpay.appId}")
private String appId;
private String appAppId;
/**
* 商户号
*/
private String mchId;
/**
* 服务商商户号
*/
private String slMchId;
/**
* APIv2密钥
*/
private String apiKey;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知回调地址
*/
private String notifyUrl;
/**
* 退款回调地址
*/
private String refundNotifyUrl;
/**
* API 证书中的 key.pem
*/
private String keyPemPath;
/**
* 商户序列号
*/
private String serialNo;
/**
* 微信支付V3-url前缀
*/
private String baseUrl;
/**
* 获取商户的私钥文件
*
* @param keyPemPath
* @return
*/
public PrivateKey getPrivateKey(String keyPemPath) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if (inputStream == null) {
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取商户的私钥文件
*
* @return
*/
public PrivateKey getPrivateKey() {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if (inputStream == null) {
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取证书管理器实例
*
* @return
*/
@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);
}
/**
* 获取支付http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, serialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
}
@Slf4j
@Configuration
public class WechatPayCommons {
@Resource
private Verifier verifier;
@Resource
private WechatPayConfig wechatPayConfig;
public Map<String, Object> validNotifyBody(String body, Map<String, String> headers) {
Map<String, Object> encrypt = this.getNotifyBody(body, headers);
return WechatPayValidator.decryptFromResource(encrypt, wechatPayConfig.getApiV3Key());
}
public Map<String, Object> getNotifyBody(String body, Map<String, String> headers) {
//处理通知参数
// String body = HttpUtils.readData(request);
log.info("回调参数:{}", body);
// 转换为Map
Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
});
String notifyId = bodyMap.get("id").toString(); // 微信的通知ID(通知的唯一ID)
WechatPayValidator wechatPayValidator = new WechatPayValidator(verifier, notifyId, body);// 验证签名信息
if (!wechatPayValidator.validate(headers)) {
throw new SecurityException("通知验签失败");
}
log.info("通知验签成功");
return bodyMap;
}
}