java实现微信支付、提现

从我入行以来,第一个老板就给我讲过,微信支付是个坑。
不过因为那时候一来公司内部有封装好的方法,按要求调用就可以了,另外多做小程序,公众号,web端,所以也没自己踩什么坑。
但是!没踩过微信的坑的开发不算是一个完整的开发!在经历了一两年的逍遥以后,我到底还要自己做一次。
首先作为一个后台,我觉得我要说一句良心话,微信对后台还是很友好的,并不是很麻烦和坑,此次测试,百分之七十的坑都是前端踩的。

从准备工作讲起。

首先,想要做微信支付,需要的必备参数,appId,mchId,key,回调路径等等。然后还需要一个证书,要在电脑上配置。
这些几乎都是必备的参数,然后还有一些依赖于微信的sdk。
首先来看一看微信官网:
微信支付开放平台
大概讲一讲,微信支付有几种,其中包括:

  • 扫码支付:就是用户提供二维码,商家扫码支付。(这种适合收银台模式,然后我还没有用过)
  • JSAPI支付:这个就比较魔性了,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。其实说起来真的很绕,反正就是类似于js的一种(这个是需要openId的)。
  • Native支付:目前我觉得最简单的一种支付,就是生成一个二维码,用户扫码付款(钱数在生成二维码时生成,只适合在web端使用)
  • APP支付:这个不用多说了吧?就是APP用来拉取微信授权跳转微信支付页面的。
  • H5支付:这个主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。和jsapi的区别就是是否在微信内部唤起吧。(我没实际用过,也不清楚)
  • 小程序支付: 没话讲,就这样。
  • 人脸支付: 高大上到让我从未接触过。
    然后我这次业务使用的是Navite和APP支付两种方式。

SDK的使用

这个有两种方式,一种就是使用微信官网直接下载的SDK,然后以一个个文件的形式导到你的项目里。
还有一种是git上有一份用于这个的依赖(两种办法因为版本不同所以是有差异的。)

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

然后懒惰如我,选择了直接用maven的jar包的导入(中间发生了一些故事,也把微信官方下载的SDK一个个引入了项目中,后来发现功能一样都跑起来了,所以又删除了)。
注意一点,以下的代码都是在引入上面的依赖的前提下实现的(中间涉及到版本的差异很大,所以千万不要弄混了,不然一点微小的区别能调N久)

开始真的实现啦!

1.**配置WXPayConfigImpl**
public class WxPayConfigImpl implements WXPayConfig {
    public static String url = "你设置的回调接口";
    private static WxPayConfigImpl wxPayConfig;
    private byte[] certData = null;

    public static WxPayConfigImpl getInstance() {
        if (wxPayConfig == null) {
            synchronized (WxPayConfigImpl.class) {
                wxPayConfig = new WxPayConfigImpl();
            }
        }
        return wxPayConfig;
    }

    public WxPayConfigImpl() {
        try {
                        //这个证书的位置不是瞎鸡儿填的,你要在这个路径真的有一个证书
            InputStream is = new FileInputStream("C:\\Windows\\System32\\cert\\apiclient_cert.p12");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] bs = new byte[1024];
            int cnt = -1;
            while ((cnt = is.read(bs)) != -1) {
                baos.write(bs, 0, cnt);
            }
            is.close();
            certData = baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getAppID() {
        return "你自己的AppId";
    }

    @Override
    public String getMchID() {
        return "你自己的设备码";
    }

    @Override
    public String getKey() {
        return "你自己的key";
    }

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

    @Override
    public int getHttpConnectTimeoutMs() {
        // TODO Auto-generated method stub
        return 8000;
    }

    @Override
    public int getHttpReadTimeoutMs() {
        // TODO Auto-generated method stub
        return 10000;
    }


    public String getPrimaryDomain() {
        return "api.mch.weixin.qq.com";
    }
    
    public String getNotifyUrl(){
        return url;
    }
}

对,一个configImpl就这么简单的实现了。其中参数是证书路径,一个是appId,设备码,key,回调接口连接是必填项。

Native支付

刚我就说觉得这个是最简单的一种调用,反正我是一次成功的。下面是实现代码:

public R wxPay(String money, String mk, String title, HttpServletRequest request, HttpServletResponse response) {
        try {
            if (mk == null) {
                return R.error().put("msg", "缺少参数,请查证后再访问");
            } else {
                WXPayConfig config = WxWebPayConfigImpl.getInstance();
                SortedMap<String, String> paramMap = new TreeMap<String, String>();
                paramMap.put("body", title);
                paramMap.put("out_trade_no", "C" + System.currentTimeMillis());
                paramMap.put("fee_type", "CNY");
                paramMap.put("total_fee", "1");
                paramMap.put("notify_url", WxPayConfigImpl.url);
                paramMap.put("trade_type", "NATIVE");
                paramMap.put("sign_type", WXPayConstants.MD5);
                String ip = getIpAddr(request);
                paramMap.put("spbill_create_ip", ip);
                WXPay pay = new WXPay(config);
                // 1.统一下单
                Map<String, String> resultMap = pay.unifiedOrder(paramMap);
                String res = resultMap.get("return_code").toString().trim();
                System.out.println(">>>>res==" + res);
                System.out.println(resultMap);
                if ("SUCCESS".equalsIgnoreCase(res)) {
                    return R.ok().put("data", resultMap);
                } else {
                    return null;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
image.png

上图是整理的一些我觉得的注意点,然后点击下单方法后会返回一个路径,这个路径可以打开一个二维码页面,因为我们是前后端分离,所以我走到这步确定res是SUCCESS就ok了。
对了,还要注意一点,这个钱数total_fee应该是前端传来的,不过我这为了demo效果,所以统一写了一分,正常应该写活。
一个完整的微信Navite支付就完成了。

APP支付

刚刚也说了,这个坑百分之七十都是前端的,因为需要什么什么什么配置之类的,我也不知道,反正就是总错,但是真正的java代码还是很简单的。

public R wxAppPay(String money, String mk, String title, HttpServletRequest request, HttpServletResponse response) {
        try {
            Double d = Double.parseDouble(money);
            Integer dd = (int) (d * 100);
            money = String.valueOf(dd);
            WXPayConfig config = WxPayConfigImpl.getInstance();
            SortedMap<String, String> paramMap = new TreeMap<String, String>();
            paramMap.put("body", title);
            paramMap.put("out_trade_no", "C" + System.currentTimeMillis());
            paramMap.put("fee_type", "CNY");
            paramMap.put("total_fee",dd);
            paramMap.put("notify_url", WxPayConfigImpl.url);
            paramMap.put("trade_type", "APP");
            paramMap.put("sign_type", WXPayConstants.MD5);
            String ip = getIpAddr(request);
            paramMap.put("spbill_create_ip", ip);
            WXPay pay = new WXPay(config);
            // 1.统一下单
            Map<String, String> resultMap = pay.unifiedOrder(paramMap);
            String res = resultMap.get("return_code").toString().trim();
            System.out.println(">>>>res==" + res);
            System.out.println(resultMap);
            if ("SUCCESS".equalsIgnoreCase(res)) {
                Map<String, String> paramMap1 = new HashMap<String, String>();
                paramMap1.put("appid", config.getAppID());
//              paramMap1.put("total", money);
                paramMap1.put("partnerid", config.getMchID());
                paramMap1.put("prepayid", (String) resultMap.get("prepay_id"));
                paramMap1.put("package", "Sign=WXPay");
                paramMap1.put("noncestr", WXPayUtil.generateNonceStr());
                // 本来生成的时间戳是13位,但是ios必须是10位,所以截取了一下
                paramMap1.put("timestamp", String.valueOf(System.currentTimeMillis()).toString().substring(0, 10));
                String sign2 = WXPayUtil.generateSignature(paramMap1, config.getKey(), SignType.MD5);
                paramMap1.put("sign", sign2);
                Gson gson = new GsonBuilder().disableHtmlEscaping().create();
                String xmlReq = gson.toJson(paramMap1);
                return R.ok().put("data", xmlReq);
            } else {
                return R.error();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return R.error();
        }
    }

我个人觉得这个就比较良心了,起码金额是写活了。然后与上一个的区别就是交易类型app,不过这个前端要配置较多的东西(具体配置啥不太清楚,反正我们项目就这里因为前端少配置总卡住,卡了一天吧)。貌似配置文件,什么什么证书。什么什么包名要一致,还有不能本地测试,会报错-100,要打包测试(反正我们做完是这样的,如果有本地也能测的亲欢迎指点)
我这里郑重声明,app方式的微信支付,配置就这么多!再有任何报错百分之九十九都是前端的了。
这个恶心的一点就是报错不知道原因,很有可能莫名其妙就报个62000,如果两边信任度不高并且没有成功的例子,可能报错了就会两边找错误,前端改前端的,后台改后台的(对,就是我血淋淋的例子),结果改了一天多才发现最初的代码其实就一点问题没有。你能想到多崩溃么?所以希望以后或者别人对自己有点信心吧,反正我上面的代码是跑通了的。

JSAPI支付

这个还有个小问题,一开始我问领导咱们这个项目用的啥方式,跟我说是JSAPI,所以我傻傻的去找了这个方式的官方文档,不过因为后来我改了,所以并没有现成的demo,大概讲一下区别:
与APP方式比,也就是交易类型改一下,并且需要一个openId的参数。这个参数是微信授权获取的,用appid生成的,我反正是作为一个参数的形式从前台拿的,然后剩下别的就是一样的了。
反正两天的微信支付就这么痛苦的完成了,大多数错误犯得莫名其妙也解决的莫名其妙(因为我后端几乎一直都是返回success,都是前端在那 调啊调)
表白我前端,真的辛苦了,啥啥资料都没有,各种需要做的没有列出来,只能错一点改一点。。还有本地不能调试简直坑死了,反正我是真心心疼做微信支付这一块儿的前端,也有可能是因为我们app 是h5开发,没有用安卓或者微信?不太清楚。继续往下说吧。

微信提现

其实本质上微信的提现就是微信企业付款到个人用户
这个还蛮多要求的,大家可以自己看看官网;
微信提现介绍

红色字是硬性要求

然后如果以上要求都满足了的话,就可以测出效果了,因为我们项目中的提现涉及到很多,比如用户账户余额金额的对比,提现成功减去用户余额等代码,所以我这里就不完整的贴方法了。仅仅是把一些必要的步骤提出来:

WXPayConfig config = null;
//这个也是与我做的业务相关的,因为我们这两个APP,也就是两个APPid啥的,如果你们没这么麻烦直接new  WxPayConfigImpl()就ok 了。
                // 5是司机
                if (sysUserEntity.getUserType() == 5) {
                    config = new WxDriverpayConfigImpl();
                    // 不是司机就是企业
                } else {
                    config = new WxPayConfigImpl();
                }
                SortedMap<String, String> paramMap = new TreeMap<String, String>();
                paramMap.put("mch_appid", config.getAppID());
                paramMap.put("mchid", config.getMchID());
                paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
                paramMap.put("partner_trade_no", "C" + System.currentTimeMillis());
                paramMap.put("openid", openId);
                paramMap.put("check_name", "NO_CHECK");
                paramMap.put("amount", (money * 100) + "");
                paramMap.put("desc", "提现");
                paramMap.put("spbill_create_ip", getIpAddr(request));
                String sign = WXPayUtil.generateSignature(paramMap, config.getKey(), SignType.MD5);
                paramMap.put("sign", sign);
                WXPay pay = new WXPay(config);
//          Map<String,String> resMap =  pay.transfer(paramMap);
                String url = "  https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
                Map<String, String> resMap;
                resMap = pay.processResponseXml(pay.requestWithCert(url, paramMap, config.getHttpConnectTimeoutMs(),
                        config.getHttpReadTimeoutMs()));
                String resultCode = resMap.get("result_code");
                if ("SUCCESS".equalsIgnoreCase(resultCode)) {
//走到这里就是提现操作成功了,可以做你自己的业务逻辑了、
} else {
                    String err_code = resMap.get("err_code");
                    if ("SYSTEMERROR".equalsIgnoreCase(err_code)) {
                        return R.error().put("msg", err_code);
                    } else if ("NOTENOUGH".equalsIgnoreCase(err_code)) {
                        return R.error().put("msg", err_code);
                    } else {
//在这把两个常见的错提了出来,剩下的统一为未知错误了,如果做个更好一些可以直接传错误信息。
                        return R.error().put("msg", "调用微信提现接口未知错误,请联系管理员!");
                    }

至此,一个提现的功能也初步完成了。

其实我这写的都不完整,仅仅是对付实现操作,回调什么的我都没写。因为!!!!领导神奇的工作分配,我这只实现操作,然后回调验证另一个同事写(我也不知道为什么这么分配),当时支付宝也是这么做的,只能笑笑吧。
至此,两天的难受的不行的微信这块儿就告一段落,只能说外界盛传的微信坑名副其实吧。
然后如果稍微帮到了你麻烦点个喜欢点个关注,另外如果遇到我没提到的问题欢迎留言或者私信,我们一起探讨啊?指不定是我踩过的坑呢。也祝大家工作顺顺利利!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容