spring boot 微信小程序支付SDK版本 微信支付統一下單

項目要求需要在小程序中添加微信支付的功能(微信小程序就不要想支付寶的事情了),折騰了半天,也在網上看了各位先行者的各種文章(強烈譴責一下摘取別人文章一半,當作自己文章的人,越看越頭疼),記錄一下這次的實驗

1:登錄微信商戶首頁,完成微信商戶的註冊

這個需要公司的一些材料(營業執照,法人,銀行卡之類的),這一步是必須的,你要做微信支付,最起碼得開通別人的服務纔行

點擊產品中心-查看我的產品 這裏能看到所有產品,找到你需要的就可以了,我這邊只是做小程序的支付,默認開通的就夠用
在這裏插入圖片描述

2:關聯你的微信程序和微信商戶號

要在微信小程序中使用微信商戶的支付功能,就必須把小程序和某個商戶關聯起來,進入小程序管理——功能——微信支付
在這裏插入圖片描述
(進去後如果沒有開通微信支付就要申請,不過我的默認開啓的,網上有的文章說是要自己申請開通),點擊關聯更多商戶號
在這裏插入圖片描述
注意,這裏會跳回到你的微信商戶的網站去,跳回的目錄是產品中心-AppId賬戶管理
在這裏插入圖片描述
(我這裏是已經關聯了),點擊關聯AppId
在這裏插入圖片描述
在這個頁面輸入小程序的AppId(在小程序——開發——開發設置中找)號,輸入正確的話,會要求你輸入小程序的註冊認證主體(小程序註冊時候企業主體的名字)

這裏點擊確認之後,在小程序那邊確認綁定,這樣就完成了關聯,就能做下一步的開發了

3:SDK的問題

微信爲了方便開發者開發,提供了自己的SDK(就是封裝了一些使用工具,不用sdk也可以,但是你就得手工去寫一些方法),但是這個SDK沒有上傳到maven中央倉庫去(但是在maven中央倉庫能搜索到微信支付的sdk,一個是2017年的,還有個最新的,想一想算了,用微信自己的sdk比較保險,畢竟是錢相關的)點擊位置支付SDK
選擇需要的版本下載,我這邊選擇的是java版本。下載完成後,你會得到一個zip包(WxPayAPI_JAVA.zip),點擊解壓縮,你會得到它的一個工程
在這裏插入圖片描述
其中reademe.md中包含了裏邊常用方法的demo(不過寫的不清不楚,真的!~該你被飛車三人組騙),進去看看
在這裏插入圖片描述

這裏是提供的所有的微信支付相關的類了。
我們的工程一般都是maven工程,所以我們需要對這個進行轉換一下
1:打開編譯器,將下載的這個工程轉換成jar包
在這裏插入圖片描述
2:用maven命令將得到的jar包安裝到maven本地倉庫去

mvn install:install-file -Dfile=wxpay.jar -DgroupId=com.github.wxpay -DartifactId=wxpay-sdk -Dversion=3.0.9 -Dpackaging=jar

注意上邊-Dfile後邊jar包所在的文件的路徑 如果dos不再當前文件的當前目錄,需要把文件路徑補全了
在當前工程中使用mvn引入wx的jar包

<dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>3.0.9</version>
        </dependency>

這樣你的工程中就能正常使用微信支付的sdk的功能了

4:證書的問題

01:微信支付要求必須在https的請求中進行,所以第一步在服務器請求地址中去設置一下https證書(這個自己百度一下),http請求是不能訪問的
02:需要在微信商戶 賬戶中心-api安全去設置兩個東西 分別爲:設置API密鑰api證書,其中密鑰記錄下來(程序中用),證書也下載下來(程序中需要讀取)
下載下來的證書如下
在這裏插入圖片描述
解壓縮包,得到三個文件,最重要的是一本apiclient_cert.p12的證書(別的兩本我沒用到。。。。),把這個文件丟到spring boot工程的resources 目錄下邊去
這樣我們的證書問題就解決了

5:配置的問題

要開始使用了,首先我們要爲接入做配置,第一個需要配置的類爲WXPayConfig 這個類是支付SDK提供的,但是我們需要在其中寫入我們自己的參數
創建類:
public class MyConfig extends WXPayConfig

這裏讓我頭疼的是,有的文章寫的是 implements WXPayConfig這是比較老的使用了,微信這裏WXPayConfig這個改成了抽象類,所以要繼承。

這裏還有個小細節,其實sdk使用有兩種辦法,一種是把下載sdk的類全部拷貝到你的工程中,第二種就是我剛纔jar包的方式。

如果是jar的方式,自己寫的這個配置類一定要放在和WXPayConfig這個類一樣的路徑下邊(如果路徑不一致,就會報錯),所以我只能在自己的程序中創建一個一模一樣的路徑存放這個配置類,最終是這樣的
在這裏插入圖片描述
com下創建了兩個包,一個是以git開頭的,另外一個是自己的工程的
全類如下:

package com.github.wxpay.sdk;

import com.github.wxpay.sdk.IWXPayDomain;
import com.github.wxpay.sdk.WXPayConfig;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

@Component("myConfig")
public class MyConfig extends WXPayConfig {
    private byte[] certData;
    public MyConfig() throws Exception {
        InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("apiclient_cert.p12");//讀取方纔存放在resource西邊的證書
        this.certData = IOUtils.toByteArray(certStream);
        certStream.close();
    }
    @Override
    public String getAppID() {
        return "你自己的小程序Id號";
    }

    public String getMchID() {
        return "你自己的商戶ID號";
    }

    public String getKey() {
        return "剛纔在商戶證書位置設置的密鑰";
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }

    @Override
    IWXPayDomain getWXPayDomain() {
        //必須實例化,否則WxPay初始化失敗
        IWXPayDomain iwxPayDomain = new IWXPayDomain() {
            public void report(String domain, long elapsedTimeMillis, Exception ex) {

            }
            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
            }
        };
        return iwxPayDomain;
    }
}

修改這幾個參數之後就能使用了

6:開始放碼

現在基礎配置已經完成了,我們開始做開發(距離勝利就一步了)
首先打開小程序支付API 文檔,點擊API列表,查看統一下單
這裏的流程簡單的來說就是
商戶在小程序中先調用該接口在微信支付服務後臺生成預支付交易單,返回正確的預支付交易後調起支付。
1:在後臺生成一個訂單信息(這個是下給微信支付服務器的)
2:將生成訂單信息的返回給前臺(調用之後返回來信息,把返回的信息發送給前端)
3:前臺發起支付(前端獲取信息後發起支付)
首先發送訂單準備代碼如下:

借鑑了這位老哥的代碼,十分感謝參考代碼地址
最開始修改sdk中的一個東西,要不然會說你的簽名錯誤
直接複製sdk中WXPay.java這個類到你之前新建的包路徑中(還是要和sdk保持包路徑統一)。找到這個方法

 public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
        this.config = config;
        this.notifyUrl = notifyUrl;
        this.autoReport = autoReport;
        this.useSandbox = useSandbox;
        if (useSandbox) {
            this.signType = SignType.MD5; // 沙箱環境
        }
        else {
            //this.signType = SignType.HMACSHA256; //位置1
            this.signType = SignType.MD5;
        }
        this.wxPayRequest = new WXPayRequest(config);
    }

位置1——原來這一行沒註釋,註釋掉這一行代碼,拷貝它下邊的一行代碼,因爲我們的加密方式使用的是md5模式,如果是默認的這一行,加密方式不對簽名就錯誤了

首先編寫方法獲取openid

 //獲取openId
    public JSONObject getOpenIdAndsessionKey(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=你的小程序ID&secret=小程序密鑰&js_code=" + code + "&grant_type=authorization_code";
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject(url, String.class);
        JSONObject jsonObject = JSONObject.parseObject(result);
      //  String openId = jsonObject.getString("openid");
        return jsonObject;
    }

尤其注意上邊的url,這種是親測可用的,別的兩種在這裏沒用

下單代碼

@RequestMapping(value = "/createOrder")
    BaseResponse<Map> createOrder(@RequestParam("code") String code) throws Exception { //位置1
        String appId = "你自己的小程序id";
        BaseResponse<Map> response = new BaseResponse<>();
        JSONObject jsonObject = getOpenIdAndsessionKey(code);
        String openid = jsonObject.getString("openid");
        Map resultMap = new HashMap();
        MyConfig conf = new MyConfig();
        WXPay wxPay = new WXPay(conf);
        Map<String, String> data = new HashMap<String, String>();
        data.put("appid", appId);//小程序號
        data.put("mch_id", "你自己的商戶號");//商戶號
        data.put("nonce_str", WXPayUtil.generateNonceStr());//隨機字符串
        data.put("body", "會員充值");//商品描述
        //商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|*且在同一個商戶號下唯一。詳見商戶訂單號
        String outTradeNo= getRandomStringByLength(16);
        data.put("out_trade_no",outTradeNo);//商戶訂單號 位置2
        System.out.println("商戶訂單號------------------------------------------"+outTradeNo);
        data.put("fee_type", "CNY");//標價幣種
        data.put("total_fee", "1");//金額  位置3
        data.put("spbill_create_ip", "服務器ip地址就可以");//支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IP
        data.put("notify_url", "https://www.你的服務器.cn/wx/notifyWeiXinPay");//異步接收微信支付結果通知的回調地址,通知url必須爲外網可訪問的url,不能攜帶參數。
        data.put("trade_type", "JSAPI");  // 此處指定爲掃碼支付 位置4
        data.put("product_id", "1");//此參數爲二維碼中包含的商品ID,商戶自行定義。
        data.put("openid", openid);
        try {
            Map<String, String> rMap = wxPay.unifiedOrder(data); //位置5
            String return_code = (String) rMap.get("return_code");
            String result_code = (String) rMap.get("result_code");
            String nonceStr = WXPayUtil.generateNonceStr();
            resultMap.put("nonceStr", nonceStr);
            Long timeStamp = System.currentTimeMillis() / 1000;
            if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {
                String prepayid = rMap.get("prepay_id");
                resultMap.put("package", "prepay_id=" + prepayid);
                resultMap.put("signType", "MD5");
                //這邊要將返回的時間戳轉化成字符串,不然小程序端調用wx.requestPayment方法會報簽名錯誤
                resultMap.put("timeStamp", timeStamp + "");
                //再次簽名,這個簽名用於小程序端調用wx.requesetPayment方法
                resultMap.put("appId", appId);
                String sign = WXPayUtil.generateSignature(resultMap, "xxxxqqwqwqwqwqxqxqxqxq");
                resultMap.put("paySign", sign);
                System.out.println("生成的簽名paySign : " + sign);
                response.setData(resultMap);
                return response; //位置6
            } else {
                return response;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return response;
        }
    }

位置1——code值是前端授權得到的值,需要前端人員動態傳遞到後臺
位置2——訂單商戶號,保存在我們自己的服務器,用來區分哪個訂單是哪個
位置3——這裏的金額必須是整數,單位是分
位置4——小程序用就寫這種
位置5——調用sdk方法,完成下單
位置6——將下單成功後的信息返回給前端用於支付
上邊代碼是個demo什麼金額啊都應該是前端參數,你自己補全就好了
這樣前端就能完成支付了,最後,當支付成功後後端需要知道支付成功信息,最後編寫支付成功API

 @RequestMapping(value = "/notifyWeiXinPay")
    @ResponseBody
    String notifyWeiXinPay(HttpServletRequest res, HttpServletResponse rsp) throws Exception {
        System.out.println("微信支付回調");
        InputStream inStream = res.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        String resultxml = new String(outSteam.toByteArray(), "utf-8");
        Map<String, String> params = WXPayUtil.xmlToMap(resultxml);
        outSteam.close();
        inStream.close();
        //支付結果返回,最後需要返回xml格式的數據
        Map<String, String> return_data = new HashMap<String, String>();
        if (!"SUCCESS".equals(params.get("return_code"))) {
            //沒有支付成功
            return_data.put("return_code", "FAIL");
            return_data.put("return_msg", "return_code不正確");
            return WXPayUtil.mapToXml(return_data);
        } else {
            //返回成功了
            //商戶訂單號
            String outTradeNo = params.get("out_trade_no");
            //訂單金額
            String totalFee = params.get("total_fee");
            //支付完成時間
            String timeEnd =params.get("time_end");
            //微信支付訂單號
            String tradeNo = params.get("transaction_id");
            System.out.println("商戶訂單號"+outTradeNo+"tradeNo"+tradeNo);
            return_data.put("return_code", "SUCCESS");
            return_data.put("return_msg", "OK");
            return WXPayUtil.mapToXml(return_data);
        }
    }

這樣當支付成功後,微信商戶服務器會主動推送到你這個地址(這個地址是前邊下單地址寫的notify_url),我們接受信息,就能知道支付成功了(裏邊outTradeNo商戶訂單號,就是我們在下單時候隨機生成的商戶訂單號)
這樣整個過程就完成了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章