UniApp + SpringBoot 實現微信支付和退款


開發准備

  • 一台用於支付的測試機,必須得是一個安卓機因為需要打支付基座才能使用。
  • 用於編寫的后端框架接口的 IDE (IDEA 或者 Eclipse 都可以
  • HBuilder X 用來編輯 UniApp 項目的編輯器和編譯器
  • 基本的 SpringBoot 的腳手架,可以去 https://start.spring.io/ 或者 IDEA 自帶的快速生成腳手架插件。
  • Jdk 11

微信支付開發

我這里省略了申請等步驟。如果沒有申請過企業支付的可以去官網申請 https://pay.weixin.qq.com/static/applyment_guide/applyment_detail_app.shtml 。安卓測試必須要打成基座,或者是正式APP應用。

后端部分

  • SpringBoot 中添加以下坐標

    <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>
    
  • resources 目錄下添加 application.yml 我們不去用默認的 application.properties 文件,畢竟 yml 更好看點。並在 yml 中添加以下內容

    # 服務啟動端口
    server:
      port: 8080
    
    # 微信支付
    wxpay:
      appId: 開放平台的AppID
      mchId: 商戶號
      mchKey: 商戶密鑰
      #  p12證書文件的絕對路徑或者以classpath:開頭的類路徑.
      keyPath: classpath:/wxpay_cert/apiclient_cert.p12
      #  apiclient_key.pem證書文件的絕對路徑或者以classpath:開頭的類路徑.
      privateKeyPath: classpath:/wxpay_cert/apiclient_key.pem
      privateCertPath: classpath:/wxpay_cert/apiclient_cert.pem
      notifyUrl: https://4789j06630.wocp.fun/wechat/pay/notify
      refundNotifyUrl: https://4789j06630.wocp.fun/wechat/pay/refund_notify
    
  • 創建一個 WechatPayConfig.java 使用上面的 ****wxpay

    @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;
        private String refundNotifyUrl;
    }
    
  • 創建一個 BizWechatPayService.java

    package com.runbrick.paytest.util.wxpay;
    
    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;
        }
    
        /**
         * 創建微信訂單給APP
         *
         * @param productTitle 商品標題
         * @param outTradeNo   訂單號
         * @param totalFee     總價
         * @return
         */
        public Object createOrder(String productTitle, String outTradeNo, Integer totalFee) {
            try {
                WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
                // 支付描述
                request.setBody(productTitle);
                // 訂單號
                request.setOutTradeNo(outTradeNo);
                // 請按照分填寫
                request.setTotalFee(totalFee);
                // 回調鏈接
                request.setNotifyUrl(wechatPayConfig.getNotifyUrl());
                // 終端IP.
                request.setSpbillCreateIp(InetAddress.getLocalHost().getHostAddress());
                // 設置類型為APP
                request.setTradeType(WxPayConstants.TradeType.APP);
                // 一定要用 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;
        }
    }
    
  • 創建一個 WechatController.java 來實現接口給前端調用時使用

    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);
    
        /**
         * 創建微信訂單給APP
         *
         * @return
         */
        @RequestMapping(value = "/unified/request", method = RequestMethod.GET)
        public Object appPayUnifiedRequest() {
            // totalFee 必須要以分為單位
            Object createOrderResult = wechatPayService.createOrder("測試支付", String.valueOf(System.currentTimeMillis()), 1);
            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("成功");
        }
    
    }
    

    上面的 controller 寫了兩個接口一個用來 app端的調用,一個給支付用來回調。回調接口的地址要放到剛才配置中的 notifyUrl 屬性里。還有一個是微信的退款接口。

  • 由於支付寶回調要使用線上的地址作為回調地址,這里我推薦兩個解決辦法

    1. 使用一台服務器+備案的域名搭建上面的后台地址
    2. 使用 花生殼 來實現本地內網穿透

    我使用的是 花生殼 作為本次的開發環境,啟動 springboot 的服務,配置好花生殼。后台部分到目前為止已經結束了。

前端部分

創建部分和我寫的支付寶那個一樣,如果不知道可以去看一下。所以跳過創建部分了,直接來到了代碼實現。要在 manifest.json 勾選微信支付支持

https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130751977-1619072450.png

  • 創建前端支付代碼 index.vue

    <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://4789j06630.wocp.fun/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>
    

    https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130751959-1929262632.png

    點擊按鈕就可以前往微信支付看下后台的生成的組合參數

    https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130751475-224595514.png

    https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130751977-1832551321.png

    跳轉微信支付之后會跳回這里,提示支付成功。查看一下后台回調

    https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130751959-396793835.png

    之后的業務按照支付邏輯開發就可以,簡單的支付已經完成。在按照剛才給的回調參數做個退款操作

  • 我們使用 apipost 一個很強大的工具,被同事安利的。那就正好拿他測測退款借口,就不寫代碼了。

    https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130751454-1787132292.png

    此時如果沒有任何錯誤,后台控制台會返回退款本地和遠程回調信息

    https://img2022.cnblogs.com/blog/1106277/202202/1106277-20220221130752005-1417712767.png

    此時微信也收到退款信息了。

整套支付流程都上傳到 github 了可以查看 github的源碼 https://github.com/runbrick/pay_spring


免責聲明!

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



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