Commit 82f0a880 authored by 刘李鹏's avatar 刘李鹏

微信支付示例完成

parent 3972936f
package cn.quant.baa.pay.acquirer; package cn.quant.baa.pay.acquirer;
import cn.quant.baa.pay.jpa.entity.PayHistoryEntity;
import cn.quant.baa.pay.model.web.PayRequestData;
import com.fasterxml.jackson.databind.JsonNode;
/** /**
* Created by Administrator on 2021/9/1 0001. * Created by Administrator on 2021/9/1 0001.
*/ */
public interface Acquirer { public interface Acquirer {
Object code(); Object code();
void pay(); JsonNode pay(PayRequestData payRequestData, PayHistoryEntity payHistoryEntity);
void refund(); void refund();
void check(); void check();
void close();
} }
package cn.quant.baa.pay.acquirer; package cn.quant.baa.pay.acquirer;
import cn.quant.baa.pay.jpa.entity.PayHistoryEntity;
import cn.quant.baa.pay.model.web.PayRequestData;
import cn.quant.spring.NotSupportedException; import cn.quant.spring.NotSupportedException;
import cn.quant.spring.security.Base64Cipher; import cn.quant.spring.security.Base64Cipher;
import cn.quant.spring.security.CharacterCipher; import cn.quant.spring.security.CharacterCipher;
import cn.quant.spring.util.RandomSequencer; import cn.quant.spring.util.RandomSequencer;
import com.fasterxml.jackson.databind.JsonNode;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.GCMParameterSpec;
...@@ -62,8 +65,8 @@ public class MerchantAcquirer implements Acquirer { ...@@ -62,8 +65,8 @@ public class MerchantAcquirer implements Acquirer {
} }
@Override @Override
public void pay() { public JsonNode pay(PayRequestData payRequestData, PayHistoryEntity payHistoryEntity) {
throw new NotSupportedException(); return acquirers.get(payRequestData.getChanId()).pay(payRequestData, payHistoryEntity);
} }
@Override @Override
...@@ -75,4 +78,9 @@ public class MerchantAcquirer implements Acquirer { ...@@ -75,4 +78,9 @@ public class MerchantAcquirer implements Acquirer {
public void check() { public void check() {
throw new NotSupportedException(); throw new NotSupportedException();
} }
@Override
public void close() {
}
} }
...@@ -2,15 +2,75 @@ package cn.quant.baa.pay.acquirer.weixin; ...@@ -2,15 +2,75 @@ package cn.quant.baa.pay.acquirer.weixin;
import cn.quant.baa.pay.acquirer.AcquirerProperties; import cn.quant.baa.pay.acquirer.AcquirerProperties;
import cn.quant.baa.pay.acquirer.MerchantAcquirer; import cn.quant.baa.pay.acquirer.MerchantAcquirer;
import cn.quant.baa.pay.jpa.entity.PayHistoryEntity;
import cn.quant.baa.pay.model.web.PayRequestData;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
/** /**
* Created by Administrator on 2021/8/31 0031. * Created by Administrator on 2021/8/31 0031.
*/ */
public class WeiXinMerchantAcquirer extends MerchantAcquirer { public class WeiXinMerchantAcquirer extends MerchantAcquirer {
private WebClient webClient;
/**
* 微信签名格式定义
*/
String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
/**
* 微信签名前缀
*/
String SCHEMA = "WECHATPAY2-SHA256-RSA2048 ";
/**
* 商户私钥
*/
PrivateKey privateKey;
/**
* 平台证书
*/
X509Certificate payCerKey;
/**
* 支付初始化
* 1. 初始化私钥
* 2. 初始化平台证书
* 3. 初始化webClient
*
* @param properties
* @return
* @throws Exception
*/
@Override @Override
public MerchantAcquirer init(AcquirerProperties properties) throws Exception { public MerchantAcquirer init(AcquirerProperties properties) throws Exception {
super.init(properties); super.init(properties);
privateKey = getPrivateKey(properties.getPrivateKey());
payCerKey = loadCertificate(properties.getPayCertKey());
this.webClient = WebClient.builder().baseUrl(properties.getDomain())
.defaultHeader("Accept", "application/json")
.defaultHeader("User-Agent", "QuantGroup PayCenter Java Api")
.build();
return this; return this;
} }
...@@ -18,4 +78,200 @@ public class WeiXinMerchantAcquirer extends MerchantAcquirer { ...@@ -18,4 +78,200 @@ public class WeiXinMerchantAcquirer extends MerchantAcquirer {
public Object code() { public Object code() {
return this.properties.getMchChanId().toString(); return this.properties.getMchChanId().toString();
} }
}
@Override
public JsonNode pay(PayRequestData payRequestData, PayHistoryEntity payHistoryEntity) {
String outTradeNo = UUID.randomUUID().toString().replace("-", "");
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode bodyNode = objectMapper.createObjectNode();
// 转换金额为分
BigInteger amount = new BigDecimal(payRequestData.getAmount()).multiply(new BigDecimal(100)).toBigInteger();
bodyNode.put("mchid", properties.getPayAcctId())
.put("appid", properties.getPayAppId())
.put("attach", payRequestData.getAttach())
.put("description", payRequestData.getSubject())
.put("notify_url", payRequestData.getNotifyUrl())
.put("out_trade_no", payRequestData.getOutTradeNo());
bodyNode.putObject("amount")
.put("total", amount);
ObjectNode sceneInfo = objectMapper.createObjectNode()
.put("payer_client_ip", "127.0.0.1");
switch (properties.getAccessCode()) {
case H5:
sceneInfo.putObject("h5_info").put("type", "Wap");
break;
case JS:
bodyNode.putObject("payer")
.put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
break;
case APP:
default:
}
bodyNode.set("scene_info", sceneInfo);
String requestBody = bodyNode.toString();
return doExecute(requestBody);
}
/**
* 发起请求
*
* @param requestBody
* @return
*/
private JsonNode doExecute(String requestBody) {
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = UUID.randomUUID().toString().replace("-", "");
String signText = joining("\n", "POST", properties.getPayAccess(), String.valueOf(timestamp), nonceStr, requestBody);
String sign = sign(signText);
String token = String.format(TOKEN_PATTERN, properties.getPayAcctId(), nonceStr, timestamp, properties.getPayCertNo(), sign);
Mono<ClientResponse> mono = webClient.post()
.uri(properties.getPayAccess())
.header("Authorization", SCHEMA.concat(token))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody).exchange();
ClientResponse response = mono.block();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode bodyJsonNode = objectMapper.createObjectNode();
if (response != null) {
Mono<String> resultMono = response.bodyToMono(String.class);
String body = resultMono.block();
try {
bodyJsonNode = objectMapper.readTree(body);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
if (response.statusCode().value() >= 200 && response.statusCode().value() < 300) {
//当前使用的微信平台证书序列号
String serial = response.headers().asHttpHeaders().getFirst("Wechatpay-Serial");
//微信服务器的时间戳
String wxTimestamp = response.headers().asHttpHeaders().getFirst("Wechatpay-Timestamp");
//微信服务器提供的随机串
String nonce = response.headers().asHttpHeaders().getFirst("Wechatpay-Nonce");
//微信平台签名
String signature = response.headers().asHttpHeaders().getFirst("Wechatpay-Signature");
//签名信息
String wsSignText = joining("\n", wxTimestamp, nonce, resultMono.block());
if (verify(wsSignText, signature)) {
return bodyJsonNode;
}
// TODO: 签名校验失败,做异常处理
// throw new Exception("签名校验失败");
} else {
// TODO: 微信返回异常,做异常处理
// throw new Exception("微信返回异常,做异常处理");
}
}
return bodyJsonNode;
}
/**
* 签名
*
* @param signText 需要签名的字符串
* @return
*/
public String sign(String signText) {
try {
Signature sign = Signature.getInstance(properties.getSignType());
sign.initSign(this.privateKey);
sign.update(signText.getBytes());
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
} catch (SignatureException e) {
throw new RuntimeException("签名计算失败", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("无效的私钥", e);
}
}
/**
* 获取证书。
*
* @param certString 证书内容
* @return X509证书
*/
public X509Certificate loadCertificate(String certString) {
try {
ByteArrayInputStream fis = new ByteArrayInputStream(certString.getBytes());
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException var3) {
throw new RuntimeException("证书已过期", var3);
} catch (CertificateNotYetValidException var4) {
throw new RuntimeException("证书尚未生效", var4);
} catch (CertificateException var5) {
throw new RuntimeException("无效的证书", var5);
}
}
/**
* RSA验签名检查
*
* @param content 待签名数据
* @param signature 签名值
* @return 布尔值
*/
public boolean verify(String content, String signature) {
try {
Signature sign = Signature.getInstance(properties.getSignType());
sign.initVerify(payCerKey);
sign.update(content.getBytes());
return sign.verify(Base64.getDecoder().decode(signature));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
} catch (SignatureException e) {
throw new RuntimeException("签名验证过程发生了错误", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("无效的证书", e);
}
}
/**
* 获取私钥。
*
* @param certString 私钥文件内容(required)
* @return 私钥对象
*/
public PrivateKey getPrivateKey(String certString) {
try {
String privateKey = certString
.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("无效的密钥格式");
}
}
/**
* 字符串数组拼接为字符串
*
* @param separator 分隔符
* @param str 字符数组
* @return 字符串
*/
public String joining(String separator, String... str) {
StringBuilder builder = new StringBuilder();
for (String s : str) {
if (s == null || s.length() == 0) {
continue;
}
builder.append(s).append(separator);
}
return builder.toString();
}
}
\ No newline at end of file
...@@ -14,4 +14,6 @@ import org.springframework.stereotype.Repository; ...@@ -14,4 +14,6 @@ import org.springframework.stereotype.Repository;
@Repository @Repository
public interface PayHistoryRepository extends JpaRepository<PayHistoryEntity, PayHistoryIds> { public interface PayHistoryRepository extends JpaRepository<PayHistoryEntity, PayHistoryIds> {
// @Query("")
// int counAAAt(PayHistoryIds ids);
} }
...@@ -9,6 +9,7 @@ import cn.quant.baa.pay.util.AssertUtils; ...@@ -9,6 +9,7 @@ import cn.quant.baa.pay.util.AssertUtils;
import cn.quant.baa.pay.model.web.GoodsDetail; import cn.quant.baa.pay.model.web.GoodsDetail;
import cn.quant.baa.pay.acquirer.AcquirerProperties; import cn.quant.baa.pay.acquirer.AcquirerProperties;
import cn.quant.baa.pay.model.web.PayRequestData; import cn.quant.baa.pay.model.web.PayRequestData;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -105,15 +106,12 @@ public class TransactionService extends BusinessService { ...@@ -105,15 +106,12 @@ public class TransactionService extends BusinessService {
@Transactional(propagation = Propagation.NOT_SUPPORTED) @Transactional(propagation = Propagation.NOT_SUPPORTED)
public void pay(PayRequestData data) { public JsonNode pay(PayRequestData data) {
TransactionSession session = payHistory(data); TransactionSession session = payHistory(data);
session.getProperty(PayHistoryEntity.class, PayHistoryEntity.class); PayHistoryEntity payHistoryEntity = session.getProperty(PayHistoryEntity.class, PayHistoryEntity.class);
System.currentTimeMillis(); System.currentTimeMillis();
// acquirer.pay(history, data); return acquirer.pay(data, payHistoryEntity);
} }
} }
...@@ -5,6 +5,7 @@ import cn.quant.baa.pay.jpa.entity.PayHistoryEntity; ...@@ -5,6 +5,7 @@ import cn.quant.baa.pay.jpa.entity.PayHistoryEntity;
import cn.quant.baa.pay.model.BusinessRequest; import cn.quant.baa.pay.model.BusinessRequest;
import cn.quant.baa.pay.model.web.PayRequestData; import cn.quant.baa.pay.model.web.PayRequestData;
import cn.quant.baa.pay.service.TransactionService; import cn.quant.baa.pay.service.TransactionService;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
...@@ -22,11 +23,12 @@ public class TransactionController extends BusinessController { ...@@ -22,11 +23,12 @@ public class TransactionController extends BusinessController {
@ResponseBody @ResponseBody
@BusinessMapping(session = 1) @BusinessMapping(session = 1)
@PostMapping("/pay") @PostMapping("/pay")
public void pay(@RequestBody BusinessRequest<PayRequestData> request) { public JsonNode pay(@RequestBody BusinessRequest<PayRequestData> request) {
PayRequestData data = request.getData(); PayRequestData data = request.getData();
transactionService.pay(data); JsonNode res = transactionService.pay(data);
System.currentTimeMillis(); System.currentTimeMillis();
return res;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment