mirror of
https://github.com/dromara/payment-spring-boot.git
synced 2026-03-14 05:43:46 +08:00
重启批次接口 营销回调接口
This commit is contained in:
@@ -60,14 +60,26 @@ public class WechatPayConfiguration {
|
|||||||
* Wechat pay v3 api.
|
* Wechat pay v3 api.
|
||||||
*
|
*
|
||||||
* @param wechatPayClient the wechat pay v 3 client
|
* @param wechatPayClient the wechat pay v 3 client
|
||||||
* @param wechatMetaBean the wechat meta bean
|
* @param wechatMetaBean the wechat meta bean
|
||||||
* @return the wechat pay v 3 api
|
* @return the wechat pay v 3 api
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public WechatPayApi wechatPayApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
public WechatPayApi wechatPayApi(WechatPayClient wechatPayClient, WechatMetaBean wechatMetaBean) {
|
||||||
return new WechatPayApi(wechatPayClient,wechatMetaBean);
|
return new WechatPayApi(wechatPayClient, wechatMetaBean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wechat pay callback.
|
||||||
|
*
|
||||||
|
* @param signatureProvider the signature provider
|
||||||
|
* @return the wechat pay callback
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public WechatPayCallback wechatPayCallback(SignatureProvider signatureProvider) {
|
||||||
|
return new WechatPayCallback(signatureProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公众号授权工具用于获取用户openId,需要配置{@link WechatPayProperties.Mp}.
|
* 公众号授权工具用于获取用户openId,需要配置{@link WechatPayProperties.Mp}.
|
||||||
*
|
*
|
||||||
@@ -76,7 +88,7 @@ public class WechatPayConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(prefix = "wechat.pay", name = "v3.mp.app-id")
|
@ConditionalOnProperty(prefix = "wechat.pay", name = "v3.mp.app-id")
|
||||||
public OAuth2AuthorizationRequestRedirectProvider oAuth2Provider(WechatPayProperties wechatPayProperties){
|
public OAuth2AuthorizationRequestRedirectProvider oAuth2Provider(WechatPayProperties wechatPayProperties) {
|
||||||
WechatPayProperties.Mp mp = wechatPayProperties.getV3().getMp();
|
WechatPayProperties.Mp mp = wechatPayProperties.getV3().getMp();
|
||||||
return new OAuth2AuthorizationRequestRedirectProvider(mp.getAppId(), mp.getAppSecret());
|
return new OAuth2AuthorizationRequestRedirectProvider(mp.getAppId(), mp.getAppSecret());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.enongm.dianji.payment.wechat.enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The enum Coupon status.
|
||||||
|
*
|
||||||
|
* @author Dax
|
||||||
|
* @since 10 :57
|
||||||
|
*/
|
||||||
|
public enum CouponStatus {
|
||||||
|
/**
|
||||||
|
* 可用.
|
||||||
|
*/
|
||||||
|
SENDED,
|
||||||
|
/**
|
||||||
|
* 已实扣.
|
||||||
|
*/
|
||||||
|
USED,
|
||||||
|
/**
|
||||||
|
* 已过期.
|
||||||
|
*/
|
||||||
|
EXPIRED
|
||||||
|
}
|
||||||
@@ -43,6 +43,10 @@ public enum WechatPayV3Type {
|
|||||||
* 激活代金券批次API.
|
* 激活代金券批次API.
|
||||||
*/
|
*/
|
||||||
MARKETING_FAVOR_STOCKS_START(HttpMethod.POST,"%s/v3/marketing/favor/stocks/{stock_id}/start"),
|
MARKETING_FAVOR_STOCKS_START(HttpMethod.POST,"%s/v3/marketing/favor/stocks/{stock_id}/start"),
|
||||||
|
/**
|
||||||
|
* 重启代金券
|
||||||
|
*/
|
||||||
|
MARKETING_FAVOR_STOCKS_RESTART(HttpMethod.POST,"%s/v3/marketing/favor/stocks/{stock_id}/restart"),
|
||||||
/**
|
/**
|
||||||
* 发放代金券API & 根据商户号查用户的券.
|
* 发放代金券API & 根据商户号查用户的券.
|
||||||
*/
|
*/
|
||||||
@@ -62,7 +66,11 @@ public enum WechatPayV3Type {
|
|||||||
/**
|
/**
|
||||||
* 营销图片上传API.
|
* 营销图片上传API.
|
||||||
*/
|
*/
|
||||||
MARKETING_IMAGE_UPLOAD(HttpMethod.POST, "%s/v3/marketing/favor/media/image-upload");
|
MARKETING_IMAGE_UPLOAD(HttpMethod.POST, "%s/v3/marketing/favor/media/image-upload"),
|
||||||
|
/**
|
||||||
|
* 设置核销回调通知API.
|
||||||
|
*/
|
||||||
|
MARKETING_FAVOR_CALLBACKS(HttpMethod.POST, "%s/v3/marketing/favor/callbacks");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package com.enongm.dianji.payment.wechat.oauth2;
|
|||||||
import com.enongm.dianji.payment.PayException;
|
import com.enongm.dianji.payment.PayException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -11,10 +12,11 @@ import org.springframework.util.LinkedMultiValueMap;
|
|||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponents;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,14 +50,17 @@ public class OAuth2AuthorizationRequestRedirectProvider {
|
|||||||
* @param redirectUri the redirect uri
|
* @param redirectUri the redirect uri
|
||||||
* @return uri components
|
* @return uri components
|
||||||
*/
|
*/
|
||||||
public UriComponents redirect(String phoneNumber, String redirectUri) {
|
@SneakyThrows
|
||||||
|
public String redirect(String phoneNumber, String redirectUri) {
|
||||||
|
Assert.hasText(redirectUri, "redirectUri is required");
|
||||||
|
String encode = URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.name());
|
||||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||||
queryParams.add("appid", appId);
|
queryParams.add("appid", appId);
|
||||||
queryParams.add("redirect_uri", redirectUri);
|
queryParams.add("redirect_uri", encode);
|
||||||
queryParams.add("response_type", "code");
|
queryParams.add("response_type", "code");
|
||||||
queryParams.add("scope", "snsapi_base");
|
queryParams.add("scope", "snsapi_base");
|
||||||
queryParams.add("state", phoneNumber);
|
queryParams.add("state", phoneNumber);
|
||||||
return UriComponentsBuilder.fromHttpUrl(AUTHORIZATION_URI).queryParams(queryParams).build();
|
return UriComponentsBuilder.fromHttpUrl(AUTHORIZATION_URI).queryParams(queryParams).build().toUriString() + "#wechat_redirect";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package com.enongm.dianji.payment.wechat.v3;
|
|||||||
|
|
||||||
|
|
||||||
import com.enongm.dianji.payment.PayException;
|
import com.enongm.dianji.payment.PayException;
|
||||||
import com.enongm.dianji.payment.wechat.enumeration.WechatPayV3Type;
|
|
||||||
import com.enongm.dianji.payment.wechat.enumeration.WeChatServer;
|
import com.enongm.dianji.payment.wechat.enumeration.WeChatServer;
|
||||||
|
import com.enongm.dianji.payment.wechat.enumeration.WechatPayV3Type;
|
||||||
|
import com.enongm.dianji.payment.wechat.v3.model.ResponseSignVerifyParams;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
@@ -107,27 +108,24 @@ public class SignatureProvider {
|
|||||||
/**
|
/**
|
||||||
* 我方对响应验签,和应答签名做比较,使用微信平台证书.
|
* 我方对响应验签,和应答签名做比较,使用微信平台证书.
|
||||||
*
|
*
|
||||||
* @param wechatpaySerial response.headers['Wechatpay-Serial'] 当前使用的微信平台证书序列号
|
* @param params the params
|
||||||
* @param wechatpaySignature response.headers['Wechatpay-Signature'] 微信平台签名
|
|
||||||
* @param wechatpayTimestamp response.headers['Wechatpay-Timestamp'] 微信服务器的时间戳
|
|
||||||
* @param wechatpayNonce response.headers['Wechatpay-Nonce'] 微信服务器提供的随机串
|
|
||||||
* @param body response.body 微信服务器的响应体
|
|
||||||
* @return the boolean
|
* @return the boolean
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) {
|
public boolean responseSignVerify(ResponseSignVerifyParams params) {
|
||||||
|
|
||||||
|
String wechatpaySerial = params.getWechatpaySerial();
|
||||||
if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
|
if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
|
||||||
refreshCertificate();
|
refreshCertificate();
|
||||||
}
|
}
|
||||||
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);
|
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);
|
||||||
|
|
||||||
final String signatureStr = createSign(wechatpayTimestamp, wechatpayNonce, body);
|
final String signatureStr = createSign(params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody());
|
||||||
Signature signer = Signature.getInstance("SHA256withRSA");
|
Signature signer = Signature.getInstance("SHA256withRSA");
|
||||||
signer.initVerify(certificate);
|
signer.initVerify(certificate);
|
||||||
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
|
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
return signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
|
return signer.verify(Base64Utils.decodeFromString(params.getWechatpaySignature()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -207,7 +205,6 @@ public class SignatureProvider {
|
|||||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||||
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
|
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
|
||||||
byte[] bytes;
|
byte[] bytes;
|
||||||
try {
|
try {
|
||||||
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
|
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
|
||||||
@@ -221,6 +218,11 @@ public class SignatureProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets wechat meta bean.
|
||||||
|
*
|
||||||
|
* @return the wechat meta bean
|
||||||
|
*/
|
||||||
public WechatMetaBean getWechatMetaBean() {
|
public WechatMetaBean getWechatMetaBean() {
|
||||||
return wechatMetaBean;
|
return wechatMetaBean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,13 +79,7 @@ public class WechatPayApi {
|
|||||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
||||||
params.setBelongMerchant(mchId);
|
params.setBelongMerchant(mchId);
|
||||||
try {
|
return postRequestEntity(uri, params);
|
||||||
return RequestEntity.post(uri)
|
|
||||||
.body(MAPPER.writeValueAsString(params));
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
throw new PayException("wechat app pay json failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,27 +91,35 @@ public class WechatPayApi {
|
|||||||
public WechatResponseEntity<ObjectNode> startStock(String stockId) {
|
public WechatResponseEntity<ObjectNode> startStock(String stockId) {
|
||||||
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
||||||
wechatPayClient.withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_START, stockId)
|
wechatPayClient.withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_START, stockId)
|
||||||
.function(this::startStockFunction)
|
.function(this::startAndRestartStockFunction)
|
||||||
.consumer(wechatResponseEntity::convert)
|
.consumer(wechatResponseEntity::convert)
|
||||||
.request();
|
.request();
|
||||||
return wechatResponseEntity;
|
return wechatResponseEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestEntity<?> startStockFunction(WechatPayV3Type type, String stockId) {
|
/**
|
||||||
|
* 重启代金券批次API.
|
||||||
|
*
|
||||||
|
* @param stockId the stock id
|
||||||
|
* @return the wechat response entity
|
||||||
|
*/
|
||||||
|
public WechatResponseEntity<ObjectNode> restartStock(String stockId) {
|
||||||
|
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
||||||
|
wechatPayClient.withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_RESTART, stockId)
|
||||||
|
.function(this::startAndRestartStockFunction)
|
||||||
|
.consumer(wechatResponseEntity::convert)
|
||||||
|
.request();
|
||||||
|
return wechatResponseEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestEntity<?> startAndRestartStockFunction(WechatPayV3Type type, String stockId) {
|
||||||
WechatPayProperties.V3 v3 = wechatMetaBean.getWechatPayProperties().getV3();
|
WechatPayProperties.V3 v3 = wechatMetaBean.getWechatPayProperties().getV3();
|
||||||
String mchId = v3.getMchId();
|
String mchId = v3.getMchId();
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("stock_creator_mchid", mchId);
|
||||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().expand(stockId).toUri();
|
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().expand(stockId).toUri();
|
||||||
|
return postRequestEntity(uri, body);
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
map.put("stock_creator_mchid", mchId);
|
|
||||||
try {
|
|
||||||
return RequestEntity.post(uri)
|
|
||||||
.body(MAPPER.writeValueAsString(map));
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
throw new PayException("wechat app pay json failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,13 +249,34 @@ public class WechatPayApi {
|
|||||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().expand(params.getOpenid()).toUri();
|
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().expand(params.getOpenid()).toUri();
|
||||||
params.setOpenid(null);
|
params.setOpenid(null);
|
||||||
try {
|
return postRequestEntity(uri, params);
|
||||||
return RequestEntity.post(uri)
|
}
|
||||||
.body(MAPPER.writeValueAsString(params));
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
e.printStackTrace();
|
/**
|
||||||
}
|
* 代金券核销回调通知.
|
||||||
throw new PayException("wechat app pay json failed");
|
*
|
||||||
|
* @param notifyUrl the notify url
|
||||||
|
* @return the wechat response entity
|
||||||
|
*/
|
||||||
|
public WechatResponseEntity<ObjectNode> marketingFavorCallback(String notifyUrl) {
|
||||||
|
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
|
||||||
|
wechatPayClient.withType(WechatPayV3Type.MARKETING_FAVOR_CALLBACKS, notifyUrl)
|
||||||
|
.function(this::marketingFavorCallbackFunction)
|
||||||
|
.consumer(wechatResponseEntity::convert)
|
||||||
|
.request();
|
||||||
|
return wechatResponseEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestEntity<?> marketingFavorCallbackFunction(WechatPayV3Type type, String notifyUrl) {
|
||||||
|
WechatPayProperties.V3 v3 = wechatMetaBean.getWechatPayProperties().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();
|
||||||
|
return postRequestEntity(uri, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -277,13 +300,19 @@ public class WechatPayApi {
|
|||||||
payParams.setMchid(v3.getMchId());
|
payParams.setMchid(v3.getMchId());
|
||||||
String httpUrl = type.uri(WeChatServer.CHINA);
|
String httpUrl = type.uri(WeChatServer.CHINA);
|
||||||
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
URI uri = UriComponentsBuilder.fromHttpUrl(httpUrl).build().toUri();
|
||||||
|
return postRequestEntity(uri, payParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static RequestEntity<?> postRequestEntity(URI uri, Object params) {
|
||||||
try {
|
try {
|
||||||
return RequestEntity.post(uri)
|
return RequestEntity.post(uri)
|
||||||
.body(MAPPER.writeValueAsString(payParams));
|
.body(MAPPER.writeValueAsString(params));
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
throw new PayException("wechat app pay json failed");
|
throw new PayException("wechat app pay json failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.enongm.dianji.payment.wechat.v3;
|
||||||
|
|
||||||
|
import com.enongm.dianji.payment.PayException;
|
||||||
|
import com.enongm.dianji.payment.wechat.v3.model.CallbackParams;
|
||||||
|
import com.enongm.dianji.payment.wechat.v3.model.CouponConsumeData;
|
||||||
|
import com.enongm.dianji.payment.wechat.v3.model.ResponseSignVerifyParams;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dax
|
||||||
|
* @since 10:21
|
||||||
|
*/
|
||||||
|
public class WechatPayCallback {
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
private final SignatureProvider signatureProvider;
|
||||||
|
|
||||||
|
static {
|
||||||
|
MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
|
||||||
|
MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WechatPayCallback(SignatureProvider signatureProvider) {
|
||||||
|
this.signatureProvider = signatureProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public CouponConsumeData wechatPayCouponCallback(ResponseSignVerifyParams params) {
|
||||||
|
|
||||||
|
if (signatureProvider.responseSignVerify(params)) {
|
||||||
|
CallbackParams callbackParams = MAPPER.readValue(params.getBody(), CallbackParams.class);
|
||||||
|
|
||||||
|
CallbackParams.Resource resource = callbackParams.getResource();
|
||||||
|
String associatedData = resource.getAssociatedData();
|
||||||
|
String nonce = resource.getNonce();
|
||||||
|
String ciphertext = resource.getCiphertext();
|
||||||
|
String data = signatureProvider.decryptResponseBody(associatedData, nonce, ciphertext);
|
||||||
|
Assert.hasText(data, "decryptData is required");
|
||||||
|
return MAPPER.readValue(data, CouponConsumeData.class);
|
||||||
|
}
|
||||||
|
throw new PayException("invalid wechat pay coupon callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package com.enongm.dianji.payment.wechat.v3;
|
|||||||
import com.enongm.dianji.payment.PayException;
|
import com.enongm.dianji.payment.PayException;
|
||||||
import com.enongm.dianji.payment.wechat.WechatPayResponseErrorHandler;
|
import com.enongm.dianji.payment.wechat.WechatPayResponseErrorHandler;
|
||||||
import com.enongm.dianji.payment.wechat.enumeration.WechatPayV3Type;
|
import com.enongm.dianji.payment.wechat.enumeration.WechatPayV3Type;
|
||||||
|
import com.enongm.dianji.payment.wechat.v3.model.ResponseSignVerifyParams;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
@@ -97,7 +98,7 @@ public class WechatPayClient {
|
|||||||
/**
|
/**
|
||||||
* Function executor.
|
* Function executor.
|
||||||
*
|
*
|
||||||
* @param requestEntityBiFunction the request entity bi function
|
* @param requestEntityBiFunction the request entity bifunction
|
||||||
* @return the executor
|
* @return the executor
|
||||||
*/
|
*/
|
||||||
public Executor<M> function(BiFunction<WechatPayV3Type, M, RequestEntity<?>> requestEntityBiFunction) {
|
public Executor<M> function(BiFunction<WechatPayV3Type, M, RequestEntity<?>> requestEntityBiFunction) {
|
||||||
@@ -170,26 +171,28 @@ public class WechatPayClient {
|
|||||||
ResponseEntity<ObjectNode> responseEntity = restOperations.exchange(requestEntity, ObjectNode.class);
|
ResponseEntity<ObjectNode> responseEntity = restOperations.exchange(requestEntity, ObjectNode.class);
|
||||||
HttpHeaders headers = responseEntity.getHeaders();
|
HttpHeaders headers = responseEntity.getHeaders();
|
||||||
ObjectNode body = responseEntity.getBody();
|
ObjectNode body = responseEntity.getBody();
|
||||||
|
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||||
|
throw new PayException("wechat pay server error,result : " + body);
|
||||||
|
}
|
||||||
if (Objects.isNull(body)) {
|
if (Objects.isNull(body)) {
|
||||||
throw new IllegalStateException("cant obtain response body");
|
throw new IllegalStateException("cant obtain response body");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ResponseSignVerifyParams params = new ResponseSignVerifyParams();
|
||||||
// 微信请求回调id
|
// 微信请求回调id
|
||||||
// String RequestId = response.header("Request-ID");
|
// String RequestId = response.header("Request-ID");
|
||||||
// 微信平台证书序列号 用来取微信平台证书
|
// 微信平台证书序列号 用来取微信平台证书
|
||||||
String wechatpaySerial = headers.getFirst("Wechatpay-Serial");
|
params.setWechatpaySerial(headers.getFirst("Wechatpay-Serial"));
|
||||||
//获取应答签名
|
//获取应答签名
|
||||||
String wechatpaySignature = headers.getFirst("Wechatpay-Signature");
|
params.setWechatpaySignature(headers.getFirst("Wechatpay-Signature"));
|
||||||
//构造验签名串
|
//构造验签名串
|
||||||
String wechatpayTimestamp = headers.getFirst("Wechatpay-Timestamp");
|
params.setWechatpayTimestamp(headers.getFirst("Wechatpay-Timestamp"));
|
||||||
String wechatpayNonce = headers.getFirst("Wechatpay-Nonce");
|
params.setWechatpayNonce(headers.getFirst("Wechatpay-Nonce"));
|
||||||
|
params.setBody(body.toString());
|
||||||
|
|
||||||
// 验证微信服务器签名
|
// 验证微信服务器签名
|
||||||
if (signatureProvider.responseSignVerify(wechatpaySerial,
|
if (signatureProvider.responseSignVerify(params)) {
|
||||||
wechatpaySignature,
|
|
||||||
wechatpayTimestamp,
|
|
||||||
wechatpayNonce,
|
|
||||||
body.toString())) {
|
|
||||||
Consumer<ResponseEntity<ObjectNode>> responseConsumer = requestEntity.getResponseBodyConsumer();
|
Consumer<ResponseEntity<ObjectNode>> responseConsumer = requestEntity.getResponseBodyConsumer();
|
||||||
if (Objects.nonNull(responseConsumer)) {
|
if (Objects.nonNull(responseConsumer)) {
|
||||||
// 验证通过消费
|
// 验证通过消费
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ public class WechatResponseEntity<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean successful() {
|
public boolean is2xxSuccessful() {
|
||||||
return this.httpStatus == HttpStatus.OK.value();
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("wechat httpStatus {}", this.httpStatus);
|
||||||
|
}
|
||||||
|
return HttpStatus.valueOf(this.httpStatus).is2xxSuccessful();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dax
|
||||||
|
* @since 10:13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CallbackParams {
|
||||||
|
private String id;
|
||||||
|
private String createTime;
|
||||||
|
private String eventType;
|
||||||
|
private String resourceType;
|
||||||
|
private String summary;
|
||||||
|
private Resource resource;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Resource {
|
||||||
|
private String algorithm;
|
||||||
|
private String ciphertext;
|
||||||
|
private String associatedData;
|
||||||
|
private String nonce;
|
||||||
|
private String originalType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已实扣代金券信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ConsumeInformation {
|
||||||
|
|
||||||
|
private String consumeMchid;
|
||||||
|
|
||||||
|
private String consumeTime;
|
||||||
|
|
||||||
|
private List<GoodsDetail> goodsDetail;
|
||||||
|
|
||||||
|
private String transactionId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信代金券核销通知参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CouponConsumeData {
|
||||||
|
|
||||||
|
|
||||||
|
private String availableBeginTime;
|
||||||
|
private String availableEndTime;
|
||||||
|
private ConsumeInformation consumeInformation;
|
||||||
|
private String couponId;
|
||||||
|
private String couponName;
|
||||||
|
private String couponType;
|
||||||
|
private String createTime;
|
||||||
|
private String description;
|
||||||
|
private DiscountTo discountTo;
|
||||||
|
private boolean noCash;
|
||||||
|
private NormalCouponInformation normalCouponInformation;
|
||||||
|
private boolean singleitem;
|
||||||
|
private SingleitemDiscountOff singleitemDiscountOff;
|
||||||
|
private String status;
|
||||||
|
private String stockCreatorMchid;
|
||||||
|
private String stockId;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 减至优惠限定字段,仅减至优惠场景有返回
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DiscountTo {
|
||||||
|
|
||||||
|
private Long cutToPrice;
|
||||||
|
private Long maxPrice;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户下单接口传的单品信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class GoodsDetail {
|
||||||
|
|
||||||
|
private Long discountAmount;
|
||||||
|
private String goodsId;
|
||||||
|
private Long price;
|
||||||
|
private Long quantity;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通满减券面额、门槛信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NormalCouponInformation {
|
||||||
|
|
||||||
|
private Long couponAmount;
|
||||||
|
private Long transactionMinimum;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信的响应签名校验参数
|
||||||
|
*
|
||||||
|
* @author Dax
|
||||||
|
* @see com.enongm.dianji.payment.wechat.v3.SignatureProvider#responseSignVerify(String, String, String, String, String)
|
||||||
|
* @since 16:32
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ResponseSignVerifyParams {
|
||||||
|
/**
|
||||||
|
* response.headers['Wechatpay-Serial'] 当前使用的微信平台证书序列号
|
||||||
|
*/
|
||||||
|
private String wechatpaySerial;
|
||||||
|
/**
|
||||||
|
* response.headers['Wechatpay-Signature'] 微信平台签名
|
||||||
|
*/
|
||||||
|
private String wechatpaySignature;
|
||||||
|
/**
|
||||||
|
* response.headers['Wechatpay-Timestamp'] 微信服务器的时间戳
|
||||||
|
*/
|
||||||
|
private String wechatpayTimestamp;
|
||||||
|
/**
|
||||||
|
* response.headers['Wechatpay-Nonce'] 微信服务器提供的随机串
|
||||||
|
*/
|
||||||
|
private String wechatpayNonce;
|
||||||
|
/**
|
||||||
|
* response.body 微信服务器的响应体
|
||||||
|
*/
|
||||||
|
private String body;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
package com.enongm.dianji.payment.wechat.v3.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单品优惠特定信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SingleitemDiscountOff {
|
||||||
|
|
||||||
|
private Long singlePriceMax;
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user