SpringBoot + 小程序 整合微信支付和退款 V2版


本文僅作記錄

參考文章: https://www.cnblogs.com/l5gw/p/15918418.html

Springboot整合支付寶支付參考: https://www.cnblogs.com/l5gw/p/15875387.html

一、開發前提

  • 微信支付商戶號接入微信支付
  • 商戶號綁定對應的小程序
  • 獲取對應的 信息
    • 開放平台: appId
    • 商戶號: mchId
    • 商戶秘鑰: mchKey
    • p12證書:
    • apiclient_key.pem證書文件

官方網站: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

二、Springboot整合微信支付

2.1 導入基本的pom依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 微信支付坐標 start-->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>4.2.5.B</version>
</dependency>
<!-- 退款用 -->
<dependency>
    <groupId>org.jodd</groupId>
    <artifactId>jodd-http</artifactId>
    <version>6.0.8</version>
</dependency>
<!-- 微信支付坐標 end-->

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2.2 在 application.yml 中添加配置

回調地址 我這里使用 ngrok內網穿透 官網: https://dashboard.ngrok.com/get-started/setup

# 服務啟動端口
server:
  port: 8081

# 微信pay相關
wxpay:
  # appId
  appId: # 自己的appId
  # 商戶id
  mchId:  # 自己的商戶id
  # 商戶秘鑰
  mchKey: # 商戶秘鑰
  # p12證書文件的絕對路徑或者以classpath:開頭的類路徑.
  keyPath: classpath:/wxpay_cert/apiclient_cert.p12
  privateKeyPath: classpath:/wxpay_cert/apiclient_key.pem
  privateCertPath: classpath:/wxpay_cert/apiclient_cert.pem
  # 微信支付的異步通知接口
  # 我這里使用的ngrok內網穿透 
  notifyUrl: https://bfb3-221-237-148-6.ap.ngrok.io/wechat/pay/notify
  # 退款回調地址
  refundNotifyUrl: https://bfb3-221-237-148-6.ap.ngrok.io/wechat/pay/refund_notify

image

2.3 創建配置文件對應的 配置類

package com.system.system.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
    private String appId;
    private String mchId;
    private String mchKey;
    private String keyPath;
    private String privateKeyPath;
    private String privateCertPath;
    private String notifyUrl;
}

2.4 在service層 創建 BizWechatPayService類

package com.system.system.service.impl;

import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;

import java.net.InetAddress;

/**
 * 微信支付
 */
@Service
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WechatPayConfig.class)
@AllArgsConstructor
public class BizWechatPayService {

    private WechatPayConfig wechatPayConfig;

    public WxPayService wxPayService() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(wechatPayConfig.getAppId());
        payConfig.setMchId(wechatPayConfig.getMchId());
        payConfig.setMchKey(wechatPayConfig.getMchKey());
        payConfig.setKeyPath(wechatPayConfig.getKeyPath());
        payConfig.setPrivateKeyPath(wechatPayConfig.getPrivateKeyPath());
        payConfig.setPrivateCertPath(wechatPayConfig.getPrivateCertPath());
        // 可以指定是否使用沙箱環境
        payConfig.setUseSandboxEnv(false);
        payConfig.setSignType("MD5");

        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }

    /**
     * 創建微信支付訂單
     *
     * @param productTitle 商品標題
     * @param outTradeNo   訂單號
     * @param totalFee     總價
     * @param openId     openId
     * @return
     */
    public Object createOrder(String productTitle, String outTradeNo, Integer totalFee, String openId) {
        try {
            WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
            // 支付描述
            request.setBody(productTitle);
            // 訂單號
            request.setOutTradeNo(outTradeNo);
            // 請按照分填寫
            request.setTotalFee(totalFee);
            // 小程序需要傳入 openid
            request.setOpenid(openId);
            // 回調鏈接
            request.setNotifyUrl(wechatPayConfig.getNotifyUrl());
            // 終端IP.
            request.setSpbillCreateIp(InetAddress.getLocalHost().getHostAddress());
            // 設置類型為JSAPI
            request.setTradeType(WxPayConstants.TradeType.JSAPI);
            // 一定要用 createOrder 不然得自己做二次校驗
            Object order = wxPayService().createOrder(request);
            return order;
        } catch (Exception e) {
            return null;
        }

    }

    /**
     * 退款
     *
     * @param tradeNo 訂單號
     * @param totalFee 總價
     * @return
     */
    public WxPayRefundResult refund(String tradeNo, Integer totalFee) {
        WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
        wxPayRefundRequest.setTransactionId(tradeNo);
        wxPayRefundRequest.setOutRefundNo(String.valueOf(System.currentTimeMillis()));
        wxPayRefundRequest.setTotalFee(totalFee);
        wxPayRefundRequest.setRefundFee(totalFee);
        wxPayRefundRequest.setNotifyUrl(wechatPayConfig.getRefundNotifyUrl());
        try {
            WxPayRefundResult refund = wxPayService().refundV2(wxPayRefundRequest);
            if (refund.getReturnCode().equals("SUCCESS") && refund.getResultCode().equals("SUCCESS")) {
                return refund;
            }

        } catch (WxPayException e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.5 創建Controller測試

package com.runbrick.paytest.controller;

import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.runbrick.paytest.util.wxpay.BizWechatPayService;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/wechat/pay")
@AllArgsConstructor
public class WechatController {

    BizWechatPayService wechatPayService;

    private static Logger logger = LoggerFactory.getLogger(WechatController.class);

    /**
     * 創建微信訂單
     *
     * @return
     */
    @RequestMapping(value = "/unified/request", method = RequestMethod.GET)
    public Object appPayUnifiedRequest() {
        // totalFee 必須要以分為單位
        Object createOrderResult = wechatPayService.createOrder("測試支付", String.valueOf(System.currentTimeMillis()), 1, "從前端傳入的openIdopendId");
        logger.info("統一下單的生成的參數:{}", createOrderResult);
        return createOrderResult;
    }

    @RequestMapping(method = RequestMethod.POST, value = "notify")
    public String notify(@RequestBody String xmlData) {
        try {
            WxPayOrderNotifyResult result = wechatPayService.wxPayService().parseOrderNotifyResult(xmlData);
            // 支付返回信息
            if ("SUCCESS".equals(result.getReturnCode())) {
                // 可以實現自己的業務邏輯
                logger.info("來自微信支付的回調:{}", result);
            }

            return WxPayNotifyResponse.success("成功");
        } catch (WxPayException e) {
            logger.error(e.getMessage());
            return WxPayNotifyResponse.fail("失敗");
        }
    }

    /**
     * 退款
     *
     * @param transaction_id
     */
    @RequestMapping(method = RequestMethod.POST, value = "refund")
    public void refund(String transaction_id) {
        // totalFee 必須要以分為單位,退款的價格可以這里只做的全部退款
        WxPayRefundResult refund = wechatPayService.refund(transaction_id, 1);
        // 實現自己的邏輯
        logger.info("退款本地回調:{}", refund);
    }

    /**
     * 退款回調
     *
     * @param xmlData
     * @return
     */
    @RequestMapping(method = RequestMethod.POST, value = "refund_notify")
    public String refundNotify(@RequestBody String xmlData) {
        // 實現自己的邏輯
        logger.info("退款遠程回調:{}", xmlData);
        // 必須要返回 SUCCESS 不過有 WxPayNotifyResponse 給整合成了 xml了
        return WxPayNotifyResponse.success("成功");
    }

}

三、前端代碼

<template>
    <view class="content">
        <view class="text-area">
            <text class="title">{{title}}</text>
        </view>
        <button type="default" @click="goPay()">點我前去支付</button>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                title: '跟我去支付'
            }
        },
        onLoad() {

        },
        methods: {
            goPay() {
                uni.request({
                    url: "https://bfb3-221-237-148-6.ap.ngrok.io/wechat/pay/unified/request",
                    success(res) {
                        let obj = {
                            appid: res.data.appId,
                            noncestr: res.data.nonceStr,
                            package: res.data.packageValue,
                            partnerid: res.data.partnerId,
                            prepayid: res.data.prepayId,
                            timestamp: parseInt(res.data.timeStamp),
                            sign: res.data.sign,
                        };

                        uni.requestPayment({
                            provider: "wxpay",
                            orderInfo: obj,
                            success(res) {
                                uni.showModal({
                                    content: "支付成功",
                                    showCancel: false
                                })
                            },
                            fail(e) {
                                uni.showModal({
                                    content: "支付失敗,原因為: " + e.errMsg,
                                    showCancel: false
                                })
                            },
                            complete() {
                                console.log("啥也沒干");
                            }
                        });

                    }
                })

            }
        }
    }
</script>

<style>
    page {
        background-color: #ff5500;
    }

    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .text-area {
        display: flex;
        justify-content: center;
    }

    .title {
        font-size: 36rpx;
        color: #8f8f94;
    }
</style>

四、測試

部分敏感信息 已刪除

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM