java使用微信sdk實現(xiàn)支付诚纸,回調(diào)撰筷,退款

其實微信支付這一塊,真的是個翻來覆去講爛了的話題畦徘,甚至我在去年也寫過一個帖子用來記錄毕籽。但是之所以還要寫這篇文章,主要一來通過最近的使用對微信支付這塊了解的更加深刻井辆,其次也是這次用到了退款的功能关筒,這個算是新鮮點(diǎn),所以想來想去決定再從頭整理一下杯缺。
結(jié)合實際說的話蒸播,我這次項目是小程序,所以支付是略有不同萍肆,我盡量一點(diǎn)點(diǎn)剝開了說袍榆。

第一步:看官網(wǎng)介紹

首先肯定是要從微信的開發(fā)文檔說起,這里附上傳送門:
微信支付開發(fā)文檔——普通商戶版

我個人是覺得微信的文檔越來越好了塘揣,再也不是三年前一提起微信就腦殼痛的時候了包雀。咱們繼續(xù)這個頁面說,雖然都是微信支付亲铡,但是也有不同的方式才写,我上面已經(jīng)說了我在做的這個項目是小程序葡兑,所以毫不猶豫的選擇小程序支付,其實各種支付方式大同小異赞草,尤其是小程序支付走的類型也是JSAPI讹堤,所以兩者沒什么區(qū)別。而且有個小技巧(目前是2020/7/17厨疙,反正現(xiàn)在為止小程序付款是沒有sdk的洲守,但是別的付款方式是有的,所以可以把別的付款方式中的sdk下下來這里用)沾凄。

第二步:引入sdk

當(dāng)然了岖沛,我習(xí)慣性的用maven直接引入依賴。
之前查了下資料搭独,其實能直接引用的有好幾種表現(xiàn)形式,我這里只列出我自己用的:

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

說一下這個包廊镜,其實是這么引用還是把sdk中的幾個工具類自己寫出來我覺得都o(jì)k吧牙肝,我也特意和sdk中的代碼對比了下,是一樣的嗤朴。

maven引入依賴中的代碼

官網(wǎng)下載來的sdk中的代碼

其實我看了一些網(wǎng)上的demo配椭,我更愿意理解為時間太久了(有的哪怕是新發(fā)布的,但是可能代碼年限很高了)雹姊,居然自己寫各種工具方法的占了一多半股缸,挺費(fèi)解的。所以我這里再次強(qiáng)調(diào):微信已經(jīng)有了非常好用的sdk吱雏,真沒必要自己寫敦姻!

第三步:邏輯流程

其實在有sdk的協(xié)助下,代碼的實現(xiàn)變得很簡單了歧杏。大概的步驟我簡單說一下镰惦。主要也是要對這個支付,回調(diào)等流程心里有數(shù)犬绒。
微信的文檔其實挺好的旺入,就是有點(diǎn)復(fù)雜(可能是我有時候心煩意亂只想速成,所以沒耐心一點(diǎn)點(diǎn)順著圖分析理解)凯力。這里說一下JSAPI/小程序支付的大概流程:

  1. 預(yù)下單(有了下單的動作茵瘾,計劃好支付多少錢了。然后微信會照著已給的信息去生成那個支付頁面咐鹤,如下圖)


    支付頁面

我簡單說下拗秘,這個最上面的支付0.01元的頁面,就是你先告訴微信慷暂,openId是XXX(你自己)的用戶想給XXXX企業(yè)支付0.01元錢聘殖。你先把所有的信息告訴微信晨雳,微信覺得沒問題了(其中檢查你的openid,企業(yè)的情況啥的)就會生成這個彈窗頁面奸腺。
所以說預(yù)下單其實可以理解為信息的提交與校驗餐禁,沒問題了就跳到微信頁面(ps:這個頁面就不屬于前端的控制范圍了,但是微信也提供一些接口可以關(guān)閉啥的突照。不過我們沒做這個操作)帮非。

  1. 回調(diào)
    剛剛已經(jīng)說了預(yù)下單之后,會彈出一個微信提供的頁面讹蘑,但是到底用戶支付沒支付要怎么判斷呢末盔?這里就用到了回調(diào)。
    官網(wǎng)也有關(guān)于回調(diào)地址的詳細(xì)要求座慰,其實本質(zhì)上就是提供一個接口陨舱,微信會把支付的情況告訴你,但是這個接口必須是外網(wǎng)的版仔,必須是https的游盲,這個回調(diào)地址可以在微信設(shè)置。這兩個要求不復(fù)雜蛮粮,但是讓本地沒辦法測試了益缎。反正代碼簡單,所以仔細(xì)點(diǎn)可以一次過然想。

  2. 退款
    這個功能就是某單的錢到了商家的微信賬戶里莺奔,根據(jù)訂單號或者微信流水號就可以退款了。當(dāng)然也有一些限制的变泄,比如微信賬戶要有錢令哟,比如訂單不能超過一年。當(dāng)然了杖刷,因為我們這個業(yè)務(wù)需求都不會涉及這些励饵,所以直接用訂單號退款了。這個差不多就是秒退滑燃,如果有問題直接接口會報錯役听,所以更簡單了。一會兒我會附上代碼表窘。

第四步:代碼實現(xiàn)

終于到了最重要的時候了典予,代碼的實現(xiàn)。

如果你下載了微信官方的sdk乐严,其實是有完成的代碼實現(xiàn)的瘤袖。我這里截圖說明:
官方下載下來的sdk

sdk解壓后

而這個demo代碼在readme里:


使用demo

然后其實照著這個來做就行。簡單說第一步:配置參數(shù):
需要的參數(shù)有證書昂验,appId捂敌,key艾扮,mchId。下面是我的配置類:

package com.dsyl.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;

public class PayConfig implements WXPayConfig{

    private byte[] certData;

    public PayConfig() throws Exception {
        File file = new File("C:\\Program Files\\apache-tomcat-8.5.38\\cert\\apiclient_cert.p12");
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    public String getAppID() {
        return "wx8888888888888";
    }

    public String getMchID() {
        return "XXXXXXXXX";
    }

    public String getKey() {
        return "xxxxxxxxxxxxxxxxxxxxxxxx";
    }

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

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }
}

第二步就是調(diào)用了占婉。首先說支付的調(diào)用(其實說真的泡嘴,差不多就是readme百分之八十復(fù)制粘貼):

    public R pay(String openId,Integer money){
        try {
            String no = (openId + "-" + money + "-" + System.currentTimeMillis()+"abcdefghigklmn").substring(0,32);
            PayConfig config = new PayConfig();
            WXPay wxpay = new WXPay(config);
            Map<String, String> data = new HashMap<String, String>();
            data.put("body", "支付");
            data.put("out_trade_no", no);
            data.put("openid", openId);
            data.put("total_fee", (money*100)+"");
            data.put("spbill_create_ip", "127.1.1.1");
            data.put("notify_url", "https://www.hongchunyan.com/crowd-funding/wxpay/notify");
            data.put("trade_type", "JSAPI");
            Map<String, String> resp = wxpay.unifiedOrder(data);
            String returnCode = resp.get("return_code");
            String resultCode = resp.get("result_code");
            String prepay_id = resp.get("prepay_id");
            if (!"SUCCESS".equals(returnCode) || !"SUCCESS".equals(resultCode) || prepay_id == null) {
                return R.error().put("code", 300).put("data", resp);
            }
            String packages = "prepay_id=" + prepay_id;
            Map<String, String> wxPayMap = new HashMap<String, String>();
            wxPayMap.put("appId", config.getAppID());
            wxPayMap.put("timeStamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
            wxPayMap.put("nonceStr", WXPayUtil.generateNonceStr());
            wxPayMap.put("package", packages);
            wxPayMap.put("signType", "MD5");
            // 加密串中包括 appId timeStamp nonceStr package signType 5個參數(shù), 通過sdk WXPayUtil類加密,
            // 注意, 此處使用 MD5加密 方式
            String sign = WXPayUtil.generateSignature(wxPayMap, config.getKey());
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("prepay_id", prepay_id);
            result.put("paySign", sign);
            result.putAll(wxPayMap);
            return R.ok().put("data", result);
        } catch (Exception e) {
            e.printStackTrace();
            return R.error();
        }
    }

簡單的說幾點(diǎn):

  • 這里的out_trade_no要求是唯一的,其實用uuid也ok逆济,我這里因為想用它存一些業(yè)務(wù)邏輯酌予,所以是自己想要存的東西的拼接(用了時間戳保證唯一性)。
  • 只有當(dāng)支付方式是JSAPI(小程序支付也這么填)的時候才需要openId奖慌。不同的支付方式參數(shù)略有不同抛虫。這個稍微注意一下就行了。
  • 我返回的這個R其實就是自己封裝的一個返回對象简僧,本質(zhì)就是鍵值對建椰。這個可以自由點(diǎn)。
  • 這其中生成sign的方法是sdk里的岛马,網(wǎng)上好多人自己寫了广凸,我比較喜歡拿現(xiàn)成的。

差不多預(yù)下單就這樣了蛛枚。

然后上面有個notify_url就是回調(diào)的接口地址了,必須是外網(wǎng)可訪問的脸哀,必須是https的蹦浦。

下面是回調(diào)接口,他的本質(zhì)就是一個對外的接口撞蜂,所以路徑要對得上:

@RestController
@RequestMapping("/wxpay")
public class WXController {
    @PostMapping("/notify")
    public String wxnotify(HttpServletRequest request,HttpServletResponse response) {
        String xmlBack = "";
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
            String line = null;
            StringBuilder sb = new StringBuilder();
            while((line = br.readLine()) != null){
                sb.append(line);
            } 
            br.close();
            //sb為微信返回的xml盲镶,微信sdk提供轉(zhuǎn)化成map的方法
            String notityXml = sb.toString();           
            System.out.println("接收到的報文:" + notityXml);
            Map<String, String> map =  WXPayUtil.xmlToMap(notityXml); 
            WXPay wxpay = new WXPay(new PayConfig());
            if (wxpay.isPayResultNotifySignatureValid(map)) {//驗證成功                  
                String return_code = map.get("return_code");//狀態(tài)
                String out_trade_no = map.get("out_trade_no");//訂單號
                String result_code = map.get("result_code");
                   //其實這里驗證兩個code都是success就說明充值成功了◎蚬睿可以處理自己的業(yè)務(wù)邏輯了溉贿。
                if (!"SUCCESS".equals(return_code) || !"SUCCESS".equals(result_code)) {
                    System.out.println("充值失敗浦旱!");
                    //支付失敗的邏輯
                    xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
                    return xmlBack;
                }
                xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
                return xmlBack;
            } else {
                System.out.println("<<<<<<<<<<<<<<<<<<<<<微信支付回調(diào)通知簽名錯誤");
                xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
                return xmlBack;
            }
        } catch (Exception e) {
            System.err.println(e);
            System.out.println("微信支付回調(diào)通知失敗");
            xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
            return xmlBack;
            }
    }
}

這個給微信的返回值是為了防止重復(fù)回調(diào)宇色。當(dāng)你給微信返回了,微信就知道關(guān)于這個訂單的回調(diào)你收到了颁湖,也就不會再給你發(fā)了(上面代碼中涉及到業(yè)務(wù)邏輯的代碼我都刪了宣蠕,因為刪刪減減的,所以不確定是不是多個括號少個括號啥的甥捺,如果報錯了檢查下代碼吧)抢蚀。

最后還有一個退款,因為當(dāng)時就有結(jié)果了镰禾,所以比較簡單皿曲,我直接附上代碼:

    public static Map<String, String> refund(String transactionId,Integer money) throws Exception{
        PayConfig config = new PayConfig();
        WXPay wxpay = new WXPay(config);
        Map<String, String> params = new HashMap<String, String>();
        params.put("appid", config.getAppID());
        params.put("mch_id", config.getMchID());
        params.put("nonce_str", System.currentTimeMillis()+"");
        params.put("transaction_id",transactionId);
        params.put("out_refund_no", System.currentTimeMillis()+"");
        params.put("total_fee", (money*100)+"");
        params.put("refund_fee", (money*100)+"");
        String sign = WXPayUtil.generateSignature(params, config.getKey());
        params.put("sign", sign);
        Map<String, String> refund = wxpay.refund(params);
        System.out.println(refund);
        return refund;
    }

注意因為我這里是退全款唱逢,所以兩個fee是一樣的。還有關(guān)于錢轉(zhuǎn)化成分屋休。我第一遍測試坞古,因為一邊乘100.一邊忘記乘了,所以退款失敗了博投,會報錯金額不同绸贡。
微信退款頁介紹
如果遇到什么退款失敗可以去查查失敗的原因,還是很容易理解的毅哗。

然后至此听怕,我這兩天這塊關(guān)于微信支付的業(yè)務(wù)就寫完了,比較簡單虑绵,而且感覺對微信支付的了解也更深了一點(diǎn)尿瞭。
本篇文章就記到這里,如果稍微幫到你了記得點(diǎn)個喜歡點(diǎn)個關(guān)注翅睛,如果有什么問題也歡迎私信或者留言交流声搁。也祝大家工作順順利利!周末愉快捕发!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疏旨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扎酷,更是在濱河造成了極大的恐慌檐涝,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件法挨,死亡現(xiàn)場離奇詭異谁榜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凡纳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門窃植,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荐糜,你說我怎么就攤上這事巷怜。” “怎么了暴氏?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵丛版,是天一觀的道長。 經(jīng)常有香客問我偏序,道長页畦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任研儒,我火速辦了婚禮豫缨,結(jié)果婚禮上独令,老公的妹妹穿的比我還像新娘。我一直安慰自己好芭,他們只是感情好燃箭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舍败,像睡著了一般招狸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上邻薯,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天裙戏,我揣著相機(jī)與錄音,去河邊找鬼厕诡。 笑死累榜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灵嫌。 我是一名探鬼主播壹罚,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寿羞!你這毒婦竟也來了猖凛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤绪穆,失蹤者是張志新(化名)和其女友劉穎形病,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霞幅,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年量瓜,在試婚紗的時候發(fā)現(xiàn)自己被綠了司恳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡绍傲,死狀恐怖扔傅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烫饼,我是刑警寧澤猎塞,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站杠纵,受9級特大地震影響荠耽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜比藻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一铝量、第九天 我趴在偏房一處隱蔽的房頂上張望倘屹。 院中可真熱鬧,春花似錦慢叨、人聲如沸纽匙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烛缔。三九已至,卻和暖如春轩拨,著一層夾襖步出監(jiān)牢的瞬間践瓷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工气嫁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留当窗,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓寸宵,卻偏偏與公主長得像崖面,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子梯影,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容