4 changed files with 298 additions and 18 deletions
Split View
Diff Options
-
4dating-agency-mall-server/src/main/java/com/qniao/dam/application/service/paymentchannelorder/PaymentChannelOrderApplicationService.java
-
3dating-agency-mall-server/src/main/java/com/qniao/dam/application/service/paymentchannelorder/processor/IChannelPayService.java
-
80dating-agency-mall-server/src/main/java/com/qniao/dam/application/service/paymentchannelorder/processor/wechat/WeChatPayProcessor.java
-
229dating-agency-mall-server/src/main/java/com/qniao/dam/application/service/paymentchannelorder/processor/wechat/utils/WXPayV3Util.java
@ -0,0 +1,229 @@ |
|||
package com.qniao.dam.application.service.paymentchannelorder.processor.wechat.utils; |
|||
|
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.qniao.framework.exception.BizException; |
|||
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.cert.CertificatesManager; |
|||
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.FileUtils; |
|||
import org.apache.http.HttpEntity; |
|||
import org.apache.http.HttpResponse; |
|||
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.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.util.Base64Utils; |
|||
|
|||
import java.io.*; |
|||
import java.net.URISyntaxException; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Paths; |
|||
import java.security.*; |
|||
import java.security.cert.X509Certificate; |
|||
import java.security.spec.InvalidKeySpecException; |
|||
import java.security.spec.PKCS8EncodedKeySpec; |
|||
import java.util.ArrayList; |
|||
import java.util.Base64; |
|||
import java.util.stream.Collectors; |
|||
import java.util.stream.Stream; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
public class WXPayV3Util { |
|||
|
|||
public static final String api_v3_placeAnOrder_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; |
|||
|
|||
public static final String api_v3_refund_url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; |
|||
|
|||
private static CertificatesManager certificatesManager; |
|||
|
|||
private String APPLICATION_JSON = "application/json"; |
|||
// 你的商户私钥 |
|||
private String privateKey; |
|||
// 你的微信支付平台证书 |
|||
private String certificate; |
|||
|
|||
private CloseableHttpClient httpClient; |
|||
|
|||
private Verifier verifier; |
|||
|
|||
@Value("${weixin.mchid}") |
|||
private String mchId; |
|||
|
|||
@Value("${weixin.apiV3Key}") |
|||
private String v3Key; |
|||
|
|||
@Value("${weixin.mchSerialNo}") |
|||
private String mchSerialNo; |
|||
|
|||
@Value("${weixin.privateKeyPath}") |
|||
private String privateKeyPath; |
|||
|
|||
@Value("${weixin.platformCertPath}") |
|||
private String platformCertPath; |
|||
|
|||
public void setup() throws Exception { |
|||
// 获取商户 |
|||
try { |
|||
InputStream apiClientKey = Files.newInputStream(new File(privateKeyPath).toPath()); |
|||
File appKey = new File("/tmp/apiclient_key_temp.crt"); |
|||
FileUtils.copyInputStreamToFile(apiClientKey, appKey); |
|||
|
|||
InputStream wechatPayKey = Files.newInputStream(new File(platformCertPath).toPath()); |
|||
File appCert = new File("/tmp/wechatpay_cert_temp.crt"); |
|||
FileUtils.copyInputStreamToFile(wechatPayKey, appCert); |
|||
|
|||
this.privateKey = WXPayV3Util.getPrivateKeyStr(appKey.getAbsolutePath()); |
|||
this.certificate = WXPayV3Util.getPrivateKeyStr(appCert.getAbsolutePath()); |
|||
|
|||
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey); |
|||
|
|||
certificatesManager = CertificatesManager.getInstance(); |
|||
// 向证书管理器增加需要自动更新平台证书的商户信息 |
|||
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId, |
|||
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), |
|||
v3Key.getBytes(StandardCharsets.UTF_8)); |
|||
// 从证书管理器中获取verifier |
|||
verifier = certificatesManager.getVerifier(mchId); |
|||
|
|||
X509Certificate wechatPayCertificate = PemUtil.loadCertificate( |
|||
new ByteArrayInputStream(certificate.getBytes(StandardCharsets.UTF_8))); |
|||
|
|||
ArrayList<X509Certificate> listCertificates = new ArrayList<>(); |
|||
listCertificates.add(wechatPayCertificate); |
|||
|
|||
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() |
|||
.withMerchant(mchId, mchSerialNo, merchantPrivateKey) |
|||
.withWechatPay(listCertificates); |
|||
httpClient = builder.build(); |
|||
} catch (IOException e) { |
|||
log.error("setup IOException " + e.getMessage(), e); |
|||
throw new BizException("IO异常:" + e.getMessage()); |
|||
} catch (NoSuchAlgorithmException e) { |
|||
log.error("setup NoSuchAlgorithmException " + e.getMessage(), e); |
|||
throw new BizException("未找到配置文件:" + e.getMessage()); |
|||
} catch (InvalidKeySpecException e) { |
|||
log.error("InvalidKeySpecException " + e.getMessage(), e); |
|||
throw new BizException("key生成异常:" + e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* api_v3下单 |
|||
* |
|||
* @param body 请求体JSON字符串 |
|||
* @return |
|||
* @throws IOException |
|||
* @throws NoSuchAlgorithmException |
|||
* @throws InvalidKeySpecException |
|||
*/ |
|||
public JSONObject doPostWeiXinV3(String url, String body) throws Exception { |
|||
if (httpClient == null) { |
|||
setup(); |
|||
} |
|||
HttpPost httpPost = new HttpPost(url); |
|||
httpPost.addHeader("Content-Type", "application/json;chartset=utf-8"); |
|||
httpPost.addHeader("Accept", "application/json"); |
|||
try { |
|||
if (body == null) { |
|||
throw new IllegalArgumentException("data参数不能为空"); |
|||
} |
|||
StringEntity stringEntity = new StringEntity(body, "utf-8"); |
|||
httpPost.setEntity(stringEntity); |
|||
// 直接执行execute方法,官方会自动处理签名和验签,并进行证书自动更新 |
|||
HttpResponse httpResponse = httpClient.execute(httpPost); |
|||
HttpEntity httpEntity = httpResponse.getEntity(); |
|||
|
|||
if (httpResponse.getStatusLine().getStatusCode() == 200) { |
|||
String jsonResult = EntityUtils.toString(httpEntity); |
|||
return JSONObject.parseObject(jsonResult); |
|||
} else { |
|||
log.error("doPostWeiXinV3 status != 200, " + EntityUtils.toString(httpEntity)); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("doPostWeiXinV3 fail, " + e.getMessage(), e); |
|||
throw new BizException("微信支付异常:" + e.getMessage()); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 获取私钥字符串。 |
|||
* |
|||
* @param filename 私钥文件路径 (required) |
|||
* @return 私钥对象 |
|||
*/ |
|||
public static String getPrivateKeyStr(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { |
|||
return new String(Files.readAllBytes(Paths.get(filename)), "UTF-8"); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* V3 SHA256withRSA 签名. |
|||
* |
|||
* @param appId |
|||
* @param timestamp |
|||
* @param nonceStr |
|||
* @param prepayId |
|||
* @return |
|||
* @throws SignatureException |
|||
* @throws NoSuchAlgorithmException |
|||
* @throws IOException |
|||
* @throws InvalidKeySpecException |
|||
* @throws InvalidKeyException |
|||
* @throws URISyntaxException |
|||
*/ |
|||
public String sign(String appId, long timestamp, String nonceStr, String prepayId) throws SignatureException, NoSuchAlgorithmException, IOException, InvalidKeySpecException, InvalidKeyException, URISyntaxException { |
|||
String signatureStr = Stream.of(appId, String.valueOf(timestamp), nonceStr, prepayId) |
|||
.collect(Collectors.joining("\n", "", "\n")); |
|||
Signature sign = Signature.getInstance("SHA256withRSA"); |
|||
File file = copyTempFileByResourcePath(privateKeyPath); |
|||
sign.initSign(WXPayV3Util.getPrivateKey(file.getAbsolutePath())); |
|||
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8)); |
|||
return Base64Utils.encodeToString(sign.sign()); |
|||
} |
|||
|
|||
/** |
|||
* 使用流获取resoucre下的文件(解决线上无法获取的问题) |
|||
* |
|||
* @param resourcePath 相对于resource目录的路径 |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public File copyTempFileByResourcePath(String resourcePath) throws IOException { |
|||
InputStream inputStream = Files.newInputStream(new File(resourcePath).toPath()); |
|||
File file = new File("/tmp/apiclient_key_temp.crt"); |
|||
FileUtils.copyInputStreamToFile(inputStream, file); |
|||
return file; |
|||
} |
|||
|
|||
/** |
|||
* 获取私钥。 |
|||
* |
|||
* @param filename 私钥文件路径 (required) |
|||
* @return 私钥对象 |
|||
*/ |
|||
public static PrivateKey getPrivateKey(String filename) throws IOException { |
|||
String content = new String(Files.readAllBytes(Paths.get(filename)), "UTF-8"); |
|||
try { |
|||
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") |
|||
.replace("-----END PRIVATE KEY-----", "") |
|||
.replaceAll("\\s+", ""); |
|||
KeyFactory kf = KeyFactory.getInstance("RSA"); |
|||
return kf.generatePrivate( |
|||
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); |
|||
} catch (NoSuchAlgorithmException e) { |
|||
throw new RuntimeException("当前Java环境不支持RSA", e); |
|||
} catch (InvalidKeySpecException e) { |
|||
throw new RuntimeException("无效的密钥格式"); |
|||
} |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save