Procházet zdrojové kódy

fix:小程序支付初次提交

刘云鑫 před 2 měsíci
rodič
revize
f9c9f4b273

+ 8 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/PaymentStrategy.java

@@ -50,6 +50,14 @@ public interface PaymentStrategy {
      */
      String handleRefund(Map<String,String> params) throws Exception;
 
+     /**
+     * 查询退款记录
+     *
+     * @param outRefundNo 退款订单号
+     * @return 退款记录信息
+     * @throws Exception 查询异常
+     */
+    R searchRefundRecordByOutRefundNo(String outRefundNo ) throws Exception;
 
 
     /**

+ 5 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/impl/AlipayPaymentStrategyImpl.java

@@ -347,6 +347,11 @@ public class AlipayPaymentStrategyImpl implements PaymentStrategy {
     }
 
     @Override
+    public R searchRefundRecordByOutRefundNo(String outRefundNo) throws Exception {
+        return null;
+    }
+
+    @Override
     public String getType() {
         return PaymentEnum.ALIPAY.getType();
     }

+ 639 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/impl/WeChatPaymentMininProgramStrategyImpl.java

@@ -0,0 +1,639 @@
+package shop.alien.store.strategy.payment.impl;
+
+
+import com.google.gson.annotations.SerializedName;
+import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
+import com.wechat.pay.java.service.refund.model.Refund;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.RefundRecord;
+import shop.alien.store.service.RefundRecordService;
+import shop.alien.store.strategy.payment.PaymentStrategy;
+import shop.alien.store.util.WXPayUtility;
+import shop.alien.util.common.UniqueRandomNumGenerator;
+import shop.alien.util.common.constant.PaymentEnum;
+import shop.alien.util.system.OSUtil;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.*;
+
+
+/**
+ * 微信支付小程序策略
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@RefreshScope
+public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
+    /**
+     * 微信支付api主机地址
+     */
+    @Value("${payment.wechatPay.host}")
+    private String wechatPayApiHost;
+    /**
+     * 微信支付商户id
+     */
+    @Value("${payment.wechatPay.business.mchId}")
+    private String mchId;
+    // TODO:小程序未注册-> 把下面的所有默认的去掉然后从配置文件中读取
+    /**
+     * 微信支付小程序应用id
+     */
+    @Value("${payment.wechatPay.business.mininprogram.appId:没有}")
+    private String appId;
+    /**
+     * 微信支付商户证书序列号
+     */
+    @Value("${payment.wechatPay.business.merchantSerialNumber}")
+    private String certificateSerialNo;
+    /**
+     * 微信支付公钥id
+     */
+    @Value("${payment.wechatPay.business.wechatPayPublicKeyId}")
+    private String wechatPayPublicKeyId;
+    /**
+     * 微信支付商户私钥路径
+     */
+    @Value("${payment.wechatPay.business.win.privateKeyPath}")
+    private String privateWinKeyPath;
+
+    /**
+     * 微信支付商户私钥路径(Linux环境)
+     */
+    @Value("${payment.wechatPay.business.linux.privateKeyPath}")
+    private String privateLinuxKeyPath;
+
+    /**
+     * 微信支付公钥路径
+     */
+    @Value("${payment.wechatPay.business.win.wechatPayPublicKeyFilePath}")
+    private String wechatWinPayPublicKeyFilePath;
+
+    /**
+     * 微信支付公钥路径(Linux环境)
+     */
+    @Value("${payment.wechatPay.business.linux.wechatPayPublicKeyFilePath}")
+    private String wechatLinuxPayPublicKeyFilePath;
+    /**
+     * 微信支付预支付路径
+     */
+    @Value("${payment.wechatPay.miniProgram.prePayPath:/v3/pay/transactions/jsapi}")
+    private String prePayPath;
+    /**
+     * 微信支付预支付通知路径
+     */
+    @Value("${payment.wechatPay.miniProgram.prePayNotifyUrl:https://www.weixin.qq.com/wxpay/pay.php}")
+    private String prePayNotifyUrl;
+    /**
+     * 微信支付退款通知路径
+     */
+    @Value("${payment.wechatPay.business.miniProgram.refundNotifyUrl:https://www.weixin.qq.com/wxpay/pay.php}")
+    private String refundNotifyUrl;
+    /**
+     * 微信支付查询退款状态路径
+     */
+    @Value("${payment.wechatPay.searchRefundStatusByOutRefundNoPath:/v3/refund/domestic/refunds/{out_refund_no}}")
+    private String searchRefundStatusByOutRefundNoPath;
+
+    private static String POSTMETHOD = "POST";
+    private static String GETMETHOD = "GET";
+
+    private PrivateKey privateKey;
+    private PublicKey wechatPayPublicKey;
+
+    private final WeChatPaymentStrategyImpl weChatPaymentStrategy;
+    private final RefundRecordService refundRecordService;
+
+    @PostConstruct
+    public void setWeChatPaymentConfig() {
+        String privateKeyPath;
+        String wechatPayPublicKeyFilePath;
+        if ("windows".equals(OSUtil.getOsName())) {
+            privateKeyPath = privateWinKeyPath;
+            wechatPayPublicKeyFilePath = wechatWinPayPublicKeyFilePath;
+        } else {
+            privateKeyPath = privateLinuxKeyPath;
+            wechatPayPublicKeyFilePath = wechatLinuxPayPublicKeyFilePath;
+        }
+        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
+        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
+    }
+
+    @Override
+    public R createPrePayOrder(String price, String subject) throws Exception {
+
+
+        DirectAPIv3JsapiPrepayRequest request = new DirectAPIv3JsapiPrepayRequest();
+        request.appid = appId;
+        request.mchid = mchId;
+        request.description = subject;
+        request.outTradeNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+//        request.timeExpire = "2018-06-08T10:34:56+08:00";
+//        request.attach = "自定义数据说明";
+        request.notifyUrl = prePayNotifyUrl;
+        request.goodsTag = "WXG";
+        request.supportFapiao = false;
+        request.amount = new CommonAmountInfo();
+        request.amount.total = 100L;
+        request.amount.currency = "CNY";
+        request.payer = new JsapiReqPayerInfo();
+        request.payer.openid = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o";
+        request.detail = new CouponInfo();
+        request.detail.costPrice = 608800L;
+        request.detail.invoiceId = "微信123";
+        request.detail.goodsDetail = new ArrayList<>();
+        {
+            GoodsDetail goodsDetailItem = new GoodsDetail();
+            goodsDetailItem.merchantGoodsId = "1246464644";
+            goodsDetailItem.wechatpayGoodsId = "1001";
+            goodsDetailItem.goodsName = "iPhoneX 256G";
+            goodsDetailItem.quantity = 1L;
+            goodsDetailItem.unitPrice = 528800L;
+            request.detail.goodsDetail.add(goodsDetailItem);
+        };
+        request.sceneInfo = new CommonSceneInfo();
+        request.sceneInfo.payerClientIp = "14.23.150.211";
+        request.sceneInfo.deviceId = "013467007045764";
+        request.sceneInfo.storeInfo = new StoreInfo();
+        request.sceneInfo.storeInfo.id = "0001";
+        request.sceneInfo.storeInfo.name = "腾讯大厦分店";
+        request.sceneInfo.storeInfo.areaCode = "440305";
+        request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
+        request.settleInfo = new SettleInfo();
+        request.settleInfo.profitSharing = false;
+        try {
+            DirectAPIv3JsapiPrepayResponse response = doCreatePrePayOrder(request);
+            // TODO: 请求成功,继续业务逻辑
+            log.info("微信预支付订单创建成功,预支付ID:{}", response.prepayId);
+            Map<String,String> result = new HashMap<>();
+            result.put("prepayId", response.prepayId);
+            result.put("appId", appId);
+            result.put("mchId", mchId);
+            result.put("orderNo", request.outTradeNo);
+            long timestamp = System.currentTimeMillis() / 1000; // 时间戳
+            String nonce = WXPayUtility.createNonce(32); // 随机字符串
+            String prepayId = response.prepayId;
+            String message = String.format("%s\n%s\n%s\n%s\n", appId, timestamp, nonce, prepayId);
+//            String sign = WXPayUtility.sign(message, sha256withRSA, privateKey);
+
+            Signature sign = Signature.getInstance("SHA256withRSA");
+            sign.initSign(privateKey);
+            sign.update(message.getBytes(StandardCharsets.UTF_8));
+            result.put("signType","RSA");
+            result.put("sign", Base64.getEncoder().encodeToString(sign.sign()));
+            result.put("timestamp", String.valueOf(timestamp));
+            result.put("nonce", nonce);
+            return R.data(result);
+        } catch (WXPayUtility.ApiException e) {
+            log.error("微信支付预支付失败,状态码:{},错误信息:{}", e.getErrorCode(), e.getMessage());
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public R handleNotify(String notifyData) throws Exception {
+        return null;
+    }
+
+    @Override
+    public R searchOrderByOutTradeNoPath(String transactionId) throws Exception {
+        return weChatPaymentStrategy.searchOrderByOutTradeNoPath(transactionId);
+    }
+
+    @Override
+    public String handleRefund(Map<String, String> params) throws Exception {
+
+        WeChatPaymentStrategyImpl.CreateRequest request = new WeChatPaymentStrategyImpl.CreateRequest();
+        // 微信支付订单号和商户订单号必须二选一,不能同时为空,查询的时候使用的是商户订单号
+        //request.transactionId = "1217752501201407033233368018";
+        request.outTradeNo = params.get("outTradeNo");
+        // 退款订单号
+        request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+        // 退款原因
+        request.reason = params.get("reason");
+        // 退款回调通知地址
+        request.notifyUrl = refundNotifyUrl;
+        // 退款资金来源选填
+        //request.fundsAccount = ReqFundsAccount.AVAILABLE;
+        // 金额信息
+        request.amount = new WeChatPaymentStrategyImpl.AmountReq();
+        request.amount.refund = new BigDecimal(params.get("refundAmount")).longValue();
+        // 退款出资账户及金额 目前不需要,需要的时候再看
+        /*
+        request.amount.from = new ArrayList<>();
+        {
+            FundsFromItem fromItem = new FundsFromItem();
+            fromItem.account = Account.AVAILABLE;
+            fromItem.amount = 444L;
+            request.amount.from.add(fromItem);
+        };*/
+        // 订单总金额
+        request.amount.total = new BigDecimal(params.get("totalAmount")).longValue();
+        // 退款币种 目前默认人民币 CNY
+        request.amount.currency = "CNY";
+        // 退款商品信息 目前不需要,需要的时候再看
+        /*
+        request.goodsDetail = new ArrayList<>();
+        {
+            GoodsDetail goodsDetailItem = new GoodsDetail();
+            goodsDetailItem.merchantGoodsId = "1217752501201407033233368018";
+            goodsDetailItem.wechatpayGoodsId = "1001";
+            goodsDetailItem.goodsName = "iPhone6s 16G";
+            goodsDetailItem.unitPrice = 528800L;
+            goodsDetailItem.refundAmount = 528800L;
+            goodsDetailItem.refundQuantity = 1L;
+            request.goodsDetail.add(goodsDetailItem);
+        };*/
+        // 记录退款请求信息
+        log.info("开始处理微信支付退款,商户订单号:{},商户退款单号:{},退款金额:{}分,订单总金额:{}分,退款原因:{}",
+                request.outTradeNo, request.outRefundNo, request.amount.refund, request.amount.total, request.reason);
+        String refundResult = "";
+        try {
+            WeChatPaymentStrategyImpl.Refund response = weChatPaymentStrategy.refundRun(request);
+            // 退款状态
+            String status = response.status != null ? response.status.name() : "UNKNOWN";
+            if ("SUCCESS".equals(status) || "PROCESSING".equals(status)) {
+                // refund_id 申请退款受理成功时,该笔退款单在微信支付侧生成的唯一标识。
+                String refundId = response.refundId;
+                // 商户申请退款时传的商户系统内部退款单号。
+                String outRefundNo = response.outRefundNo != null ? response.outRefundNo : request.outRefundNo;
+                // 微信支付订单号
+                String transactionId = response.transactionId;
+
+                // 退款金额信息
+                String refundAmount = response.amount != null && response.amount.refund != null
+                        ? String.valueOf(response.amount.refund) : String.valueOf(request.amount.refund);
+                String totalAmount = response.amount != null && response.amount.total != null
+                        ? String.valueOf(response.amount.total) : String.valueOf(request.amount.total);
+                // 退款成功时间
+                String successTime = response.successTime;
+                // 退款创建时间
+                String createTime = response.createTime;
+                // 退款渠道
+                String channel = response.channel != null ? response.channel.name() : "UNKNOWN";
+
+                // 记录退款成功详细信息
+                log.info("微信支付退款成功 - 商户订单号:{},微信支付订单号:{},商户退款单号:{},微信退款单号:{}," +
+                                "退款状态:{},退款金额:{}分,订单总金额:{}分,退款渠道:{},创建时间:{},成功时间:{}",
+                        request.outTradeNo, transactionId, outRefundNo, refundId, status, refundAmount,
+                        totalAmount, channel, createTime, successTime != null ? successTime : "未完成");
+
+                // 保存到通用退款记录表
+                try {
+                    RefundRecord refundRecord = weChatPaymentStrategy.buildRefundRecordFromWeChatResponse(response, request, params);
+                    refundRecord.setPayType(PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType());
+                    if (refundRecord != null) {
+                        // 检查是否已存在,避免重复插入
+                        long count = refundRecordService.lambdaQuery()
+                                .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                                .count();
+                        if (count == 0) {
+                            refundRecordService.save(refundRecord);
+                            log.info("微信支付退款记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        } else {
+                            log.info("微信支付退款记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("保存微信支付退款记录到RefundRecord表失败", e);
+                    // 不抛出异常,避免影响原有逻辑
+                }
+
+                refundResult = "调用成功";
+                return refundResult;
+            } else {
+                log.error("微信支付退款失败 - 商户订单号:{},商户退款单号:{},退款金额:{}分,订单总金额:{}分," +
+                                "退款状态:{}",
+                        request.outTradeNo, request.outRefundNo, request.amount.refund,
+                        request.amount.total, status);
+
+                // 保存失败记录到通用退款记录表
+                try {
+                    RefundRecord refundRecord = weChatPaymentStrategy.buildRefundRecordFromWeChatError(response, request, params, status);
+                    refundRecord.setPayType(PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType());
+                    if (refundRecord != null) {
+                        // 检查是否已存在,避免重复插入
+                        long count = refundRecordService.lambdaQuery()
+                                .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                                .count();
+                        if (count == 0) {
+                            refundRecordService.save(refundRecord);
+                            log.info("微信支付退款失败记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        } else {
+                            log.info("微信支付退款失败记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("保存微信支付退款失败记录到RefundRecord表失败", e);
+                }
+
+                return "退款失败";
+            }
+        }
+        catch (Exception e) {
+            // 记录其他异常
+            log.error("微信支付退款异常 - 商户订单号:{},商户退款单号:{},退款金额:{}分,订单总金额:{}分," +
+                            "退款原因:{},异常信息:{}",
+                    request.outTradeNo, request.outRefundNo, request.amount.refund,
+                    request.amount.total, request.reason, e.getMessage(), e);
+
+            // 保存异常记录到通用退款记录表
+            try {
+                RefundRecord refundRecord = weChatPaymentStrategy.buildRefundRecordFromWeChatException(request, params, e);
+                refundRecord.setPayType(PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType());
+                if (refundRecord != null) {
+                    // 检查是否已存在,避免重复插入
+                    long count = refundRecordService.lambdaQuery()
+                            .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                            .count();
+                    if (count == 0) {
+                        refundRecordService.save(refundRecord);
+                        log.info("微信支付退款异常记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                    } else {
+                        log.info("微信支付退款异常记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                    }
+                }
+            } catch (Exception ex) {
+                log.error("保存微信支付退款异常记录到RefundRecord表失败", ex);
+            }
+
+            refundResult = "退款处理异常:" + e.getMessage();
+            return refundResult;
+        }
+    }
+
+    @Override
+    public R searchRefundRecordByOutRefundNo(String outRefundNo) throws Exception {
+        // 1. 初始化日志(推荐使用SLF4J,替代System.out.println)
+        Logger logger = LoggerFactory.getLogger(this.getClass());
+
+        // 2. 参数校验(前置防御,避免无效请求)
+        if (outRefundNo == null || outRefundNo.trim().isEmpty()) {
+            logger.error("微信退款查询失败:外部退款单号为空");
+            return R.fail("外部退款单号不能为空");
+        }
+
+        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
+        request.setOutRefundNo(outRefundNo);
+
+        try {
+            Refund response = doSearchRefundRecordByOutRefundNo(request);
+
+            // 3. 空值校验(避免response为空导致空指针)
+            if (response == null) {
+                logger.error("微信退款查询失败:外部退款单号{},返回结果为空", outRefundNo);
+                return R.fail("微信支付查询退款记录失败:返回结果为空");
+            }
+
+            logger.info("微信退款查询结果:外部退款单号{},退款状态{}",
+                    outRefundNo, response.getStatus());
+
+            // 4. 细化退款状态判断(覆盖微信支付核心退款状态)
+            String refundStatus = String.valueOf(response.getStatus());
+            switch (refundStatus) {
+                case "SUCCESS":
+                    // 退款成功:执行成功业务逻辑(如更新订单状态、通知用户等)
+                    logger.info("退款成功:外部退款单号{}", outRefundNo);
+                    // TODO: 补充你的成功业务逻辑(例:updateOrderRefundStatus(outRefundNo, "SUCCESS");)
+                    return R.data(response);
+
+                case "REFUNDCLOSE":
+                    // 退款关闭:执行关闭逻辑(如记录关闭原因、人工介入等)
+                    logger.warn("退款关闭:外部退款单号{},原因{}", outRefundNo);
+                    return R.fail("微信支付退款已关闭");
+
+                case "PROCESSING":
+                    // 退款处理中:执行等待逻辑(如提示用户等待、定时任务重试等)
+                    logger.info("退款处理中:外部退款单号{}", outRefundNo);
+                    return R.fail("微信支付退款处理中,请稍后再查");
+
+                case "CHANGE":
+                    // 退款异常:执行异常处理(如记录异常、人工核查等)
+                    logger.error("退款异常:外部退款单号{}", outRefundNo);
+                    return R.fail("微信支付退款异常");
+
+                default:
+                    // 未知状态:兜底处理
+                    logger.error("退款状态未知:外部退款单号{},状态码{}", outRefundNo, refundStatus);
+                    return R.fail("微信支付查询退款记录失败:未知状态码" + refundStatus);
+            }
+
+        } catch (WXPayUtility.ApiException e) {
+            // 5. 异常处理:细化异常日志,便于排查
+            logger.error("微信退款查询API异常:外部退款单号{},错误码{},错误信息{}",
+                    outRefundNo, e.getErrorCode(), e.getMessage(), e);
+            return R.fail("微信支付查询退款记录失败:" + e.getMessage() + "(错误码:" + e.getErrorCode() + ")");
+        } catch (Exception e) {
+            // 6. 兜底异常:捕获非API异常(如空指针、网络异常等)
+            logger.error("微信退款查询系统异常:外部退款单号{}", outRefundNo, e);
+            return R.fail("微信支付查询退款记录失败:系统异常,请联系管理员");
+        }
+    }
+
+    @Override
+    public String getType() {
+        return PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType();
+    }
+
+    public Refund doSearchRefundRecordByOutRefundNo(QueryByOutRefundNoRequest request) {
+        String uri = searchRefundStatusByOutRefundNoPath;
+        uri = uri.replace("{out_refund_no}", WXPayUtility.urlEncode(request.getOutRefundNo()));
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, certificateSerialNo, privateKey, GETMETHOD, uri, null));
+        reqBuilder.method(GETMETHOD, null);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, Refund.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    public DirectAPIv3JsapiPrepayResponse doCreatePrePayOrder(DirectAPIv3JsapiPrepayRequest request) {
+        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", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, certificateSerialNo,privateKey, POSTMETHOD, uri, reqBody));
+        reqBuilder.addHeader("Content-Type", "application/json");
+        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
+        reqBuilder.method(POSTMETHOD, requestBody);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, DirectAPIv3JsapiPrepayResponse.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    public static class DirectAPIv3JsapiPrepayRequest {
+        @SerializedName("appid")
+        public String appid;
+
+        @SerializedName("mchid")
+        public String mchid;
+
+        @SerializedName("description")
+        public String description;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("time_expire")
+        public String timeExpire;
+
+        @SerializedName("attach")
+        public String attach;
+
+        @SerializedName("notify_url")
+        public String notifyUrl;
+
+        @SerializedName("goods_tag")
+        public String goodsTag;
+
+        @SerializedName("support_fapiao")
+        public Boolean supportFapiao;
+
+        @SerializedName("amount")
+        public CommonAmountInfo amount;
+
+        @SerializedName("payer")
+        public JsapiReqPayerInfo payer;
+
+        @SerializedName("detail")
+        public CouponInfo detail;
+
+        @SerializedName("scene_info")
+        public CommonSceneInfo sceneInfo;
+
+        @SerializedName("settle_info")
+        public SettleInfo settleInfo;
+    }
+
+    public static class DirectAPIv3JsapiPrepayResponse {
+        @SerializedName("prepay_id")
+        public String prepayId;
+    }
+
+    public static class CommonAmountInfo {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("currency")
+        public String currency;
+    }
+
+    public static class JsapiReqPayerInfo {
+        @SerializedName("openid")
+        public String openid;
+    }
+
+    public static class CouponInfo {
+        @SerializedName("cost_price")
+        public Long costPrice;
+
+        @SerializedName("invoice_id")
+        public String invoiceId;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+    }
+
+    public static class CommonSceneInfo {
+        @SerializedName("payer_client_ip")
+        public String payerClientIp;
+
+        @SerializedName("device_id")
+        public String deviceId;
+
+        @SerializedName("store_info")
+        public StoreInfo storeInfo;
+    }
+
+    public static class SettleInfo {
+        @SerializedName("profit_sharing")
+        public Boolean profitSharing;
+    }
+
+    public static class GoodsDetail {
+        @SerializedName("merchant_goods_id")
+        public String merchantGoodsId;
+
+        @SerializedName("wechatpay_goods_id")
+        public String wechatpayGoodsId;
+
+        @SerializedName("goods_name")
+        public String goodsName;
+
+        @SerializedName("quantity")
+        public Long quantity;
+
+        @SerializedName("unit_price")
+        public Long unitPrice;
+    }
+
+    public static class StoreInfo {
+        @SerializedName("id")
+        public String id;
+
+        @SerializedName("name")
+        public String name;
+
+        @SerializedName("area_code")
+        public String areaCode;
+
+        @SerializedName("address")
+        public String address;
+    }
+
+}
+

+ 9 - 12
alien-store/src/main/java/shop/alien/store/strategy/payment/impl/WeChatPaymentStrategyImpl.java

@@ -28,11 +28,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.Base64;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 微信支付策略
@@ -213,10 +209,6 @@ public class WeChatPaymentStrategyImpl implements PaymentStrategy {
             result.put("mchId", mchId);
             result.put("orderNo", request.outTradeNo);
             // 生成sign
-//            appId
-
-//            随机字符串
-//            prepay_id
             long timestamp = System.currentTimeMillis() / 1000; // 时间戳
             String nonce = WXPayUtility.createNonce(32); // 随机字符串
             String prepayId = response.prepayId;
@@ -417,6 +409,11 @@ public class WeChatPaymentStrategyImpl implements PaymentStrategy {
         }
     }
 
+    @Override
+    public R searchRefundRecordByOutRefundNo(String outRefundNo) throws Exception {
+        return null;
+    }
+
 
     @Override
     public String getType() {
@@ -981,7 +978,7 @@ public class WeChatPaymentStrategyImpl implements PaymentStrategy {
     /**
      * 从微信支付退款响应构建RefundRecord对象(成功情况)
      */
-    private RefundRecord buildRefundRecordFromWeChatResponse(Refund response, CreateRequest request, Map<String, String> params) {
+    public RefundRecord buildRefundRecordFromWeChatResponse(Refund response, CreateRequest request, Map<String, String> params) {
         try {
             RefundRecord record = new RefundRecord();
             
@@ -1083,7 +1080,7 @@ public class WeChatPaymentStrategyImpl implements PaymentStrategy {
     /**
      * 从微信支付退款错误响应构建RefundRecord对象(失败情况)
      */
-    private RefundRecord buildRefundRecordFromWeChatError(Refund response, CreateRequest request, Map<String, String> params, String status) {
+    public RefundRecord buildRefundRecordFromWeChatError(Refund response, CreateRequest request, Map<String, String> params, String status) {
         try {
             RefundRecord record = new RefundRecord();
             
@@ -1155,7 +1152,7 @@ public class WeChatPaymentStrategyImpl implements PaymentStrategy {
     /**
      * 从微信支付退款异常构建RefundRecord对象(异常情况)
      */
-    private RefundRecord buildRefundRecordFromWeChatException(CreateRequest request, Map<String, String> params, Exception e) {
+    public RefundRecord buildRefundRecordFromWeChatException(CreateRequest request, Map<String, String> params, Exception e) {
         try {
             RefundRecord record = new RefundRecord();
             

+ 2 - 0
alien-util/src/main/java/shop/alien/util/common/constant/PaymentEnum.java

@@ -11,6 +11,8 @@ public enum PaymentEnum {
     ALIPAY("alipay"),
     /** 微信支付 */
     WECHAT_PAY("wechatPay"),
+    /** 微信支付小程序 */
+    WECHAT_PAY_MININ_PROGRAM("wechatPayMininProgram"),
     /** 银联支付 */
     UNION_PAY("unionPay");