完善微信支付的下单问题

This commit is contained in:
wanggeng 2021-08-26 07:45:50 +08:00
parent 5fe82f38d4
commit 4bdf5fc58f
6 changed files with 217 additions and 69 deletions

View File

@ -7,6 +7,8 @@ import ink.wgink.module.wechat.service.pay.v3.jsapi.IJsapiService;
import ink.wgink.pojo.result.ErrorResult;
import ink.wgink.util.OrderUtil;
import io.swagger.annotations.*;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
@ -41,6 +43,7 @@ public class MiniappPayAppController {
PayPlaceOrder payPlaceOrder = new PayPlaceOrder();
payPlaceOrder.setDescription("测试商品");
payPlaceOrder.setOutTradeNo(atomicOrder);
payPlaceOrder.setTimeExpire(DateTime.now().plusMinutes(1).toString(DateTimeFormat.forPattern(ISystemConstant.DATE_YYYY_MM_DD_T_HH_MM_SS_TIMEZONE)));
payPlaceOrder.setNotifyUrl("https://www.wgink.ink/study/wechat/pay/notice");
PayPlaceOrder.Amount amount = new PayPlaceOrder.Amount();
@ -48,7 +51,7 @@ public class MiniappPayAppController {
payPlaceOrder.setAmount(amount);
PayPlaceOrder.Payer payer = new PayPlaceOrder.Payer();
payer.setOpenid("");
payer.setOpenid("oZBi65WehgT4W9ZxztEuRgp0QKvU");
payPlaceOrder.setPayer(payer);
return jsapiService.placeOrder(payPlaceOrder);

View File

@ -1,9 +1,10 @@
package ink.wgink.module.wechat.request.pay.v3;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.utils.pay.PaySignAuthorizationUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import ink.wgink.util.UUIDUtil;
@ -13,10 +14,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import javax.crypto.Cipher;
@ -26,11 +30,14 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
/**
* @ClassName: PayRequestUtil
@ -39,7 +46,7 @@ import java.util.Base64;
* @Date: 2021/8/18 9:41 下午
* @Version: 1.0
*/
public abstract class AbstractPayRequest<RequestBody, Response> {
public abstract class AbstractPayRequest<RequestBean, BodyType, ResponseType> {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractPayRequest.class);
public static final String PAY_SERIAL = "Wechatpay-Serial";
@ -47,26 +54,28 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
private RestTemplate restTemplate;
public AbstractPayRequest() {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setConnectTimeout(20 * 1000);
simpleClientHttpRequestFactory.setReadTimeout(60 * 1000);
this.restTemplate = new RestTemplate(simpleClientHttpRequestFactory);
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpComponentsClientHttpRequestFactory.setConnectTimeout(20 * 1000);
httpComponentsClientHttpRequestFactory.setReadTimeout(60 * 1000);
this.restTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory);
}
/**
* bodys实体转json字符串
*
* @param requestBody
* @param requestBean
* @return
*/
public abstract String bodyJsonString(RequestBody requestBody);
public abstract BodyType bodyJson(RequestBean requestBean);
/**
* 响应结果处理
*
* @param responseEntity
*/
public abstract Response response(ResponseEntity<String> responseEntity);
public abstract ResponseType response(ResponseEntity<String> responseEntity);
public abstract void error(HttpClientErrorException e);
/**
* GET 请求
@ -75,11 +84,18 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
*/
public Response get(String url, String authorization, String serialNumber) {
HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
checkResponseCode(responseEntity);
return response(responseEntity);
public ResponseType get(String url, String authorization, String serialNumber) {
try {
HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
return response(responseEntity);
} catch (Exception e) {
if (e instanceof HttpClientErrorException) {
HttpClientErrorException exception = (HttpClientErrorException) e;
error(exception);
}
throw new SystemException(e.getMessage(), e);
}
}
/**
@ -90,11 +106,18 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
* @param serialNumber 公钥证书序列号
* @param jsonBody 请求参数
*/
public Response post(String url, String authorization, String serialNumber, String jsonBody) {
HttpEntity<String> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
checkResponseCode(responseEntity);
return response(responseEntity);
public ResponseType post(String url, String authorization, String serialNumber, BodyType jsonBody) {
try {
HttpEntity<BodyType> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
return response(responseEntity);
} catch (Exception e) {
if (e instanceof HttpClientErrorException) {
HttpClientErrorException exception = (HttpClientErrorException) e;
error(exception);
}
throw new SystemException(e.getMessage(), e);
}
}
/**
@ -105,11 +128,18 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
* @param serialNumber 公钥证书序列号
* @param jsonBody 请求参数
*/
public Response put(String url, String authorization, String serialNumber, String jsonBody) {
HttpEntity<String> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
checkResponseCode(responseEntity);
return response(responseEntity);
public ResponseType put(String url, String authorization, String serialNumber, BodyType jsonBody) {
try {
HttpEntity<BodyType> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
return response(responseEntity);
} catch (Exception e) {
if (e instanceof HttpClientErrorException) {
HttpClientErrorException exception = (HttpClientErrorException) e;
error(exception);
}
throw new SystemException(e.getMessage(), e);
}
}
/**
@ -119,11 +149,18 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
*/
public Response delete(String url, String authorization, String serialNumber) {
HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
checkResponseCode(responseEntity);
return response(responseEntity);
public ResponseType delete(String url, String authorization, String serialNumber) {
try {
HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
return response(responseEntity);
} catch (Exception e) {
if (e instanceof HttpClientErrorException) {
HttpClientErrorException exception = (HttpClientErrorException) e;
error(exception);
}
throw new SystemException(e.getMessage(), e);
}
}
/**
@ -135,15 +172,22 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
* @param jsonData 请求参数
* @param uploadFile 上传文件
*/
public Response upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) {
FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", fileSystemResource);
form.add("meta", jsonData);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(form, getUploadHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
checkResponseCode(responseEntity);
return response(responseEntity);
public ResponseType upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) {
try {
FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", fileSystemResource);
form.add("meta", jsonData);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(form, getUploadHeaders(authorization, serialNumber));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
return response(responseEntity);
} catch (Exception e) {
if (e instanceof HttpClientErrorException) {
HttpClientErrorException exception = (HttpClientErrorException) e;
error(exception);
}
throw new SystemException(e.getMessage(), e);
}
}
/**
@ -161,7 +205,7 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
String randomSubStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
return PaySignAuthorizationUtil.buildAuthorization(requestMethod, urlSuffix,
payProperties.getMchid(), serialNumber,
payProperties.getKeyPath(), jsonBody, randomSubStr, System.currentTimeMillis(),
payProperties.getKeyPath(), jsonBody, randomSubStr, System.currentTimeMillis() / 1000,
payAuthorizationTypeEnum.getValue());
}
@ -173,7 +217,7 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
*/
private HttpHeaders getHttpBaseHeaders(String authorization) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
httpHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
httpHeaders.add("Authorization", authorization);
return httpHeaders;
}
@ -187,10 +231,11 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
*/
private HttpHeaders getHttpHeaders(String authorization, String serialNumber) {
HttpHeaders httpHeaders = getHttpBaseHeaders(authorization);
httpHeaders.add("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
httpHeaders.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
if (!StringUtils.isBlank(serialNumber)) {
httpHeaders.add(PAY_SERIAL, serialNumber);
}
LOG.debug("header: {}", httpHeaders);
return httpHeaders;
}
@ -207,21 +252,77 @@ public abstract class AbstractPayRequest<RequestBody, Response> {
if (!StringUtils.isBlank(serialNumber)) {
httpHeaders.add(PAY_SERIAL, serialNumber);
}
LOG.debug(httpHeaders.toString());
return httpHeaders;
}
/**
* 检查返回状态码
*
* @param responseEntity
*/
private void checkResponseCode(ResponseEntity<String> responseEntity) {
if (responseEntity.getStatusCode().value() == HttpStatus.OK.value()) {
return;
@Deprecated
private static class HttpMessageCoverBuilder {
/**
* string 消息转换
*
* @return
*/
private static StringHttpMessageConverter getStringHttpMessageConverter() {
StringHttpMessageConverter stringConvert = new StringHttpMessageConverter();
List<MediaType> stringMediaTypes = new ArrayList<MediaType>() {{
//添加响应数据格式不匹配会报401
add(MediaType.TEXT_PLAIN);
add(MediaType.TEXT_HTML);
add(MediaType.APPLICATION_JSON);
add(MediaType.APPLICATION_JSON_UTF8);
}};
stringConvert.setSupportedMediaTypes(stringMediaTypes);
return stringConvert;
}
/**
* fastjson 消息转换
*
* @return
*/
private static FastJsonHttpMessageConverter getFastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
//2添加fastjson的配置信息
FastJsonConfig fastJsonConfig = new FastJsonConfig();
SerializerFeature[] serializerFeatures = new SerializerFeature[]{
// 输出key是包含双引号
// SerializerFeature.QuoteFieldNames,
// 是否输出为null的字段,若为null 则显示该字段
// SerializerFeature.WriteMapNullValue,
// 数值字段如果为null则输出为0
SerializerFeature.WriteNullNumberAsZero,
// List字段如果为null,输出为[],而非null
SerializerFeature.WriteNullListAsEmpty,
// 字符类型字段如果为null,输出为"",而非null
SerializerFeature.WriteNullStringAsEmpty,
// Boolean字段如果为null,输出为false,而非null
SerializerFeature.WriteNullBooleanAsFalse,
// Date的日期转换器
SerializerFeature.WriteDateUseDateFormat,
// 循环引用
SerializerFeature.DisableCircularReferenceDetect,
};
fastJsonConfig.setSerializerFeatures(serializerFeatures);
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
return fastJsonHttpMessageConverter;
}
/**
* 返回格式解析
*
* @return
*/
public static List<HttpMessageConverter<?>> getMessageConverts() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(getStringHttpMessageConverter());
messageConverters.add(getFastJsonHttpMessageConverter());
return messageConverters;
}
LOG.error(responseEntity.getBody());
PayErrorResponse payErrorResponse = JSONObject.parseObject(responseEntity.getBody(), PayErrorResponse.class);
throw new SystemException(payErrorResponse.getMessage());
}
/**

View File

@ -2,10 +2,13 @@ package ink.wgink.module.wechat.request.pay.v3.jsapi;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPlaceOrder;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import java.util.List;
@ -16,10 +19,10 @@ import java.util.List;
* @Date: 2021/8/19 3:22 下午
* @Version: 1.0
*/
public class PlaceOrderPayRequestImpl extends AbstractPayRequest<PayPlaceOrder, PayPrepay> {
public class PlaceOrderPayRequestImpl extends AbstractPayRequest<PayPlaceOrder, JSONObject, PayPrepay> {
@Override
public String bodyJsonString(PayPlaceOrder payPlaceOrder) {
public JSONObject bodyJson(PayPlaceOrder payPlaceOrder) {
JSONObject bodyObject = new JSONObject();
bodyObject.put("appid", payPlaceOrder.getAppid());
bodyObject.put("mchid", payPlaceOrder.getMchid());
@ -40,7 +43,7 @@ public class PlaceOrderPayRequestImpl extends AbstractPayRequest<PayPlaceOrder,
if (payPlaceOrder.getSettleInfo() != null) {
bodyObject.put("settle_info", getSettleInfo(payPlaceOrder.getSettleInfo()));
}
return bodyObject.toJSONString();
return bodyObject;
}
@Override
@ -49,6 +52,14 @@ public class PlaceOrderPayRequestImpl extends AbstractPayRequest<PayPlaceOrder,
return null;
}
@Override
public void error(HttpClientErrorException e) {
LOG.debug("Response error status code{}", e.getStatusCode());
PayErrorResponse payErrorResponse = JSONObject.parseObject(e.getResponseBodyAsString(), PayErrorResponse.class);
LOG.debug("Response error detail: {}", payErrorResponse.getDetail());
throw new SystemException(payErrorResponse.getMessage());
}
/**
* 订单金额
*

View File

@ -0,0 +1,34 @@
package ink.wgink.module.wechat.service.pay;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.PropertiesException;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @ClassName: BasePayService
* @Description: 基础支付业务
* @Author: wanggeng
* @Date: 2021/8/24 11:02 上午
* @Version: 1.0
*/
public class BasePayService extends DefaultBaseService {
public static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com";
public static final String PAY_SANDBOXNEW = "sandboxnew";
@Autowired
protected PayProperties payProperties;
protected String getPayBaseUrl() {
if (!payProperties.getActive()) {
throw new PropertiesException("微信支付未激活");
}
if (!payProperties.getProduce()) {
// 沙河环境
return PAY_BASE_URL + "/" + PAY_SANDBOXNEW;
}
return PAY_BASE_URL;
}
}

View File

@ -1,16 +1,16 @@
package ink.wgink.module.wechat.service.pay.v3.jsapi.impl;
import ink.wgink.common.base.DefaultBaseService;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pojo.pay.v3.PaySign;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPlaceOrder;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.request.pay.v3.jsapi.PlaceOrderPayRequestImpl;
import ink.wgink.module.wechat.service.pay.BasePayService;
import ink.wgink.module.wechat.service.pay.v3.jsapi.IJsapiService;
import ink.wgink.module.wechat.utils.pay.PayCertificateUtil;
import ink.wgink.module.wechat.utils.pay.PayPrivateKeyUtil;
import ink.wgink.properties.wechat.miniapp.MiniappProperties;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import ink.wgink.util.BeanPropertyCheckUtil;
import ink.wgink.util.UUIDUtil;
import ink.wgink.util.string.WStringUtil;
@ -29,10 +29,9 @@ import java.security.cert.X509Certificate;
* @Version: 1.0
*/
@Service
public class JsapiServiceImpl extends DefaultBaseService implements IJsapiService {
public class JsapiServiceImpl extends BasePayService implements IJsapiService {
@Autowired
private PayProperties payProperties;
@Autowired
private MiniappProperties miniappProperties;
@ -42,7 +41,7 @@ public class JsapiServiceImpl extends DefaultBaseService implements IJsapiServic
payPlaceOrder.setMchid(payProperties.getMchid());
BeanPropertyCheckUtil.checkField(payPlaceOrder);
String urlSuffix = "/v3/pay/transactions/jsapi";
String url = "https://api.mch.weixin.qq.com" + urlSuffix;
String url = getPayBaseUrl() + urlSuffix;
LOG.debug("获得证书");
X509Certificate certificate = PayCertificateUtil.getCertificate(new FileInputStream(payProperties.getCertificatePath()));
@ -50,8 +49,8 @@ public class JsapiServiceImpl extends DefaultBaseService implements IJsapiServic
LOG.debug("调用微信支付,发起预支付");
PlaceOrderPayRequestImpl placeOrderPayRequest = new PlaceOrderPayRequestImpl();
String body = placeOrderPayRequest.bodyJsonString(payPlaceOrder);
String authorization = placeOrderPayRequest.getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body, PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
JSONObject body = placeOrderPayRequest.bodyJson(payPlaceOrder);
String authorization = placeOrderPayRequest.getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
LOG.debug("请求获得结果");
PayPrepay payPrepay = placeOrderPayRequest.post(url, authorization, serialNumber, body);

View File

@ -103,13 +103,13 @@ public class PaySignAuthorizationUtil {
// 拼接时不包括最后一个&字符
if (i == keys.size() - 1) {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append('"');
content.append(key).append("=").append('\"').append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append('\"');
} else {
content.append(key).append("=").append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value);
}
} else {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append('"').append(connStr);
content.append(key).append("=").append('\"').append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append('\"').append(connStr);
} else {
content.append(key).append("=").append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append(connStr);
}