微信支付
parent
0c1b1ea48e
commit
d2715834ce
|
@ -0,0 +1,34 @@
|
|||
package com.muyu.pay.wechat.clients;
|
||||
|
||||
import com.dtflys.forest.annotation.BaseRequest;
|
||||
import com.dtflys.forest.annotation.JSONBody;
|
||||
import com.dtflys.forest.annotation.Post;
|
||||
import com.muyu.pay.wechat.clients.interceptor.WeChatAuthInterceptor;
|
||||
import com.muyu.pay.wechat.domain.req.WeChatPayAppReq;
|
||||
|
||||
/**
|
||||
* @author DongZl
|
||||
* @description: 微信App支付
|
||||
* @Date 2023/8/29 12:44
|
||||
*/
|
||||
|
||||
@BaseRequest(
|
||||
baseURL = "https://api.mch.weixin.qq.com/v3/pay/transactions",
|
||||
contentType = "application/json",
|
||||
headers = {
|
||||
"Accept: application/json"
|
||||
},
|
||||
interceptor = WeChatAuthInterceptor.class
|
||||
)
|
||||
public interface WeChatAppClient {
|
||||
|
||||
/**
|
||||
* App 支付下单
|
||||
* @param weChatPayReq 请求参数
|
||||
* @return 响应结果
|
||||
*/
|
||||
@Post(
|
||||
url = "/app"
|
||||
)
|
||||
public String order(@JSONBody WeChatPayAppReq weChatPayReq);
|
||||
}
|
|
@ -4,7 +4,7 @@ import com.dtflys.forest.annotation.BaseRequest;
|
|||
import com.dtflys.forest.annotation.JSONBody;
|
||||
import com.dtflys.forest.annotation.Post;
|
||||
import com.muyu.pay.wechat.clients.interceptor.WeChatAuthInterceptor;
|
||||
import com.muyu.pay.wechat.domain.req.WeChatPayReqWeChatPay;
|
||||
import com.muyu.pay.wechat.domain.req.WeChatPayNativeReq;
|
||||
|
||||
/**
|
||||
* @author DongZl
|
||||
|
@ -23,11 +23,11 @@ public interface WeChatNativeClient {
|
|||
|
||||
/**
|
||||
* Native 支付下单
|
||||
* @param jsonObject 请求参数
|
||||
* @param weChatPayReq 请求参数
|
||||
* @return 响应结果
|
||||
*/
|
||||
@Post(
|
||||
url = "/native"
|
||||
)
|
||||
public String order(@JSONBody WeChatPayReqWeChatPay weChatPayReq);
|
||||
public String order(@JSONBody WeChatPayNativeReq weChatPayReq);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.dtflys.forest.interceptor.Interceptor;
|
|||
import com.dtflys.forest.reflection.ForestMethod;
|
||||
import com.muyu.pay.wechat.config.WechatPayConfig;
|
||||
import com.muyu.pay.wechat.domain.WeChatPayBaseReq;
|
||||
import com.muyu.pay.wechat.domain.model.WeChatSignModel;
|
||||
import com.muyu.pay.wechat.service.WeChatSignService;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -57,17 +58,16 @@ public class WeChatAuthInterceptor<T> implements Interceptor<T> {
|
|||
log.debug("微信请求前拦截 - 添加Authorization授权请求头");
|
||||
ForestBody forestBody = req.getBody();
|
||||
String body = forestBody.encodeToString();
|
||||
ForestMethod forestMethod = req.getMethod();
|
||||
String methodName = req.getType().getName();
|
||||
String url = req.getUrl();
|
||||
String authorization = null;
|
||||
WeChatSignModel weChatSignModel = null;
|
||||
if (HttpMethod.GET.matches(methodName)){
|
||||
authorization = weChatSignService.tokenByGet(url);
|
||||
weChatSignModel = weChatSignService.tokenByGet(url);
|
||||
}else if (HttpMethod.POST.matches(methodName)){
|
||||
authorization = weChatSignService.tokenByPost(url, body);
|
||||
weChatSignModel = weChatSignService.tokenByPost(url, body);
|
||||
}
|
||||
// 添加Header
|
||||
req.addHeader("Authorization", authorization);
|
||||
req.addHeader("Authorization", weChatSignModel.getSign());
|
||||
// 继续执行请求返回true
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,18 @@ package com.muyu.pay.wechat.config;
|
|||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
import static com.muyu.pay.wechat.constant.WechatPayConstant.*;
|
||||
|
||||
/**
|
||||
|
@ -12,6 +24,26 @@ import static com.muyu.pay.wechat.constant.WechatPayConstant.*;
|
|||
@Configuration
|
||||
public class WechatPayConfigure {
|
||||
|
||||
public PrivateKey getPrivateKey(String filename) {
|
||||
|
||||
try {
|
||||
String content = new String(Files.readAllBytes(Paths.get(filename)), StandardCharsets.UTF_8);
|
||||
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("无效的密钥格式");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化微信支付配置
|
||||
* @return 微信支付配置
|
||||
|
@ -19,15 +51,15 @@ public class WechatPayConfigure {
|
|||
@Bean
|
||||
public WechatPayConfig initWechatPayConfig(){
|
||||
return WechatPayConfig.builder()
|
||||
.appid("wx1a60b1d281c88896")
|
||||
.merchantId("1645775803")
|
||||
.payKey("wentaoyun1234567wentaoyun1234567")
|
||||
.appid("*****")
|
||||
.merchantId("*****")
|
||||
.payKey("*****")
|
||||
.algorithmType(ALGORITHM_TYPE)
|
||||
.schema(SCHEMA)
|
||||
// 自己填我不知道
|
||||
.serialNo(null)
|
||||
.serialNo("******")
|
||||
// 自己填我不知道
|
||||
.privateKey(null)
|
||||
.privateKey(getPrivateKey("D:\\work\\demo\\wechat-pay\\src\\main\\resources\\wechat\\wxpaykey.pem"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package com.muyu.pay.wechat.domain.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author DongZl
|
||||
* @description: 微信支付Sign签名对象
|
||||
* @Date 2023/8/29 12:54
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WeChatSignModel {
|
||||
|
||||
private String sign;
|
||||
|
||||
|
||||
private long timestamp;
|
||||
|
||||
|
||||
private String nonceStr;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.muyu.pay.wechat.domain.req;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.muyu.pay.wechat.domain.WeChatPayBaseReq;
|
||||
import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* @author DongZl
|
||||
* @description: 微信支付模型
|
||||
* @Date 2023-8-21 下午 03:17
|
||||
*/
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WeChatPayAppReq extends WeChatPayBaseReq {
|
||||
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
@JSONField(name = "out_trade_no")
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 通知地址
|
||||
*/
|
||||
@JSONField(name = "notify_url")
|
||||
private String notifyUrl;
|
||||
|
||||
|
||||
/**
|
||||
* 订单金额
|
||||
*/
|
||||
@JSONField(name = "amount")
|
||||
private WeChatPayAmountModel payAmountModel;
|
||||
}
|
|
@ -17,7 +17,7 @@ import lombok.experimental.SuperBuilder;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WeChatPayReqWeChatPay extends WeChatPayBaseReq {
|
||||
public class WeChatPayNativeReq extends WeChatPayBaseReq {
|
||||
|
||||
/**
|
||||
* 商品描述
|
|
@ -1,18 +1,25 @@
|
|||
package com.muyu.pay.wechat.service;
|
||||
|
||||
import com.muyu.pay.wechat.config.WechatPayConfig;
|
||||
import com.muyu.pay.wechat.domain.model.WeChatSignModel;
|
||||
import com.muyu.utils.IdUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import okhttp3.HttpUrl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.muyu.pay.wechat.constant.WechatPayConstant.ALGORITHM_TYPE;
|
||||
|
||||
/**
|
||||
* @author DongZl
|
||||
|
@ -28,72 +35,57 @@ public class WeChatSignService {
|
|||
*/
|
||||
private final WechatPayConfig wechatPayConfig;
|
||||
|
||||
|
||||
public String tokenByPost(String url, String bodyJson){
|
||||
return wechatPayConfig.getSchema() + " " + getToken("POST", HttpUrl.parse(url), bodyJson);
|
||||
public WeChatSignModel tokenByPost(String url, String bodyJson){
|
||||
return getToken("POST", HttpUrl.parse(url), bodyJson);
|
||||
}
|
||||
|
||||
public String tokenByGet(String url){
|
||||
return wechatPayConfig.getSchema() + " " + getToken("GET", HttpUrl.parse(url), "");
|
||||
public WeChatSignModel tokenByGet(String url){
|
||||
return getToken("POST", HttpUrl.parse(url), "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
* @param method 请求方式
|
||||
* @param url url
|
||||
* @param body 请求参数
|
||||
* @return 签名 token
|
||||
*/
|
||||
public String getToken(String method, HttpUrl url, String body) {
|
||||
String nonceStr = IdUtils.simpleUUID().toUpperCase();
|
||||
public WeChatSignModel getToken(String method, HttpUrl url, String body) {
|
||||
String nonceStr = IdUtils.simpleUUID();
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
String message = buildMessage(method, url, timestamp, nonceStr, body);
|
||||
String signature = sign(message.getBytes(StandardCharsets.UTF_8));
|
||||
return "mchid=\"" + wechatPayConfig.getPayKey() + "\","
|
||||
+ "nonce_str=\"" + nonceStr + "\","
|
||||
+ "timestamp=\"" + timestamp + "\","
|
||||
+ "serial_no=\"" + wechatPayConfig.getSerialNo() + "\","
|
||||
+ "signature=\"" + signature + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行算法签名
|
||||
* @param message 信息
|
||||
* @return 签名信息
|
||||
*/
|
||||
private String sign(byte[] message) {
|
||||
Signature sign = null;
|
||||
String signature = null;
|
||||
try {
|
||||
sign = Signature.getInstance(wechatPayConfig.getAlgorithmType());
|
||||
sign.initSign(wechatPayConfig.getPrivateKey());
|
||||
sign.update(message);
|
||||
return Base64.getEncoder().encodeToString(sign.sign());
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
||||
log.info("微信支付获取签名失败:{}", e.getMessage(), e);
|
||||
throw new RuntimeException(e.getMessage());
|
||||
signature = sign(message.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException | SignatureException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return WeChatSignModel.builder()
|
||||
.sign(
|
||||
wechatPayConfig.getSchema()+" mchid=\""+wechatPayConfig.getMerchantId()+"\","
|
||||
+ "nonce_str=\""+nonceStr+"\","
|
||||
+ "timestamp=\""+timestamp+"\","
|
||||
+ "serial_no=\""+wechatPayConfig.getSerialNo()+"\","
|
||||
+ "signature=\""+signature+"\""
|
||||
)
|
||||
.timestamp(timestamp)
|
||||
.nonceStr(nonceStr)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建签名明细
|
||||
* @param method 请求方式
|
||||
* @param url 请求rul
|
||||
* @param timestamp 请求时间戳
|
||||
* @param nonceStr 随机字符串
|
||||
* @param body 请求体
|
||||
* @return 请求明细
|
||||
*/
|
||||
private String buildMessage(String method, HttpUrl url, long timestamp,
|
||||
String nonceStr, String body) {
|
||||
private String sign(byte[] message) throws NoSuchAlgorithmException, IOException, InvalidKeyException, SignatureException {
|
||||
Signature sign = Signature.getInstance(ALGORITHM_TYPE);
|
||||
sign.initSign(wechatPayConfig.getPrivateKey());
|
||||
sign.update(message);
|
||||
|
||||
return Base64.getEncoder().encodeToString(sign.sign());
|
||||
}
|
||||
|
||||
private String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
|
||||
String canonicalUrl = url.encodedPath();
|
||||
if (url.encodedQuery() != null) {
|
||||
canonicalUrl += "?" + url.encodedQuery();
|
||||
}
|
||||
|
||||
|
||||
return method + "\n"
|
||||
+ canonicalUrl + "\n"
|
||||
+ timestamp + "\n"
|
||||
+ nonceStr + "\n"
|
||||
+ body + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package com.muyu.wechat.pay;
|
||||
|
||||
import com.muyu.WechatPayApplication;
|
||||
import com.muyu.pay.wechat.clients.WeChatAppClient;
|
||||
import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel;
|
||||
import com.muyu.pay.wechat.domain.req.WeChatPayAppReq;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* @author DongZl
|
||||
* @description: 微信支付下单测试类
|
||||
* @Date 2023-8-21 下午 02:55
|
||||
*/
|
||||
@Log4j2
|
||||
@SpringBootTest(classes = WechatPayApplication.class)
|
||||
public class AppClientTest {
|
||||
|
||||
/**
|
||||
* 微信 native支付
|
||||
*/
|
||||
@Autowired
|
||||
private WeChatAppClient weChatAppClient;
|
||||
|
||||
@Test
|
||||
public void order(){
|
||||
String orderResult = weChatAppClient.order(
|
||||
WeChatPayAppReq.builder()
|
||||
.description("测试商品")
|
||||
.outTradeNo("12341684615")
|
||||
.notifyUrl("http://doc.qinmian.online")
|
||||
.payAmountModel(
|
||||
WeChatPayAmountModel.totalBuild(1)
|
||||
)
|
||||
.build()
|
||||
);
|
||||
log.info(orderResult);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package com.muyu.wechat.pay;
|
|||
import com.muyu.WechatPayApplication;
|
||||
import com.muyu.pay.wechat.clients.WeChatNativeClient;
|
||||
import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel;
|
||||
import com.muyu.pay.wechat.domain.req.WeChatPayReqWeChatPay;
|
||||
import com.muyu.pay.wechat.domain.req.WeChatPayNativeReq;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -27,7 +27,7 @@ public class NativeClientTest {
|
|||
@Test
|
||||
public void order(){
|
||||
String orderResult = weChatNativeClient.order(
|
||||
WeChatPayReqWeChatPay.builder()
|
||||
WeChatPayNativeReq.builder()
|
||||
.description("测试商品")
|
||||
.outTradeNo("12341684615")
|
||||
.notifyUrl("http://doc.qinmian.online")
|
||||
|
|
Loading…
Reference in New Issue