354 lines
15 KiB
Java
354 lines
15 KiB
Java
package com.ycwl.basic.pay.adapter;
|
||
|
||
import com.wechat.pay.java.core.Config;
|
||
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
|
||
import com.wechat.pay.java.core.notification.NotificationConfig;
|
||
import com.wechat.pay.java.core.notification.NotificationParser;
|
||
import com.wechat.pay.java.core.notification.RequestParam;
|
||
import com.wechat.pay.java.core.util.PemUtil;
|
||
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
|
||
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
|
||
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
|
||
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
|
||
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
|
||
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
|
||
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
|
||
import com.wechat.pay.java.service.payments.model.Transaction;
|
||
import com.wechat.pay.java.service.refund.RefundService;
|
||
import com.wechat.pay.java.service.refund.model.AmountReq;
|
||
import com.wechat.pay.java.service.refund.model.CreateRequest;
|
||
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
|
||
import com.wechat.pay.java.service.refund.model.Refund;
|
||
import com.wechat.pay.java.service.refund.model.RefundNotification;
|
||
import com.ycwl.basic.constant.NumberConstant;
|
||
import com.ycwl.basic.pay.entity.CancelOrderRequest;
|
||
import com.ycwl.basic.pay.entity.CreateOrderRequest;
|
||
import com.ycwl.basic.pay.entity.CreateOrderResponse;
|
||
import com.ycwl.basic.pay.entity.PayResponse;
|
||
import com.ycwl.basic.pay.entity.RefundResponse;
|
||
import com.ycwl.basic.pay.entity.RefundOrderRequest;
|
||
import com.ycwl.basic.pay.entity.RefundOrderResponse;
|
||
import com.ycwl.basic.pay.entity.WxMpPayConfig;
|
||
import com.ycwl.basic.pay.exceptions.PayWrongConfigException;
|
||
import org.springframework.util.Base64Utils;
|
||
|
||
import javax.servlet.ServletInputStream;
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import java.io.BufferedReader;
|
||
import java.io.IOException;
|
||
import java.io.InputStreamReader;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.security.InvalidKeyException;
|
||
import java.security.NoSuchAlgorithmException;
|
||
import java.security.PrivateKey;
|
||
import java.security.Signature;
|
||
import java.security.SignatureException;
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
import java.util.UUID;
|
||
import java.util.stream.Collectors;
|
||
import java.util.stream.Stream;
|
||
|
||
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_NONCE;
|
||
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SERIAL;
|
||
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SIGNATURE;
|
||
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_TIMESTAMP;
|
||
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;
|
||
|
||
public class WxMpPayAdapter implements IPayAdapter {
|
||
private WxMpPayConfig config;
|
||
public static final String WECHAT_PAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type";
|
||
public WxMpPayAdapter() {
|
||
|
||
}
|
||
public WxMpPayAdapter(WxMpPayConfig config) {
|
||
this.config = config;
|
||
}
|
||
@Override
|
||
public void loadConfig(Map<String, String> _config) {
|
||
this.config = new WxMpPayConfig();
|
||
if (_config != null) {
|
||
this.config.setMerchantId(_config.get("merchantId"));
|
||
this.config.setAppId(_config.get("appId"));
|
||
this.config.setPrivateKey(_config.get("privateKey"));
|
||
this.config.setSerialNumber(_config.get("serialNumber"));
|
||
this.config.setApiV3Key(_config.get("apiV3Key"));
|
||
}
|
||
}
|
||
|
||
private Config clientConfig;
|
||
|
||
private Config getConfig() {
|
||
if (clientConfig == null) {
|
||
clientConfig = new RSAAutoCertificateConfig.Builder()
|
||
.merchantId(config.getMerchantId())
|
||
.privateKey(config.getPrivateKey())
|
||
.merchantSerialNumber(config.getSerialNumber())
|
||
.apiV3Key(config.getApiV3Key())
|
||
.build();
|
||
}
|
||
return clientConfig;
|
||
}
|
||
|
||
@Override
|
||
public CreateOrderResponse createOrder(CreateOrderRequest request) {
|
||
CreateOrderResponse resp = new CreateOrderResponse();
|
||
if (request.getPrice() <= 0) {
|
||
resp.setSkipPay(true);
|
||
return resp;
|
||
}
|
||
Config wxConfig = getConfig();
|
||
JsapiService service = new JsapiService.Builder().config(wxConfig).build();
|
||
PrepayRequest prepayRequest = new PrepayRequest();
|
||
Amount amount = new Amount();
|
||
amount.setTotal(request.getPrice());
|
||
prepayRequest.setAmount(amount);
|
||
prepayRequest.setAppid(config.getAppId());
|
||
prepayRequest.setMchid(config.getMerchantId());
|
||
prepayRequest.setDescription(request.getDescription());
|
||
prepayRequest.setNotifyUrl(request.getNotifyUrl());
|
||
prepayRequest.setOutTradeNo(request.getOrderNo());
|
||
Payer payer = new Payer();
|
||
payer.setOpenid(request.getUserIdentify());
|
||
prepayRequest.setPayer(payer);
|
||
PrepayResponse response = service.prepay(prepayRequest);
|
||
resp.setSuccess(true);
|
||
resp.setSkipPay(false);
|
||
resp.setOrderNo(response.getPrepayId());
|
||
return resp;
|
||
}
|
||
|
||
@Override
|
||
public Map<String, Object> getPaymentParams(CreateOrderResponse response) {
|
||
Map<String, Object> params = new HashMap<>();
|
||
if (response.isSkipPay()) {
|
||
return params;
|
||
}
|
||
Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
|
||
params.put("appId", config.getAppId());
|
||
params.put("timeStamp", timeStamp);
|
||
String nonce = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
|
||
params.put("nonceStr", nonce);
|
||
String signStr = Stream.of(config.getAppId(), String.valueOf(timeStamp), nonce, "prepay_id=" + response.getOrderNo())
|
||
.collect(Collectors.joining("\n", "", "\n"));
|
||
String sign;
|
||
try {
|
||
sign = getSign(signStr, config.getPrivateKey());
|
||
} catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
|
||
throw new PayWrongConfigException("配置错误");
|
||
}
|
||
params.put("paySign", sign);
|
||
params.put("signType", "RSA-SHA256");
|
||
params.put("prepayId", "prepay_id=" + response.getOrderNo());
|
||
return params;
|
||
}
|
||
|
||
@Override
|
||
public PayResponse handleCallback(HttpServletRequest request) throws IOException {
|
||
ServletInputStream inputStream = request.getInputStream();
|
||
StringBuffer body = new StringBuffer();
|
||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||
String s;
|
||
// 读取回调请求体
|
||
while ((s = bufferedReader.readLine()) != null) {
|
||
body.append(s);
|
||
}
|
||
PayResponse resp = new PayResponse();
|
||
|
||
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
|
||
String nonce = request.getHeader(WECHAT_PAY_NONCE);
|
||
String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);
|
||
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
|
||
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
|
||
NotificationConfig config = (NotificationConfig) getConfig();
|
||
NotificationParser parser = new NotificationParser(config);
|
||
RequestParam requestParam = new RequestParam.Builder()
|
||
.serialNumber(serialNo)
|
||
.nonce(nonce)
|
||
.signature(signature)
|
||
.timestamp(timestamp)
|
||
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
|
||
.signType(signType)
|
||
.body(body.toString())
|
||
.build();
|
||
Transaction parse = parser.parse(requestParam, Transaction.class);
|
||
resp.setValid(true);
|
||
resp.setOrderNo(parse.getOutTradeNo());
|
||
if (parse.getAmount() != null) {
|
||
resp.setOrderPrice(parse.getAmount().getTotal());
|
||
resp.setPayPrice(parse.getAmount().getPayerTotal());
|
||
}
|
||
switch (parse.getTradeState()) {
|
||
case SUCCESS:
|
||
resp.setState(PayResponse.PAY_STATE.SUCCESS);
|
||
break;
|
||
case NOTPAY:
|
||
case CLOSED:
|
||
case REVOKED:
|
||
resp.setState(PayResponse.PAY_STATE.FAIL);
|
||
break;
|
||
case REFUND:
|
||
resp.setState(PayResponse.PAY_STATE.REFUND);
|
||
break;
|
||
default:
|
||
resp.setState(PayResponse.PAY_STATE.CANCEL);
|
||
break;
|
||
}
|
||
resp.setPayTime(parse.getSuccessTime());
|
||
resp.setOriginalResponse(parse);
|
||
return resp;
|
||
}
|
||
|
||
@Override
|
||
public PayResponse queryOrder(String orderNo) {
|
||
Config wxConfig = getConfig();
|
||
JsapiService service = new JsapiService.Builder().config(wxConfig).build();
|
||
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
|
||
queryRequest.setMchid(config.getMerchantId());
|
||
queryRequest.setOutTradeNo(orderNo);
|
||
PayResponse resp = new PayResponse();
|
||
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
|
||
resp.setValid(true);
|
||
resp.setOrderNo(result.getOutTradeNo());
|
||
if (result.getAmount() != null) {
|
||
resp.setOrderPrice(result.getAmount().getTotal());
|
||
resp.setPayPrice(result.getAmount().getPayerTotal());
|
||
}
|
||
switch (result.getTradeState()) {
|
||
case SUCCESS:
|
||
resp.setState(PayResponse.PAY_STATE.SUCCESS);
|
||
break;
|
||
case NOTPAY:
|
||
case CLOSED:
|
||
case REVOKED:
|
||
resp.setState(PayResponse.PAY_STATE.FAIL);
|
||
break;
|
||
case REFUND:
|
||
resp.setState(PayResponse.PAY_STATE.REFUND);
|
||
break;
|
||
default:
|
||
resp.setState(PayResponse.PAY_STATE.CANCEL);
|
||
break;
|
||
}
|
||
resp.setPayTime(result.getSuccessTime());
|
||
resp.setOriginalResponse(result);
|
||
return resp;
|
||
}
|
||
|
||
@Override
|
||
public RefundOrderResponse refund(RefundOrderRequest request) {
|
||
RefundOrderResponse resp = new RefundOrderResponse();
|
||
Config wxConfig = getConfig();
|
||
RefundService service = new RefundService.Builder().config(wxConfig).build();
|
||
CreateRequest createRequest = new CreateRequest();
|
||
createRequest.setOutTradeNo(request.getOrderNo());
|
||
createRequest.setOutRefundNo(request.getRefundNo());
|
||
AmountReq amountReq = new AmountReq();
|
||
amountReq.setTotal(Long.valueOf(request.getPrice()));
|
||
amountReq.setRefund(Long.valueOf(request.getRefundPrice()));
|
||
amountReq.setCurrency("CNY");
|
||
createRequest.setAmount(amountReq);
|
||
createRequest.setNotifyUrl(request.getNotifyUrl());
|
||
Refund refund = service.create(createRequest);
|
||
if (refund.getStatus() == SUCCESS) {
|
||
resp.setSuccess(true);
|
||
resp.setRefundNo(refund.getOutRefundNo());
|
||
} else {
|
||
resp.setSuccess(false);
|
||
}
|
||
return resp;
|
||
}
|
||
|
||
@Override
|
||
public RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException {
|
||
ServletInputStream inputStream = request.getInputStream();
|
||
StringBuffer body = new StringBuffer();
|
||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||
String s;
|
||
// 读取回调请求体
|
||
while ((s = bufferedReader.readLine()) != null) {
|
||
body.append(s);
|
||
}
|
||
RefundResponse resp = new RefundResponse();
|
||
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
|
||
String nonce = request.getHeader(WECHAT_PAY_NONCE);
|
||
String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);
|
||
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
|
||
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
|
||
NotificationConfig config = (NotificationConfig) getConfig();
|
||
NotificationParser parser = new NotificationParser(config);RequestParam requestParam = new RequestParam.Builder()
|
||
.serialNumber(serialNo)
|
||
.nonce(nonce)
|
||
.signature(signature)
|
||
.timestamp(timestamp)
|
||
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
|
||
.signType(signType)
|
||
.body(body.toString())
|
||
.build();
|
||
RefundNotification parse = parser.parse(requestParam, RefundNotification.class);
|
||
resp.setValid(true);
|
||
resp.setOriginalResponse(parse);
|
||
if (parse.getRefundStatus() == SUCCESS) {
|
||
//退款成功
|
||
resp.setSuccess();
|
||
resp.setRefundTime(parse.getSuccessTime());
|
||
resp.setOrderNo(parse.getOutTradeNo());
|
||
resp.setRefundNo(parse.getRefundId());
|
||
if (parse.getAmount() != null) {
|
||
resp.setRefundPrice(Math.toIntExact(parse.getAmount().getPayerRefund()));
|
||
resp.setOrderPrice(Math.toIntExact(parse.getAmount().getTotal()));
|
||
}
|
||
} else {
|
||
//退款失败
|
||
resp.setFail();
|
||
}
|
||
return resp;
|
||
}
|
||
|
||
@Override
|
||
public RefundResponse checkRefundStatus(String refundNo) {
|
||
Config wxConfig = getConfig();
|
||
RefundService service = new RefundService.Builder().config(wxConfig).build();
|
||
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
|
||
request.setOutRefundNo(refundNo);
|
||
RefundResponse resp = new RefundResponse();
|
||
Refund result = service.queryByOutRefundNo(request);
|
||
resp.setValid(true);
|
||
resp.setOriginalResponse(result);
|
||
if (result.getStatus() == SUCCESS) {
|
||
//退款成功
|
||
resp.setSuccess();
|
||
resp.setRefundTime(result.getSuccessTime());
|
||
resp.setOrderNo(result.getOutTradeNo());
|
||
resp.setRefundNo(result.getRefundId());
|
||
if (result.getAmount() != null) {
|
||
resp.setRefundPrice(Math.toIntExact(result.getAmount().getPayerRefund()));
|
||
resp.setOrderPrice(Math.toIntExact(result.getAmount().getTotal()));
|
||
}
|
||
} else {
|
||
//退款失败
|
||
resp.setFail();
|
||
}
|
||
return resp;
|
||
}
|
||
|
||
@Override
|
||
public void cancelOrder(CancelOrderRequest request) {
|
||
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
|
||
closeOrderRequest.setOutTradeNo(request.getOrderNo());
|
||
closeOrderRequest.setMchid(config.getMerchantId());
|
||
Config config = getConfig();
|
||
JsapiService service = new JsapiService.Builder().config(config).build();
|
||
service.closeOrder(closeOrderRequest);
|
||
}
|
||
|
||
public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
|
||
String replace = privateKey.replace("\\n", "\n");
|
||
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(replace);
|
||
Signature sign = Signature.getInstance("SHA256withRSA");
|
||
sign.initSign(merchantPrivateKey);
|
||
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
|
||
return Base64Utils.encodeToString(sign.sign());
|
||
}
|
||
}
|