mirror of
https://github.com/dromara/payment-spring-boot.git
synced 2026-03-14 05:43:46 +08:00
多租户
This commit is contained in:
@@ -8,6 +8,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The type Wechat pay configuration.
|
||||
*/
|
||||
@@ -24,24 +26,32 @@ public class WechatPayConfiguration {
|
||||
* @return the wechat cert bean
|
||||
*/
|
||||
@Bean
|
||||
WechatMetaBean wechatMetaBean(WechatPayProperties wechatPayProperties) {
|
||||
WechatMetaContainer wechatMetaContainer(WechatPayProperties wechatPayProperties) {
|
||||
|
||||
String certPath = wechatPayProperties.getV3().getCertPath();
|
||||
String mchId = wechatPayProperties.getV3().getMchId();
|
||||
WechatMetaBean wechatMetaBean = new KeyPairFactory().createPKCS12(certPath, CERT_ALIAS, mchId);
|
||||
wechatMetaBean.setWechatPayProperties(wechatPayProperties);
|
||||
return wechatMetaBean;
|
||||
Map<String, WechatPayProperties.V3> v3Map = wechatPayProperties.getV3();
|
||||
WechatMetaContainer container = new WechatMetaContainer();
|
||||
v3Map.keySet().forEach(tenantId -> {
|
||||
WechatPayProperties.V3 v3 = v3Map.get(tenantId);
|
||||
String certPath = v3.getCertPath();
|
||||
String mchId = v3.getMchId();
|
||||
WechatMetaBean wechatMetaBean = new KeyPairFactory().createPKCS12(certPath, CERT_ALIAS, mchId);
|
||||
wechatMetaBean.setV3(v3);
|
||||
wechatMetaBean.setTenantId(tenantId);
|
||||
container.addWechatMeta(tenantId, wechatMetaBean);
|
||||
container.addTenant(tenantId);
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付V3签名工具.
|
||||
*
|
||||
* @param wechatMetaBean the wechat meta bean
|
||||
* @param wechatMetaContainer the wechat meta container
|
||||
* @return the signature provider
|
||||
*/
|
||||
@Bean
|
||||
SignatureProvider signatureProvider(WechatMetaBean wechatMetaBean) {
|
||||
return new SignatureProvider(wechatMetaBean);
|
||||
SignatureProvider signatureProvider(WechatMetaContainer wechatMetaContainer) {
|
||||
return new SignatureProvider(wechatMetaContainer);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,34 +62,10 @@ public class WechatPayConfiguration {
|
||||
* @return the wechat pay service
|
||||
*/
|
||||
@Bean
|
||||
public WechatPayClient wechatPayService(SignatureProvider signatureProvider) {
|
||||
public WechatPayClient wechatPayClient(SignatureProvider signatureProvider) {
|
||||
return new WechatPayClient(signatureProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付API.
|
||||
*
|
||||
* @param wechatPayClient the wechat pay v 3 client
|
||||
* @param wechatMetaBean the wechat meta bean
|
||||
* @return the wechat pay v 3 api
|
||||
*/
|
||||
@Bean
|
||||
public WechatPayApi wechatPayApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
||||
return new WechatPayApi(wechatPayClient, wechatMetaBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信营销API.
|
||||
*
|
||||
* @param wechatPayClient the wechat pay client
|
||||
* @param wechatMetaBean the wechat meta bean
|
||||
* @return the wechat marketing api
|
||||
*/
|
||||
@Bean
|
||||
public WechatMarketingFavorApi wechatMarketingApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
||||
return new WechatMarketingFavorApi(wechatPayClient, wechatMetaBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付回调工具.
|
||||
*
|
||||
@@ -99,10 +85,9 @@ public class WechatPayConfiguration {
|
||||
* @return the o auth 2 authorization request redirect provider
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "wechat.pay", name = "v3.mp.app-id")
|
||||
public OAuth2AuthorizationRequestRedirectProvider oAuth2Provider(WechatPayProperties wechatPayProperties) {
|
||||
WechatPayProperties.Mp mp = wechatPayProperties.getV3().getMp();
|
||||
return new OAuth2AuthorizationRequestRedirectProvider(mp.getAppId(), mp.getAppSecret());
|
||||
Map<String, WechatPayProperties.V3> v3Map = wechatPayProperties.getV3();
|
||||
return new OAuth2AuthorizationRequestRedirectProvider(v3Map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The type Wechat pay properties.
|
||||
*/
|
||||
@@ -14,7 +16,7 @@ public class WechatPayProperties {
|
||||
* wechat pay V3 properties
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private V3 v3;
|
||||
private Map<String,V3> v3;
|
||||
|
||||
/**
|
||||
* wechat pay v3 properties.
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.felord.payment.wechat.oauth2;
|
||||
|
||||
|
||||
import cn.felord.payment.PayException;
|
||||
import cn.felord.payment.wechat.WechatPayProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||
@@ -18,6 +19,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -34,21 +36,18 @@ public class OAuth2AuthorizationRequestRedirectProvider {
|
||||
private static final String TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/access_token";
|
||||
private final RestOperations restOperations = new RestTemplate();
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String appId;
|
||||
private final String secret;
|
||||
private final Map<String, WechatPayProperties.V3> v3Map;
|
||||
|
||||
/**
|
||||
* Instantiates a new O auth 2 authorization request redirect provider.
|
||||
*
|
||||
* @param appId the app id
|
||||
* @param secret the secret
|
||||
* @param v3Map the v 3 map
|
||||
*/
|
||||
public OAuth2AuthorizationRequestRedirectProvider(String appId, String secret) {
|
||||
public OAuth2AuthorizationRequestRedirectProvider(Map<String, WechatPayProperties.V3> v3Map) {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
this.appId = appId;
|
||||
this.secret = secret;
|
||||
this.v3Map = v3Map;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,11 +58,12 @@ public class OAuth2AuthorizationRequestRedirectProvider {
|
||||
* @return uri components
|
||||
*/
|
||||
@SneakyThrows
|
||||
public String redirect(String state, String redirectUri) {
|
||||
public String redirect(String tenantId,String state, String redirectUri) {
|
||||
Assert.hasText(redirectUri, "redirectUri is required");
|
||||
String encode = URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.name());
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
queryParams.add("appid", appId);
|
||||
WechatPayProperties.V3 v3 = v3Map.get(tenantId);
|
||||
queryParams.add("appid", v3.getMp().getAppId());
|
||||
queryParams.add("redirect_uri", encode);
|
||||
queryParams.add("response_type", "code");
|
||||
queryParams.add("scope", "snsapi_base");
|
||||
@@ -75,16 +75,19 @@ public class OAuth2AuthorizationRequestRedirectProvider {
|
||||
/**
|
||||
* 微信服务器授权成功后调用redirectUri的处理逻辑.
|
||||
*
|
||||
* @param code the code
|
||||
* @param code the code
|
||||
* @param state the state
|
||||
* @return the string
|
||||
*/
|
||||
@SneakyThrows
|
||||
public OAuth2Exchange exchange(String code, String state) {
|
||||
public OAuth2Exchange exchange(String tenantId,String code, String state) {
|
||||
Assert.hasText(code, "wechat pay oauth2 code is required");
|
||||
Assert.hasText(state, "wechat pay oauth2 state is required");
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
queryParams.add("appid", appId);
|
||||
queryParams.add("secret", secret);
|
||||
WechatPayProperties.V3 v3 = v3Map.get(tenantId);
|
||||
WechatPayProperties.Mp mp = v3.getMp();
|
||||
queryParams.add("appid", mp.getAppId());
|
||||
queryParams.add("secret", mp.getAppSecret());
|
||||
queryParams.add("code", code);
|
||||
queryParams.add("state", state);
|
||||
queryParams.add("grant_type", "authorization_code");
|
||||
|
||||
@@ -16,15 +16,22 @@ import java.net.URI;
|
||||
public abstract class AbstractApi {
|
||||
private final ObjectMapper mapper;
|
||||
private final WechatPayClient wechatPayClient;
|
||||
private final WechatMetaBean wechatMetaBean;
|
||||
private final String tenantId;
|
||||
|
||||
|
||||
public AbstractApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
||||
|
||||
|
||||
public AbstractApi(WechatPayClient wechatPayClient,String tenantId) {
|
||||
this.mapper = new ObjectMapper();
|
||||
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
this.wechatPayClient = wechatPayClient;
|
||||
this.wechatMetaBean = wechatMetaBean;
|
||||
|
||||
if (!container().getTenantIds().contains(tenantId)) {
|
||||
throw new PayException("tenantId is not in wechatMetaContainer ");
|
||||
}
|
||||
this.tenantId = tenantId;
|
||||
|
||||
}
|
||||
|
||||
public ObjectMapper getMapper() {
|
||||
@@ -35,8 +42,12 @@ public abstract class AbstractApi {
|
||||
return wechatPayClient;
|
||||
}
|
||||
|
||||
public WechatMetaBean meta() {
|
||||
return wechatMetaBean;
|
||||
public String tenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public WechatMetaContainer container() {
|
||||
return wechatPayClient.signatureProvider().wechatMetaContainer();
|
||||
}
|
||||
|
||||
protected RequestEntity<?> post(URI uri, Object params) {
|
||||
|
||||
@@ -51,38 +51,41 @@ public class SignatureProvider {
|
||||
|
||||
private static final IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator();
|
||||
private static final String SCHEMA = "WECHATPAY2-SHA256-RSA2048 ";
|
||||
private final RestOperations restOperations = new RestTemplate();
|
||||
/**
|
||||
* The constant TOKEN_PATTERN.
|
||||
*/
|
||||
public static final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
|
||||
private final WechatMetaBean wechatMetaBean;
|
||||
/**
|
||||
* 微信平台证书容器 key = 序列号 value = 证书对象
|
||||
*/
|
||||
private static final Map<String, Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>();
|
||||
private final RestOperations restOperations = new RestTemplate();
|
||||
private final WechatMetaContainer wechatMetaContainer;
|
||||
|
||||
/**
|
||||
* Instantiates a new Signature provider.
|
||||
*
|
||||
* @param wechatMetaBean the wechat meta bean
|
||||
* @param wechatMetaContainer the wechat meta container
|
||||
*/
|
||||
public SignatureProvider(WechatMetaBean wechatMetaBean) {
|
||||
this.wechatMetaBean = wechatMetaBean;
|
||||
public SignatureProvider(WechatMetaContainer wechatMetaContainer) {
|
||||
this.wechatMetaContainer = wechatMetaContainer;
|
||||
wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 我方请求时加签,使用API证书.
|
||||
*
|
||||
* @param tenantId the properties key
|
||||
* @param method the method
|
||||
* @param canonicalUrl the canonical url
|
||||
* @param body the body
|
||||
* @return the string
|
||||
*/
|
||||
@SneakyThrows
|
||||
public String requestSign(String method, String canonicalUrl, String body) {
|
||||
public String requestSign(String tenantId,String method, String canonicalUrl, String body) {
|
||||
Signature signer = Signature.getInstance("SHA256withRSA");
|
||||
WechatMetaBean wechatMetaBean = wechatMetaContainer.getWechatMeta(tenantId);
|
||||
signer.initSign(wechatMetaBean.getKeyPair().getPrivate());
|
||||
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
@@ -97,7 +100,7 @@ public class SignatureProvider {
|
||||
String serialNo = wechatMetaBean.getSerialNumber();
|
||||
// 生成token
|
||||
String token = String.format(TOKEN_PATTERN,
|
||||
wechatMetaBean.getWechatPayProperties().getV3().getMchId(),
|
||||
wechatMetaBean.getV3().getMchId(),
|
||||
nonceStr, timestamp, serialNo, encode);
|
||||
return SCHEMA.concat(token);
|
||||
}
|
||||
@@ -113,7 +116,7 @@ public class SignatureProvider {
|
||||
|
||||
String wechatpaySerial = params.getWechatpaySerial();
|
||||
if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
|
||||
refreshCertificate();
|
||||
wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate);
|
||||
}
|
||||
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);
|
||||
|
||||
@@ -130,7 +133,7 @@ public class SignatureProvider {
|
||||
* 当我方服务器不存在平台证书或者证书同当前响应报文中的证书序列号不一致时应当刷新 调用/v3/certificates
|
||||
*/
|
||||
@SneakyThrows
|
||||
private synchronized void refreshCertificate() {
|
||||
private synchronized void refreshCertificate(String propertiesKey) {
|
||||
String url = WechatPayV3Type.CERT.uri(WeChatServer.CHINA);
|
||||
|
||||
UriComponents uri = UriComponentsBuilder.fromHttpUrl(url).build();
|
||||
@@ -143,7 +146,7 @@ public class SignatureProvider {
|
||||
}
|
||||
// 签名
|
||||
HttpMethod httpMethod = WechatPayV3Type.CERT.method();
|
||||
String authorization = requestSign(httpMethod.name(), canonicalUrl, "");
|
||||
String authorization = requestSign(propertiesKey,httpMethod.name(), canonicalUrl, "");
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
@@ -166,7 +169,7 @@ public class SignatureProvider {
|
||||
String associatedData = encryptCertificate.get("associated_data").asText();
|
||||
String nonce = encryptCertificate.get("nonce").asText();
|
||||
String ciphertext = encryptCertificate.get("ciphertext").asText();
|
||||
String publicKey = decryptResponseBody(associatedData, nonce, ciphertext);
|
||||
String publicKey = decryptResponseBody(propertiesKey,associatedData, nonce, ciphertext);
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
|
||||
Certificate certificate = null;
|
||||
@@ -187,15 +190,16 @@ public class SignatureProvider {
|
||||
/**
|
||||
* 解密响应体.
|
||||
*
|
||||
* @param tenantId the properties key
|
||||
* @param associatedData the associated data
|
||||
* @param nonce the nonce
|
||||
* @param ciphertext the ciphertext
|
||||
* @return the string
|
||||
*/
|
||||
public String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
|
||||
public String decryptResponseBody(String tenantId,String associatedData, String nonce, String ciphertext) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
String apiV3Key = wechatMetaBean.getWechatPayProperties().getV3().getAppV3Secret();
|
||||
String apiV3Key = wechatMetaContainer.getWechatMeta(tenantId).getV3().getAppV3Secret();
|
||||
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
@@ -215,13 +219,14 @@ public class SignatureProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets wechat meta bean.
|
||||
* Wechat meta container.
|
||||
*
|
||||
* @return the wechat meta bean
|
||||
* @return the wechat meta container
|
||||
*/
|
||||
public WechatMetaBean getWechatMetaBean() {
|
||||
return wechatMetaBean;
|
||||
public WechatMetaContainer wechatMetaContainer() {
|
||||
return wechatMetaContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.felord.payment.wechat.v3;
|
||||
|
||||
/**
|
||||
* @author Dax
|
||||
* @since 17:32
|
||||
*/
|
||||
public class WechatApiProvider {
|
||||
private final WechatPayClient wechatPayClient;
|
||||
|
||||
public WechatApiProvider(WechatPayClient wechatPayClient) {
|
||||
this.wechatPayClient = wechatPayClient;
|
||||
}
|
||||
|
||||
public WechatMarketingFavorApi favorApi(String tenantId){
|
||||
return new WechatMarketingFavorApi(this.wechatPayClient,tenantId);
|
||||
}
|
||||
|
||||
public WechatPayApi payApi(String tenantId){
|
||||
return new WechatPayApi(wechatPayClient,tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.felord.payment.wechat.v3;
|
||||
|
||||
import cn.felord.payment.PayException;
|
||||
import cn.felord.payment.wechat.WechatPayProperties;
|
||||
import cn.felord.payment.wechat.enumeration.StockStatus;
|
||||
import cn.felord.payment.wechat.enumeration.WeChatServer;
|
||||
@@ -35,16 +36,13 @@ import java.util.function.Consumer;
|
||||
* @since 18 :22
|
||||
*/
|
||||
public class WechatMarketingFavorApi extends AbstractApi {
|
||||
/**
|
||||
* Instantiates a new Wechat marketing api.
|
||||
*
|
||||
* @param wechatPayClient the wechat pay client
|
||||
* @param wechatMetaBean the wechat meta bean
|
||||
*/
|
||||
public WechatMarketingFavorApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
||||
super(wechatPayClient, wechatMetaBean);
|
||||
|
||||
|
||||
public WechatMarketingFavorApi(WechatPayClient wechatPayClient, String tenantId) {
|
||||
super(wechatPayClient,tenantId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建代金券批次API
|
||||
*
|
||||
@@ -61,9 +59,11 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
}
|
||||
|
||||
private RequestEntity<?> createStocksFunction(WechatPayV3Type type, StocksCreateParams params) {
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
|
||||
String mchId = v3.getMchId();
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.toUri();
|
||||
params.setBelongMerchant(mchId);
|
||||
@@ -103,11 +103,12 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
|
||||
private RequestEntity<?> sendStocksFunction(WechatPayV3Type type, StocksSendParams params) {
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
// 服务号
|
||||
params.setAppid(v3.getMp().getAppId());
|
||||
params.setStockCreatorMchid(v3.getMchId());
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.expand(params.getOpenid())
|
||||
.toUri();
|
||||
@@ -146,12 +147,13 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
}
|
||||
|
||||
private RequestEntity<?> startAndRestartAndPauseStockFunction(WechatPayV3Type type, String stockId) {
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
String mchId = v3.getMchId();
|
||||
Map<String, String> body = new HashMap<>();
|
||||
body.put("stock_creator_mchid", mchId);
|
||||
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.expand(stockId)
|
||||
.toUri();
|
||||
@@ -178,9 +180,11 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
private RequestEntity<?> queryStocksFunction(WechatPayV3Type type, StocksQueryParams params) {
|
||||
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
queryParams.add("pay_tenantId", this.tenantId());
|
||||
queryParams.add("offset", String.valueOf(params.getOffset()));
|
||||
queryParams.add("limit", String.valueOf(params.getLimit()));
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
|
||||
queryParams.add("stock_creator_mchid", v3.getMchId());
|
||||
LocalDateTime createStartTime = params.getCreateStartTime();
|
||||
if (Objects.nonNull(createStartTime)) {
|
||||
@@ -231,10 +235,12 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
|
||||
private RequestEntity<?> stockDetailFunction(WechatPayV3Type type, String stockId) {
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
|
||||
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
queryParams.add("stock_creator_mchid", v3.getMchId());
|
||||
queryParams.add("pay_tenantId", this.tenantId());
|
||||
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParams(queryParams)
|
||||
@@ -263,10 +269,12 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
private RequestEntity<?> couponDetailFunction(WechatPayV3Type type, CouponDetailsQueryParams params) {
|
||||
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
|
||||
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
queryParams.add("appid", v3.getMp().getAppId());
|
||||
queryParams.add("pay_tenantId", this.tenantId());
|
||||
|
||||
MultiValueMap<String, String> pathParams = new LinkedMultiValueMap<>();
|
||||
pathParams.add("openid", params.getOpenId());
|
||||
@@ -329,10 +337,12 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
private RequestEntity<?> queryUserCouponsFunction(WechatPayV3Type type, UserCouponsQueryParams params) {
|
||||
final String ignore = "available_mchid";
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
|
||||
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
queryParams.add("appid", v3.getMp().getAppId());
|
||||
queryParams.add("pay_tenantId", this.tenantId());
|
||||
String stockId = params.getStockId();
|
||||
if (StringUtils.hasText(stockId)) {
|
||||
queryParams.add("stock_id", stockId);
|
||||
@@ -398,6 +408,7 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
private RequestEntity<?> downloadFlowFunction(WechatPayV3Type type, String stockId) {
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.expand(stockId)
|
||||
.toUri();
|
||||
@@ -428,13 +439,16 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
byte[] digest = SHA256.Digest.getInstance("SHA-256").digest(file.getBytes());
|
||||
meta.put("sha256", Hex.toHexString(digest));
|
||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
||||
MultiValueMap<Object, Object> body = new LinkedMultiValueMap<>();
|
||||
body.add("meta", meta);
|
||||
body.add("file", file.getResource());
|
||||
// 签名
|
||||
String metaStr = this.getMapper().writeValueAsString(meta);
|
||||
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.toUri();
|
||||
return RequestEntity.post(uri)
|
||||
.header("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
.header("Meta-Info", metaStr)
|
||||
@@ -443,9 +457,10 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
|
||||
/**
|
||||
* 代金券核销回调通知API
|
||||
*
|
||||
* @param notifyUrl the notify url
|
||||
* @see WechatPayCallback#wechatPayCouponCallback(ResponseSignVerifyParams, Consumer)
|
||||
* @return the wechat response entity
|
||||
* @see WechatPayCallback#wechatPayCouponCallback(String, ResponseSignVerifyParams, Consumer) WechatPayCallback#wechatPayCouponCallback(ResponseSignVerifyParams, Consumer)
|
||||
*/
|
||||
public WechatResponseEntity<ObjectNode> setMarketingFavorCallback(String notifyUrl) {
|
||||
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
||||
@@ -457,13 +472,16 @@ public class WechatMarketingFavorApi extends AbstractApi {
|
||||
}
|
||||
|
||||
private RequestEntity<?> setMarketingFavorCallbackFunction(WechatPayV3Type type, String notifyUrl) {
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
|
||||
Map<String, Object> body = new HashMap<>(3);
|
||||
body.put("mchid", v3.getMchId());
|
||||
body.put("notify_url", notifyUrl);
|
||||
body.put("switch", true);
|
||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.toUri();
|
||||
return post(uri, body);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,14 @@ import java.security.KeyPair;
|
||||
public class WechatMetaBean implements InitializingBean {
|
||||
private KeyPair keyPair;
|
||||
private String serialNumber;
|
||||
private WechatPayProperties wechatPayProperties;
|
||||
private String tenantId;
|
||||
private WechatPayProperties.V3 v3;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(wechatPayProperties, "wechatPayProperties is required");
|
||||
Assert.notNull(v3, "wechatPayProperties.V3 is required");
|
||||
Assert.notNull(keyPair, "wechat pay p12 certificate is required");
|
||||
Assert.hasText(serialNumber, "wechat pay p12 certificate SerialNumber is required");
|
||||
Assert.hasText(tenantId, "wechat pay tenantId is required");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.felord.payment.wechat.v3;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 配置容器
|
||||
*
|
||||
* @author Dax
|
||||
* @since 15 :18
|
||||
*/
|
||||
public class WechatMetaContainer {
|
||||
private final Map<String, WechatMetaBean> wechatMetaBeanMap = new HashMap<>();
|
||||
private final Set<String> tenantIds = new HashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* Add wechat meta boolean.
|
||||
*
|
||||
* @param tenantId the tenantId
|
||||
* @param wechatMetaBean the wechat meta bean
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean addWechatMeta(String tenantId, WechatMetaBean wechatMetaBean) {
|
||||
return Objects.nonNull(this.wechatMetaBeanMap.put(tenantId, wechatMetaBean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove wechat meta wechat meta bean.
|
||||
*
|
||||
* @param tenantId the tenantId
|
||||
* @return the wechat meta bean
|
||||
*/
|
||||
public WechatMetaBean removeWechatMeta(String tenantId) {
|
||||
return this.wechatMetaBeanMap.remove(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets wechat meta.
|
||||
*
|
||||
* @param tenantId the tenantId
|
||||
* @return the wechat meta
|
||||
*/
|
||||
public WechatMetaBean getWechatMeta(String tenantId) {
|
||||
return Objects.requireNonNull(this.wechatMetaBeanMap.get(tenantId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add key boolean.
|
||||
*
|
||||
* @param tenantId the tenant id
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean addTenant(String tenantId) {
|
||||
return tenantIds.add(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets properties keys.
|
||||
*
|
||||
* @return the properties keys
|
||||
*/
|
||||
public Set<String> getTenantIds() {
|
||||
return tenantIds;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package cn.felord.payment.wechat.v3;
|
||||
|
||||
import cn.felord.payment.PayException;
|
||||
import cn.felord.payment.wechat.enumeration.WeChatServer;
|
||||
import cn.felord.payment.wechat.WechatPayProperties;
|
||||
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
|
||||
import cn.felord.payment.wechat.v3.model.AppPayParams;
|
||||
import cn.felord.payment.wechat.v3.model.PayParams;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -18,18 +19,17 @@ import java.net.URI;
|
||||
*/
|
||||
public class WechatPayApi extends AbstractApi {
|
||||
|
||||
|
||||
public WechatPayApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
||||
super(wechatPayClient, wechatMetaBean);
|
||||
public WechatPayApi(WechatPayClient wechatPayClient, String tenantId) {
|
||||
super(wechatPayClient, tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* APP下单API.
|
||||
* APP下单API
|
||||
*
|
||||
* @param payParams the pay params
|
||||
* @return the wechat response entity
|
||||
*/
|
||||
public WechatResponseEntity<ObjectNode> appPay(AppPayParams payParams) {
|
||||
public WechatResponseEntity<ObjectNode> app(PayParams payParams) {
|
||||
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
||||
this.client().withType(WechatPayV3Type.APP, payParams)
|
||||
.function(this::appPayFunction)
|
||||
@@ -38,12 +38,29 @@ public class WechatPayApi extends AbstractApi {
|
||||
return wechatResponseEntity;
|
||||
}
|
||||
|
||||
private RequestEntity<?> appPayFunction(WechatPayV3Type type, AppPayParams payParams) {
|
||||
WechatPayProperties.V3 v3 = this.meta().getWechatPayProperties().getV3();
|
||||
/**
|
||||
* JSAPI/小程序下单API
|
||||
*
|
||||
* @param payParams the pay params
|
||||
* @return wechat response entity
|
||||
*/
|
||||
public WechatResponseEntity<ObjectNode> js(PayParams payParams) {
|
||||
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
||||
this.client().withType(WechatPayV3Type.JSAPI, payParams)
|
||||
.function(this::appPayFunction)
|
||||
.consumer(wechatResponseEntity::convert)
|
||||
.request();
|
||||
return wechatResponseEntity;
|
||||
}
|
||||
|
||||
private RequestEntity<?> appPayFunction(WechatPayV3Type type, PayParams payParams) {
|
||||
WechatPayProperties.V3 v3 = this.container().getWechatMeta(tenantId()).getV3();
|
||||
payParams.setAppid(v3.getAppId());
|
||||
payParams.setMchid(v3.getMchId());
|
||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
|
||||
.queryParam("pay_tenantId", this.tenantId())
|
||||
.build()
|
||||
.toUri();
|
||||
return post(uri, payParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +44,13 @@ public class WechatPayCallback {
|
||||
/**
|
||||
* 微信支付代金券核销回调工具.
|
||||
*
|
||||
* @param tenantId the tenant id
|
||||
* @param params the params
|
||||
* @param couponConsumeDataConsumer the coupon consume data consumer
|
||||
* @return the map
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Map<String, ?> wechatPayCouponCallback(ResponseSignVerifyParams params, Consumer<CouponConsumeData> couponConsumeDataConsumer) {
|
||||
public Map<String, ?> wechatPayCouponCallback(String tenantId, ResponseSignVerifyParams params, Consumer<CouponConsumeData> couponConsumeDataConsumer) {
|
||||
|
||||
if (signatureProvider.responseSignVerify(params)) {
|
||||
CallbackParams callbackParams = MAPPER.readValue(params.getBody(), CallbackParams.class);
|
||||
@@ -58,7 +59,7 @@ public class WechatPayCallback {
|
||||
String associatedData = resource.getAssociatedData();
|
||||
String nonce = resource.getNonce();
|
||||
String ciphertext = resource.getCiphertext();
|
||||
String data = signatureProvider.decryptResponseBody(associatedData, nonce, ciphertext);
|
||||
String data = signatureProvider.decryptResponseBody(tenantId,associatedData, nonce, ciphertext);
|
||||
Assert.hasText(data, "decryptData is required");
|
||||
CouponConsumeData couponConsumeData = MAPPER.readValue(data, CouponConsumeData.class);
|
||||
couponConsumeDataConsumer.accept(couponConsumeData);
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.springframework.http.*;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -148,17 +149,22 @@ public class WechatPayClient {
|
||||
}
|
||||
// 签名
|
||||
HttpMethod httpMethod = requestEntity.getMethod();
|
||||
|
||||
Assert.notNull(httpMethod, "httpMethod is required");
|
||||
HttpHeaders headers = requestEntity.getHeaders();
|
||||
|
||||
T entityBody = requestEntity.getBody();
|
||||
String body = requestEntity.hasBody() ? Objects.requireNonNull(entityBody).toString() : "";
|
||||
if (WechatPayV3Type.MARKETING_IMAGE_UPLOAD.pattern().contains(canonicalUrl)) {
|
||||
body = Objects.requireNonNull(requestEntity.getHeaders().get("Meta-Info")).get(0);
|
||||
body = Objects.requireNonNull(headers.get("Meta-Info")).get(0);
|
||||
}
|
||||
String authorization = signatureProvider.requestSign(httpMethod.name(), canonicalUrl, body);
|
||||
MultiValueMap<String, String> queryParams = uri.getQueryParams();
|
||||
String tenantId = queryParams.getFirst("pay_tenantId");
|
||||
Assert.notNull(tenantId, "tenantId is required");
|
||||
String authorization = signatureProvider.requestSign(tenantId,httpMethod.name(), canonicalUrl, body);
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.addAll(requestEntity.getHeaders());
|
||||
httpHeaders.addAll(headers);
|
||||
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
// 兼容图片上传,自定义优先级最高
|
||||
if (Objects.isNull(httpHeaders.getContentType())) {
|
||||
@@ -211,6 +217,10 @@ public class WechatPayClient {
|
||||
|
||||
}
|
||||
|
||||
public SignatureProvider signatureProvider() {
|
||||
return signatureProvider;
|
||||
}
|
||||
|
||||
private void applyDefaultRestTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
DefaultResponseErrorHandler errorHandler = new WechatPayResponseErrorHandler();
|
||||
|
||||
@@ -10,7 +10,7 @@ import lombok.Data;
|
||||
* @since 17 :10
|
||||
*/
|
||||
@Data
|
||||
public class AppPayParams {
|
||||
public class PayParams {
|
||||
private String appid;
|
||||
private String mchid;
|
||||
/**
|
||||
Reference in New Issue
Block a user