分销后台相关接口 现金充值 记录明细

This commit is contained in:
张松
2025-10-27 10:31:09 +08:00
parent 6c3f5e22d7
commit ac002cb1dd
27 changed files with 1406 additions and 582 deletions

View File

@@ -0,0 +1,633 @@
package com.czg.service.Impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.czg.exception.CzgException;
import com.czg.service.RedisService;
import com.czg.system.service.SysParamsService;
import com.czg.system.service.WxService;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.RsaKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.enums.v3.OtherApiEnum;
import com.ijpay.wxpay.enums.v3.ProfitSharingApiEnum;
import com.ijpay.wxpay.model.ReceiverModel;
import com.ijpay.wxpay.model.v3.*;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* 微信支付service
* @author Administrator
*/
@DubboService
@Slf4j
public class WxServiceImpl implements WxService {
@Resource
private RedisService autoRedisService;
@DubboReference
private SysParamsService paramsService;
private static RedisService redisService;
@PostConstruct
public void init() {
// 小程序id
appId = paramsService.getSysParamValue("shop_wx_appid");
// 小程序secrete
appSecret = paramsService.getSysParamValue("shop_wx_secrete");
certPath = "";
// 微信支付公钥
pubKey = paramsService.getSysParamValue("wx_pub_key");
// api支付证书私钥
apiCertKey = paramsService.getSysParamValue("wx_apiclient_key");
// api支付证书公钥
apiCert = paramsService.getSysParamValue("wx_apiclient_cert");
try {
privateKey = RsaKit.loadPrivateKey(apiCertKey);
} catch (Exception e) {
log.warn("微信加载api证书失败");
}
// 平台证书
platformCertPath = "";
// 平台证书编号
platformCertNo = "";
// 商户号
mchId = paramsService.getSysParamValue("wx_mch_id");
// v3密钥
apiV3Key = paramsService.getSysParamValue("wx_v3_key");
apiV2Key = "";
// 回调地址
notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay";
refundNotifyUrl = "";
transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer";
redisService = this.autoRedisService;
}
// static copies for use in static methods
private static String appId;
private static String appSecret;
private static String certPath;
private static String pubKey;
private static String apiCertKey;
private static PrivateKey privateKey;
private static String apiCert;
private static String platformCertPath;
private static String platformCertNo;
private static String mchId;
private static String apiV3Key;
private static String apiV2Key;
private static String notifyUrl;
private static String refundNotifyUrl;
private static String transferNotifyUrl;
private static String getAccessToken(boolean refresh) {
Object token = redisService.get("access_token");
if (!refresh && token instanceof String) {
return (String) token;
}
String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token",
Map.of("grant_type", "client_credential", "appid", appId, "secret", appSecret)
);
log.info("获取access_token响应: {}", response);
JSONObject jsonObject = JSONObject.parseObject(response);
String accessToken = jsonObject.getString("access_token");
if (accessToken == null) {
throw new RuntimeException("获取access_token失败");
}
Long expiresIn = jsonObject.getLong("expires_in");
if (expiresIn == null) {
expiresIn = 7200L;
}
redisService.set("access_token", accessToken, expiresIn - 200);
return accessToken;
}
@Override
public String getPhone(String code) {
String requestBody = JSONObject.toJSONString(Map.of("code", code));
String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody);
log.info("获取手机号响应: {}", response);
JSONObject jsonObject = JSONObject.parseObject(response);
Integer errCode = jsonObject.getInteger("errcode");
if (Integer.valueOf(0).equals(errCode)) {
return jsonObject.getJSONObject("phone_info").getString("phoneNumber");
} else if (Integer.valueOf(40001).equals(errCode)) {
getAccessToken(true);
}
throw new RuntimeException("获取手机号失败");
}
@Override
public String getOpenId(String code) {
String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session",
Map.of("appid", appId, "secret", appSecret, "js_code", code, "grant_type", "authorization_code")
);
log.info("获取openId响应: {}", response);
JSONObject jsonObject = JSONObject.parseObject(response);
String openId = jsonObject.getString("openid");
if (openId != null && !openId.isBlank()) {
return openId;
}
throw new RuntimeException("获取openId失败");
}
/**
* 使用微信支付平台证书公钥加密敏感信息OAEP
*/
private static String encryptByPlatformCert(String content) {
try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertPath)) {
if (certStream == null) {
throw new RuntimeException("未找到微信支付平台证书文件: " + platformCertPath);
}
X509Certificate certificate = PayKit.getCertificate(certStream);
if (certificate == null) throw new RuntimeException("读取证书失败");
return PayKit.rsaEncryptOAEP(content, certificate);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getSerialNumberFromPem(String certContent) {
try {
// 去掉 PEM 头尾并清理空格换行
String pem = certContent
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.replaceAll("\\s+", "");
// Base64 解码
byte[] certBytes = Base64.getDecoder().decode(pem);
try (ByteArrayInputStream bis = new ByteArrayInputStream(certBytes)) {
X509Certificate certificate = PayKit.getCertificate(bis);
if (certificate != null) {
String serialNo = certificate.getSerialNumber()
.toString(16)
.toUpperCase(Locale.getDefault());
System.out.println("证书序列号:" + serialNo);
return serialNo;
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("无效的证书", e);
}
return null;
}
public static void main(String[] args) {
// // 文件路径(支持绝对路径或相对路径)
// String filePath = "D:/certs/apiclient_cert.pem";
// try (var certStream = new FileInputStream("E:\\workspace\\cashier_server1\\cash-api\\order-server\\src\\main\\resources\\cert.pem")) {
// X509Certificate certificate = PayKit.getCertificate(certStream);
// if (certificate != null) {
// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2);
// log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
// log.info("证书序列号: {}", serialNo);
// }
// } catch (Exception e) {
// log.error("读取证书失败", e);
// }
getSerialNumberFromPem("-----BEGIN CERTIFICATE-----\n" +
"MIIEJDCCAwygAwIBAgIUctkT9cVeYPVI02uqwzEw2Xg/QLQwDQYJKoZIhvcNAQEL\n" +
"BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n" +
"FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n" +
"Q0EwHhcNMjUxMDI1MDI0NzU3WhcNMzAxMDI0MDI0NzU3WjB+MRMwEQYDVQQDDAox\n" +
"NzMwMDkxMjAzMRswGQYDVQQKDBLlvq7kv6HllYbmiLfns7vnu58xKjAoBgNVBAsM\n" +
"IemZleilv+i2heaOjOafnOenkeaKgOaciemZkOWFrOWPuDELMAkGA1UEBhMCQ04x\n" +
"ETAPBgNVBAcMCFNoZW5aaGVuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" +
"AQEAqCcrDNKTGCBk2euqL6ENu2akxEdmlQQjlf8AVKdGA4nPp0GkWF+WQxFFxAHo\n" +
"jOLSeOUoxqQ5Nr9wa14Nfv3m6R4UIo+JeG0TkNyzum4GRokwoK70vQa8IXZW9vmM\n" +
"lVce/2mxb8sxcnVfAABPfo2A7L14PKPMvAPF4HgJOi5tnMco1sVlg4uwxejoMl7q\n" +
"CiE6lozFW2IRaZZrtmBJMW3oSxCGuLtaWmkXDyj4tLzAJspDxNMQHCVNzYodPREA\n" +
"OP9Z1Wy2v0xIseoxGnxgefE7LpF5jCZiGLkiOg1Xa79tpX02zZ22A8JvTfPWhKJJ\n" +
"nYV0r3PZi/32IU0bBmURYjuh5QIDAQABo4G5MIG2MAkGA1UdEwQCMAAwCwYDVR0P\n" +
"BAQDAgP4MIGbBgNVHR8EgZMwgZAwgY2ggYqggYeGgYRodHRwOi8vZXZjYS5pdHJ1\n" +
"cy5jb20uY24vcHVibGljL2l0cnVzY3JsP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFE\n" +
"Mzk3NTQ5ODQ2QzAxQzNFOEVCRDImc2c9SEFDQzQ3MUI2NTQyMkUxMkIyN0E5RDMz\n" +
"QTg3QUQxQ0RGNTkyNkUxNDAzNzEwDQYJKoZIhvcNAQELBQADggEBAKLBRR+E8hrK\n" +
"hQB+T0GIqQBgkFaCiakylUbw+jdEdQxJD8gO14tHSOTkqOMf5cZONnZiff2rIn+Y\n" +
"kFjR/lO//v5PsTIsby+siwnoc4MOz9tJ5qNUlleuXi3i/Fb1kU9nW6HC8GJdjZxG\n" +
"YqE3UyS23SG5VnrNeBplLHWS3ttmzduNKbl+180R5SoMIyCT35k0EStcZPlVrXnt\n" +
"MqJftxS2Jl1lDHer62tOTgKRGZFfS6z0DOD97mJfyRJ5agUnJTD0L1LCb3lefNRV\n" +
"KgmAkkE7q9cRRIKf8SifdvFDaGhFFi1ezXI/vorSh3PnpMqd8SCc81/IhWmm09RA\n" +
"RLdEZXoPSmM=\n" +
"-----END CERTIFICATE-----\n");
}
private static String getSerialNumber() {
try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certPath)) {
X509Certificate certificate = PayKit.getCertificate(certStream);
if (certificate != null) {
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2);
log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
log.info("证书序列号: {}", serialNo);
return serialNo;
}
} catch (Exception e) {
log.error("读取证书失败", e);
}
return null;
}
private static String getPlatSerialNumber() {
try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertNo)) {
X509Certificate certificate = PayKit.getCertificate(certStream);
if (certificate != null) {
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2);
log.info("平台证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
log.info("平台证书序列号: {}", serialNo);
return serialNo;
}
} catch (Exception e) {
log.error("读取平台证书失败", e);
}
return null;
}
public static JSONObject verifySignature(HttpServletRequest request) {
try {
log.info("开始校验签名并解密");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
String result = request.getReader().lines().reduce((a, b) -> a + b).orElse("");
String resp = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, apiV3Key, platformCertPath);
log.info("解密明文{}", resp);
return JSONObject.parseObject(resp);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void getPlatCert() throws Exception {
String response = WxPayApi.v3(
RequestMethodEnum.GET,
WxDomainEnum.CHINA.toString(),
OtherApiEnum.GET_CERTIFICATES.toString(),
mchId,
getSerialNumber(),
null,
apiCertKey,
""
).getBody();
JSONObject jsonObject = JSONObject.parseObject(response);
var dataArray = jsonObject.getJSONArray("data");
var encryptObject = dataArray.getJSONObject(0);
var encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
String associatedData = encryptCertificate.getString("associated_data");
String cipherText = encryptCertificate.getString("ciphertext");
String nonce = encryptCertificate.getString("nonce");
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String publicKey = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText);
log.info(publicKey);
}
@Override
public Map<String, String> v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) {
if (desc == null) desc = "订单支付";
UnifiedOrderModel model = new UnifiedOrderModel();
model.setAppid(appId);
model.setMchid(mchId);
model.setDescription(desc);
model.setOut_trade_no(tradeNo);
int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact();
model.setAmount(new Amount(total, "CNY"));
model.setNotify_url(notifyUrl + "/" + type);
model.setPayer(new Payer(openId, null, null));
String payInfo = JSONObject.toJSONString(model);
log.info("统一下单参数: {}", payInfo);
IJPayHttpResponse resp = null;
try {
resp = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.JS_API_PAY.toString(),
mchId,
getSerialNumberFromPem(apiCert),
null,
privateKey,
payInfo
);
log.info("统一下单响应: {}", resp);
String body = resp.getBody();
JSONObject jsonObject = JSONObject.parseObject(body);
String prepayId = jsonObject.getString("prepay_id");
if (StrUtil.isBlank(prepayId)) {
throw new RuntimeException(jsonObject.getString("message"));
}
return WxPayKit.jsApiCreateSign(appId, prepayId, privateKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Map<String, String> v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) {
Map<String, String> payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder()
.appid(appId)
.mch_id(mchId)
.body(desc)
.out_trade_no(tradeNo)
.total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "")
.fee_type("CNY")
.notify_url(notifyUrl)
.openid(openId)
.trade_type("JSAPI")
.build()
.createSign(apiV2Key, SignType.MD5);
log.info("统一下单参数: {}", payModel);
String resp = WxPayApi.pushOrder(false, payModel);
log.info("统一下单响应: {}", resp);
Map<String, String> resultMap = WxPayKit.xmlToMap(resp);
String returnCode = resultMap.get("return_code");
String returnMsg = resultMap.get("return_msg");
if (!WxPayKit.codeIsOk(returnCode)) {
throw new CzgException(returnMsg);
}
String resultCode = resultMap.get("result_code");
if (!WxPayKit.codeIsOk(resultCode)) {
throw new CzgException(returnMsg);
}
String prepayId = resultMap.get("prepay_id");
return WxPayKit.prepayIdCreateSign(prepayId, appId, appSecret, SignType.MD5);
}
public static String refund(String tradeNo, String refundTradeNo, BigDecimal amount) {
int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact();
RefundModel model = new RefundModel();
model.setOut_trade_no(tradeNo);
model.setOut_refund_no(refundTradeNo);
model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount));
model.setNotify_url(refundNotifyUrl);
String info = JSONObject.toJSONString(model);
log.info("统一退款参数: {}", info);
IJPayHttpResponse response;
try {
response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.REFUND.toString(),
mchId,
getSerialNumber(),
getSerialNumber(),
apiCertKey,
info
);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("统一退款响应 {}", response);
String body = response.getBody();
JSONObject jsonObject = JSONObject.parseObject(body);
if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) {
throw new CzgException("退款异常," + jsonObject.getString("message"));
}
return jsonObject.getString("refund_id");
}
public static String genCode(int shopId, int id, String path) {
Map<String, Object> params = Map.of(
"scene", "id=" + id + "&shopId=" + shopId,
"page", path,
"width", 430
);
var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false))
.body(JSONObject.toJSONString(params))
.execute();
byte[] bodyBytes = response.bodyBytes();
String str = new String(bodyBytes);
if (str.contains("errmsg")) {
JSONObject json = JSONObject.parseObject(str);
throw new CzgException(json.getString("errmsg"));
}
return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes);
}
public static String addProfitSharingUser(AddReceivers params) throws Exception {
getPlatCert();
params.setAppid(appId);
if (params.getName() != null && !params.getName().isBlank()) {
params.setName(encryptByPlatformCert(params.getName()));
} else {
params.setName(null);
}
log.info("添加分账方参数: {}", JSONObject.toJSONString(params));
var response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
ProfitSharingApiEnum.PROFIT_SHARING_RECEIVERS_ADD.toString(),
mchId,
getSerialNumber(),
platformCertNo,
apiCertKey,
JSONObject.toJSONString(params)
);
log.info("添加分账方响应 {}", response.getBody());
JSONObject resp = JSONObject.parseObject(response.getBody());
if (resp.containsKey("code")) {
throw new CzgException("分账添加失败" + resp.getString("message"));
}
return resp.getString("account");
}
public static JSONObject getMchConfig(String mchIdParam) throws Exception {
var response = WxPayApi.v3(
RequestMethodEnum.GET,
WxDomainEnum.CHINA.toString(),
String.format(ProfitSharingApiEnum.PROFIT_SHARING_MERCHANT_CONFIGS.toString(), mchIdParam),
mchId,
getSerialNumber(),
getSerialNumber(),
apiCertKey,
""
);
log.info("查询分账响应 {}", response.getBody());
JSONObject resp = JSONObject.parseObject(response.getBody());
if (resp.containsKey("code")) {
throw new CzgException("分账查询失败" + resp.getString("message"));
}
return resp;
}
/**
* 获取可分账金额
*/
public static BigDecimal getRemainAmount(String transactionId) {
IJPayHttpResponse response;
try {
response = WxPayApi.v3(
RequestMethodEnum.GET,
WxDomainEnum.CHINA.toString(),
String.format(ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE_QUERY.toString(), transactionId),
mchId,
getSerialNumber(),
getSerialNumber(),
apiCertKey,
""
);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("查询可分账响应 {}", response.getBody());
JSONObject resp = JSONObject.parseObject(response.getBody());
if (resp.containsKey("code")) {
throw new CzgException("分账查询失败" + resp.getString("message"));
}
return resp.getBigDecimal("unsplit_amount");
}
public static void profitSharing(String transactionId, ReceiverModel... receivers) {
for (ReceiverModel r : receivers) {
if (r.getName() != null && !r.getName().isBlank()) {
r.setName(encryptByPlatformCert(r.getName()));
}
}
String tradeNo = IdUtil.simpleUUID();
ProfitSharingModel model = new ProfitSharingModel();
model.setAppid(appId);
model.setTransaction_id(transactionId);
model.setReceivers(List.of(receivers));
model.setUnfreeze_unsplit(true);
model.setOut_order_no(tradeNo);
String params = JSONObject.toJSONString(model);
log.info("分账参数: {}", params);
IJPayHttpResponse response;
try {
response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
ProfitSharingApiEnum.PROFIT_SHARING_ORDERS.toString(),
mchId,
getSerialNumber(),
platformCertNo,
apiCertKey,
params
);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("分账响应 {}", response.getBody());
JSONObject resp = JSONObject.parseObject(response.getBody());
if (resp.containsKey("code")) {
throw new CzgException("分账失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
}
}
public static void unFreezeSharing(String transactionId, String orderNo) {
com.ijpay.wxpay.model.ProfitSharingModel paramsModel = new com.ijpay.wxpay.model.ProfitSharingModel();
paramsModel.setTransaction_id(transactionId);
paramsModel.setOut_order_no(orderNo);
paramsModel.setDescription("分账解冻");
String params = JSONObject.toJSONString(paramsModel);
log.info("分账解冻参数: {}", params);
IJPayHttpResponse response;
try {
response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE.toString(),
mchId,
getSerialNumber(),
platformCertNo,
apiCertKey,
params
);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("分账解冻响应 {}", response.getBody());
JSONObject resp = JSONObject.parseObject(response.getBody());
if (resp.containsKey("code")) {
throw new CzgException("分账解冻失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
}
}
public static JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) {
String remark = remarkTxt == null ? "佣金" : remarkTxt;
String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt;
Map<String, Object> params = Map.of(
"appid", appId,
"out_bill_no", billNo,
"transfer_scene_id", "1005",
"openid", openId,
"user_name", encryptByPlatformCert(name),
"transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(),
"transfer_remark", remark,
"notify_url", transferNotifyUrl,
"transfer_scene_report_infos", new Object[]{
Map.of("info_type", "岗位类型", "info_content", "加盟商"),
Map.of("info_type", "报酬说明", "info_content", "测试")
}
);
log.info("转账到零钱参数: {}", JSONObject.toJSONString(params));
IJPayHttpResponse response = null;
try {
response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
"/v3/fund-app/mch-transfer/transfer-bills",
mchId,
getSerialNumber(),
platformCertNo,
apiCertKey,
JSONObject.toJSONString(params)
);
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("转账到零钱响应 {}", response.getBody());
JSONObject resp = JSONObject.parseObject(response.getBody());
if (resp.containsKey("code")) {
throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
}
return resp;
}
}

View File

@@ -1,554 +0,0 @@
//package com.czg.service;
//
//import cn.hutool.core.util.IdUtil;
//import cn.hutool.core.util.StrUtil;
//import cn.hutool.http.HttpRequest;
//import cn.hutool.http.HttpUtil;
//import com.alibaba.fastjson.JSONObject;
//import com.czg.exception.CzgException;
//import com.ijpay.core.IJPayHttpResponse;
//import com.ijpay.core.enums.RequestMethodEnum;
//import com.ijpay.core.enums.SignType;
//import com.ijpay.core.kit.AesUtil;
//import com.ijpay.core.kit.PayKit;
//import com.ijpay.core.kit.RsaKit;
//import com.ijpay.core.kit.WxPayKit;
//import com.ijpay.wxpay.WxPayApi;
//import com.ijpay.wxpay.enums.WxDomainEnum;
//import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
//import com.ijpay.wxpay.enums.v3.OtherApiEnum;
//import com.ijpay.wxpay.enums.v3.ProfitSharingApiEnum;
//import com.ijpay.wxpay.enums.v3.TransferApiEnum;
//import com.ijpay.wxpay.model.ReceiverModel;
//import com.ijpay.wxpay.model.v3.*;
//import jakarta.annotation.PostConstruct;
//import jakarta.annotation.Resource;
//import jakarta.servlet.http.HttpServletRequest;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Component;
//
//import java.math.BigDecimal;
//import java.math.RoundingMode;
//import java.nio.charset.StandardCharsets;
//import java.security.cert.X509Certificate;
//import java.util.Base64;
//import java.util.List;
//import java.util.Locale;
//import java.util.Map;
//
//@Component
//@Slf4j
//public class WxService {
//
// @Value("${wx.appId}")
// private String appIdInstance;
// @Value("${wx.appSecret}")
// private String appSecretInstance;
// @Value("${wx.pay.certPath}")
// private String certPathInstance;
// @Value("${wx.pay.certKeyPath}")
// private String certKeyPathInstance;
// @Value("${wx.pay.pubKey}")
// private String pubKeyInstance;
// @Value("${wx.pay.platformCertPath}")
// private String platformCertPathInstance;
// @Value("${wx.pay.platformCertNo}")
// private String platformCertNoInstance;
// @Value("${wx.pay.mchId}")
// private String mchIdInstance;
// @Value("${wx.pay.apiV3Key}")
// private String apiV3KeyInstance;
// @Value("${wx.pay.apiV2Key}")
// private String apiV2KeyInstance;
// @Value("${wx.pay.notifyUrl}")
// private String notifyUrlInstance;
// @Value("${wx.pay.refundNotifyUrl}")
// private String refundNotifyUrlInstance;
// @Value("${wx.pay.transferNotifyUrl}")
// private String transferNotifyUrlInstance;
//
// @Resource
// private RedisService autoRedisService;
// private static RedisService redisService;
//
// @PostConstruct
// public void init() {
// appId = appIdInstance;
// appSecret = appSecretInstance;
// certPath = certPathInstance;
// pubKey = pubKeyInstance;
// certKeyPath = certKeyPathInstance;
// platformCertPath = platformCertPathInstance;
// platformCertNo = platformCertNoInstance;
// mchId = mchIdInstance;
// apiV3Key = apiV3KeyInstance;
// apiV2Key = apiV2KeyInstance;
// notifyUrl = notifyUrlInstance;
// refundNotifyUrl = refundNotifyUrlInstance;
// transferNotifyUrl = transferNotifyUrlInstance;
// redisService = this.autoRedisService;
// }
//
// // static copies for use in static methods
// private static String appId;
// private static String appSecret;
// private static String certPath;
// private static String pubKey;
// private static String certKeyPath;
// private static String platformCertPath;
// private static String platformCertNo;
// private static String mchId;
// private static String apiV3Key;
// private static String apiV2Key;
// private static String notifyUrl;
// private static String refundNotifyUrl;
// private static String transferNotifyUrl;
//
// private static String getAccessToken(boolean refresh) {
// Object token = redisService.get("access_token");
// if (!refresh && token instanceof String) {
// return (String) token;
// }
//
// String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token",
// Map.of("grant_type", "client_credential", "appid", appId, "secret", appSecret)
// );
//
// log.info("获取access_token响应: {}", response);
// JSONObject jsonObject = JSONObject.parseObject(response);
// String accessToken = jsonObject.getString("access_token");
// if (accessToken == null) {
// throw new RuntimeException("获取access_token失败");
// }
// Long expiresIn = jsonObject.getLong("expires_in");
// if (expiresIn == null) {
// expiresIn = 7200L;
// }
// redisService.set("access_token", accessToken, expiresIn - 200);
// return accessToken;
// }
//
// public static String getPhone(String code) {
// String requestBody = JSONObject.toJSONString(Map.of("code", code));
// String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody);
// log.info("获取手机号响应: {}", response);
// JSONObject jsonObject = JSONObject.parseObject(response);
// Integer errCode = jsonObject.getInteger("errcode");
//
// if (Integer.valueOf(0).equals(errCode)) {
// return jsonObject.getJSONObject("phone_info").getString("phoneNumber");
// } else if (Integer.valueOf(40001).equals(errCode)) {
// getAccessToken(true);
// }
// throw new RuntimeException("获取手机号失败");
// }
//
// public static String getOpenId(String code) {
// String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session",
// Map.of("appid", appId, "secret", appSecret, "js_code", code, "grant_type", "authorization_code")
// );
// log.info("获取openId响应: {}", response);
// JSONObject jsonObject = JSONObject.parseObject(response);
// String openId = jsonObject.getString("openid");
// if (openId != null && !openId.isBlank()) {
// return openId;
// }
// throw new RuntimeException("获取openId失败");
// }
//
// /**
// * 使用微信支付平台证书公钥加密敏感信息OAEP
// */
// private static String encryptByPlatformCert(String content) {
// try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertPath)) {
// if (certStream == null) {
// throw new RuntimeException("未找到微信支付平台证书文件: " + platformCertPath);
// }
// X509Certificate certificate = PayKit.getCertificate(certStream);
// if (certificate == null) throw new RuntimeException("读取证书失败");
// return PayKit.rsaEncryptOAEP(content, certificate);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
//
// private static String getSerialNumber() {
// try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certPath)) {
// X509Certificate certificate = PayKit.getCertificate(certStream);
// if (certificate != null) {
// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2);
// log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
// log.info("证书序列号: {}", serialNo);
// return serialNo;
// }
// } catch (Exception e) {
// log.error("读取证书失败", e);
// }
// return null;
// }
//
// private static String getPlatSerialNumber() {
// try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertNo)) {
// X509Certificate certificate = PayKit.getCertificate(certStream);
// if (certificate != null) {
// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2);
// log.info("平台证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
// log.info("平台证书序列号: {}", serialNo);
// return serialNo;
// }
// } catch (Exception e) {
// log.error("读取平台证书失败", e);
// }
// return null;
// }
//
// public static JSONObject verifySignature(HttpServletRequest request) {
// try {
// log.info("开始校验签名并解密");
// String timestamp = request.getHeader("Wechatpay-Timestamp");
// String nonce = request.getHeader("Wechatpay-Nonce");
// String serialNo = request.getHeader("Wechatpay-Serial");
// String signature = request.getHeader("Wechatpay-Signature");
// String result = request.getReader().lines().reduce((a, b) -> a + b).orElse("");
// String resp = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, apiV3Key, platformCertPath);
// log.info("解密明文{}", resp);
// return JSONObject.parseObject(resp);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
//
// public static void getPlatCert() throws Exception {
// String response = WxPayApi.v3(
// RequestMethodEnum.GET,
// WxDomainEnum.CHINA.toString(),
// OtherApiEnum.GET_CERTIFICATES.toString(),
// mchId,
// getSerialNumber(),
// null,
// certKeyPath,
// ""
// ).getBody();
//
// JSONObject jsonObject = JSONObject.parseObject(response);
// var dataArray = jsonObject.getJSONArray("data");
// var encryptObject = dataArray.getJSONObject(0);
// var encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
// String associatedData = encryptCertificate.getString("associated_data");
// String cipherText = encryptCertificate.getString("ciphertext");
// String nonce = encryptCertificate.getString("nonce");
// AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
// String publicKey = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText);
// log.info(publicKey);
// }
//
// public static Map<String, String> pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) throws Exception {
// if (desc == null) desc = "订单支付";
// UnifiedOrderModel model = new UnifiedOrderModel();
// model.setAppid(appId);
// model.setMchid(mchId);
// model.setDescription(desc);
// model.setOut_trade_no(tradeNo);
// int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact();
// model.setAmount(new Amount(total, "CNY"));
// model.setNotify_url(notifyUrl + "/" + type);
// model.setPayer(new Payer(openId, null, null));
//
// String payInfo = JSONObject.toJSONString(model);
// log.info("统一下单参数: {}", payInfo);
//
// var resp = WxPayApi.v3(
// RequestMethodEnum.POST,
// WxDomainEnum.CHINA.toString(),
// BasePayApiEnum.JS_API_PAY.toString(),
// mchId,
// getSerialNumber(),
// getSerialNumber(),
// certKeyPath,
// payInfo
// );
//
// log.info("统一下单响应: {}", resp);
// String body = resp.getBody();
// JSONObject jsonObject = JSONObject.parseObject(body);
// String prepayId = jsonObject.getString("prepay_id");
// if (StrUtil.isBlank(prepayId)) {
// throw new RuntimeException(jsonObject.getString("message"));
// }
// try {
// return WxPayKit.jsApiCreateSign(appId, prepayId, certKeyPath);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
//
// public static Map<String, String> v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) {
// Map<String, String> payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder()
// .appid(appId)
// .mch_id(mchId)
// .body(desc)
// .out_trade_no(tradeNo)
// .total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "")
// .fee_type("CNY")
// .notify_url(notifyUrl)
// .openid(openId)
// .trade_type("JSAPI")
// .build()
// .createSign(apiV2Key, SignType.MD5);
//
// log.info("统一下单参数: {}", payModel);
// String resp = WxPayApi.pushOrder(false, payModel);
// log.info("统一下单响应: {}", resp);
//
// Map<String, String> resultMap = WxPayKit.xmlToMap(resp);
// String returnCode = resultMap.get("return_code");
// String returnMsg = resultMap.get("return_msg");
// if (!WxPayKit.codeIsOk(returnCode)) {
// throw new CzgException(returnMsg);
// }
// String resultCode = resultMap.get("result_code");
// if (!WxPayKit.codeIsOk(resultCode)) {
// throw new CzgException(returnMsg);
// }
// String prepayId = resultMap.get("prepay_id");
// return WxPayKit.prepayIdCreateSign(prepayId, appId, appSecret, SignType.MD5);
// }
//
// public static String refund(String tradeNo, String refundTradeNo, BigDecimal amount) {
// int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact();
// RefundModel model = new RefundModel();
// model.setOut_trade_no(tradeNo);
// model.setOut_refund_no(refundTradeNo);
// model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount));
// model.setNotify_url(refundNotifyUrl);
//
// String info = JSONObject.toJSONString(model);
// log.info("统一退款参数: {}", info);
// IJPayHttpResponse response;
// try {
// response = WxPayApi.v3(
// RequestMethodEnum.POST,
// WxDomainEnum.CHINA.toString(),
// BasePayApiEnum.REFUND.toString(),
// mchId,
// getSerialNumber(),
// getSerialNumber(),
// certKeyPath,
// info
// );
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// log.info("统一退款响应 {}", response);
// String body = response.getBody();
// JSONObject jsonObject = JSONObject.parseObject(body);
// if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) {
// throw new CzgException("退款异常," + jsonObject.getString("message"));
// }
// return jsonObject.getString("refund_id");
// }
//
// public static String genCode(int shopId, int id, String path) {
// Map<String, Object> params = Map.of(
// "scene", "id=" + id + "&shopId=" + shopId,
// "page", path,
// "width", 430
// );
// var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false))
// .body(JSONObject.toJSONString(params))
// .execute();
//
// byte[] bodyBytes = response.bodyBytes();
// String str = new String(bodyBytes);
// if (str.contains("errmsg")) {
// JSONObject json = JSONObject.parseObject(str);
// throw new CzgException(json.getString("errmsg"));
// }
// return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes);
// }
//
// public static String addProfitSharingUser(AddReceivers params) throws Exception {
// getPlatCert();
// params.setAppid(appId);
// if (params.getName() != null && !params.getName().isBlank()) {
// params.setName(encryptByPlatformCert(params.getName()));
// } else {
// params.setName(null);
// }
// log.info("添加分账方参数: {}", JSONObject.toJSONString(params));
// var response = WxPayApi.v3(
// RequestMethodEnum.POST,
// WxDomainEnum.CHINA.toString(),
// ProfitSharingApiEnum.PROFIT_SHARING_RECEIVERS_ADD.toString(),
// mchId,
// getSerialNumber(),
// platformCertNo,
// certKeyPath,
// JSONObject.toJSONString(params)
// );
// log.info("添加分账方响应 {}", response.getBody());
// JSONObject resp = JSONObject.parseObject(response.getBody());
// if (resp.containsKey("code")) {
// throw new CzgException("分账添加失败" + resp.getString("message"));
// }
// return resp.getString("account");
// }
//
// public static JSONObject getMchConfig(String mchIdParam) throws Exception {
// var response = WxPayApi.v3(
// RequestMethodEnum.GET,
// WxDomainEnum.CHINA.toString(),
// String.format(ProfitSharingApiEnum.PROFIT_SHARING_MERCHANT_CONFIGS.toString(), mchIdParam),
// mchId,
// getSerialNumber(),
// getSerialNumber(),
// certKeyPath,
// ""
// );
// log.info("查询分账响应 {}", response.getBody());
// JSONObject resp = JSONObject.parseObject(response.getBody());
// if (resp.containsKey("code")) {
// throw new CzgException("分账查询失败" + resp.getString("message"));
// }
// return resp;
// }
//
// /**
// * 获取可分账金额
// */
// public static BigDecimal getRemainAmount(String transactionId) {
// IJPayHttpResponse response;
// try {
// response = WxPayApi.v3(
// RequestMethodEnum.GET,
// WxDomainEnum.CHINA.toString(),
// String.format(ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE_QUERY.toString(), transactionId),
// mchId,
// getSerialNumber(),
// getSerialNumber(),
// certKeyPath,
// ""
// );
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// log.info("查询可分账响应 {}", response.getBody());
// JSONObject resp = JSONObject.parseObject(response.getBody());
// if (resp.containsKey("code")) {
// throw new CzgException("分账查询失败" + resp.getString("message"));
// }
// return resp.getBigDecimal("unsplit_amount");
// }
//
// public static void profitSharing(String transactionId, ReceiverModel... receivers) {
// for (ReceiverModel r : receivers) {
// if (r.getName() != null && !r.getName().isBlank()) {
// r.setName(encryptByPlatformCert(r.getName()));
// }
// }
// String tradeNo = IdUtil.simpleUUID();
// ProfitSharingModel model = new ProfitSharingModel();
// model.setAppid(appId);
// model.setTransaction_id(transactionId);
// model.setReceivers(List.of(receivers));
// model.setUnfreeze_unsplit(true);
// model.setOut_order_no(tradeNo);
//
// String params = JSONObject.toJSONString(model);
// log.info("分账参数: {}", params);
// IJPayHttpResponse response;
// try {
// response = WxPayApi.v3(
// RequestMethodEnum.POST,
// WxDomainEnum.CHINA.toString(),
// ProfitSharingApiEnum.PROFIT_SHARING_ORDERS.toString(),
// mchId,
// getSerialNumber(),
// platformCertNo,
// certKeyPath,
// params
// );
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// log.info("分账响应 {}", response.getBody());
// JSONObject resp = JSONObject.parseObject(response.getBody());
// if (resp.containsKey("code")) {
// throw new CzgException("分账失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
// }
// }
//
// public static void unFreezeSharing(String transactionId, String orderNo) {
// com.ijpay.wxpay.model.ProfitSharingModel paramsModel = new com.ijpay.wxpay.model.ProfitSharingModel();
// paramsModel.setTransaction_id(transactionId);
// paramsModel.setOut_order_no(orderNo);
// paramsModel.setDescription("分账解冻");
// String params = JSONObject.toJSONString(paramsModel);
// log.info("分账解冻参数: {}", params);
// IJPayHttpResponse response;
// try {
// response = WxPayApi.v3(
// RequestMethodEnum.POST,
// WxDomainEnum.CHINA.toString(),
// ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE.toString(),
// mchId,
// getSerialNumber(),
// platformCertNo,
// certKeyPath,
// params
// );
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// log.info("分账解冻响应 {}", response.getBody());
// JSONObject resp = JSONObject.parseObject(response.getBody());
// if (resp.containsKey("code")) {
// throw new CzgException("分账解冻失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
// }
// }
//
// public static JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) {
// String remark = remarkTxt == null ? "佣金" : remarkTxt;
// String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt;
// Map<String, Object> params = Map.of(
// "appid", appId,
// "out_bill_no", billNo,
// "transfer_scene_id", "1005",
// "openid", openId,
// "user_name", encryptByPlatformCert(name),
// "transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(),
// "transfer_remark", remark,
// "notify_url", transferNotifyUrl,
// "transfer_scene_report_infos", new Object[]{
// Map.of("info_type", "岗位类型", "info_content", "加盟商"),
// Map.of("info_type", "报酬说明", "info_content", "测试")
// }
// );
// log.info("转账到零钱参数: {}", JSONObject.toJSONString(params));
// IJPayHttpResponse response = null;
// try {
// response = WxPayApi.v3(
// RequestMethodEnum.POST,
// WxDomainEnum.CHINA.toString(),
// "/v3/fund-app/mch-transfer/transfer-bills",
// mchId,
// getSerialNumber(),
// platformCertNo,
// certKeyPath,
// JSONObject.toJSONString(params)
// );
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// log.info("转账到零钱响应 {}", response.getBody());
// JSONObject resp = JSONObject.parseObject(response.getBody());
// if (resp.containsKey("code")) {
// throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
// }
// return resp;
// }
//}
//