微信支付

master
DongZeLiang 2023-08-29 13:18:10 +08:00
parent 0c1b1ea48e
commit d2715834ce
11 changed files with 240 additions and 67 deletions

View File

@ -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);
}

View File

@ -4,7 +4,7 @@ import com.dtflys.forest.annotation.BaseRequest;
import com.dtflys.forest.annotation.JSONBody; import com.dtflys.forest.annotation.JSONBody;
import com.dtflys.forest.annotation.Post; import com.dtflys.forest.annotation.Post;
import com.muyu.pay.wechat.clients.interceptor.WeChatAuthInterceptor; 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 * @author DongZl
@ -23,11 +23,11 @@ public interface WeChatNativeClient {
/** /**
* Native * Native
* @param jsonObject * @param weChatPayReq
* @return * @return
*/ */
@Post( @Post(
url = "/native" url = "/native"
) )
public String order(@JSONBody WeChatPayReqWeChatPay weChatPayReq); public String order(@JSONBody WeChatPayNativeReq weChatPayReq);
} }

View File

@ -6,6 +6,7 @@ import com.dtflys.forest.interceptor.Interceptor;
import com.dtflys.forest.reflection.ForestMethod; import com.dtflys.forest.reflection.ForestMethod;
import com.muyu.pay.wechat.config.WechatPayConfig; import com.muyu.pay.wechat.config.WechatPayConfig;
import com.muyu.pay.wechat.domain.WeChatPayBaseReq; import com.muyu.pay.wechat.domain.WeChatPayBaseReq;
import com.muyu.pay.wechat.domain.model.WeChatSignModel;
import com.muyu.pay.wechat.service.WeChatSignService; import com.muyu.pay.wechat.service.WeChatSignService;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -57,17 +58,16 @@ public class WeChatAuthInterceptor<T> implements Interceptor<T> {
log.debug("微信请求前拦截 - 添加Authorization授权请求头"); log.debug("微信请求前拦截 - 添加Authorization授权请求头");
ForestBody forestBody = req.getBody(); ForestBody forestBody = req.getBody();
String body = forestBody.encodeToString(); String body = forestBody.encodeToString();
ForestMethod forestMethod = req.getMethod();
String methodName = req.getType().getName(); String methodName = req.getType().getName();
String url = req.getUrl(); String url = req.getUrl();
String authorization = null; WeChatSignModel weChatSignModel = null;
if (HttpMethod.GET.matches(methodName)){ if (HttpMethod.GET.matches(methodName)){
authorization = weChatSignService.tokenByGet(url); weChatSignModel = weChatSignService.tokenByGet(url);
}else if (HttpMethod.POST.matches(methodName)){ }else if (HttpMethod.POST.matches(methodName)){
authorization = weChatSignService.tokenByPost(url, body); weChatSignModel = weChatSignService.tokenByPost(url, body);
} }
// 添加Header // 添加Header
req.addHeader("Authorization", authorization); req.addHeader("Authorization", weChatSignModel.getSign());
// 继续执行请求返回true // 继续执行请求返回true
return true; return true;
} }

View File

@ -2,6 +2,18 @@ package com.muyu.pay.wechat.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.*; import static com.muyu.pay.wechat.constant.WechatPayConstant.*;
/** /**
@ -12,6 +24,26 @@ import static com.muyu.pay.wechat.constant.WechatPayConstant.*;
@Configuration @Configuration
public class WechatPayConfigure { 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 * @return
@ -19,15 +51,15 @@ public class WechatPayConfigure {
@Bean @Bean
public WechatPayConfig initWechatPayConfig(){ public WechatPayConfig initWechatPayConfig(){
return WechatPayConfig.builder() return WechatPayConfig.builder()
.appid("wx1a60b1d281c88896") .appid("*****")
.merchantId("1645775803") .merchantId("*****")
.payKey("wentaoyun1234567wentaoyun1234567") .payKey("*****")
.algorithmType(ALGORITHM_TYPE) .algorithmType(ALGORITHM_TYPE)
.schema(SCHEMA) .schema(SCHEMA)
// 自己填我不知道 // 自己填我不知道
.serialNo(null) .serialNo("******")
// 自己填我不知道 // 自己填我不知道
.privateKey(null) .privateKey(getPrivateKey("D:\\work\\demo\\wechat-pay\\src\\main\\resources\\wechat\\wxpaykey.pem"))
.build(); .build();
} }
} }

View File

@ -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;
}

View File

@ -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;
}

View File

@ -17,7 +17,7 @@ import lombok.experimental.SuperBuilder;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class WeChatPayReqWeChatPay extends WeChatPayBaseReq { public class WeChatPayNativeReq extends WeChatPayBaseReq {
/** /**
* *

View File

@ -1,18 +1,25 @@
package com.muyu.pay.wechat.service; package com.muyu.pay.wechat.service;
import com.muyu.pay.wechat.config.WechatPayConfig; import com.muyu.pay.wechat.config.WechatPayConfig;
import com.muyu.pay.wechat.domain.model.WeChatSignModel;
import com.muyu.utils.IdUtils; import com.muyu.utils.IdUtils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException; import java.nio.file.Files;
import java.security.NoSuchAlgorithmException; import java.nio.file.Paths;
import java.security.Signature; import java.security.*;
import java.security.SignatureException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import static com.muyu.pay.wechat.constant.WechatPayConstant.ALGORITHM_TYPE;
/** /**
* @author DongZl * @author DongZl
@ -28,72 +35,57 @@ public class WeChatSignService {
*/ */
private final WechatPayConfig wechatPayConfig; private final WechatPayConfig wechatPayConfig;
public WeChatSignModel tokenByPost(String url, String bodyJson){
public String tokenByPost(String url, String bodyJson){ return getToken("POST", HttpUrl.parse(url), bodyJson);
return wechatPayConfig.getSchema() + " " + getToken("POST", HttpUrl.parse(url), bodyJson);
} }
public String tokenByGet(String url){ public WeChatSignModel tokenByGet(String url){
return wechatPayConfig.getSchema() + " " + getToken("GET", HttpUrl.parse(url), ""); return getToken("POST", HttpUrl.parse(url), "");
} }
/** public WeChatSignModel getToken(String method, HttpUrl url, String body) {
* token String nonceStr = IdUtils.simpleUUID();
* @param method
* @param url url
* @param body
* @return token
*/
public String getToken(String method, HttpUrl url, String body) {
String nonceStr = IdUtils.simpleUUID().toUpperCase();
long timestamp = System.currentTimeMillis() / 1000; long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(method, url, timestamp, nonceStr, body); String message = buildMessage(method, url, timestamp, nonceStr, body);
String signature = sign(message.getBytes(StandardCharsets.UTF_8)); String signature = null;
return "mchid=\"" + wechatPayConfig.getPayKey() + "\"," try {
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+"\"," + "nonce_str=\""+nonceStr+"\","
+ "timestamp=\""+timestamp+"\"," + "timestamp=\""+timestamp+"\","
+ "serial_no=\""+wechatPayConfig.getSerialNo()+"\"," + "serial_no=\""+wechatPayConfig.getSerialNo()+"\","
+ "signature=\"" + signature + "\""; + "signature=\""+signature+"\""
)
.timestamp(timestamp)
.nonceStr(nonceStr)
.build();
} }
/** private String sign(byte[] message) throws NoSuchAlgorithmException, IOException, InvalidKeyException, SignatureException {
* Signature sign = Signature.getInstance(ALGORITHM_TYPE);
* @param message
* @return
*/
private String sign(byte[] message) {
Signature sign = null;
try {
sign = Signature.getInstance(wechatPayConfig.getAlgorithmType());
sign.initSign(wechatPayConfig.getPrivateKey()); sign.initSign(wechatPayConfig.getPrivateKey());
sign.update(message); sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign()); return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
log.info("微信支付获取签名失败:{}", e.getMessage(), e);
throw new RuntimeException(e.getMessage());
}
} }
/** private String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
*
* @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) {
String canonicalUrl = url.encodedPath(); String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) { if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery(); canonicalUrl += "?" + url.encodedQuery();
} }
return method + "\n" return method + "\n"
+ canonicalUrl + "\n" + canonicalUrl + "\n"
+ timestamp + "\n" + timestamp + "\n"
+ nonceStr + "\n" + nonceStr + "\n"
+ body + "\n"; + body + "\n";
} }
} }

View File

@ -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);
}
}

View File

@ -3,7 +3,7 @@ package com.muyu.wechat.pay;
import com.muyu.WechatPayApplication; import com.muyu.WechatPayApplication;
import com.muyu.pay.wechat.clients.WeChatNativeClient; import com.muyu.pay.wechat.clients.WeChatNativeClient;
import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel; 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 lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -27,7 +27,7 @@ public class NativeClientTest {
@Test @Test
public void order(){ public void order(){
String orderResult = weChatNativeClient.order( String orderResult = weChatNativeClient.order(
WeChatPayReqWeChatPay.builder() WeChatPayNativeReq.builder()
.description("测试商品") .description("测试商品")
.outTradeNo("12341684615") .outTradeNo("12341684615")
.notifyUrl("http://doc.qinmian.online") .notifyUrl("http://doc.qinmian.online")