|
@@ -0,0 +1,425 @@
|
|
|
|
|
+package shop.alien.store.strategy.merchantPayment.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import okhttp3.*;
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
|
+import shop.alien.entity.result.R;
|
|
|
|
|
+import shop.alien.entity.store.MerchantPaymentOrder;
|
|
|
|
|
+import shop.alien.entity.store.RefundRecord;
|
|
|
|
|
+import shop.alien.entity.store.StorePaymentConfig;
|
|
|
|
|
+import shop.alien.entity.store.UserReservationOrder;
|
|
|
|
|
+import shop.alien.store.service.MerchantPaymentOrderService;
|
|
|
|
|
+import shop.alien.store.service.RefundRecordService;
|
|
|
|
|
+import shop.alien.store.service.StorePaymentConfigService;
|
|
|
|
|
+import shop.alien.store.service.UserReservationOrderService;
|
|
|
|
|
+import shop.alien.store.strategy.merchantPayment.MerchantPaymentStrategy;
|
|
|
|
|
+import shop.alien.store.strategy.payment.impl.WeChatPaymentStrategyImpl;
|
|
|
|
|
+import shop.alien.store.util.WXPayUtility;
|
|
|
|
|
+import shop.alien.util.common.UniqueRandomNumGenerator;
|
|
|
|
|
+import shop.alien.util.common.constant.PaymentEnum;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.security.PrivateKey;
|
|
|
|
|
+import java.security.PublicKey;
|
|
|
|
|
+import java.security.Signature;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 商户微信支付策略(预订订单,使用 StorePaymentConfig 按门店配置,业务逻辑与支付宝一致)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author system
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Component
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrategy {
|
|
|
|
|
+
|
|
|
|
|
+ private static final String POSTMETHOD = "POST";
|
|
|
|
|
+ private static final String GETMETHOD = "GET";
|
|
|
|
|
+ private static final String REDIS_PREPAY_KEY_PREFIX = "merchant:wechat:prepay:order:";
|
|
|
|
|
+ private static final long REDIS_PREPAY_EXPIRE_SECONDS = 15 * 60;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${payment.wechatPay.host:https://api.mch.weixin.qq.com}")
|
|
|
|
|
+ private String wechatPayApiHost;
|
|
|
|
|
+ @Value("${payment.wechatPay.prePayPath:/v3/pay/transactions/app}")
|
|
|
|
|
+ private String prePayPath;
|
|
|
|
|
+ @Value("${payment.wechatPay.searchOrderByOutTradeNoPath:/v3/pay/transactions/out-trade-no/{out_trade_no}}")
|
|
|
|
|
+ private String searchOrderByOutTradeNoPath;
|
|
|
|
|
+ @Value("${payment.wechatPay.refundPath:/v3/refund/domestic/refunds}")
|
|
|
|
|
+ private String refundPath;
|
|
|
|
|
+ @Value("${payment.wechatPay.business.prePayNotifyUrl:}")
|
|
|
|
|
+ private String prePayNotifyUrl;
|
|
|
|
|
+ @Value("${payment.wechatPay.business.refundNotifyUrl:}")
|
|
|
|
|
+ private String refundNotifyUrl;
|
|
|
|
|
+
|
|
|
|
|
+ private final StorePaymentConfigService storePaymentConfigService;
|
|
|
|
|
+ private final UserReservationOrderService userReservationOrderService;
|
|
|
|
|
+ private final MerchantPaymentOrderService merchantPaymentOrderService;
|
|
|
|
|
+ private final RefundRecordService refundRecordService;
|
|
|
|
|
+ private final StringRedisTemplate stringRedisTemplate;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<Map<String, Object>> createPrePay(Integer storeId, Integer orderId, String amountYuan, String subject, Integer userId) {
|
|
|
|
|
+ if (storeId == null || orderId == null) {
|
|
|
|
|
+ return R.fail("门店ID和订单ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(amountYuan) || new BigDecimal(amountYuan).compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return R.fail("支付金额必须大于0");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(subject)) {
|
|
|
|
|
+ return R.fail("订单描述不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
|
|
+ if (config == null) {
|
|
|
|
|
+ return R.fail("该门店未配置支付参数");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(config.getWechatAppId()) || StringUtils.isBlank(config.getWechatMchId())
|
|
|
|
|
+ || StringUtils.isBlank(config.getMerchantSerialNumber()) || StringUtils.isBlank(config.getApiV3Key())
|
|
|
|
|
+ || StringUtils.isBlank(config.getWechatPayPublicKeyId()) || StringUtils.isBlank(config.getWechatPrivateKeyFile())
|
|
|
|
|
+ || StringUtils.isBlank(config.getWechatPayPublicKeyFile())) {
|
|
|
|
|
+ return R.fail("门店微信支付配置不完整");
|
|
|
|
|
+ }
|
|
|
|
|
+ UserReservationOrder order = userReservationOrderService.getById(orderId);
|
|
|
|
|
+ if (order == null) {
|
|
|
|
|
+ return R.fail("预订订单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!order.getStoreId().equals(storeId)) {
|
|
|
|
|
+ return R.fail("订单与门店不匹配");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (order.getPaymentStatus() != null && order.getPaymentStatus() == 1) {
|
|
|
|
|
+ return R.fail("订单已支付");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String redisKey = REDIS_PREPAY_KEY_PREFIX + orderId;
|
|
|
|
|
+ String cached = stringRedisTemplate.opsForValue().get(redisKey);
|
|
|
|
|
+ if (StringUtils.isNotBlank(cached)) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ Map<String, Object> data = JSON.parseObject(cached, Map.class);
|
|
|
|
|
+ if (data != null && !data.isEmpty()) {
|
|
|
|
|
+ log.info("商户预订订单微信预支付命中缓存,orderId={}", orderId);
|
|
|
|
|
+ return R.data(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("解析微信预支付缓存失败,将重新发起,orderId={}", orderId, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int deleted = merchantPaymentOrderService.logicDeleteByOrderId(orderId);
|
|
|
|
|
+ if (deleted > 0) {
|
|
|
|
|
+ log.info("未命中缓存,已逻辑删除该订单下 {} 条支付单,orderId={}", deleted, orderId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String outTradeNo = UniqueRandomNumGenerator.generateUniqueCode(19);
|
|
|
|
|
+ BigDecimal amount = new BigDecimal(amountYuan);
|
|
|
|
|
+
|
|
|
|
|
+ MerchantPaymentOrder paymentOrder = new MerchantPaymentOrder();
|
|
|
|
|
+ paymentOrder.setPaymentNo(merchantPaymentOrderService.generatePaymentNo());
|
|
|
|
|
+ paymentOrder.setOrderType("reservation_order");
|
|
|
|
|
+ paymentOrder.setOrderId(order.getId());
|
|
|
|
|
+ paymentOrder.setOrderSn(order.getOrderSn());
|
|
|
|
|
+ paymentOrder.setStoreId(storeId);
|
|
|
|
|
+ paymentOrder.setPayType(PaymentEnum.WECHAT_PAY.getType());
|
|
|
|
|
+ paymentOrder.setOutTradeNo(outTradeNo);
|
|
|
|
|
+ paymentOrder.setPayAmount(amount);
|
|
|
|
|
+ paymentOrder.setPayStatus(0);
|
|
|
|
|
+ paymentOrder.setPayerUserId(userId);
|
|
|
|
|
+ paymentOrder.setSubject(subject);
|
|
|
|
|
+ paymentOrder.setCreatedTime(new Date());
|
|
|
|
|
+ paymentOrder.setUpdatedTime(new Date());
|
|
|
|
|
+ merchantPaymentOrderService.save(paymentOrder);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromString(config.getWechatPrivateKeyFile());
|
|
|
|
|
+
|
|
|
|
|
+ WeChatPaymentStrategyImpl.CommonPrepayRequest request = new WeChatPaymentStrategyImpl.CommonPrepayRequest();
|
|
|
|
|
+ request.appid = config.getWechatAppId();
|
|
|
|
|
+ request.mchid = config.getWechatMchId();
|
|
|
|
|
+ request.description = subject;
|
|
|
|
|
+ request.outTradeNo = outTradeNo;
|
|
|
|
|
+ request.notifyUrl = StringUtils.isNotBlank(prePayNotifyUrl) ? prePayNotifyUrl : "";
|
|
|
|
|
+ request.amount = new WeChatPaymentStrategyImpl.CommonAmountInfo();
|
|
|
|
|
+ request.amount.total = amount.multiply(new BigDecimal(100)).longValue();
|
|
|
|
|
+ request.amount.currency = "CNY";
|
|
|
|
|
+
|
|
|
|
|
+ WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse response = prePayOrderRun(config, privateKey, request);
|
|
|
|
|
+ if (response == null || StringUtils.isBlank(response.prepayId)) {
|
|
|
|
|
+ return R.fail("微信预支付失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long timestamp = System.currentTimeMillis() / 1000;
|
|
|
|
|
+ String nonce = WXPayUtility.createNonce(32);
|
|
|
|
|
+ String message = String.format("%s\n%s\n%s\n%s\n", config.getWechatAppId(), timestamp, nonce, response.prepayId);
|
|
|
|
|
+ Signature sign = Signature.getInstance("SHA256withRSA");
|
|
|
|
|
+ sign.initSign(privateKey);
|
|
|
|
|
+ sign.update(message.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ String signStr = Base64.getEncoder().encodeToString(sign.sign());
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
|
|
+ data.put("outTradeNo", outTradeNo);
|
|
|
|
|
+ data.put("orderSn", order.getOrderSn());
|
|
|
|
|
+ data.put("orderId", order.getId());
|
|
|
|
|
+ data.put("paymentNo", paymentOrder.getPaymentNo());
|
|
|
|
|
+ data.put("prepayId", response.prepayId);
|
|
|
|
|
+ data.put("appId", config.getWechatAppId());
|
|
|
|
|
+ data.put("mchId", config.getWechatMchId());
|
|
|
|
|
+ data.put("sign", signStr);
|
|
|
|
|
+ data.put("timestamp", String.valueOf(timestamp));
|
|
|
|
|
+ data.put("nonce", nonce);
|
|
|
|
|
+ data.put("orderStr", "");
|
|
|
|
|
+
|
|
|
|
|
+ String cacheJson = JSON.toJSONString(data);
|
|
|
|
|
+ stringRedisTemplate.opsForValue().set(redisKey, cacheJson != null ? cacheJson : "{}", REDIS_PREPAY_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
|
|
+ log.info("商户预订订单微信预支付成功并写入缓存,storeId={}, orderSn={}, outTradeNo={}", storeId, order.getOrderSn(), outTradeNo);
|
|
|
|
|
+ return R.data(data);
|
|
|
|
|
+ } catch (WXPayUtility.ApiException e) {
|
|
|
|
|
+ log.error("商户预订订单微信预支付异常,storeId={}, orderId={}", storeId, orderId, e);
|
|
|
|
|
+ return R.fail("预支付失败:" + e.getMessage());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("商户微信预支付异常,storeId={}", storeId, e);
|
|
|
|
|
+ return R.fail("预支付失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<Object> queryPayStatus(Integer storeId, String outTradeNo) {
|
|
|
|
|
+ if (storeId == null || StringUtils.isBlank(outTradeNo)) {
|
|
|
|
|
+ return R.fail("门店ID和商户订单号不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
|
|
+ if (config == null) {
|
|
|
|
|
+ return R.fail("该门店未配置支付参数");
|
|
|
|
|
+ }
|
|
|
|
|
+ MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
|
|
|
|
|
+ if (paymentOrder == null) {
|
|
|
|
|
+ return R.fail("支付单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ UserReservationOrder order = userReservationOrderService.getById(paymentOrder.getOrderId());
|
|
|
|
|
+ if (order == null) {
|
|
|
|
|
+ return R.fail("预订订单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromString(config.getWechatPrivateKeyFile());
|
|
|
|
|
+ PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromString(config.getWechatPayPublicKeyFile());
|
|
|
|
|
+
|
|
|
|
|
+ WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest req = new WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest();
|
|
|
|
|
+ req.transactionId = outTradeNo;
|
|
|
|
|
+ req.mchid = config.getWechatMchId();
|
|
|
|
|
+ WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse response = searchOrderRun(config, privateKey, wechatPayPublicKey, req);
|
|
|
|
|
+ if (response == null) {
|
|
|
|
|
+ return R.fail("查询失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ if ("SUCCESS".equals(response.tradeState)) {
|
|
|
|
|
+ Date now = new Date();
|
|
|
|
|
+ paymentOrder.setPayStatus(1);
|
|
|
|
|
+ paymentOrder.setTradeNo(response.transactionId);
|
|
|
|
|
+ paymentOrder.setPayTime(now);
|
|
|
|
|
+ paymentOrder.setUpdatedTime(now);
|
|
|
|
|
+ merchantPaymentOrderService.updateById(paymentOrder);
|
|
|
|
|
+
|
|
|
|
|
+ order.setPaymentStatus(1);
|
|
|
|
|
+ order.setPayTime(now);
|
|
|
|
|
+ order.setPaymentMethod("微信支付");
|
|
|
|
|
+ order.setOrderStatus(1);
|
|
|
|
|
+ if (StringUtils.isBlank(order.getVerificationCode())) {
|
|
|
|
|
+ order.setVerificationCode("YS" + UniqueRandomNumGenerator.generateUniqueCode(10));
|
|
|
|
|
+ }
|
|
|
|
|
+ order.setUpdatedTime(now);
|
|
|
|
|
+ userReservationOrderService.updateById(order);
|
|
|
|
|
+ return R.success("支付成功");
|
|
|
|
|
+ }
|
|
|
|
|
+ if ("CLOSED".equals(response.tradeState)) {
|
|
|
|
|
+ return R.fail("交易已关闭");
|
|
|
|
|
+ }
|
|
|
|
|
+ if ("NOTPAY".equals(response.tradeState) || "USERPAYING".equals(response.tradeState)) {
|
|
|
|
|
+ return R.fail("等待用户付款");
|
|
|
|
|
+ }
|
|
|
|
|
+ return R.fail("订单状态:" + (response.tradeStateDesc != null ? response.tradeStateDesc : response.tradeState));
|
|
|
|
|
+ } catch (WXPayUtility.ApiException e) {
|
|
|
|
|
+ log.error("查询商户微信订单状态异常,outTradeNo={}", outTradeNo, e);
|
|
|
|
|
+ return R.fail("查询异常:" + e.getMessage());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("商户微信查询支付状态异常,storeId={}", storeId, e);
|
|
|
|
|
+ return R.fail("查询失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<String> refund(Integer storeId, String outTradeNo, String refundAmount, String refundReason) {
|
|
|
|
|
+ if (storeId == null || StringUtils.isBlank(outTradeNo)) {
|
|
|
|
|
+ return R.fail("门店ID和商户订单号不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(refundAmount) || new BigDecimal(refundAmount).compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return R.fail("退款金额必须大于0");
|
|
|
|
|
+ }
|
|
|
|
|
+ StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
|
|
+ if (config == null) {
|
|
|
|
|
+ return R.fail("该门店未配置支付参数");
|
|
|
|
|
+ }
|
|
|
|
|
+ MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
|
|
|
|
|
+ if (paymentOrder == null) {
|
|
|
|
|
+ return R.fail("支付单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ UserReservationOrder order = userReservationOrderService.getById(paymentOrder.getOrderId());
|
|
|
|
|
+ if (order == null) {
|
|
|
|
|
+ return R.fail("预订订单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (order.getPaymentStatus() == null || order.getPaymentStatus() != 1) {
|
|
|
|
|
+ return R.fail("订单未支付或已退款,无法退款");
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromString(config.getWechatPrivateKeyFile());
|
|
|
|
|
+ PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromString(config.getWechatPayPublicKeyFile());
|
|
|
|
|
+
|
|
|
|
|
+ WeChatPaymentStrategyImpl.CreateRequest request = new WeChatPaymentStrategyImpl.CreateRequest();
|
|
|
|
|
+ request.outTradeNo = outTradeNo;
|
|
|
|
|
+ request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
|
|
|
|
|
+ request.reason = StringUtils.isNotBlank(refundReason) ? refundReason : "用户申请退款";
|
|
|
|
|
+ request.notifyUrl = StringUtils.isNotBlank(refundNotifyUrl) ? refundNotifyUrl : "";
|
|
|
|
|
+ request.amount = new WeChatPaymentStrategyImpl.AmountReq();
|
|
|
|
|
+ request.amount.refund = new BigDecimal(refundAmount).multiply(new BigDecimal(100)).longValue();
|
|
|
|
|
+ request.amount.total = order.getTotalAmount() != null ? order.getTotalAmount().multiply(new BigDecimal(100)).longValue() : request.amount.refund;
|
|
|
|
|
+ request.amount.currency = "CNY";
|
|
|
|
|
+
|
|
|
|
|
+ WeChatPaymentStrategyImpl.Refund response = refundRun(config, privateKey, wechatPayPublicKey, request);
|
|
|
|
|
+ if (response == null) {
|
|
|
|
|
+ return R.fail("退款失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ String status = response.status != null ? response.status.name() : "";
|
|
|
|
|
+ if (!"SUCCESS".equals(status) && !"PROCESSING".equals(status)) {
|
|
|
|
|
+ return R.fail("退款失败:" + status);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Date now = new Date();
|
|
|
|
|
+ BigDecimal refundAmountDecimal = new BigDecimal(refundAmount);
|
|
|
|
|
+ paymentOrder.setPayStatus(3);
|
|
|
|
|
+ paymentOrder.setUpdatedTime(now);
|
|
|
|
|
+ merchantPaymentOrderService.updateById(paymentOrder);
|
|
|
|
|
+
|
|
|
|
|
+ order.setPaymentStatus(2);
|
|
|
|
|
+ order.setRefundAmount(refundAmountDecimal);
|
|
|
|
|
+ order.setRefundTime(now);
|
|
|
|
|
+ order.setRefundReason(refundReason);
|
|
|
|
|
+ order.setUpdatedTime(now);
|
|
|
|
|
+ userReservationOrderService.updateById(order);
|
|
|
|
|
+
|
|
|
|
|
+ RefundRecord record = new RefundRecord();
|
|
|
|
|
+ record.setPayType(PaymentEnum.WECHAT_PAY.getType());
|
|
|
|
|
+ record.setOutTradeNo(outTradeNo);
|
|
|
|
|
+ record.setTransactionId(response.transactionId);
|
|
|
|
|
+ record.setOutRefundNo(response.outRefundNo != null ? response.outRefundNo : request.outRefundNo);
|
|
|
|
|
+ record.setRefundId(response.refundId);
|
|
|
|
|
+ record.setRefundStatus("SUCCESS");
|
|
|
|
|
+ if (order.getTotalAmount() != null) {
|
|
|
|
|
+ record.setTotalAmount(order.getTotalAmount().multiply(new BigDecimal(100)).longValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ record.setRefundAmount(refundAmountDecimal.multiply(new BigDecimal(100)).longValue());
|
|
|
|
|
+ record.setRefundReason(refundReason);
|
|
|
|
|
+ record.setOrderId(String.valueOf(order.getId()));
|
|
|
|
|
+ record.setStoreId(storeId);
|
|
|
|
|
+ record.setUserId(order.getUserId());
|
|
|
|
|
+ record.setCreatedTime(now);
|
|
|
|
|
+ record.setDeleteFlag(0);
|
|
|
|
|
+ refundRecordService.save(record);
|
|
|
|
|
+
|
|
|
|
|
+ log.info("商户预订订单微信退款成功,outTradeNo={}", outTradeNo);
|
|
|
|
|
+ return R.data("退款成功");
|
|
|
|
|
+ } catch (WXPayUtility.ApiException e) {
|
|
|
|
|
+ log.error("商户预订订单微信退款异常,outTradeNo={}", outTradeNo, e);
|
|
|
|
|
+ return R.fail("退款失败:" + e.getMessage());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("商户微信退款异常,storeId={}", storeId, e);
|
|
|
|
|
+ return R.fail("退款失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String getType() {
|
|
|
|
|
+ return PaymentEnum.WECHAT_PAY.getType();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse prePayOrderRun(StorePaymentConfig config,
|
|
|
|
|
+ PrivateKey privateKey,
|
|
|
|
|
+ WeChatPaymentStrategyImpl.CommonPrepayRequest request) throws IOException {
|
|
|
|
|
+ String uri = prePayPath;
|
|
|
|
|
+ String reqBody = WXPayUtility.toJson(request);
|
|
|
|
|
+ Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
|
|
|
|
|
+ reqBuilder.addHeader("Accept", "application/json");
|
|
|
|
|
+ reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
|
|
|
|
|
+ reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, POSTMETHOD, uri, reqBody));
|
|
|
|
|
+ reqBuilder.addHeader("Content-Type", "application/json");
|
|
|
|
|
+ MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
|
|
|
|
|
+ RequestBody body = RequestBody.create(reqBody, jsonMediaType);
|
|
|
|
|
+ reqBuilder.method(POSTMETHOD, body);
|
|
|
|
|
+ OkHttpClient client = new OkHttpClient.Builder().build();
|
|
|
|
|
+ try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
|
|
|
|
|
+ String respBody = WXPayUtility.extractBody(httpResponse);
|
|
|
|
|
+ if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
|
|
|
|
|
+ PublicKey publicKey = WXPayUtility.loadPublicKeyFromString(config.getWechatPayPublicKeyFile());
|
|
|
|
|
+ WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), publicKey, httpResponse.headers(), respBody);
|
|
|
|
|
+ return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse searchOrderRun(StorePaymentConfig config,
|
|
|
|
|
+ PrivateKey privateKey,
|
|
|
|
|
+ PublicKey wechatPayPublicKey,
|
|
|
|
|
+ WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest request) throws IOException {
|
|
|
|
|
+ String uri = searchOrderByOutTradeNoPath.replace("{out_trade_no}", WXPayUtility.urlEncode(request.transactionId));
|
|
|
|
|
+ Map<String, Object> args = new HashMap<>();
|
|
|
|
|
+ args.put("mchid", config.getWechatMchId());
|
|
|
|
|
+ String queryString = WXPayUtility.urlEncode(args);
|
|
|
|
|
+ if (!queryString.isEmpty()) {
|
|
|
|
|
+ uri = uri + "?" + queryString;
|
|
|
|
|
+ }
|
|
|
|
|
+ Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
|
|
|
|
|
+ reqBuilder.addHeader("Accept", "application/json");
|
|
|
|
|
+ reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
|
|
|
|
|
+ reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, GETMETHOD, uri, null));
|
|
|
|
|
+ reqBuilder.method(GETMETHOD, null);
|
|
|
|
|
+ OkHttpClient client = new OkHttpClient.Builder().build();
|
|
|
|
|
+ try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
|
|
|
|
|
+ String respBody = WXPayUtility.extractBody(httpResponse);
|
|
|
|
|
+ if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
|
|
|
|
|
+ WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
|
|
+ return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private WeChatPaymentStrategyImpl.Refund refundRun(StorePaymentConfig config,
|
|
|
|
|
+ PrivateKey privateKey,
|
|
|
|
|
+ PublicKey wechatPayPublicKey,
|
|
|
|
|
+ WeChatPaymentStrategyImpl.CreateRequest request) throws IOException {
|
|
|
|
|
+ String uri = refundPath;
|
|
|
|
|
+ String reqBody = WXPayUtility.toJson(request);
|
|
|
|
|
+ Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
|
|
|
|
|
+ reqBuilder.addHeader("Accept", "application/json");
|
|
|
|
|
+ reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
|
|
|
|
|
+ reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, POSTMETHOD, uri, reqBody));
|
|
|
|
|
+ reqBuilder.addHeader("Content-Type", "application/json");
|
|
|
|
|
+ MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
|
|
|
|
|
+ RequestBody body = RequestBody.create(reqBody, jsonMediaType);
|
|
|
|
|
+ reqBuilder.method(POSTMETHOD, body);
|
|
|
|
|
+ OkHttpClient client = new OkHttpClient.Builder().build();
|
|
|
|
|
+ try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
|
|
|
|
|
+ String respBody = WXPayUtility.extractBody(httpResponse);
|
|
|
|
|
+ if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
|
|
|
|
|
+ WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
|
|
+ return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.Refund.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|