支付

1.小程序支付

1.1 前置条件

申请相关权限

1.2 接口规则

1.3 接口参数

1.3.1 商品号保存

  • sql
CREATE TABLE "shop_mch_info" (
  "id" int8 NOT NULL DEFAULT nextval('shop_payment_info_id_seq'::regclass),
  "shop_id" int8 NOT NULL,
  "wx_mch_id" varchar(255) COLLATE "pg_catalog"."default",
  "wx_mch_serial_no" varchar(255) COLLATE "pg_catalog"."default",
  "wx_api_key" varchar(255) COLLATE "pg_catalog"."default",
  "wx_private_key" varchar(5000) COLLATE "pg_catalog"."default",
  "wx_pay_notify_url" varchar(255) COLLATE "pg_catalog"."default",
  "wx_refund_notify_url" varchar(255) COLLATE "pg_catalog"."default",
  "wx_key_path" varchar COLLATE "pg_catalog"."default",
  "device_id" varchar COLLATE "pg_catalog"."default",
  "orgunit_id" varchar COLLATE "pg_catalog"."default"
);

COMMENT ON COLUMN "shop_mch_info"."shop_id" IS '业务门店ID';
COMMENT ON COLUMN "shop_mch_info"."wx_mch_id" IS '微信商户号ID';
COMMENT ON COLUMN "shop_mch_info"."wx_mch_serial_no" IS '微信证书序列号';
COMMENT ON COLUMN "shop_mch_info"."wx_api_key" IS '微信api秘钥';
COMMENT ON COLUMN "shop_mch_info"."wx_private_key" IS '微信证书私钥';
COMMENT ON COLUMN "shop_mch_info"."wx_pay_notify_url" IS '微信支付回调通知地址';
COMMENT ON COLUMN "shop_mch_info"."wx_refund_notify_url" IS '微信退款回调通知地址';
COMMENT ON COLUMN "shop_mch_info"."orgunit_id" IS '业务门店ID';
COMMENT ON TABLE "shop_mch_info" IS '门店商户号相关信息';

1.3.2 配置类

  • WxPayConfigHolder
@Slf4j
@Component
public class WxPayConfigHolder {
    private static final Map<Long, RSAAutoCertificateConfig> configMap = new ConcurrentHashMap<>();

    @Autowired
    private CafeShopMchInfoService cafeShopMchInfoService;

    @Autowired
    private WxPayV3Bean wxPayV3Bean;

    @PostConstruct
    public void init() {
        try {
            // 为每个商户号创建一个RSAAutoCertificateConfig
            List<CafeShopMchInfoEntity> list = cafeShopMchInfoService.list();
            boolean failed = false;
            try {
                for (CafeShopMchInfoEntity mchInfoEntity : list) {
                    String wxMchId = mchInfoEntity.getWxMchId();
                    if (wxMchId == null) {
                        continue;
                    }
                    String privateKey = mchInfoEntity.getWxPrivateKey().replace("-----BEGIN PRIVATE KEY-----", "")
                            .replace("-----END PRIVATE KEY-----", "")
                            .replaceAll("\\s+", "");
                    String wxMchSerialNo = mchInfoEntity.getWxMchSerialNo();
                    String apiV3Key = mchInfoEntity.getWxApiKey();

                    RSAAutoCertificateConfig config =
                            new RSAAutoCertificateConfig.Builder()
                                    .merchantId(wxMchId)
                                    .privateKey(privateKey)
                                    .merchantSerialNumber(wxMchSerialNo)
                                    .apiV3Key(apiV3Key)
                                    .build();

                    configMap.put(mchInfoEntity.getShopId(), config);
                    log.info("不使用代理初始化微信支付配置成功,shopId: {}", mchInfoEntity.getShopId());
                }
            } catch (HttpException e) {
                log.error("不使用代理初始化微信支付配置出错:{}", e.getMessage());
            }
    }

    public RSAAutoCertificateConfig get(Long shopId) {
        return configMap.get(shopId);
    }
}
  • WxPayV3Bean
@Component
@ConfigurationProperties(prefix = "wechat")
@Data
@ToString
public class WxPayV3Bean {
    //小程序appid
    private String appId;
    //商户号
    private String mchId;
    //证书序列号
    private String mchSerialNo;
    //小程序秘钥
    private String appSecret;
    //api秘钥
    private String apiKey;
    //回调接口地址
    private String notifyUrl;
    //证书地址
    private String keyPath;
    // 代理地址
    private String proxyUrl;
}

1.4 流程

  1. 调用提交订单接口获取订单ID (自己业务)
  2. 调用微信预支付接口获取prepay_id等参数
  3. 小程序拉起微信支付(小程序能力)等待用户付款
  4. 轮询订单支付查询接口查询付款结果

1.5 代码

1.5.1 xml

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.8</version>
</dependency>
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.12</version>
</dependency>

1.5.2 预支付

WXPayOrderReqVO req = new WXPayOrderReqVO();
        req.setMchId(mchInfo.getWxMchId());
        req.setMchSerialNo(mchInfo.getWxMchSerialNo());
        req.setApiKey(mchInfo.getWxApiKey());
        req.setKeyPath(mchInfo.getWxPrivateKey());
        req.setNotifyUrl(mchInfo.getWxPayNotifyUrl());
        req.setOpenId(user.getOpenid());
        req.setTradeNo(orderCode);
        req.setDescription(sj);
        req.setAmount(orderEntity.getTotalAmount().multiply(new BigDecimal(100)).intValue());
        req.setOrderType(OrderType.MINI_PROGRAM.getDesc());
        req.setShopId(shopId);
        req.setAttach(shopEntity.getName());

    @Override
    public WXPayOrderResVO createOrder(WXPayOrderReqVO req) throws NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException, InvalidKeyException {
        String appId = wxPayV3Bean.getAppId();
        // 使用自动更新平台证书的RSA配置,配置微信支付的自动证书管理功能
        RSAAutoCertificateConfig config = wxPayConfigHolder.get(req.getShopId());
        // 构建service,用于处理JSAPI支付相关的操作
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
        // 创建预支付订单的请求对象
        PrepayRequest prepayRequest = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(req.getAmount());
        prepayRequest.setAmount(amount);
        prepayRequest.setAppid(appId);
        prepayRequest.setMchid(req.getMchId());
        prepayRequest.setNotifyUrl(req.getNotifyUrl());
        prepayRequest.setDescription(req.getDescription());
        prepayRequest.setOutTradeNo(req.getTradeNo());
        prepayRequest.setAttach(req.getAttach());
        Date dateFromNow = DateUtil.getDateFromNow(Calendar.MINUTE, 5); // 5分钟后关闭订单
        prepayRequest.setTimeExpire(DateUtil.formatWithTimezone(dateFromNow));
        Payer payer = new Payer();
        payer.setOpenid(req.getOpenId());
        prepayRequest.setPayer(payer);
        // 调用下单方法,得到应答
        PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(prepayRequest);
        log.info("微信下单方法请求返回:" + response);
        // 自定义返回类
        WXPayOrderResVO vo = new WXPayOrderResVO();
        return vo;
    }

1.5.3 查询

  • PaymentStatusEnum
    SUCCESS(1, "支付成功"),
    REFUND(2, "已退款"),
    NOTPAY(3, "未支付"),
    CLOSED(4, "已关闭"),
    REVOKED(5, "已撤销"),
    USERPAYING(6, "用户支付中"),
    PAYERROR(7, "支付失败"),
    FINISHED(8, "交易结束,不可退款(支付宝在超过退款期限之后会推送这个状态)"),
    REFUNDING(9, "退款中"), // 直营专用的退款状态
    REFUNDAPPLY(10,"退款申请(待审批)"),
    UNKNOWN(99, "未知状态");
  • 查询逻辑
// 业务逻辑
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(mchInfo.getWxMchId());
queryRequest.setOutTradeNo(orderCode);
RSAAutoCertificateConfig config = wxPayConfigHolder.get(shopId);
// 构建service,用于处理JSAPI支付相关的操作
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();

com.wechat.pay.java.service.payments.model.Transaction result = service.queryOrderByOutTradeNo(queryRequest);
log.info("微信查询订单返回:{}", JSONObject.toJSONString(result));

PaymentStatus paymentStatus = PaymentStatus.valueOf(result.getTradeState().toString());
// 业务逻辑

1.5.4 退款

    @Override
    public PaymentRefundVO refund(OrderRefundVO vo) {
        Long shopId = vo.getShopId();
        BigDecimal totalAmount = vo.getTotalAmount();
        String orderCode = vo.getOrderCode();
        RSAAutoCertificateConfig config = wxPayConfigHolder.get(shopId);
        // 构建退款service
        RefundService service = new RefundService.Builder().config(config).build();
        //构建退款请求
        CreateRequest request = new CreateRequest();
        // request.setXxx(val)设置所需参数,具体参数可见Request定义
        //构建订单金额信息
        AmountReq amountReq = new AmountReq();
        //退款金额
        long refundAmount = totalAmount.multiply(new BigDecimal(100)).longValue();
        amountReq.setRefund(refundAmount);
        //原订单金额
        amountReq.setTotal(refundAmount);
        //货币类型(默认人民币)
        amountReq.setCurrency("CNY");
        request.setAmount(amountReq);
        request.setOutTradeNo(orderCode);
        request.setReason(vo.getReason());
        //商户退款单号
        request.setOutRefundNo(orderCode);
        //退款通知回调地址
        request.setNotifyUrl(vo.getRefundNotifyUrl());

        // 调用微信sdk退款接口
        Refund refund = service.create(request);
        log.info("微信请求退款返回:" + refund);

        PaymentRefundVO refundVO = new PaymentRefundVO();
        refundVO.setOrderCode(orderCode);
        refundVO.setPaymentCode(refund.getTransactionId());
        refundVO.setRefundCode(refund.getRefundId());
        refundVO.setRefundAmount(new BigDecimal(refund.getAmount().getPayerRefund()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
        Status status = refund.getStatus();
        if (status.equals(Status.SUCCESS)) {
            refundVO.setRefundStatus(RefundStatus.SUCCESS.getCode());
        } else if (status.equals(Status.ABNORMAL)) {
            refundVO.setRefundStatus(RefundStatus.ABNORMAL.getCode());
        } else if (status.equals(Status.CLOSED)) {
            refundVO.setRefundStatus(RefundStatus.CLOSED.getCode());
        } else if (status.equals(Status.PROCESSING)) {
            refundVO.setRefundStatus(RefundStatus.PROCESSING.getCode());
        }

        return refundVO;
    }
posted @ 2025-03-22 14:21  lwx_R  阅读(23)  评论(0)    收藏  举报