├── .gitbook └── assets │ ├── er-wei-ma.jpg │ ├── fang-zhen-ce-shi-xi-tong-de-api-yan-zheng-qian-ming.png │ ├── jie-kou-bian-geng-gai-lan.png │ ├── mian-chong-zhi-chan-pin-gong-neng-shi-yong-zhi-yin.png │ ├── mian-chong-zhi-yan-shou-jin-du-cha-xun.png │ ├── qi-ye-fu-kuan-dao-yue1.png │ ├── ru-he-jin-hang-jie-kou-sheng-ji-guan-fang-jie-da-qun.png │ ├── tu-2-shang-hu-jie-ru-yan-shou-liu-cheng.png │ ├── wei-xin-ce-shi-hao-1.png │ ├── wei-xin-ce-shi-hao-2.png │ ├── wei-xin-ce-shi-hao-3.png │ ├── wei-xin-ce-shi-hao-4.png │ ├── wei-xin-zhi-fu-fang-zhen-ce-shi-xi-tong-1.png │ ├── xiao-xi-chu-da-gui-ze.png │ └── yan-shou-bu-zhou.png ├── README.md ├── SUMMARY.md ├── activity └── createcardactivity.md ├── base ├── introduce.md └── preparation.md ├── coupon ├── avoid-top-up.md ├── coupon.md └── wx-card.md ├── pay-to-people └── transfers.md ├── pay ├── authorize.md ├── orderquery.md ├── pay.md └── wxnotify.md ├── redpack └── hong-bao.md ├── refund ├── downloadbill.md └── refund.md └── sandbox ├── sandboxnew.md └── yanshou.md /.gitbook/assets/er-wei-ma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/er-wei-ma.jpg -------------------------------------------------------------------------------- /.gitbook/assets/fang-zhen-ce-shi-xi-tong-de-api-yan-zheng-qian-ming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/fang-zhen-ce-shi-xi-tong-de-api-yan-zheng-qian-ming.png -------------------------------------------------------------------------------- /.gitbook/assets/jie-kou-bian-geng-gai-lan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/jie-kou-bian-geng-gai-lan.png -------------------------------------------------------------------------------- /.gitbook/assets/mian-chong-zhi-chan-pin-gong-neng-shi-yong-zhi-yin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/mian-chong-zhi-chan-pin-gong-neng-shi-yong-zhi-yin.png -------------------------------------------------------------------------------- /.gitbook/assets/mian-chong-zhi-yan-shou-jin-du-cha-xun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/mian-chong-zhi-yan-shou-jin-du-cha-xun.png -------------------------------------------------------------------------------- /.gitbook/assets/qi-ye-fu-kuan-dao-yue1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/qi-ye-fu-kuan-dao-yue1.png -------------------------------------------------------------------------------- /.gitbook/assets/ru-he-jin-hang-jie-kou-sheng-ji-guan-fang-jie-da-qun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/ru-he-jin-hang-jie-kou-sheng-ji-guan-fang-jie-da-qun.png -------------------------------------------------------------------------------- /.gitbook/assets/tu-2-shang-hu-jie-ru-yan-shou-liu-cheng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/tu-2-shang-hu-jie-ru-yan-shou-liu-cheng.png -------------------------------------------------------------------------------- /.gitbook/assets/wei-xin-ce-shi-hao-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/wei-xin-ce-shi-hao-1.png -------------------------------------------------------------------------------- /.gitbook/assets/wei-xin-ce-shi-hao-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/wei-xin-ce-shi-hao-2.png -------------------------------------------------------------------------------- /.gitbook/assets/wei-xin-ce-shi-hao-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/wei-xin-ce-shi-hao-3.png -------------------------------------------------------------------------------- /.gitbook/assets/wei-xin-ce-shi-hao-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/wei-xin-ce-shi-hao-4.png -------------------------------------------------------------------------------- /.gitbook/assets/wei-xin-zhi-fu-fang-zhen-ce-shi-xi-tong-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/wei-xin-zhi-fu-fang-zhen-ce-shi-xi-tong-1.png -------------------------------------------------------------------------------- /.gitbook/assets/xiao-xi-chu-da-gui-ze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/xiao-xi-chu-da-gui-ze.png -------------------------------------------------------------------------------- /.gitbook/assets/yan-shou-bu-zhou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YClimb/wxpay-gitbook/7f9c3631a97db65b1236579322eab9b3773224e8/.gitbook/assets/yan-shou-bu-zhou.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第一篇,主要会介绍一下为何写下这个系列以及对于微信支付的一点小经验,与君共勉。 3 | --- 4 | 5 | # 前篇大纲 6 | 7 | 以下会分几个步骤讲一下我学习微信支付的过程,也是一部辛酸史,也是希望朋友们不要再次跌进坑里,节省时间。 8 | 9 | #### 1、公司需要 10 | 11 | 如标题一般,公司来了这么个需求,需要接入微信支付,那就必须搞定他了,相信大部分的小伙伴都是这样接触微信支付的吧。 12 | 13 | 首先我们需要明确一个需求,如果公司是做 App 支付,除了接入微信支付还需要接入支付宝、银联等第三方支付,那么就需要考虑几个问题,公司是选择自主开发还是借助第三方聚合支付(如Ping++),从成本等方面考虑,其实接入聚合支付也是一个不错的选择哦。 14 | 15 | 如果我们选择自主开发微信支付,那么问题就接踵而至, 16 | 17 | ```text 18 | 什么是微信支付? 19 | 微信支付能做什么? 20 | 我们需要怎么实现它? 21 | 网络上是否有大神们写好的demo直接拿来即可? 22 | 微信官方是否提供了明确的文档? 23 | ``` 24 | 25 | 上面的问题我们这个【浅析微信支付】都会将其一一解开,敬请期待!!! 26 | 27 | #### 2、翻车现场 28 | 29 | 明确了要做微信支付后,我们第一时间当然是去找微信官方文档咯,地址如下: `https://pay.weixin.qq.com/wiki/doc/api/index.html`; 30 | 31 | 现在小程序非常火,我们就拿小程序来举例子吧,进入小程序支付的开发文档页面: `https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1`; 非常遗憾的是,微信的文档已知是给他们自己的程序员看的,如果是小白瞬间闯入,直接就是一脸懵,我到底该如何入手???黑人问号??嗯? 32 | 33 | 莫慌!其实微信支付套路也很简单,他们是将简单问题复杂化了,生怕开发者看不懂,文档写的非常的详细(围笑)导致咋们不知如何入手,从这里开始一直到支付的所有环节都如此,文档真正关键的地方模模糊糊,细节上确实非常细致了。 34 | 35 | 下面讲一下我是如何去学习微信支付的。 36 | 37 | #### 3、逼上梁山 38 | 39 | 绝对是逼上梁山的,简单讲,一个微信支付开发的流程如下: `https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3`; 40 | 41 | ```text 42 | 商户系统和微信支付系统主要交互: 43 | 1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】 44 | 2、商户server调用支付统一下单,api参见公共api【统一下单API】 45 | 3、商户server调用再次签名,api参见公共api【再次签名】 46 | 4、商户server接收支付通知,api参见公共api【支付结果通知API】 47 | 5、商户server查询支付结果,api参见公共api【查询订单API】 48 | ``` 49 | 50 | 上面几步中难点的在于第二步和第三步,中间会有一些坑,为何?举个例子, 51 | 52 | ```text 53 | 微信支付接口文档中的参数有的是驼峰有的却是下划线但官方并没有强烈提示说明... 54 | 因为版本关系有些参数还会不一样... 55 | 对于再次签名所需要的参数和如何签名说的非常的简单,需要一再试错... 56 | 最大的问题是:对Java开发的支持并不怎么好,官方的 sdk demo 真的不能直接拿来就能用,真的就是个 demo... 57 | ``` 58 | 59 | 没有经历过的朋友可能不太能体会这种感觉,真挺难受的;因为上面的问题无法解决只好手动百度+谷歌,然而问题来了,网上的文章同质化非常严重,往往点进去五篇文章,其中三篇都一毛一样啊,流泪...还有人出收费教程,这也是赚钱的好办法... 60 | 61 | 如上经历所以才有了这个系列的文章,当然最终我也在网络大神们的文章中筛选出了精华,实现了微信支付的大部分主体功能,足够满足常规的企业开发了。 62 | 63 | #### 4、初衷 64 | 65 | 这个系列的文章其实早就有写的想法了,因为在我解决问题的过程中,遇到很多同行抱怨非常痛苦,但是实在是懒+并没有打磨到拿来即用的地步,现在自我感觉也差不多了,所以分享给大家。 66 | 67 | 下面会贴出我整理的这个系列主要功能点,如下所示: 68 | 69 | ```text 70 | 1.浅析微信支付:前篇大纲 71 | 2.微信支付简单介绍 72 | 3.开发前的准备 73 | 4.统一下单接口 74 | 5.支付结果通知 75 | 6.查询订单 76 | 7.关闭订单 77 | 8.申请退款 78 | 9.退款结果通知 79 | 10.查询退款 80 | 11.下载对账单 81 | 12.下载资金账单 82 | 83 | 13.如何使用沙箱环境测试 84 | 14.支付验收指引 85 | 15.刷卡支付验收用例 86 | 16.扫码支付验收用例 87 | 17.公众号支付验收用例 88 | 18.免充值产品功能使用指引 89 | 90 | 19.(余额提现)企业付款到零钱资金使用商户号余额资金 91 | 20.商户平台-现金红包-发放普通红包 92 | 21.商户平台-现金红包-查询红包记录 93 | 94 | 22.商户平台-代金券或立减优惠-发放代金券 95 | 23.商户平台-代金券或立减优惠-查询代金券信息 96 | 24.商户平台-代金券或立减优惠-查询代金券批次 97 | 98 | 25.公众平台-微信卡券-创建卡券 99 | 26.公众平台-微信卡券-HTML5线上发券(JS-SDK接口) 100 | 27.公众平台-微信卡券-查看卡券详情 101 | 102 | 28.公众平台-社交立减金活动-概述 103 | 29.公众平台-社交立减金活动-开通产品权限 104 | 30.公众平台-社交立减金活动-完成免充值模式验收 105 | 31.公众平台-社交立减金活动-创建代金券并设置跳转小程序 106 | 32.公众平台-社交立减金活动-创建支付后领取立减金活动接口 107 | 108 | 番外篇: 109 | 1.微信公众号网页授权 110 | 2.微信公众号模板消息发送 111 | 3.生成永久无限制微信小程序二维码 112 | 4.多个微信公众号同一商户平台的支付处理 113 | 5.微信退款时jdk更换安全包的处理 114 | 6.微信支付 MD5、HMACSHA256、SHA1、AES 加解密工具类 115 | ``` 116 | 117 | 对于以上功能点的说明只会多不会少,可能其中还会穿插一些遇到的问题或异常处理,数据库和业务处理逻辑等等; 118 | 119 | 我也创建了一个微信群来提供给大家交流,一起共同进步吧。 120 | 121 | #### 结语 122 | 123 | 希望这个系列文章能给大家带来一些启发,帮助解决一些问题,与君共勉!!! 124 | 125 | 如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下:`https://github.com/YClimb/wxpay-sdk/blob/master/README.md` 126 | 127 | 加作者私人微信,作者微信号如下 `yclimb`,回复 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 128 | 129 | 到此本文就结束了,关注公众号查看更多推送!!! 130 | 131 | ![关注我的微信公众号](.gitbook/assets/er-wei-ma.jpg) 132 | 133 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [前篇大纲](README.md) 4 | 5 | ## 前期准备 6 | 7 | * [微信支付方式简单介绍](base/introduce.md) 8 | * [开发前的准备](base/preparation.md) 9 | 10 | ## 支付&订单 11 | 12 | * [微信公众号网页授权](pay/authorize.md) 13 | * [统一下单接口](pay/pay.md) 14 | * [支付结果通知](pay/wxnotify.md) 15 | * [查询订单和关闭订单](pay/orderquery.md) 16 | 17 | ## 退款&对账单 18 | 19 | * [申请退款、退款回调接口、查询退款](refund/refund.md) 20 | * [下载对账单和资金账单](refund/downloadbill.md) 21 | 22 | ## 沙箱环境&验收用例 23 | 24 | * [如何使用沙箱环境测试](sandbox/sandboxnew.md) 25 | * [支付验收示例和验收指引](sandbox/yanshou.md) 26 | 27 | ## 企业付款 28 | 29 | * [\(余额提现\)企业付款到微信用户零钱或银行卡账户](pay-to-people/transfers.md) 30 | 31 | ## 现金红包 32 | 33 | * [商户平台开通现金红包、指定用户发放、红包记录查询](redpack/hong-bao.md) 34 | 35 | ## 代金券 36 | 37 | * [商户平台代金券或立减优惠开通、指定用户代金券发放、查询等](coupon/coupon.md) 38 | * [开通免充值产品功能及如何进行接口升级指引](coupon/avoid-top-up.md) 39 | * [公众平台卡券功能开通、HTML5线上发券(JS-SDK接口)、查看卡券详情](coupon/wx-card.md) 40 | 41 | ## 社交立减金 42 | 43 | * [开通社交立减金活动、创建立减金及领取使用的相关文档和源码](activity/createcardactivity.md) 44 | 45 | -------------------------------------------------------------------------------- /activity/createcardactivity.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十七篇,主要讲解在在微信平台中,如何创建优惠券,开通社交立减金,并为用户配置发送立减金。 3 | --- 4 | 5 | # 开通社交立减金活动、创建立减金及领取使用的相关文档和源码 6 | 7 | 上篇文章已经为大家讲解了如何在微信公众平台创建优惠券并为用户发券,这片文章是优惠券的一个进阶,讲解微信平台上的`社交立减金`用法,希望可以帮助到大家。 8 | 9 | #### 应用场景 10 | 11 | 小程序社交立减金是一款帮助商家快速生成具备裂变传播属性的小程序经营工具,用户通过支付、扫码等场景可以参与社交立减金活动,将社交立减金礼包分享至朋友后自己可获取一份,朋友在会话中可随机获取社交立减金,并直达商家小程序使用。 12 | 13 | 产品优势 1. 打通“小程序+支付+优惠”,支付成功页分发社交立减金,支付时抵扣使用; 2. 通过聊天分享和裂变,触达更多潜在用户,降低拉新成本; 3. 根据用户标签属性,可配置分发不同金额的社交立减金,如新老用户、是否会员等; 4. 结合用户偏好兴趣,可配置个性化商品推荐,提高转化。 14 | 15 | 官方文档地址: 16 | 17 | ```text 18 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21515658940X5pIn 19 | ``` 20 | 21 | #### 接入流程 22 | 23 | 进入上面文档地址,文中有开通`公众号活动配置`权限,这个权限必须开启,开启指南如下: 24 | 25 | ```text 26 | 社交立减金可通过微信支付后向用户下发消息领取,在配置活动时需要登录发起支付的商户号开通权限。 27 | 28 | Q:在微信支付平台开通“公众号活动配置”权限 29 | A:使用管理员帐号(管理员为申请商户号时给用户分配的登录账号)登录微信支付商户平台pay.weixin.qq.com,进入“产品中心-我的产品-运营工具”,进入并开通“公众号活动配置”权限。 30 | ``` 31 | 32 | 开通权限之后,还需要`完成免充值模式验收`,这个验收在我的前几篇文章中已经讲过,下面贴上对应的链接查看: [浅析微信支付:开通免充值产品功能及如何进行接口升级指引](https://mp.weixin.qq.com/s/78JA5-mdQ0I0g_ob5EgUBg) 33 | 34 | 完成免充值模式验收后,即可通过接口创建与支付打通的代金券,为配置立减金活动做准备。 35 | 36 | 若已完成免充值模式验收,可直接调用`创建代金券并设置跳转小程序`,下面是创建的流程和对应代码。 37 | 38 | #### 创建社交立减金的步骤说明 39 | 40 | 首先,我们来理解一下社交立减金的构成和创建流程,这样才能不被微信给弄迷糊了,下面是几个步骤的说明: 1. 开通微信公众平台中的`微信卡券`功能 2. 开通微信商户平台中的`公众号活动配置`功能 3. 开通微信商户平台中的`免充值`相关产品功能 4. 完成免充值模式验收 5. 在`微信商户平台`创建或者通过接口创建微信代金券 6. 登陆`微信支付商户平台 -营销中心-管理代金券-草稿箱`中激活对应卡券 7. 通过`创建支付后领取立减金活动接口`创建活动 8. 登陆`微信支付商户平台 -营销中心-营销活动-满额送-管理-草稿箱`激活活动。 9. 下单购买某个商品后,在`服务通知`消息中可以领取或者转发社交立减金 10. 领取,结束 41 | 42 | 上面的步骤,缺一不可,需要小伙伴一步步的调试和试错,幸运的是,我已经都试过错,所以把经验总结一下,希望小伙伴们不会再次趟坑,创建社交立减金活动的源码也是有的哦~ 43 | 44 | 好了,话不多说,对于上面的10步,前4步不需要讲,不清楚的小伙伴可以看看我的历史文章,有对应的讲解,下面从第5步开始说起。 45 | 46 | #### 创建代金券并设置跳转小程序 47 | 48 | 下面先讲解接口的方式,完成配置社交立减金活动奖品准备环节,通过本接口开发者可以创建和支付打通的代金券,并设置代金券跳转小程序使用,在 base\_info 中新增设置字段"pay\_info",详情参考创建卡券接口。 49 | 50 | ```text 51 | 协议:https 52 | http请求方式: POST 53 | 请求URL:https://api.weixin.qq.com/card/create?access_token=ACCESS_TOKEN 54 | POST数据格式:JSON 55 | ``` 56 | 57 | 对于POST的数据可以参考我的历史文章`微信卡券创建`,或者查看以下官方文档: 58 | 59 | ```text 60 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21515658940X5pIn 61 | ``` 62 | 63 | 注意: 1. 卡券提交前需检查是否有传:"pay\_info"、"is\_swipe\_card": true 字段; 2. 卡券的起止时间应大于当前时间;如当前时间为:2018/1/17 0:0:0,起止时间需大于当前时间:2018/1/17 13:30:03 (时间精确到秒); 3. 需设置最低消费门槛,如:5元代金券,满10元可用。 "reduce\_cost": 5 ,"least\_cost":10 。 64 | 65 | 推荐查看我的文章,里面有一些字段的注意事项,这里就不细讲了,下面说一下第二种方式,就是直接通过微信商户平台创建代金券,创建完成后,需要在“微信支付商户平台 -营销中心-管理代金券-草稿箱”中激活对应卡券,获取卡券id后,可继续创建立减金活动。 66 | 67 | #### 创建支付后领取立减金活动接口 68 | 69 | 重头戏来了,可以通过此接口创建立减金活动。将已创建的代金券cardid、跳转小程序appid、发起支付的商户号等信息通过此接口创建立减金活动,成功返回活动id即为创建成功。 70 | 71 | ```text 72 | 协议:https 73 | http请求方式: POST 74 | 请求URL:https://api.weixin.qq.com/card/mkt/activity/create?access_token=ACCESS_TOKEN 75 | POST数据格式:JSON 76 | ``` 77 | 78 | 大白话解释一下,上面的代金券cardid就是通过接口返回的卡券ID,或者是通过手动创建后的卡包ID,就是乱七八糟一串英文的那一个,例如:`pX2-vjpU_MT1gFDsP8lNl15PdaFS`,官方的POST示例可以查看文档: 79 | 80 | ```text 81 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21515658940X5pIn 82 | ``` 83 | 84 | 好消息来了,这个接口我已经封装在sdk中了,封装为方法便于大家使用,下面是调用方法和具体方法的代码。 85 | 86 | 调用方法: 87 | 88 | ```text 89 | /** 90 | * 创建支付后领取立减金活动接口 91 | * 92 | * @author yclimb 93 | * @date 2018/9/18 94 | */ 95 | private void createCardActivity() { 96 | WXUtils wxUtils = new WXUtils(); 97 | wxUtils.createCardActivity("2018-09-18 18:00:00", "2018-09-18 19:59:59", 3, 1, 98 | 1, "pX2-vjpU_MT1gFDsP8lNl15PdaZE", "100", 99 | null, false); 100 | } 101 | ``` 102 | 103 | 方法源码: 104 | 105 | ```text 106 | /** 107 | * 创建支付后领取立减金活动接口 108 | * 通过此接口创建立减金活动。 109 | * 将已创建的代金券cardid、跳转小程序appid、发起支付的商户号等信息通过此接口创建立减金活动,成功返回活动id即为创建成功。 110 | * 接口地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21515658940X5pIn 111 | * 112 | * @param begin_time 活动开始时间,精确到秒 113 | * @param end_time 活动结束时间,精确到秒 114 | * @param gift_num 单个礼包社交立减金数量(3-15个) 115 | * @param max_partic_times_act 每个用户活动期间最大领取次数,最大为50,默认为1 116 | * @param max_partic_times_one_day 每个用户活动期间单日最大领取次数,最大为50,默认为1 117 | * @param card_id 卡券ID 118 | * @param min_amt 最少支付金额,单位是元 119 | * @param membership_appid 奖品指定的会员卡appid。如用户标签有选择商户会员,则需要填写会员卡appid,该appid需要跟所有发放商户号有绑定关系。 120 | * @param new_tinyapp_user 可以指定为是否小程序新用户(membership_appid为空、new_tinyapp_user为false时,指定为所有用户) 121 | * @return json 122 | * @author yclimb 123 | * @date 2018/9/18 124 | */ 125 | public JSONObject createCardActivity(String begin_time, String end_time, int gift_num, int max_partic_times_act, 126 | int max_partic_times_one_day, String card_id, String min_amt, 127 | String membership_appid, boolean new_tinyapp_user) { 128 | try { 129 | 130 | // 创建活动接口之前的验证 131 | String msg = checkCardActivity(begin_time, end_time, gift_num, max_partic_times_act, max_partic_times_one_day, min_amt); 132 | if (null != msg) { 133 | JSONObject resultJson = new JSONObject(2); 134 | resultJson.put("errcode", "1"); 135 | resultJson.put("errmsg", msg); 136 | return resultJson; 137 | } 138 | 139 | // 获取[商户名称]公众号的 access_token 140 | String accessToken = this.getAccessToken(WXConstants.WX_MINI_PROGRAM_CODE); 141 | 142 | // 调用接口传入参数 143 | JSONObject paramJson = new JSONObject(1); 144 | 145 | // info 包含 basic_info、card_info_list、custom_info 146 | JSONObject info = new JSONObject(3); 147 | 148 | // 基础信息对象 149 | JSONObject basic_info = new JSONObject(8); 150 | // activity_bg_color 是 活动封面的背景颜色,可参考:选取卡券背景颜色 151 | basic_info.put("activity_bg_color", CardBgColorEnum.COLOR_090.getBgName()); 152 | // activity_tinyappid 是 用户点击链接后可静默添加到列表的小程序appid; 153 | basic_info.put("activity_tinyappid", WXPayConstants.APP_ID); 154 | // mch_code 是 支付商户号 155 | basic_info.put("mch_code", WXPayConstants.MCH_ID); 156 | // begin_time 是 活动开始时间,精确到秒(unix时间戳) 157 | basic_info.put("begin_time", DateTimeUtil.getTenTimeByDate(begin_time)); 158 | // end_time 是 活动结束时间,精确到秒(unix时间戳) 159 | basic_info.put("end_time", DateTimeUtil.getTenTimeByDate(end_time)); 160 | // gift_num 是 单个礼包社交立减金数量(3-15个) 161 | basic_info.put("gift_num", gift_num); 162 | // max_partic_times_act 否 每个用户活动期间最大领取次数,最大为50,不填默认为1 163 | basic_info.put("max_partic_times_act", max_partic_times_act); 164 | // max_partic_times_one_day 否 每个用户活动期间单日最大领取次数,最大为50,不填默认为1 165 | basic_info.put("max_partic_times_one_day", max_partic_times_one_day); 166 | 167 | // card_info_list 是 可以配置两种发放规则:小程序新老用户、新老会员 168 | JSONArray card_info_list = new JSONArray(1); 169 | JSONObject card_info = new JSONObject(3); 170 | // card_id 是 卡券ID 171 | card_info.put("card_id", card_id); 172 | // min_amt 是 最少支付金额,单位是分 173 | card_info.put("min_amt", String.valueOf(new BigDecimal(min_amt).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); 174 | /* 175 | * membership_appid 是 奖品指定的会员卡appid。如用户标签有选择商户会员,则需要填写会员卡appid,该appid需要跟所有发放商户号有绑定关系。 176 | * new_tinyapp_user 是 可以指定为是否小程序新用户 177 | * total_user 是 可以指定为所有用户 178 | * membership_appid、new_tinyapp_user、total_user以上字段3选1,未选择请勿填,不必故意填写false 179 | */ 180 | if (StringUtils.isNotBlank(membership_appid)) { 181 | card_info.put("membership_appid", membership_appid); 182 | } else { 183 | if (new_tinyapp_user) { 184 | card_info.put("new_tinyapp_user", true); 185 | } else { 186 | card_info.put("total_user", true); 187 | } 188 | } 189 | card_info_list.add(card_info); 190 | 191 | // 自定义字段,表示支付后领券 192 | JSONObject custom_info = new JSONObject(1); 193 | custom_info.put("type", "AFTER_PAY_PACKAGE"); 194 | 195 | // 拼装json对象 196 | info.put("basic_info", basic_info); 197 | info.put("card_info_list", card_info_list); 198 | info.put("custom_info", custom_info); 199 | paramJson.put("info", info); 200 | 201 | // 请求微信接口,得到返回结果[json] 202 | HttpEntity entity = new HttpEntity<>(paramJson, this.getHttpHeadersUTF8JSON()); 203 | JSONObject resultJson = restTemplate.postForObject(WXURL.WX_CARD_ACTIVITY_CREATE_URL, entity, JSONObject.class, accessToken); 204 | 205 | // {"errcode":0,"errmsg":"ok","activity_id":"4728935"} 206 | System.out.println(resultJson.toJSONString()); 207 | 208 | return resultJson; 209 | } catch (Exception e) { 210 | WXPayUtil.getLogger().error(e.getMessage(), e); 211 | } 212 | return null; 213 | } 214 | 215 | /** 216 | * 创建活动接口之前的验证 217 | * 218 | * @param begin_time 活动开始时间,精确到秒 219 | * @param end_time 活动结束时间,精确到秒 220 | * @param gift_num 单个礼包社交立减金数量(3-15个) 221 | * @param max_partic_times_act 每个用户活动期间最大领取次数,最大为50,默认为1 222 | * @param max_partic_times_one_day 每个用户活动期间单日最大领取次数,最大为50,默认为1 223 | * @param min_amt 最少支付金额,单位是元 224 | * @return msg str 225 | * @author yclimb 226 | * @date 2018/9/18 227 | */ 228 | public String checkCardActivity(String begin_time, String end_time, int gift_num, int max_partic_times_act, 229 | int max_partic_times_one_day, String min_amt) { 230 | 231 | // 开始时间不能小于结束时间 232 | if (DateTimeUtil.latterThan(end_time, begin_time, DateTimeUtil.TIME_FORMAT_NORMAL)) { 233 | return "活动开始时间不能小于活动结束时间"; 234 | } 235 | 236 | // 单个礼包社交立减金数量(3-15个) 237 | if (gift_num < 3 || gift_num > 15) { 238 | return "单个礼包社交立减金数量(3-15个)"; 239 | } 240 | 241 | // 每个用户活动期间最大领取次数,最大为50,默认为1 242 | if (max_partic_times_act <= 0 || max_partic_times_act > 50) { 243 | return "每个用户活动期间最大领取次数,最大为50,默认为1"; 244 | } 245 | 246 | // 每个用户活动期间单日最大领取次数,最大为50,默认为1 247 | if (max_partic_times_one_day <= 0 || max_partic_times_one_day > 50) { 248 | return "每个用户活动期间单日最大领取次数,最大为50,默认为1"; 249 | } 250 | 251 | // 最少支付金额,单位是元 252 | if (BigDecimal.ONE.compareTo(new BigDecimal(min_amt)) > 0) { 253 | return "最少支付金额必须大于1元"; 254 | } 255 | 256 | return null; 257 | } 258 | ``` 259 | 260 | 对应的全部代码,可以查看我的github,地址如下: 261 | 262 | ```text 263 | https://github.com/YClimb/wxpay-sdk/blob/master/src/main/java/com/weixin/pay/util/WXUtils.java 264 | ``` 265 | 266 | 创建成功后,会返回一个 "activity\_id": "123456" 的json数据,也就是立减金活动id咯,如果创建失败,也不要着急,查看上面说到的官方文档,创建接口下有对应的返回码说明, 已知是足够解决问题了。 267 | 268 | 活动创建成功后,需要登陆“微信支付商户平台 -营销中心-营销活动-满额送-管理-草稿箱”激活活动,激活后,社交立减金投放成功。可以通过支付行为进行验证。 269 | 270 | PS:激活活动后,活动是通过满额送行为发送给用户的,而且是自动发放,如果你在平台上购买的商品价格满足规则,则会在`服务通知`中显示社交立减金的信息,此活动需要用户主动领取才行,用户领取的券最终就是咋们最初创建的`代金券`,此代金券使用规则和正常代金券一样。 271 | 272 | PPS:社交立减金可以分享转发给朋友,这个是他的特点,对于每个活动的代金券规则和份数,大家一定要注意,根据场景投放。 273 | 274 | #### 结语 275 | 276 | 以上为`社交立减金`相关的解释和源码,小伙伴们一定要注意看看官方文档哦,具体的源码可以看我的github,里面对每个方法有详细的注释。 277 | 278 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 279 | 280 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 281 | 282 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 283 | 284 | 到此本文就结束了,关注公众号查看更多推送!!! 285 | 286 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 287 | 288 | -------------------------------------------------------------------------------- /base/introduce.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第二篇,主要讲解一下普通商户接入的支付方式以及其中的不同之处(小程序、公众号、App、H5)。 3 | --- 4 | 5 | # 微信支付方式简单介绍 6 | 7 | 上篇文章讲了本系列的大纲,没有看过的朋友们可以看一下。 8 | 9 | [浅析微信支付:前篇大纲](https://mp.weixin.qq.com/s/pH0GGwUANJxJt-YHs6y_pQ) 10 | 11 | 微信支付是集成在微信客户端的支付功能,用户可以通过手机完成快速的支付流程。微信支付以余额、绑定银行卡的快捷支付为基础,向用户提供安全、快捷、高效的支付服务。 12 | 13 | 微信支付开通需要申请商户平台功能,所以,个人的订阅号是不能开通微信支付的,只有服务号可以开通,申请成为公众账号支付商户必须满足以下条件: 14 | 15 | ```text 16 | 1)拥有公众帐号,且为服务号; 17 | 2)公众帐号须通过微信认证; 18 | ``` 19 | 20 | 微信认证资质审核通过后,即可申请微信支付功能。 21 | 22 | 这里就不演示如何开通微信支付了,百度有现成的例子。 23 | 24 | 下面讲开通微信支付后的开发选择。 25 | 26 | #### 1、支付方式 27 | 28 | 从微信支付商户平台的首页 `https://pay.weixin.qq.com/wiki/doc/api/index.html` 进入,选择 `普通商户` 版本的支付方式,主要分为以下六种: 29 | 30 | ```text 31 | 1.刷卡支付:用户打开微信钱包的刷卡的界面,商户扫码后提交完成支付 32 | 2.公众号支付:用户在微信内进入商家H5页面,页面内调用JSSDK完成支付 33 | 3.扫码支付:用户打开"微信扫一扫“,扫描商户的二维码后完成支付 34 | 4.APP支付:商户APP中集成微信SDK,用户点击后跳转到微信内完成支付 35 | 5.H5支付:用户在微信以外的手机浏览器请求微信支付的场景唤起微信支付 36 | 6.小程序支付:用户在微信小程序中使用微信支付的场景 37 | ``` 38 | 39 | 以上六种支付方式,比较常用的是 2、4、6 这三种,刷卡、扫码、H5现在相对来说用的比较少,本文就主要讲 2、4、6 这三种支付方式了。 40 | 41 | **1.1.公众号支付** 42 | 43 | 公众号支付用途非常广泛,主要用于商家在微信服务号中自建网页商城网站,用户通过链接或者二维码进入微信内部网页浏览器时,进行购买下单等支付操作时调用的流程。 44 | 45 | 开发前准备: 46 | 47 | `https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3` 48 | 49 | 一、设置支付目录 请确保实际支付时的请求目录与后台配置的目录一致(现在已经支持配置根目录,配置后有一定的生效时间,一般5分钟内生效),否则将无法成功唤起微信支付。 50 | 51 | 在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台-->产品中心-->开发配置。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。 52 | 53 | 二、设置授权域名 开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。 54 | 55 | PS:简单讲就是需要填写一个微信认证通过的域名,这个域名就是咋们访问的页面链接,需要注意的一点是,请尽量认证绝对域名,比如使用 `www.yclimb.com`,不使用 `www.yclimb.com/blog` 这样,认证成功后就可以使用微信的网页授权接口来获取用户信息了。 56 | 57 | 如果需要本地测试,则需要去申请一下测试号来测试本地的用户信息,详情见如下链接: `https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login` 58 | 59 | 注意:测试号不支持微信支付!!! 60 | 61 | 官方场景介绍如下:`https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1` 62 | 63 | 开发流程: 64 | 65 | ```text 66 | 1.用户通过链接或二维码进入网页 -> 67 | 2.调用微信网页授权接口[官方接口] -> 68 | 3.用户授权后获取用户信息[官方接口] -> 69 | 4.封装商品信息并下单 -> 70 | 5.调用微信统一下单接口[官方接口] -> 71 | 6.根据统一下单接口返回信息组装前端需要的支付参数[官方验证] -> 72 | 7.前端使用支付参数唤起微信支付界面[官方接口] -> 73 | 8.支付成功后微信异步调用统一下单时传入的回调接口[官方接口] -> 74 | 9.更新商户订单信息 75 | ``` 76 | 77 | 基本流程就如上所述,详细介绍见下一章。 78 | 79 | **1.2.APP支付** 80 | 81 | APP支付适用于商户在移动端APP中集成微信支付功能。 商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户APP内,最后展示支付结果。 目前微信支付支持手机系统有:IOS(苹果)、Android(安卓)和WP(Windows Phone)。 82 | 83 | 官方场景介绍如下:`https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1` 84 | 85 | 开发流程: 86 | 87 | ```text 88 | 1.用户进入商户APP,选择商品下单、确认购买,进入支付环节。商户服务后台生成支付订单,签名后将数据传输到APP端。 89 | 2.用户点击后发起支付操作,进入到微信界面,调起微信支付,出现确认支付界面。 90 | 3.用户确认收款方和金额,点击立即支付后出现输入密码界面,可选择零钱或银行卡支付。 91 | 4.输入正确密码后,支付完成,用户端微信出现支付详情页面。 92 | 5.回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果。 93 | ``` 94 | 95 | PS:APP支付和公众号支付有几个不同的点: 96 | 97 | ```text 98 | 1.公众号支付需要进行用户授权获取用户信息,统一下单接口需要用户的openid 99 | 2.APP支付不需要用户授权,也不需要用户的openid 100 | 3.APP支付依赖于平台,如iOS、Android,需要前端小伙伴配合SDK开发,公众号直接调取http/https接口即可 101 | 4.统一下单时类型不同,开发时注意个别字段的值变化 102 | ``` 103 | 104 | **1.3.小程序支付** 105 | 106 | 小程序支付开发步骤:`https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1` 107 | 108 | 如果开发者已做过JSAPI(公众号支付)或JSSDK(H5)调起微信支付,接入小程序支付非常相似,以下是三种接入方式的对比: 109 | 110 | | 对比栏目 | JSAPI | JSSDK | 小程序 | 111 | | :--- | :--- | :--- | :--- | 112 | | 统一下单 | 都需要先获取到Openid,调用相同的API | | | 113 | | 调起数据签名 | 五个字段参与签名\(区分大小写\):appId,nonceStr,package,signType,timeStamp | | | 114 | | 调起支付页面协议 | HTTP或HTTPS | HTTP或HTTPS | HTTPS | 115 | | 支付目录 | 有 | 有 | 无 | 116 | | 授权域名 | 有 | 有 | 无 | 117 | | 回调函数 | 有 | success回调 | complete、fail、success回调函数 | 118 | 119 | PS:小程序访问商户服务都是通过HTTPS,开发部署的时候需要安装HTTPS服务器 120 | 121 | 商户系统和微信支付系统主要交互: 122 | 123 | ```text 124 | 1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】 125 | 2、商户server调用支付统一下单,api参见公共api【统一下单API】 126 | 3、商户server调用再次签名,api参见公共api【再次签名】 127 | 4、商户server接收支付通知,api参见公共api【支付结果通知API】 128 | 5、商户server查询支付结果,api参见公共api【查询订单API】 129 | ``` 130 | 131 | #### 2、支付工具 132 | 133 | `普通商户` 版本的支付工具,主要分为以下三种: 134 | 135 | ```text 136 | 1.代金券或立减优惠:商户营销和运营的能力,给用户发放代金券或立减优惠的相关说明 137 | 2.现金红包:提供给商户营销的能力,商户给用户派发现金红包相关说明 138 | 3.企业付款:企业付款至用户微信支付零钱或银行卡 139 | ``` 140 | 141 | 支付工具待将支付接口讲完之后,会单独拿几章来讲一下,这里就不细讲了! 142 | 143 | #### 3、注意事项 144 | 145 | 上面主要讲了小程序、公众号、APP这三种支付方式,我们需要注意的是,无论哪种支付方式,最终都会去调取 `统一下单接口`,这个接口主要是将咋们商户中的订单信息拼接为支付信息传入到微信平台,微信平台会返回一个 `预支付单` 信息,我们对这个信息进行再次加密后拼接为实际支付所需的参数(`五个字段参与签名(区分大小写):appId,nonceStr,package,signType,timeStamp`),给到微信提供的官方接口中即可调起支付页面了; 146 | 147 | 关于上面的步骤这里不细讲,后面几章会着重说明调取微信支付的前期准备,以公众号支付为例子,讲一下如何获取授权、拉取用户信息,生产支付信息、调起微信支付等... 148 | 149 | #### 结语 150 | 151 | 前面几章都是一些比较琐碎的概念型文章,从下一章开始进入实操代码环节,下一章会讲 `进入微信支付的前期准备` 尽情期待! 152 | 153 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: `https://github.com/YClimb/wxpay-sdk/blob/master/README.md` 154 | 155 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 156 | 157 | 到此本文就结束了,关注公众号查看更多推送!!! 158 | 159 | ![关注我的微信公众号](../.gitbook/assets/er-wei-ma.jpg) 160 | 161 | -------------------------------------------------------------------------------- /base/preparation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第三篇,主要会讲一下在开发前的一些注意事项。 3 | --- 4 | 5 | # 开发前的准备 6 | 7 | 浅析微信支付系列已经更新两篇了哟~,没有看过的朋友们可以看一下。 8 | 9 | [浅析微信支付:前篇大纲](https://mp.weixin.qq.com/s/pH0GGwUANJxJt-YHs6y_pQ) 10 | 11 | [浅析微信支付:微信支付简单介绍(小程序、公众号、App、H5)](https://mp.weixin.qq.com/s/h9Mx4IBik0MLKSj4c_Qolg) 12 | 13 | #### 1、开发必备 14 | 15 | 因为作者是Java开发,所以这个系列仅针对Java开发人员,其他语言的同学也可以简单看看,了解一下,大概的步骤都是一样的; 16 | 17 | 接下来我们来了解一下开发所需要的准备: 18 | 19 | ```text 20 | 1. 注册一个服务号,并认证通过 21 | 2. 开通微信支付功能 22 | 3. 如果不是管理员,则需要添加服务号和商户平台的用户管理权限 23 | 4. 配置公众号 `JS接口安全域名` 和 `网页授权域名` 24 | 5. 在商户平台开通相应的支付产品功能 25 | 6. 在商户平台设置支付的相关ip授权 26 | ``` 27 | 28 | **1.1. 注册一个服务号,并认证通过** 29 | 30 | 微信公众平台地址:`https://mp.weixin.qq.com` ,开发者可以使用公司邮箱,根据微信的官方引导注册 `服务号`,一定要是公司的邮箱,以后用经常用到的; 31 | 32 | 注册完成之后,进行微信认证,路径:点击左上方头像 -> 选择认证详情 -> 在出来的界面按要求申请认证即可。 33 | 34 | **1.2. 开通微信支付功能** 35 | 36 | 认证通过以后,可在微信公众平台申请开通微信支付,路径:平台首页 -> 点击左侧微信支付 -> 点击支付申请 -> 根据官方引导一步步申请即可。 37 | 38 | 不会的小伙伴可以百度一下,很多的例子可以参考,这里就不重复造轮子了。 39 | 40 | 开通微信支付需要注册登陆 `微信商户平台`,微信支付相关的信息都需要在这个平台上进行操作。 41 | 42 | 注册登陆商户平台,进入账户中心 -> 支付申请 -> 按要求填写即可; 43 | 44 | PS:一定要注意服务号和商户平台必须是一个账户主体,也就是认证的公司需要一致,否则不是同一个商户。 45 | 46 | **1.3. 如果不是管理员,则需要添加服务号和商户平台的用户管理权限** 47 | 48 | 通常开通微信公众平台和商户平台的人都是管理员,也就是你的老大等人员,我们开发者需要登陆使用功能时也不会使用管理员,所以需要添加自己微信号的权限; 49 | 50 | 微信公众平台的权限叫做 `运营者微信号`,在公众平台的左侧 -> 人员设置中添加,需要管理员为我们绑定一个长期的运营者账号; 51 | 52 | 商户平台地址:`https://pay.weixin.qq.com` 53 | 54 | 微信商户平台的权限叫做 `员工账号`,在商户平台 -> 账户中心 -> 左侧员工账号管理 -> 选择某个角色(通常是管理员)-> 新增账号 -> 按要求填写之后即可; 55 | 56 | 如果添加后开发者还是没有需要的相关权限,可以在角色右上方 `配置权限` 中授权修改。 57 | 58 | **1.4. 配置公众号 JS接口安全域名 和 网页授权域名** 59 | 60 | 首先,微信强制规定,如果要使用公众号支付、H5支付、小程序支付等产品时,必须获取到用户的openid,也就是用户唯一标识,如果获取呢?公众号支付需要网页授权,而网页授权就必须配置 `JS接口安全域名` 和 `网页授权域名`这两个域名,小程序支付也一致; 61 | 62 | 不同点是,公众号支付的域名可以是http/https,而小程序则必须是https; 63 | 64 | 配置路径:公众平台 -> 左侧公众号设置 -> 功能设置 -> JS接口安全域名/网页授权域名 65 | 66 | 需要下载微信的安全配置文件,放到咋们的服务器上,根据 授权的域名+认证文件 可以访问后即可配置完成;需要注意的是,每次修改认证域名都会再次重新认证域名,所以认证以后文件请不要轻易删除。 67 | 68 | PS:设置IP白名单,在IP白名单内的IP来源,获取access\_token接口才可调用成功。路径:公众平台首页 -> 基本配置 69 | 70 | **1.5. 在商户平台开通相应的支付产品功能** 71 | 72 | 登陆微信商户平台,进入产品中心,可以开通需要的支付产品,如公众号支付、扫码支付、刷卡支付、H5支付; 73 | 74 | 需要注意的是,在商户平台上小程序也属于公众号支付,不需要单独开通。 75 | 76 | PS:如果公司需要做提现等功能,需要直接向用户付款,那么需要开通 `企业付款到零钱` 产品功能,此功能主要用来解决合理的商户对用户付款需求,最终金额会直接到用户微信零钱中; 77 | 78 | 如果公司需要向用户银行卡付款,则需要开通 `企业付款到个人银行卡` 产品功能,该功能提供由商户直接付钱至指定银行卡账户的能力,主要用来解决合理的商户对用户付款需求。 79 | 80 | 如果公司提现是用公众号为用户发放红包,那么需要开通 `现金红包` 产品功能,企业向指定用户发放现金红包,红包会显示在服务号中,需要用户领取,用户在客户端领取到红包之后,所得金额进入微信钱包,可用于转账、支付或提取到银行卡。 81 | 82 | 现金红包PS:开通条件:入账方式为即时入账至商户号,结算周期为T+1的商户需满足以下两个条件:1.入驻满90天,2.连续正常交易30天。其余结算周期的商户无限制 83 | 84 | **1.6. 在商户平台设置支付的相关ip授权** 85 | 86 | 这里以公众号支付为例,开通公众号支付后,这是还不能进行开发,我们需要拿到商户的几个重要信息: 87 | 88 | ```text 89 | 1. APP_ID(公众平台获取):公众号/小程序开发者ID(AppID) -> 公众平台首页 -> 基本配置 90 | 2. APP_SECRET(公众平台获取):开发者密码(AppSecret) -> 公众平台首页 -> 基本配置 91 | 3. MCH_ID(商户平台获取):商户号 -> 商户平台首页 -> 账户中心 -> 账户信息 92 | 4. API_KEY(商户平台获取):API密钥 -> 商户平台首页 -> 账户中心 -> API安全 93 | 5. APICLIENT_CERT(商户平台获取):安全证书路径 -> 商户平台首页 -> 账户中心 -> API安全 94 | ``` 95 | 96 | PS:如果需要将小程序和公众号联通,需要在 公众平台首页 -> 基本配置 中绑定同一个微信开放平台帐号 97 | 98 | #### 2、开发工具 99 | 100 | 如果以上都完成以后,我们就可以进入开发了;开发前,我们还需要 `绑定开发者账号` 和下载微信官方的 `web开发者工具`,路径:公众平台首页 -> 左侧开发者工具 -> web开发者工具 -> 绑定开发者微信号;开发者工具下载链接:`https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140`,在此链接最下面,可以下载客户端。 101 | 102 | #### 结语 103 | 104 | 开发者工具支持公众号网页、小程序等开发,如果前面的准备工作都完成了,那么我们随后就能进入微信开发的实际代码层面操作,下一篇为大家讲解 `微信公众号网页授权`,因为要使用公众号、小程序支付,必须先获取用户授权,拿到用户openid和unionid,然后再进行支付等操作。 105 | 106 | PS:unionid是微信跨平台时保证用户唯一标识,openid是用户单平台的唯一标识;简单理解:如果咋们商户有一个服务号,一个小程序,那么同一个用户在服务号和小程序中的openid都是不一样的,怎么区分这是同一个用户?就需要使用unionid,他能保证在同一个商户平台下,同一个用户只有一个unionid。 107 | 108 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: `https://github.com/YClimb/wxpay-sdk/blob/master/README.md` 109 | 110 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 111 | 112 | 到此本文就结束了,关注公众号查看更多推送!!! 113 | 114 | ![关注我的微信公众号](../.gitbook/assets/er-wei-ma.jpg) 115 | 116 | -------------------------------------------------------------------------------- /coupon/avoid-top-up.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十五篇,主要讲解如何开通免充值产品功能流程和其中的注意事项,对于接口升级会重要讲解,避免爬坑。 3 | --- 4 | 5 | # 开通免充值产品功能及如何进行接口升级指引 6 | 7 | 浅析微信支付系列已经更新十五篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:商户平台代金券或立减优惠开通、指定用户代金券发放、查询等](https://mp.weixin.qq.com/s/RGLjNNxBW8ccQ4AVWjYEbA) 10 | 11 | [浅析微信支付:商户平台开通现金红包、指定用户发放、红包记录查询](https://mp.weixin.qq.com/s/3fi9TjNMpZ9AbrcNZF1vKA) 12 | 13 | [浅析微信支付:支付验收示例和验收指引](https://mp.weixin.qq.com/s/YESU2V5byxfM8z9YQXgnuA) 14 | 15 | [浅析微信支付:如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 16 | 17 | 上篇文章讲解使用微信支付的代金券功能,我们使用了`预充值代金券`、`预充值立减和折扣`,但是限制是商户必须先充值足够的`预算金额`才可以使用功能,如果想要`免充值`即可使用,需要开通`免充值代金券`、`免充值立减和折扣`,开通该两项功能需要走`免充值产品功能使用指引`,该功能还需要接口升级。 18 | 19 | 简单来说,`预充值`就是你需要做活动,就必须先充值足够的活动预算,比如要发1000元的券,那你的账户里面就必须有这1000块钱,不然就不能创建活动,而`免充值`也很好理解,就是不需要账户里面有足够的钱也可以创建活动,比如要发1000元券,满十减一,直接先创建活动就行,用户支付时,账户自动扣减。 20 | 21 | #### 免充值产品功能使用指引 22 | 23 | 首先来一波官方说明地址: 24 | 25 | ```text 26 | https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=23_15# 27 | ``` 28 | 29 | ![免充值产品功能使用指引](../.gitbook/assets/mian-chong-zhi-chan-pin-gong-neng-shi-yong-zhi-yin.png) 30 | 31 | 上面为官方的开通步骤,注意,一定要按照步骤进行哟,否则可能会出现一些不可描述的问题!!! 32 | 33 | 重点来了,首先,这里以`普通商户`为栗子,要开通免充值,必须进行接口升级,所以,下面是接口升级的流程。 34 | 35 | #### 接口升级 36 | 37 | 下面是重点!重点!重点! 38 | 39 | Q1:为什么要进行接口升级? A1:商户开通免充值类产品功能后,微信支付接口和账单格式会有调整,商户内部系统需要适配升级后的接口参数。查看接口升级说明: 40 | 41 | [服务商或子商户点击下载](https://pay.weixin.qq.com/wiki/doc/api/download/fws_mczjksj.pdf); [普通商户点击下载](https://pay.weixin.qq.com/wiki/doc/api/download/ptsh_mczjksj.pdf) 42 | 43 | 根据链接,我们可以下载微信官方给出的pdf文档,文档有这句话:开通免充值业务功能后,有 `6` 个支付业务接口部分参数将会按照以下表格变更,请确认是否优先进行内部系 统升级以适配新的接口参数(api 接口中,只有使用了免充值券才会返回新增参数); 44 | 45 | 说明,至少有六个接口是需要改变的,改变的是什么呢?说白了就是接口的参数新增了几个,具体的见如下图: 46 | 47 | 48 | 49 | ![接口变更概览](../.gitbook/assets/jie-kou-bian-geng-gai-lan.png) 50 | 51 | Q2:如何进行接口升级? A2:商户的技术人员需要按照测试用例,逐个case进行测试。[下载测试用例](https://pay.weixin.qq.com/wiki/doc/api/download/mczyscsyl.pdf) 如果你在升级过程中遇到困难,请扫码加入微信群,会有微信支付技术人员答疑。 ![如何进行接口升级-官方解答群](../.gitbook/assets/ru-he-jin-hang-jie-kou-sheng-ji-guan-fang-jie-da-qun.png) 52 | 53 | Q3:如何查询接口升级进度? A3:请点击,[查询验收进度](https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=15_6&index=4) 54 | 55 | Q4:我公司同一集团/品牌旗下有多个商户号,有无简便的操作方案? A4:同一品牌旗下多个商户号,在接口升级、开通产品权限、配置活动等环节会面临诸多重复操作,推荐你公司申请同品牌商户号 56 | 57 | (下载同品牌商户号申请模板),会带来如下便利: 1. 仅一个商户号开通产品功能即可,其他商户号由微信支付代为开通产品功能; 2. 同品牌组内商户号可任意互相添加为可核销优惠商户; 3. 同品牌组创建的免充值优惠活动,活动可免审核直接激活生效。 58 | 59 | Q5:开通功能后,我可以添加哪些商户号为可用(可核销优惠)商户? A5: 1. 普通商户:可添加自己或自己的同品牌商户(什么是同品牌商户,见上个问题Q4); 2. 服务商子商户:可添加自己或自己的同品牌商户; 3. 银行服务商:可直接添加旗下任意子商户,不需要子商户授权; 4. 普通服务商:可添加已授权的子商户(一次授权,长期有效)。 60 | 61 | (1)服务商查看已授权的子商户。查看地址:产品中心-特约商户授权产品-免充值代金券/免充值立减与折扣 (2)服务商查看并邀请未授权子商户授权。查看邀请地址:产品中心-特约商户授权产品-免充值代金券/免充值立减与折扣-未授权 (3)子商户的管理员登录并操作授权。授权地址:产品中心-我授权的产品 62 | 63 | Q6:使用免充值优惠,如果发生退款,怎么退? A6: 1. 退款金额以商户提交的订单金额为基准计算。退款金额=用户申请退款商户的订单金额_(用户实际支付金额/订单总金额),即按订单优惠比例退款。 2. 举例说明:小王购买两件商品每件50元,总订单金额100元,使用微信支付免充值券抵消10元,实际支付金额90元;小王支付成功后,决定退货一件商品,其对应收到的退款金额为:50元_(90元/100元)=45元。 64 | 65 | Q7:使用免充值优惠,财务人员需要重点关注哪些变化? A7:首先是账单下载:如果你是人工登录商户平台手工下载账单,那么开通权限前后的账单会因格式不同,分开两份文件;如果你是API下载账单,你公司技术人员对内部系统升级适配新的API字段即可,不需要财务特别关注。其次是新账单字段定义:订单金额=买单支付金额+充值券金额+免充值券金额;订单金额=应结订单金额+免充值券金额;代金券金额=充值券金额+免充值券金额。如需了解更详细字段变化情况,请咨询你公司技术人员。 66 | 67 | #### 升级示例 68 | 69 | 如果上面的文字都已经看过了,那么现在可以跟着作者一起来进行接口升级了,主要分为以下几步: 1. 下载测试用例 2. [查询验收进度](https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=15_6&index=4) 3. 查询升级所需完成的用例(1001、1002、1005)、(1003、1004、1005)两种组合,全部完成验收最好 4. 根据官方用例调用对应的接口,调用完成后再查询验收进度 5. 完成验收 70 | 71 | **查询验收进度** 72 | 73 | 这里第一步下载测试用例就跳过不说了,直接查询验收进度,点击上面链接可以跳转到查询页面,如下图: 74 | 75 | 76 | 77 | ![免充值验收进度查询](../.gitbook/assets/mian-chong-zhi-yan-shou-jin-du-cha-xun.png) 78 | 79 | 这个验收进度是实时的,当我们根据用例调用不同的接口后,查询会显示不同的`完成状态`,帮助我们完成升级。 80 | 81 | **验收用例** 82 | 83 | 首先,接口升级根据刷卡和公众号等分为两组不同的接口升级,(1001、1002、1005)、(1003、1004、1005)两种组合,升级完成任何一组都视为完成升级,比如上图就是完成了公众号接口升级,并没有完成刷卡支付的升级。 84 | 85 | PS:最好完成所有示例升级,保证安全及对接口有了解。 86 | 87 | **调用示例接口** 88 | 89 | 小伙伴参考官方示例会发现,示例中的接口都是调用`沙箱环境`,如果有不明白什么是沙箱环境的小伙伴,可以参考作者的这两篇文章: [支付验收示例和验收指引](https://mp.weixin.qq.com/s/YESU2V5byxfM8z9YQXgnuA)、[如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 90 | 91 | 根据用例升级会发现,其实很简单,根据官方示例传入对应的参数,然后调用接口即可,然后查看一下文档中返回的参数是否有官方文档标红的参数即可,举个栗子: 92 | 93 | ```text 94 | 3.3 【1003-可选用例-公众号/APP/扫码正常支付】 95 | 3.3.1 用例简述 96 | 订单金额 5.51 元,其中 0.01 元使用免充值券,实际支付 5.50 元。 验证正常支付流程,商户使用免充值代金券支付。 97 | 98 | 3.3.2 测试准备 99 | (1)开通公众号/AP/扫描支付; 100 | (2)注意该用例不产生实际扣款; 101 | (3)确保程序中所有调微信支付的 api 都增加了/sandboxnew/路径,已对接仿真系统。 102 | 103 | 3.3.3 测试步骤 104 | (1)用客户端扫码,选择商品下单(此操作可选,若扫码时出现二维码过期等提示,则忽略该 提示,不影响测试结果),调起微信支付交易确认页(支付时出现收银台 total_fee 错误可忽 105 | 略,因为微信支付生产环境里不存在此笔沙箱订单,所以会有此错误提示),或直接组包调用 统一下单 api(https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder); 106 | (2)根据商户内部单号(out_trade_no),调用查单 api (https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery)查询订单状态,与商户 自有订单的关键信息进行核对。 107 | 108 | 3.3.4 预期返回 109 | 下载用例后参考 110 | ``` 111 | 112 | 以上为公众号jsapi支付的升级用例,需要注意的是,官方的订单金额`5.51`不能修改,否则不通过,对于`0.01`等券的金额不需要理会,调用接口后返回结果中会提现。 113 | 114 | 第二步查询订单是和第一步下单绑定的,小伙伴一定要注意根据下单时的订单号去查询,否则升级不通过。 115 | 116 | 第一步调用后就可以去查询验收进度,这时查询会看到进度状态栏显示:已经调用支付接口,还未调用查询单接口,这是咋们调用查询订单接口之后就会显示已完成状态,说明一个用例就升级完成了。 117 | 118 | 退款接口、交易对账单和上面类似,只要根据官方文档一步步操作就行。 119 | 120 | **开通免充值产品** 121 | 122 | 验收完成后,就可以在微信支付商户平台开通`免充值代金券`、`免充值立减和折扣`产品功能了,地址:商户平台 - 产品中心 - 免充值xxx - 开通即可 123 | 124 | 开通后即可体验免充值产品功能了。 125 | 126 | 这里说点题外话,免充值产品开通前接口升级的变更参数,小伙伴们一定要写入程序流程中,这个接口升级最主要的作用就是帮助我们理解免充值的产品对于微信支付的接口变动,根据用例可以更好的修正现有的接口参数,也是挺好的功能。 127 | 128 | #### 结语 129 | 130 | 本文没有贴出具体如何调用升级用例的源码,因为在作者前面文章 `支付验收示例和验收指引` 已经有相关的栗子了,如果不知道如何操作的小伙伴可以先看一下,已经看过的小伙伴相信你们肯定已经明白了,哈哈哈。 131 | 132 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 133 | 134 | 预告:下一篇文章会讲公众平台的卡券 `公众平台卡券功能开通、HTML5线上发券(JS-SDK接口)、查看卡券详情`,敬请期待!!! 135 | 136 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 137 | 138 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 139 | 140 | 到此本文就结束了,关注公众号查看更多推送!!! 141 | 142 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 143 | 144 | -------------------------------------------------------------------------------- /coupon/coupon.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十四篇,主要讲解在如何开通商户平台的代金券或立减优惠功能,商家向指定用户发送代金券,查询发送记录,代金券信息等。 3 | --- 4 | 5 | # 商户平台代金券或立减优惠开通、指定用户代金券发放、查询等 6 | 7 | 浅析微信支付系列已经更新十四篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:商户平台开通现金红包、指定用户发放、红包记录查询](https://mp.weixin.qq.com/s/3fi9TjNMpZ9AbrcNZF1vKA) 10 | 11 | [浅析微信支付:\(余额提现\)企业付款到微信用户零钱或银行卡账户](https://mp.weixin.qq.com/s/YZkrbYm5t8HJPT_S4W9zrQ) 12 | 13 | [浅析微信支付:支付验收示例和验收指引](https://mp.weixin.qq.com/s/YESU2V5byxfM8z9YQXgnuA) 14 | 15 | [浅析微信支付:如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 16 | 17 | 首先我们需要了解一下什么是代金券和立减优惠? 18 | 19 | 代金券是微信支付为商家提供的一个营销工具,他的主要功能可以简单理解为商家的满减券,比如常见的“满十减一”、“满x减x”这类,需要用户主动领取或者平台主动为用户发放,核销时会在微信支付调起界面显示优惠券信息。 20 | 21 | 立减优惠是微信支付为商家提供的另一种自主核销优惠,为何叫自主核销?因为此优惠是一个门槛,不需要用户领取,商家设置一个用户群里,比如全员优惠“满十减一”,那么所有人都可以享受这个优惠,直接在购买商品时自动扣减金额。 22 | 23 | 以上为简单的解释,下面我会结合官方文档来解释这两个优惠方式。 24 | 25 | #### 代金券 26 | 27 | 微信支付代金券业务是基于微信支付,为了协助商户方便地实现营销优惠措施。针对部分有开发能力的商户,微信支付提供通过API接口实现运营代金券的功能 28 | 29 | 官方文档地址: 30 | 31 | ```text 32 | https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_2&index=2 33 | ``` 34 | 35 | 首先,这里我们讲接口发放代金券的方式,下面是代金券的三个接口: 36 | 37 | ![代金券接口介绍](https://img-blog.csdnimg.cn/20181120163710717.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lDbGltYg==,size_16,color_FFFFFF,t_70) 38 | 39 | 操作代金券开通和如何手动创建的官方文档如下: 40 | 41 | ```text 42 | https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_7&index=3 43 | ``` 44 | 45 | 这里说一下重点需要注意的地方,首先,代金券分为单品券和全场券,简单理解: 1. 单品券:指定某几个商品ID的商品可以使用的代金券 2. 全场券:所有商品都可以使用的代金券 46 | 47 | PS:通过高级接口发放的代金券不能插入卡包,并且用户是没有感知的,这是一个缺点,大家一定要记住。 48 | 49 | 微信支付的代金券核销时都是在微信的确认支付窗口,如果有多个代金券,可以选择或者合并代金券支付,支付后在支付通知中会显示记录代金券使用的记录。 50 | 51 | 还需要注意一点,单品代金券核销时需要验证商品ID,这个商品ID在`预支付单`中`单品优惠活动detail字段`传入,json格式必填参数,字段中`goods_id`就是我们在商户后台创建代金券时填入的商品ID,具体的代码可以看我的`统一下单接口`文章和GitHub源码。 52 | 53 | [浅析微信支付:统一下单接口](https://mp.weixin.qq.com/s/lYPiad1pyZ6ZdqH-sg66eg) 54 | 55 | show me the code: 56 | 57 | ```text 58 | /** 59 | * [单品优惠券] - 根据订单VO拼接统一下单需要的 detail 参数,此参数用于[单品优惠券]时自动抵扣
60 | * 统一下单API(支持单品优惠参数) - 享受了单品优惠的订单不支持部分退款,对账单与普通支付保持一致
61 | * 接口地址:https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_102&index=2 62 | * @param orderList 订单list 63 | * @return 微信支付统一下单 detail 参数 64 | * 65 | * @author yclimb 66 | * @date 2018/9/14 67 | */ 68 | public JSONObject setWxPayUnifiedOrderDetail(List orderList) { 69 | if (orderList == null || orderList.isEmpty()) { 70 | return null; 71 | } 72 | // 单品优惠活动detail字段列表说明: 73 | JSONObject detail = new JSONObject(); 74 | /* 订单原价 cost_price 否 int 608800 75 | 1.商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的交易金额。 76 | 2.当订单原价与支付金额不相等,则不享受优惠。 77 | 3.该字段主要用于防止同一张小票分多次支付,以享受多次优惠的情况,正常支付订单不必上传此参数。*/ 78 | // detail.put("cost_price", createTradeVo.getTrade().getTotalPayMoney()); 79 | // 商品小票ID receipt_id 否 String(32) wx123 商家小票ID 80 | // detail.put("receipt_id", ""); 81 | 82 | // 单品优惠活动goods_detail字段说明: 83 | JSONArray goodsDetailList = new JSONArray(); 84 | 85 | for (Order order : orderList) { 86 | JSONObject goodsDetail = new JSONObject(); 87 | // 商品编码 goods_id 是 String(32) 商品编码 由半角的大小写字母、数字、中划线、下划线中的一种或几种组成 88 | goodsDetail.put("goods_id", order.getProductId()); 89 | // 微信侧商品编码 wxpay_goods_id 否 String(32) 1001 微信支付定义的统一商品编号(没有可不传) 90 | // goodsDetail.put("wxpay_goods_id", ""); 91 | // 商品名称 goods_name 否 String(256) iPhone6s 16G 商品的实际名称 92 | goodsDetail.put("goods_name", order.getProductName()); 93 | // 商品数量 quantity 是 int 1 用户购买的数量 94 | goodsDetail.put("quantity", order.getItemNum()); 95 | // 商品单价 price 是 int 528800 单位为:分。如果商户有优惠,需传输商户优惠后的单价(例如:用户对一笔100元的订单使用了商场发的纸质优惠券100-50,则活动商品的单价应为原单价-50) 96 | goodsDetail.put("price", NumberUtil.mul(order.getPayMoney(), 100)); 97 | 98 | // 加入单品优惠集合 99 | goodsDetailList.add(goodsDetail); 100 | } 101 | 102 | // 单品列表 goods_detail 是 String 示例见下文 单品信息,使用Json数组格式提交 103 | detail.put("goods_detail", goodsDetailList); 104 | 105 | return detail; 106 | } 107 | ``` 108 | 109 | **发放代金券接口链接** 110 | 111 | ```text 112 | https://api.mch.weixin.qq.com/mmpaymkttransfers/send_coupon 113 | ``` 114 | 115 | **是否需要证书** 116 | 117 | 请求需要双向证书。 118 | 119 | **调用接口** 120 | 121 | 用于商户主动调用接口给用户发放代金券的场景,已做防小号处理,给小号发放代金券将返回错误码。 122 | 123 | 注意:通过接口发放的代金券不会进入微信卡包 124 | 125 | 接口很简单,需要代金券批次ID和用户openid,代金券批次ID在哪里?每个代金券创建后就会有一个代金券批次ID,在商户平台-营销管理-代金券管理中可以看到。 126 | 127 | 下面为调用方式: 128 | 129 | ```text 130 | // 微信支付对象 131 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 132 | 133 | // 调用发送代金券接口 134 | Map resultMap = wxPay.sendCoupon(coupon_stock_id, partner_trade_no, openid); 135 | ``` 136 | 137 | 微信接口调用: 138 | 139 | ```text 140 | /** 141 | * 作用:商户平台-代金券或立减优惠-发放代金券
142 | * 场景:用于商户主动调用接口给用户发放代金券的场景,已做防小号处理,给小号发放代金券将返回错误码。 143 | * 注意:通过接口发放的代金券不会进入微信卡包 144 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_3&index=4 145 | * 146 | * @param coupon_stock_id 代金券批次id 147 | * @param partner_trade_no 商户单据号 148 | * @param openid 用户openid 149 | * @return API返回数据 150 | * @throws Exception e 151 | * 152 | * @author yclimb 153 | * @date 2018/9/14 154 | */ 155 | public Map sendCoupon(String coupon_stock_id, String partner_trade_no, String openid) throws Exception { 156 | 157 | /** 构造请求参数数据 **/ 158 | Map data = new HashMap<>(); 159 | 160 | // 代金券批次id coupon_stock_id 是 1757 String 代金券批次id 161 | data.put("coupon_stock_id", coupon_stock_id); 162 | // openid记录数 openid_count 是 1 int openid记录数(目前支持num=1) 163 | data.put("openid_count", "1"); 164 | // 商户单据号 partner_trade_no 是 1000009820141203515766 String 商户此次发放凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性 165 | data.put("partner_trade_no", partner_trade_no); 166 | // 用户openid openid 是 onqOjjrXT-776SpHnfexGm1_P7iE String Openid信息,用户在appid下的唯一标识 167 | data.put("openid", openid); 168 | 169 | /** 以下参数为非必填参数 **/ 170 | // 操作员 op_user_id 否 10000098 String(32) 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限 171 | // 设备号 device_info 否 String(32) 微信支付分配的终端设备号 172 | // 协议版本 version 否 1.0 String(32) 默认1.0 173 | // 协议类型 type 否 XML String(32) XML【目前仅支持默认XML】 174 | 175 | 176 | /** 以下四个参数,在 this.fillRequestData 方法中会自动赋值 **/ 177 | // 公众账号ID appid 是 wx5edab3bdfba3dc1c String(32) 微信为发券方商户分配的公众账号ID,接口传入的所有appid应该为公众号的appid(在mp.weixin.qq.com申请的),不能为APP的appid(在open.weixin.qq.com申请的)。 178 | // 商户号 mch_id 是 10000098 String(32) 微信为发券方商户分配的商户号 179 | // 随机字符串 nonce_str 是 1417574675 String(32) 随机字符串,不长于32位 180 | // 签名 sign 是 841B3002FE2220C87A2D08ABD8A8F791 String(32) 签名参数,详见签名生成算法 181 | 182 | // 微信调用接口 183 | Map resultMap = this.sendCoupon(data); 184 | 185 | WXPayUtil.getLogger().info("wxPay.sendCoupon:" + resultMap); 186 | 187 | return resultMap; 188 | } 189 | ``` 190 | 191 | 以上为发放代金券相关代码,下面是查询代金券批次和代金券领取记录接口。 解释下什么叫做代金券批次和代金券记录: 1. 代金券批次:商户平台创建的一个批次代金券,包含x张代金券 2. 代金券:代金券批次下的一张代金券,代金券ID在用户领取代金券后由领取接口获取 3. 代金券记录:用户领券的代金券记录,与代金券1:1,一个批次下有多个领取记录 192 | 193 | #### 代金券批次查询 194 | 195 | 官方文档如下: 196 | 197 | ```text 198 | https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_4&index=5 199 | ``` 200 | 201 | 是否需要证书:否 202 | 203 | 请求参数主要为代金券批次id`coupon_stock_id`,下面是调用接口代码: 204 | 205 | ```text 206 | /** 207 | * 作用:商户平台-代金券或立减优惠-查询代金券批次
208 | * 场景:查询代金券批次信息 209 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_4&index=5 210 | * 211 | * @param coupon_stock_id 代金券批次id 212 | * @return API返回数据 213 | * @throws Exception e 214 | * 215 | * @author yclimb 216 | * @date 2018/9/14 217 | */ 218 | public Map queryCouponStock(String coupon_stock_id) throws Exception { 219 | 220 | /** 构造请求参数数据 **/ 221 | Map data = new HashMap<>(); 222 | 223 | // 代金券批次id coupon_stock_id 是 1757 String 代金券批次id 224 | data.put("coupon_stock_id", coupon_stock_id); 225 | 226 | /** 以下参数为非必填参数 **/ 227 | // 操作员 op_user_id 否 10000098 String(32) 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限 228 | // 设备号 device_info 否 String(32) 微信支付分配的终端设备号 229 | // 协议版本 version 否 1.0 String(32) 默认1.0 230 | // 协议类型 type 否 XML String(32) XML【目前仅支持默认XML】 231 | 232 | 233 | /** 以下四个参数,在 this.fillRequestData 方法中会自动赋值 **/ 234 | // 公众账号ID appid 是 wx5edab3bdfba3dc1c String(32) 微信为发券方商户分配的公众账号ID,接口传入的所有appid应该为公众号的appid(在mp.weixin.qq.com申请的),不能为APP的appid(在open.weixin.qq.com申请的)。 235 | // 商户号 mch_id 是 10000098 String(32) 微信为发券方商户分配的商户号 236 | // 随机字符串 nonce_str 是 1417574675 String(32) 随机字符串,不长于32位 237 | // 签名 sign 是 841B3002FE2220C87A2D08ABD8A8F791 String(32) 签名参数,详见签名生成算法 238 | 239 | // 微信调用接口 240 | Map resultMap = this.queryCouponStock(data); 241 | 242 | WXPayUtil.getLogger().info("wxPay.queryCouponStock:" + resultMap); 243 | 244 | return resultMap; 245 | } 246 | ``` 247 | 248 | 此接口主要用于在商家系统主动查询代金券时使用,如果需要实时同步领券数量等,需要定时任务来同步;推荐做法,如果商家自身系统已经发券,就不要使用微信商户平台的发券方式,自身系统发券即可;或者可以做一个手动同步的口子,某一个时间点手动触发同步机制。 249 | 250 | #### 查询代金券信息 251 | 252 | 官方文档如下: 253 | 254 | ```text 255 | https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_5&index=6 256 | ``` 257 | 258 | 此接口主要作用是查询某个用户的领券状态,代金券状态。 需要三个主要参数:`coupon_id` 代金券id、`stock_id` 批次号、`openid` 用户openid。 259 | 260 | 调用接口代码如下: 261 | 262 | ```text 263 | /** 264 | * 作用:商户平台-代金券或立减优惠-查询代金券信息
265 | * 场景:查询代金券信息 266 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_5&index=6 267 | * 268 | * @param coupon_id 代金券id 269 | * @param stock_id 批次号 270 | * @param openid 用户openid 271 | * @return API返回数据 272 | * @throws Exception e 273 | * 274 | * @author yclimb 275 | * @date 2018/9/14 276 | */ 277 | public Map queryCouponsInfo(String coupon_id, String stock_id, String openid) throws Exception { 278 | 279 | /** 构造请求参数数据 **/ 280 | Map data = new HashMap<>(); 281 | 282 | // 代金券id coupon_id 是 1565 String 代金券id 283 | data.put("coupon_id", coupon_id); 284 | // 用户openid openid 是 onqOjjrXT-776SpHnfexGm1_P7iE String Openid信息,用户在appid下的唯一标识 285 | data.put("openid", openid); 286 | // 批次号 stock_id 是 58818 String(32) 代金劵对应的批次号 287 | data.put("stock_id", stock_id); 288 | 289 | /** 以下参数为非必填参数 **/ 290 | // 操作员 op_user_id 否 10000098 String(32) 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限 291 | // 设备号 device_info 否 String(32) 微信支付分配的终端设备号 292 | // 协议版本 version 否 1.0 String(32) 默认1.0 293 | // 协议类型 type 否 XML String(32) XML【目前仅支持默认XML】 294 | 295 | 296 | /** 以下四个参数,在 this.fillRequestData 方法中会自动赋值 **/ 297 | // 公众账号ID appid 是 wx5edab3bdfba3dc1c String(32) 微信为发券方商户分配的公众账号ID,接口传入的所有appid应该为公众号的appid(在mp.weixin.qq.com申请的),不能为APP的appid(在open.weixin.qq.com申请的)。 298 | // 商户号 mch_id 是 10000098 String(32) 微信为发券方商户分配的商户号 299 | // 随机字符串 nonce_str 是 1417574675 String(32) 随机字符串,不长于32位 300 | // 签名 sign 是 841B3002FE2220C87A2D08ABD8A8F791 String(32) 签名参数,详见签名生成算法 301 | 302 | // 微信调用接口 303 | Map resultMap = this.queryCouponsInfo(data); 304 | 305 | WXPayUtil.getLogger().info("wxPay.queryCouponsInfo:" + resultMap); 306 | 307 | return resultMap; 308 | } 309 | ``` 310 | 311 | #### 立减优惠折扣 312 | 313 | 在商户平台 - 产品中心 - 预充值立减与折扣 中开通功能即可,预充值立减与折扣是微信支付为商户提供的基础营销工具之一,商户可以在商户平台-营销中心配置预充值型立减或折扣,开展营销活动。 314 | 315 | 可自定义活动标题、减价面额、减价门槛、可用商户、预算、用户领取次数限制,也可以配置指定会员可用、指定某些商品享受优惠等。 316 | 317 | 此功能不需要开发,创建活动审核开通即生效,在微信支付时自动扣减。 318 | 319 | 关于立减功能的使用,这里就不多说了,很简单,小伙伴们可以在微信商户平台上阅读一下官方解释,进入产品详情创建一个活动测试一下即可。 320 | 321 | #### 结语 322 | 323 | 这一篇讲解了如何开通代金券和立减优惠折扣,并贴上如何发送代金券、查询代金券等接口的源码,小伙伴需要仔细阅读官方文档,对照本篇文章,应该不会有什么问题。 324 | 325 | 这里主要是使用了`预充值代金券`、`预充值立减和折扣`,必须先充值足够的`预算金额`才可以使用功能,如果想要`免充值`即可使用,需要开通`免充值代金券`、`免充值立减和折扣`,开通该两项功能需要走`免充值产品功能使用指引`,该功能还需要接口升级,下一篇文章为大家介绍如何`接口升级及开通免充值产品功能`。 326 | 327 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 328 | 329 | 预告:下一篇文章会讲 `接口升级及开通免充值产品功能`,敬请期待!!! 330 | 331 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 332 | 333 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 334 | 335 | 到此本文就结束了,关注公众号查看更多推送!!! 336 | 337 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 338 | 339 | -------------------------------------------------------------------------------- /coupon/wx-card.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十六篇,主要讲解如何使用微信公众平台的卡券功能、如何使用HTML5在网页展示用户领券以及微信卡券和商户平台代金券的关系。 3 | --- 4 | 5 | # 公众平台卡券功能开通、HTML5线上发券(JS-SDK接口)、查看卡券详情 6 | 7 | 浅析微信支付系列已经更新十六篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:开通免充值产品功能及如何进行接口升级指引](https://mp.weixin.qq.com/s/78JA5-mdQ0I0g_ob5EgUBg) 10 | 11 | [浅析微信支付:商户平台代金券或立减优惠开通、指定用户代金券发放、查询等](https://mp.weixin.qq.com/s/RGLjNNxBW8ccQ4AVWjYEbA) 12 | 13 | [浅析微信支付:商户平台开通现金红包、指定用户发放、红包记录查询](https://mp.weixin.qq.com/s/3fi9TjNMpZ9AbrcNZF1vKA) 14 | 15 | [浅析微信支付:支付验收示例和验收指引](https://mp.weixin.qq.com/s/YESU2V5byxfM8z9YQXgnuA) 16 | 17 | [浅析微信支付:如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 18 | 19 | 前几篇文章主要介绍了如何在【微信商户平台】使用代金券和满减优惠折扣等产品功能,有不少小伙伴说到,【微信公众平台】也有一个卡券功能,那么他们有什么差别呢?这个卡券功能该如何使用?本文会给大家一个解释。 20 | 21 | #### 两者的差别 22 | 23 | 首先,我们来解释商户平台和微信平台各自优惠券的区别,如果有人试过,那么应该知道,两者是不通用的,不通用的,不通用的!!! 24 | 25 | 至于这里要重点标识不通用?因为在开通微信卡券功能后,在商户平台也会出现微信卡券对应优惠券信息,虽然没有发券的功能,只是展示,但如果我们走接口发券,就会出现 `发券失败,不支持发送xxx类型的优惠券` 的错误,这时就尴尬了; 26 | 27 | 还没完,因为平台不同,所以微信卡券和支付优惠券发送、领取的方式(接口)也是不同的,包括用户领取时跳转到的页面也不相同,这个也请大家注意。 28 | 29 | 所以,我们需要将这两种券作为两种不同种类的券来处理就可以了。 30 | 31 | #### 公众平台卡券功能开通 32 | 33 | 登陆公众平台 `https://mp.weixin.qq.com`,点击左侧\[功能-添加功能插件\],进入插件库页面,进入\[卡券功能\],可以开通卡券功能。 34 | 35 | 申请条件: 必须开通微信支付功能!!!! 36 | 37 | 功能介绍: 卡券功能,是提供给商户或第三方的一套派发优惠券,经营管理会员的工具,可在公众平台或通过接口创建卡券,多种渠道投放给用户,用户用券时需核销卡券,核销后可查看数据、进行对账。 38 | 39 | 主要能力: 40 | 41 | ●朋友共享的优惠券——可利用社交链快速扩散传播,一人领券,本人和朋友皆可看到并使用。查看视频介绍 42 | 43 | ●普通优惠券——传统优惠券电子版,领取后仅本人可见可用,支持多种类型:折扣券、代金券、兑换券、团购券、优惠券。 44 | 45 | ●会员卡——支持折扣、积分等玩法,并提供会员管理、数据报表等丰富工具,便于商户高效运营会员。 46 | 47 | ●微信买单——无需进行微信支付开发,同时与会员卡,代金券,折扣券打通,为你积累用户消费数据,用于经营参考 48 | 49 | ●储值功能——会员卡商户无需申请,可直接通过API接口,使用“余额展示”功能,将会员余额显示在微信会员卡首页。具有预付卡资质的商家可申请“储值”功能,申请成功后,可通过API接口设置此入口,帮助会员通过微信支付为会员卡充值。 50 | 51 | ●第三方代制模式——经商户授权后,可代子商户快速接入并使用卡券功能,支持通过公众平台或API接口实现该功能。 52 | 53 | 官方开发文档地址: 54 | 55 | ```text 56 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141229 57 | ``` 58 | 59 | 这里就已知小伙伴们已经开通卡券功能,如何手动创建优惠券可以在公众平台上操作,很简单,这里就不说了,本文主要讲如何使用接口的方式来创建卡券、用户领取、查询卡券详情。 60 | 61 | #### 微信卡券指引 62 | 63 | 官方文档地址: 64 | 65 | ```text 66 | https://mp.weixin.qq.com/cgi-bin/readtemplate?t=cardticket/faq_tmpl&type=info&token=&lang=zh_CN#0 67 | ``` 68 | 69 | 下面是`卡券功能开通指引`对应的申请渠道、申请条件: 70 | 71 | ![卡券功能开通指引](https://img-blog.csdnimg.cn/20181204202143255.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lDbGltYg==,size_16,color_FFFFFF,t_70) 72 | 73 | 有需要的小伙伴可以通读一下上面官方文档,读完之后就会对微信卡券有所认识了,差不多基础业务都能做到心中有数咯。 74 | 75 | 如果是开发者,直接看下面`微信卡券接口文档`就行,毕竟我们更关心接口相关的信息,官方文档如下: 76 | 77 | ```text 78 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141229 79 | ``` 80 | 81 | 下载卡券资料包: 82 | 83 | ```text 84 | https://mp.weixin.qq.com/zh_CN/htmledition/comm_htmledition/res/cardticket/wx_card_document.zip?token=&lang=zh_CN 85 | ``` 86 | 87 | 根据官方文档的步骤,需要整整七步,下面为具体步骤: 1. 获取access\_token 2. 上传卡券logo 3. 创建卡券 4. 创建二维码投放 5. 显示二维码 6. 设置测试白名单 7. 核销卡劵 88 | 89 | 官方的文档主要是使用了`沙箱测试账号`来测试并验证,关于接口测试号申请可以通过以下链接来取得: 90 | 91 | ```text 92 | https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 93 | ``` 94 | 95 | 下面我们来一步步分析接口。 96 | 97 | #### 获取access\_token 98 | 99 | 页面地址:`http://mp.weixin.qq.com/debug/` 100 | 101 | 接口类型:基础支持 102 | 103 | 接口列表:获取access\_token接口 104 | 105 | 注意事项:参数填写开发者的appid和secret 106 | 107 | 点击检查问题,即可返回access\_token,access\_token的有效期是两小时,两小时之后须重新获取 108 | 109 | 接口地址:获取access\_token接口 `https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183` 110 | 111 | 这里的获取微信全局access\_token接口就不讲了,能看到这篇文章的小伙伴应该早就写过了,哈哈哈。 112 | 113 | #### 上传卡券logo 114 | 115 | 页面地址:`http://mp.weixin.qq.com/debug/` 116 | 117 | 接口类型:基础支持 118 | 119 | 接口列表:上传图片素材接口 120 | 121 | access\_token: 上一步获得的access\_token 122 | 123 | buffer:你选择的图片 124 | 125 | 点击检查问题,即可获取图片url,在下一步创建卡劵的参数中需要 126 | 127 | 接口地址:上传图片接口 `https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025056` 128 | 129 | 此接口就是上传商户logo,可以获得一个卡券logo链接,基本上就用一次,如果不想使用此接口来上传logo,可以在公众平台手动创建优惠券时上传logo,上传后可以在logo图片上鼠标右键-复制图片路径,也是一样的(不想调接口的可以用这个歪招哈哈哈哈)。 130 | 131 | #### 创建卡券 132 | 133 | 页面地址:`http://mp.weixin.qq.com/debug/` 134 | 135 | 接口类型:卡劵接口 136 | 137 | 接口列表:创建卡劵接口 138 | 139 | access\_token:第一步获得的access\_token 140 | 141 | 创建卡券接口地址: 142 | 143 | ```text 144 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025056 145 | ``` 146 | 147 | 以下为调用接口示例: 148 | 149 | ```text 150 | https://api.weixin.qq.com/card/create?access_token=xxx 151 | ``` 152 | 153 | JSON示例: 154 | 155 | ```text 156 | { 157 | "card": { 158 | "card_type": "CASH", 159 | "cash": { 160 | "base_info": { 161 | "logo_url": "https://mmbiz.qlogo.cn/mmbiz_png/E5Q3G4ku9nf7UiafOetcAPLyfia7kdWWWauHukNN7ZXnggtZcTzEPGa8IUDiaLIv14EkNdPvmbCFyHibh0G8tia7Eibw/0?wx_fmt=png", // 此处是上传logo的图片地址 162 | "pay_info": { 163 | "swipe_card": { 164 | "use_mid_list": [ 165 | "xxx" // 商户号 166 | ], 167 | "create_mid": "xxx", // 商户号 168 | "is_swipe_card": true 169 | } 170 | }, 171 | "brand_name": "测试代金券", 172 | "code_type": "CODE_TYPE_NONE", 173 | "title": "111", 174 | "color": "Color090", // 主题颜色 175 | "service_phone": "18888888888", 176 | "description": "不可与其他优惠同享如需团购券发票,请在消费时向商户提出", 177 | "date_info": { 178 | "type": "DATE_TYPE_FIX_TIME_RANGE", 179 | "begin_timestamp": 1536768000, // 开始时间 180 | "end_timestamp": 1536940800 // 结束时间 181 | }, 182 | "can_share": false, 183 | "center_title": "立即使用", 184 | "center_app_brand_user_name": "gh_7195ea80d2e6@app", 185 | "center_app_brand_pass": "pages/index/index", 186 | "can_give_friend": false, 187 | "sku": { 188 | "quantity": 500000 189 | }, 190 | "get_limit": 30, 191 | "custom_url_name": "立即使用", 192 | "custom_url": "http://www.qq.com", 193 | "custom_url_sub_title": "6个汉字tips", 194 | "promotion_url_name": "更多优惠", 195 | "promotion_url": "http://www.qq.com" 196 | }, 197 | "advanced_info": { 198 | "use_condition": { 199 | "accept_category": "鞋类", 200 | "reject_category": "阿迪达斯", 201 | "can_use_with_other_discount": true, 202 | "least_cost": "51" 203 | }, 204 | "abstract": { 205 | "abstract": "微信餐厅推出多种新季菜品,期待您的光临", 206 | "icon_url_list": [ 207 | "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sjpiby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0" 208 | ] 209 | }, 210 | "text_image_list": [ 211 | { 212 | "image_url": "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sjpiby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0", 213 | "text": "此菜品精选食材,以独特的烹饪方法,最大程度地刺激食 客的味蕾" 214 | }, 215 | { 216 | "image_url": "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sj piby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0", 217 | "text": "此菜品迎合大众口味,老少皆宜,营养均衡" 218 | } 219 | ], 220 | "time_limit": [ 221 | { 222 | "type": "MONDAY", 223 | "begin_hour": 0, 224 | "end_hour": 10, 225 | "begin_minute": 10, 226 | "end_minute": 59 227 | }, 228 | { 229 | "type": "HOLIDAY" 230 | } 231 | ], 232 | "business_service": [ 233 | "BIZ_SERVICE_FREE_WIFI", 234 | "BIZ_SERVICE_WITH_PET", 235 | "BIZ_SERVICE_FREE_PARK", 236 | "BIZ_SERVICE_DELIVER" 237 | ] 238 | }, 239 | "reduce_cost": 5 240 | } 241 | } 242 | } 243 | ``` 244 | 245 | 通过以上接口和参数可以创建一个优惠券信息,json示例也可以使用官方的,这里有几个问题需要重点说一下: 1. use\_mid\_list 商户号需要填写本商户的 2. color 颜色可以根据文档中选择 3. begin\_timestamp、end\_timestamp 这两个时间非常重要,首先结束时间必须大于开始时间,并且需要大于一定的限度,测试时最好跨度大于一天;还需要注意的是时间格式必须是10位数值,例如:1536768000,其他格式会报错。 4. time\_limit 下的值根据文档中的要求填写 5. sku、get\_limit 参数按要求填写 6. logo\_url 使用微信官方的logo图片地址 246 | 247 | 注意事项:date\_info中用的是Unix时间戳,注意把begin\_timestamp修改小于当前时间,end\_timestamp修改成今天之后的时间,这样在后面核销卡劵测试才能成功 248 | 249 | #### 创建二维码投放 250 | 251 | 页面地址:[http://mp.weixin.qq.com/debug](http://mp.weixin.qq.com/debug) 252 | 253 | 接口类型:卡劵接口 254 | 255 | 接口列表:创建二维码ticket接口 256 | 257 | access\_token:第一步获得的access\_token 258 | 259 | 接口文档地址: 260 | 261 | ```text 262 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025062 263 | ``` 264 | 265 | 接口调用示例: 266 | 267 | ```text 268 | https://api.weixin.qq.com/card/qrcode/create?access_token=TOKEN 269 | ``` 270 | 271 | json参数: 272 | 273 | ```text 274 | { 275 | "action_name":"QR_CARD", 276 | "expire_seconds":1800, 277 | "action_info":{ 278 | "card":{ 279 | "card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc", 280 | "code":"198374613512", 281 | "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA", 282 | "is_unique_code":false, 283 | "outer_str":"12b" 284 | } 285 | } 286 | } 287 | ``` 288 | 289 | #### 显示二维码 290 | 291 | 在上一步的返回中点击字段show\_qrcode\_url字段中的链接,即可显示卡券领取二维码。 打开微信扫一扫,然后领取卡劵,如果显示卡劵未通过审核,那么需要下一步设置测试白名单,如果可以领取就忽略第六步。 292 | 293 | #### 设置测试白名单 294 | 295 | 文档地址: 296 | 297 | ```text 298 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025062 299 | ``` 300 | 301 | #### 核销卡劵 302 | 303 | 文档地址: 304 | 305 | ```text 306 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025239 307 | ``` 308 | 309 | 注意事项:仅支持审核通过且在有效期内的卡劵 310 | 311 | #### HTML5网页发券(JS-SDK) 312 | 313 | 上面通过二维码发券的方式,我觉得官方文档已经解释清楚了,且接口也比较简单,所以这里就不赘述,主要需要讲的是这里的 `HTML5网页发券(JS-SDK)`,官方文档链接如下: 314 | 315 | ```text 316 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025062 317 | ``` 318 | 319 | ![HTML5线上发券(JS-SDK接口)](https://img-blog.csdnimg.cn/20181204202210256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lDbGltYg==,size_16,color_FFFFFF,t_70) 320 | 321 | 看了上面的图,小伙伴大概知道是什么意思了吧,简单点说,就是用户在咋们系统H5中点击按钮,可以弹出微信官方的领取优惠券界面,官方的页面是微信提供的,我们无需开发,只需要关注如何调用官方的方法即可。 322 | 323 | 官方解释如下:微信提供的addCard接口供商户前端网页调用,用于将一张或多张卡券添加到用户卡包 324 | 325 | 接口地址如下\(在以下页面搜索addCard即可直达\): 326 | 327 | ```text 328 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 329 | ``` 330 | 331 | ![批量添加卡券接口](https://img-blog.csdnimg.cn/20181204202223449.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lDbGltYg==,size_16,color_FFFFFF,t_70) 332 | 333 | 上面是添加卡券的接口,我们重点关注这几个参数: 1. cardId:卡券ID,如果是商户平台,则是批次ID 2. cardExt:卡券的扩展参数。需进行 JSON 序列化为字符串传入 3. cardExt>openid:指定领取者的 openid,只有该用户能领取。 bind\_openid 字段为 true 的卡券必须填写,bind\_openid 字段为 false 不可填写。 4. cardExt>code:用户领取的 code,仅自定义 code 模式的卡券须填写,非自定义 code 模式卡券不可填写 5. cardExt>nonce\_str:随机字符串,由开发者设置传入,加强安全性(若不填写可能被重放请求)。随机字符串,不长于 32 位。推荐使用大小写字母和数字,不同添加请求的 nonce\_str 须动态生成,若重复将会导致领取失败。 6. cardExt>signature:签名,商户将接口列表中的参数按照指定方式进行签名,签名方式使用 SHA1,具体签名方案参见:卡券签名 7. cardExt>timestamp:时间戳,东八区时间,UTC+8,单位为秒 8. cardExt>outer\_str:领取渠道参数,用于标识本次领取的渠道值。 9. cardExt>fixed\_begintimestamp:卡券在第三方系统的实际领取时间,为东八区时间戳(UTC+8,精确到秒)。当卡券的有效期类为 DATE\_TYPE\_FIX\_TERM 时专用,标识卡券的实际生效时间,用于解决商户系统内起始时间和领取微信卡券时间不同步的问题。 334 | 335 | **根据代金券批次ID得到组合的cardList** 336 | 337 | 首先,获取cardList接口需要先获取access\_token,再获取api\_ticket,最后组装成想要的集合,下面是示例代码。 338 | 339 | 根据代金券批次ID得到组合的cardList: 340 | 341 | ```text 342 | /** 343 | * 根据代金券批次ID得到组合的cardList 344 | * 345 | * @param cardId 卡包ID 346 | * @return cardList 347 | * @author yclimb 348 | * @date 2018/9/21 349 | */ 350 | public JSONArray getCardList(String cardId) { 351 | if (StringUtils.isBlank(cardId)) { 352 | return null; 353 | } 354 | try { 355 | 356 | // 获取[商户名称]公众号的 access_token 357 | String accessToken = this.getAccessToken(WXConstants.WX_MINI_PROGRAM_CODE); 358 | String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp()); 359 | String nonce_str = WXPayUtil.generateNonceStr(); 360 | 361 | // 卡券的扩展参数。需进行 JSON 序列化为字符串传入 362 | JSONObject cardExt = new JSONObject(); 363 | //cardExt.put("code", ""); 364 | //cardExt.put("openid", ""); 365 | //cardExt.put("fixed_begintimestamp", ""); 366 | //cardExt.put("outer_str", ""); 367 | cardExt.put("timestamp", timestamp); 368 | cardExt.put("nonce_str", nonce_str); 369 | 370 | /** 371 | * 1.将 api_ticket、timestamp、card_id、code、openid、nonce_str的value值进行字符串的字典序排序。 372 | * 2.将所有参数字符串拼接成一个字符串进行sha1加密,得到signature。 373 | * 3.signature中的timestamp,nonce字段和card_ext中的timestamp,nonce_str字段必须保持一致。 374 | */ 375 | Map map = new HashMap<>(8); 376 | //map.put("code", ""); 377 | //map.put("openid", ""); 378 | map.put("api_ticket", this.getWxCardApiTicket(accessToken)); 379 | map.put("timestamp", timestamp); 380 | map.put("card_id", cardId); 381 | map.put("nonce_str", nonce_str); 382 | cardExt.put("signature", WXPayUtil.SHA1(WXPayUtil.dictionaryOrder(map, 2))); 383 | 384 | // 卡券对象 385 | JSONObject cardInfo = new JSONObject(); 386 | cardInfo.put("cardId", cardId); 387 | cardInfo.put("cardExt", cardExt.toJSONString()); 388 | 389 | // 需要添加的卡券列表 390 | JSONArray cardList = new JSONArray(1); 391 | cardList.add(cardInfo); 392 | 393 | return cardList; 394 | } catch (Exception e) { 395 | WXPayUtil.getLogger().error(e.getMessage(), e); 396 | } 397 | return null; 398 | } 399 | ``` 400 | 401 | 获取微信全局accessToken: 402 | 403 | ```text 404 | /** 405 | * 获取微信全局accessToken 406 | * 407 | * @param code 标识 408 | * @return accessToken 409 | */ 410 | public String getAccessToken(String code) { 411 | 412 | // 取redis数据 413 | String key = WXConstants.WECHAT_ACCESSTOKEN + code; 414 | String accessToken = (String) redisTemplate.opsForValue().get(key); 415 | if (accessToken != null) { 416 | return accessToken; 417 | } 418 | 419 | // 通过接口取得access_token 420 | JSONObject jsonObject = restTemplate.getForObject(MessageFormat.format(WXURL.BASE_ACCESS_TOKEN, WXPayConstants.APP_ID, WXPayConstants.SECRET), JSONObject.class); 421 | String token = (String) jsonObject.get("access_token"); 422 | if (StringUtils.isNotBlank(token)) { 423 | // 存储redis 424 | redisTemplate.opsForValue().set(key, token, 7000, TimeUnit.SECONDS); 425 | return token; 426 | } else { 427 | log.error("获取微信accessToken出错,微信返回信息为:[{}]", jsonObject.toString()); 428 | } 429 | return null; 430 | } 431 | ``` 432 | 433 | 获取卡券 api\_ticket 的 api: 434 | 435 | ```text 436 | /** 437 | * 获取卡券 api_ticket 的 api 438 | * 请求路径:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=wx_card 439 | * 440 | * @param access_token token 441 | * @return api_ticket json obj 442 | * @author yclimb 443 | * @date 2018/9/21 444 | */ 445 | public String getWxCardApiTicket(String access_token) { 446 | if (StringUtils.isBlank(access_token)) { 447 | return null; 448 | } 449 | try { 450 | 451 | // redis key 452 | String redisKey = RedisKeyUtil.keyBuilder(RedisKeyEnum.IMALL_WXCARD_APITICKET, access_token); 453 | 454 | // 从redis中获取缓存 455 | Object obj = redisTemplate.opsForValue().get(redisKey); 456 | if (obj != null) { 457 | return obj.toString(); 458 | } 459 | 460 | // 获取卡券 api_ticket 461 | String api_ticket = restTemplate.getForObject(WXURL.BASE_API_TICKET, String.class, access_token); 462 | WXPayUtil.getLogger().info("getWxCardApiTicket:api_ticket:{}", api_ticket); 463 | if (StringUtils.isBlank(api_ticket)) { 464 | return null; 465 | } 466 | JSONObject jsonObject = JSON.parseObject(api_ticket); 467 | if (0 != jsonObject.getIntValue("errcode")) { 468 | return null; 469 | } 470 | 471 | // 设置到redis中,下次取直接拿缓存即可,防止多次生成 472 | String ticket = jsonObject.getString("ticket"); 473 | redisTemplate.opsForValue().set(redisKey, ticket, jsonObject.getIntValue("expires_in"), TimeUnit.SECONDS); 474 | 475 | return ticket; 476 | } catch (Exception e) { 477 | WXPayUtil.getLogger().error(e.getMessage(), e); 478 | } 479 | return null; 480 | } 481 | ``` 482 | 483 | 以上代码可以获取cardList,将此参数替换到微信官方方法中即可唤起领券页面;需要注意的是,公众平台和商户平台的券领取的方式不同,这里是以公众平台为例子,如果小伙伴要用商户平台这样为用户发券,是无法成功的哟。 484 | 485 | #### 查看卡券详情 486 | 487 | 开发者可以调用该接口查询某个card\_id的创建信息、审核状态以及库存数量。 488 | 489 | 接口调用: 490 | 491 | ```text 492 | HTTP请求方式:POST 493 | URL:https://api.weixin.qq.com/card/get?access_token=TOKEN 494 | 参数:card_id,卡券ID 495 | ``` 496 | 497 | 接口文档\(进入链接后查询 `查看卡券详情` 可快速定位\): 498 | 499 | ```text 500 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272 501 | ``` 502 | 503 | 此接口官方介绍已经很详细,这里就不细讲了,大家参考官方文档即可。 504 | 505 | #### 结语 506 | 507 | 以上为`微信公众平台-卡券功能`相关的解释和源码,小伙伴们一定要注意看看官方文档哦,具体的源码可以看作者的github,里面对每个方法有详细的注释。 508 | 509 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 510 | 511 | 预告:下一篇文章会讲发放奖励的另一种方式 `公众平台-社交立减金活动`,敬请期待!!! 512 | 513 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 514 | 515 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 516 | 517 | 到此本文就结束了,关注公众号查看更多推送!!! 518 | 519 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 520 | 521 | -------------------------------------------------------------------------------- /pay-to-people/transfers.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十二篇,主要讲解在商户存在的提现、商户付款到微信用户零钱或者银行卡需求。 3 | --- 4 | 5 | # \(余额提现\)企业付款到微信用户零钱或银行卡账户 6 | 7 | 浅析微信支付系列已经更新十二篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:支付验收示例和验收指引](https://mp.weixin.qq.com/s/YESU2V5byxfM8z9YQXgnuA) 10 | 11 | [浅析微信支付:如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 12 | 13 | [浅析微信支付:下载对账单和资金账单](https://mp.weixin.qq.com/s/XCR1Ts-uabuC573_vLb3Qg) 14 | 15 | [浅析微信支付:申请退款、退款回调接口、查询退款](https://mp.weixin.qq.com/s/IyWjWB__-VsqKO8SL0DL3Q) 16 | 17 | 如果你是做电商或者某些有福利返利的系统,基本上会遇到诸如 `余额提现` 这类需求,主要就是平台向用户返利现金,积累到某一个门槛,可以领取到自己的余额账号、银行卡;或者是使用为用户发送现金红包的方式。 18 | 19 | 接下来的两篇文章,会为大家描述在微信支付中,像用户付款的以上三种方式。 20 | 21 | 以下为三种付款方式的必要条件: 22 | 23 | 1. 商户号(或同主体其他非服务商商户号)已入驻90日 24 | 25 | 2. 商户号(或同主体其他非服务商商户号)有30天连续正常交易 26 | 27 | 3. 登录微信支付商户平台-产品中心,开通企业付款。 28 | 29 | #### 企业付款到微信用户零钱 30 | 31 | 企业付款提供由商户直接付钱至用户微信零钱的能力,支持平台操作及接口调用两种方式,资金到账速度快,使用及查询方便。主要用来解决合理的商户对用户付款需求,比如:保险理赔、彩票兑换等等。 32 | 33 | 如何开通? 34 | 35 | 1. 入驻成为商户:在线提交营业执照、身份证、银行账户等基本信息,快速提交申请; 36 | 37 | 2. 超级管理员开通:前往商户平台-产品中心-企业付款到零钱-申请开通; 38 | 39 | 3. **特殊要求:交易资金是即时入账到商户号基本户的商户,需要满足以下要求:需入驻满90天,连续交易30天。** 40 | 41 | 所需资料:开通企业付款到零钱功能无需提供额外的材料。 费用:试用期间免费使用。 42 | 43 | **应用场景** 44 | 45 | 企业付款为企业提供付款至用户零钱的能力,支持通过API接口付款,或通过微信支付商户平台(pay.weixin.qq.com)网页操作付款。 46 | 47 | 以下为官方的解释: 48 | 49 | ```text 50 | https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1 51 | ``` 52 | 53 | 抓重点,首先需要知道的是,开通了`运营账户`的商户,付款时会从运营账户余额中扣除,这个一定要注意,以免金额不足时付款失败(可以使用主账户为运营账户充值,参考\[交易中心\]-\[充值/转入\])。 54 | 55 | 以下为特别需要注意的地方,为大家标记出来,设计系统时一定要参考一下,以免入坑。 56 | 57 | ![企业付款到余额-1](../.gitbook/assets/qi-ye-fu-kuan-dao-yue1.png) 58 | 59 | **接口链接** 60 | 61 | ```text 62 | https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers 63 | ``` 64 | 65 | **是否需要证书** 66 | 67 | 请求需要双向证书。 68 | 69 | **调用接口** 70 | 71 | 注意事项: 72 | 73 | ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 74 | 75 | ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 76 | 77 | ◆ 因为错误代码字段err\_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 78 | 79 | ◆ 错误代码描述字段err\_code\_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 80 | 81 | PS:目前支持向指定微信用户的openid付款。 82 | 83 | 官方文档如下: 84 | 85 | ```text 86 | https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 87 | ``` 88 | 89 | 具体的传入参数,这里就不一一列举了,请大家参考一下官方文档,下面贴上具体的实现源码: 90 | 91 | ```text 92 | /** 93 | * [微信支付提现接口] - 保存调用的相关记录 94 | * @param payment 支付对象 95 | * @param wxPayConfig 微信支付单例对象 96 | * @return map 97 | * 98 | * @author yclimb 99 | * @date 2018/7/30 100 | */ 101 | public Map saveWxPayTransfers(Payment payment, WXPayConfig wxPayConfig) throws Exception { 102 | // 支付前验证 103 | 104 | // 微信支付对象 105 | // WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 106 | WXPay wxPay = new WXPay(wxPayConfig); 107 | 108 | // 微信退款接口 109 | Map resultMap = wxPay.transfers(...); 110 | logger.info("saveWxPayTransfers:resultMap:" + resultMap.toString()); 111 | 112 | // 下单失败,进行处理 113 | if (WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RETURN_CODE)) || WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RESULT_CODE))) { 114 | 115 | // 处理结果返回,无需继续执行 116 | 117 | // 余额不足提醒 118 | if (WXPayCodeEnum.ERR_CODE_NOTENOUGH.getCode().equals(resultMap.get(WXPayConstants.ERR_CODE))) { 119 | // 发送余额不足的消息提醒 120 | 121 | } 122 | } 123 | 124 | // 付款记录修改 & 记录付款日志 125 | 126 | return resultMap; 127 | } 128 | ``` 129 | 130 | 以上为调用的应用方法,下面为大家贴出微信接口调用代码 `imall.weixin.sdk.WXPay`: 131 | 132 | ```text 133 | /** 134 | * 作用:企业向微信用户个人付款
135 | * 场景:企业付款为企业提供付款至用户零钱的能力,支持通过API接口付款,或通过微信支付商户平台(pay.weixin.qq.com)网页操作付款。 136 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 137 | * 138 | * @param partner_trade_no 商户订单号 139 | * @param openid 用户openid 140 | * @param amount 企业付款金额 141 | * @param desc 企业付款描述信息 142 | * @param spbill_create_ip 该IP可传用户端或者服务端的IP 143 | * @return API返回数据 144 | * @throws Exception e 145 | */ 146 | public Map transfers(String partner_trade_no, String openid, String amount, String desc, String spbill_create_ip) throws Exception { 147 | 148 | /** 构造请求参数数据 **/ 149 | Map data = new HashMap<>(); 150 | 151 | // 商户订单号 partner_trade_no 是 10000098201411111234567890 String 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号) 152 | data.put("partner_trade_no", partner_trade_no); 153 | // 用户openid openid 是 oxTWIuGaIt6gTKsQRLau2M0yL16E String 商户appid下,某用户的openid 154 | data.put("openid", openid); 155 | // 校验用户姓名选项 check_name 是 FORCE_CHECK String NO_CHECK:不校验真实姓名,FORCE_CHECK:强校验真实姓名 156 | data.put("check_name", "NO_CHECK"); 157 | // 金额 amount 是 10099 int 企业付款金额,单位为分 158 | data.put("amount", String.valueOf(new BigDecimal(amount).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); 159 | // 企业付款描述信息 desc 是 理赔 String 企业付款操作说明信息。必填。 160 | data.put("desc", desc); 161 | // Ip地址 spbill_create_ip 是 192.168.0.1 String(32) 该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP。 162 | data.put("spbill_create_ip", spbill_create_ip); 163 | 164 | /** 以下参数为非必填参数 **/ 165 | 166 | /*// 设备号 device_info 否 013467007045764 String(32) 微信支付分配的终端设备号 167 | data.put("device_info", "xxx"); 168 | // 收款用户姓名 re_user_name 可选 王小王 String 收款用户真实姓名。(如果check_name设置为FORCE_CHECK,则必填用户真实姓名) 169 | data.put("re_user_name", "xxx");*/ 170 | 171 | // 微信调用接口 172 | Map resultMap = this.transfers(data); 173 | 174 | WXPayUtil.getLogger().info("wxPay.transfers:" + resultMap); 175 | 176 | return resultMap; 177 | } 178 | ``` 179 | 180 | PS:推荐数据库中对于金额存储为数值单位,以分为单位来存储,1.1元可以储存为101,这样和微信对应,会方便很多。 181 | 182 | 对于企业付款查询的接口,这里就不详细描述了,以下为具体的官方文档链接: 183 | 184 | ```text 185 | https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3 186 | ``` 187 | 188 | 需要的朋友,根据文档进行接口查询即可,非高频接口。 189 | 190 | #### 企业付款到银行卡 191 | 192 | 企业付款到银行卡提供由商户直接付钱至指定银行卡账户的能力,支持平台操作及接口调用两种方式,资金到账速度快,使用及查询方便。主要用来解决合理的商户对用户付款需求,比如:保险理赔、彩票兑换等等。 193 | 194 | 开通流程: 195 | 196 | 1. 入驻成为商户:在线提交营业执照、身份证、银行账户等基本信息,快速提交申请; 197 | 198 | 2. 超级管理员开通:前往商户平台-产品中心-企业付款到银行卡-申请开通; 199 | 200 | 3. 特殊要求:交易资金是即时入账到商户号基本户的商户,需要满足以下要求:需入驻满90天,连续交易30天。 201 | 202 | **所需资料:开通企业付款到银行卡功能无需提供额外的材料。 费用:此功能需收取手续费,按照单笔金额收取,每笔收取0.1%,最低1元,最高25元。** 203 | 204 | **应用场景** 205 | 206 | 微信支付已上线企业付款至银行卡功能。商户可以将商户号余额付款至指定的收款银行账户。通过指定收款银行账户户名、卡号,以及收款银行信息即可实现付款。 207 | 208 | 官方文档地址: 209 | 210 | ```text 211 | https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_1&index=1 212 | ``` 213 | 214 | 功能说明: 215 | 216 | 1. 企业付款至银行卡只支持新资金流类型账户 217 | 218 | 2. 目前企业付款到银行卡支持17家银行,更多银行逐步开放中 219 | 220 | 3. 付款到账实效为1-3日,最快次日到账 221 | 222 | 4. 每笔按付款金额收取手续费,按金额0.1%收取,最低1元,最高25元,如果商户开通了运营账户,手续费和付款的金额都从运营账户出。如果没有开通,则都从基本户出。 223 | 224 | 5. 每个商户号每天可以出款100万,单商户给同一银行卡付款每天限额5万 225 | 226 | 6. 发票:在账户中心-发票信息页面申请开票的商户会按月收到发票(已申请的无需重复申请)。 企业付款到银行卡发票与交易手续费发票为拆分单独开具。 227 | 228 | 需要注意的是,微信支持的银行有限,具体的支持银行见如下链接: 229 | 230 | ```text 231 | https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4&index=5 232 | ``` 233 | 234 | 所以肯定会出现不支持的银行,小伙伴们在开发的时候,可以在前后端控制用户选择提现银行来解决。 235 | 236 | 平台上手动付款流程: 237 | 238 | 1. 在产品中心,开通企业付款到个人银行卡功能 239 | 240 | 2. 进入交易中心-企业付款到银行卡页面进行付款 241 | 242 | 3. 指定收款银行账号、户名、收款方开户行,及付款金额信息,即可实现付款 243 | 244 | **接口链接** 245 | 246 | ```text 247 | https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank 248 | ``` 249 | 250 | **是否需要证书** 251 | 252 | 请求需要双向证书。 253 | 254 | **调用接口** 255 | 256 | 接口介绍: 用于企业向微信用户银行卡付款 目前支持接口API的方式向指定微信用户的银行卡付款。 257 | 258 | 接口调用规则: 259 | 260 | ◆ 单商户日限额——单日100w 261 | 262 | ◆ 单次限额——单次5w 263 | 264 | ◆ 单商户给同一银行卡单日限额——单日5w 265 | 266 | 注意:重点来了,首先,收款方银行卡号`enc_bank_no`、收款方用户名`enc_true_name` 这两个入参是需要 `采用标准RSA算法,公钥由微信侧提供` 得到的,所以还需要先拿到这个密钥,下面是官方文档地址: 267 | 268 | ```text 269 | https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_7&index=4 270 | ``` 271 | 272 | 以上文档详细介绍了如何得到具体的密钥方式,如果有看不明白的小伙伴,可以直接百度 `获取RSA加密公钥API`,可以得到很多示例,这里我就不讲了。 273 | 274 | 除入参和`企业付款到微信用户零钱`有所不一致之外,其他方面都差不多,小伙伴们可以参考上面付款到零钱的接口来实现付款到银行卡接口。 275 | 276 | #### 结语 277 | 278 | 以上为`微信余额提现`相关的解释和源码,小伙伴们一定要注意看看官方文档哦,具体的源码可以看作者的github,里面对每个方法有详细的注释。 279 | 280 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 281 | 282 | 预告:下一篇文章会讲发放奖励的另一种方式 `商户平台-现金红包`,敬请期待!!! 283 | 284 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 285 | 286 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 287 | 288 | 到此本文就结束了,关注公众号查看更多推送!!! 289 | 290 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 291 | 292 | -------------------------------------------------------------------------------- /pay/authorize.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第四篇,主要讲解微信支付前如何获取获取网页授权及用户信息获取。 3 | --- 4 | 5 | # 微信公众号网页授权 6 | 7 | 浅析微信支付系列已经更新三篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:开发前的准备](https://mp.weixin.qq.com/s/mkxukWU4aMw92NxnL91dng) 10 | 11 | [浅析微信支付:前篇大纲](https://mp.weixin.qq.com/s/pH0GGwUANJxJt-YHs6y_pQ) 12 | 13 | [浅析微信支付:微信支付简单介绍(小程序、公众号、App、H5)](https://mp.weixin.qq.com/s/h9Mx4IBik0MLKSj4c_Qolg) 14 | 15 | #### 1、开发前的准备 16 | 17 | 首先,如果没有看过本系列 `浅析微信支付:开发前的准备` 的朋友需要看一下这篇文章,链接在上方;本文需要用到开发前准备中的几个知识点:设置安全、设置白名单、得到公众号一系列信息(appid\apiKey等);如果需要在本地开发测试,还需要下载 `微信开发者工具`和 `微信公众平台接口测试帐号` 。 18 | 19 | 下面我们开始进入开发阶段。 20 | 21 | #### 2、设置测试号相关信息 22 | 23 | 因为项目在开发阶段的时候,需要本地调试,所以需要使用测试号来验证代码是否正确,所以下面会讲如何设置测试号相关配置; 24 | 25 | 首先我们进入 [微信公众平台接口测试帐号申请](https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login) 页面,点击登录即可使用微信账号登录(一个微信号只有一个测试号)。 26 | 27 | 登录后可以进入测试号管理界面,如下: 28 | 29 | 30 | 31 | ![微信测试号-1](../.gitbook/assets/wei-xin-ce-shi-hao-1.png) 32 | 33 | ![微信测试号-2](../.gitbook/assets/wei-xin-ce-shi-hao-2.png) 34 | 35 | 36 | 37 | ![微信测试号-3](../.gitbook/assets/wei-xin-ce-shi-hao-3.png) 38 | 39 | 40 | 41 | ![微信测试号-4](../.gitbook/assets/wei-xin-ce-shi-hao-4.png) 42 | 43 | 按要求设置上方图片中的配置,即可减少80%的问题,如果调用时还有其他问题,请到文末添加作为微信,可进入讨论群和大家一起交流。 44 | 45 | #### 3、获取微信网页授权 46 | 47 | 使用以下代码获取微信网页授权: 48 | 49 | 微信官方js文件: 50 | 51 | ```text 52 | 53 | ``` 54 | 55 | 封装的微信工具文件`weixin_util.js`: 56 | 57 | ```text 58 | 96 | ``` 97 | 98 | 调用微信config方法并获取网页授权: 99 | 100 | ```text 101 | 110 | ``` 111 | 112 | 如果访问页面路径,得到以下显示,就已经成功获取网页授权,其他均为失败: ![微信网页授权-1](https://img-blog.csdnimg.cn/20181105163522882.png) 113 | 114 | 如果出现其他错误,需要检查一下第一步时设置的路径安全接口、域名是否正确,开启weixinConfig中的debug模式,看是否为没有正确设置功能模块;如果还不能解决,请到文末看楼主上一篇文章或者添加作者进群即可。 115 | 116 | #### 4、获取微信用户信息 117 | 118 | 这里说一下上面获取用户授权的具体参数: 119 | 120 | 第一步,获取config需要的基础参数: 121 | 122 | ```text 123 | /** 124 | * 根据appid获取wx.config需要的基础参数 125 | * @param reqMap requestUrl 请求页面地址、appid appid 126 | * @return json 127 | * 128 | * @author yclimb 129 | * @date 2018/9/25 130 | */ 131 | @ApiOperation(value = "微信公众号|config需要的基础参数", httpMethod = "POST", notes = "config需要的基础参数") 132 | @PostMapping("/getSignature") 133 | public AppMessage getSignature(@RequestBody Map reqMap) { 134 | Map map = Maps.newHashMap(); 135 | switch (reqMap.get("appid")) { 136 | case WXPayConstants.APP_ID: 137 | map = wxUtils.getSignature(reqMap.get("requestUrl"), reqMap.get("appid"), BaseConstants.WX_MINI_PROGRAM_YUEDIAN_CODE); 138 | break; 139 | case WXPayConstants.APP_ID_CHUNBO: 140 | map = wxUtils.getSignature(reqMap.get("requestUrl"), reqMap.get("appid"), BaseConstants.WX_CHUNBO_JSAPI_YUEDIAN_CODE); 141 | break; 142 | } 143 | return AppMessage.success(map); 144 | } 145 | ``` 146 | 147 | 第二部分:获取用户授权的基础信息: 148 | 149 | ```text 150 | // 这里的appid就是咋们测试号的appid 151 | $appid="xxx"; 152 | 153 | // 这里的地址需要http://且必须encodeURI,此地址为获取用户信息后,微信自动转发的服务器端接口,用来接收微信的授权code,在后端处理而得到用户基本信息 154 | $redirect_uri=encodeURI("http://127.0.0.1:8888/weixin/auth/authorize.do"); 155 | 156 | // 微信官方的授权接口 157 | $url="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+$appid+"&redirect_uri="+$redirect_uri+"&response_type=code&scope=snsapi_userinfo&state=" + state + "#wechat_redirect"; 158 | 159 | // state可以用来区分特殊参数和配置 160 | ``` 161 | 162 | 具体可见微信官方文档,文档如下:[微信网页授权接口](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842) 163 | 164 | 以上为js页面处理,下面我们来具体讲解 redirect\_uri 这个接口中需要处理的逻辑; 165 | 166 | `WXAuthController`: 167 | 168 | ```text 169 | /** 170 | * 微信网页授权 171 | * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 172 | * 第一步:用户同意授权,获取code 173 | * 第二步:通过code换取网页授权access_token 174 | * @return str 175 | * 176 | * @author yclimb 177 | * @date 2018/7/30 178 | */ 179 | @ApiOperation(value = "微信用户|网页授权", httpMethod = "GET", notes = "获取前端微信用户的网页授权,得到用户基础信息") 180 | @GetMapping("/authorize") 181 | public AppMessage authorize(HttpServletRequest request) { 182 | 183 | // 跳转页面标识 184 | String state = request.getParameter("state"); 185 | // 通过code获取access_token 186 | String code = request.getParameter("code"); 187 | log.info("authorize:code:{}", code); 188 | 189 | String appid; 190 | String secret; 191 | 192 | // 根据不同的state得到不同的微信公众号网页授权 193 | switch (state) { 194 | case STATE_ASYD: 195 | appid = WXPayConstants.APP_ID_ASYD; 196 | secret = WXPayConstants.SECRET_ASYD; 197 | break; 198 | default: 199 | appid = WXPayConstants.APP_ID_CHUNBO; 200 | secret = WXPayConstants.SECRET_CHUNBO; 201 | break; 202 | } 203 | 204 | // 获取access_token和openid 205 | JSONObject jsonToken = wxUtils.getJsapiAccessTokenByCode(code, appid, secret); 206 | if (null == jsonToken) { 207 | return AppMessage.error(-2); 208 | } 209 | 210 | return AppMessage.success(jsonToken); 211 | } 212 | ``` 213 | 214 | `WXUtils`: 215 | 216 | ```text 217 | /** 218 | * 网页授权获取用户信息时用于获取access_token以及openid 219 | * 请求路径:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code(最后一个参数不变) 220 | * 221 | * @param code c 222 | * @return access_token json obj 223 | * @author yclimb 224 | * @date 2018/7/30 225 | */ 226 | public JSONObject getJsapiAccessTokenByCode(String code, String appid, String secret) { 227 | if (StringUtils.isBlank(code)) { 228 | return null; 229 | } 230 | try { 231 | // 获取access_token 232 | String access_token_json = restTemplate.getForObject(WeChatURL.OAUTH_ACCESS_TOKEN_URL, String.class, appid, secret, code); 233 | logger.info("getJsapiAccessTokenByCode:access_token_json:{}", access_token_json); 234 | if (StringUtils.isBlank(access_token_json)) { 235 | return null; 236 | } 237 | JSONObject jsonObject = JSON.parseObject(access_token_json); 238 | if (StringUtils.isBlank(jsonObject.getString("access_token"))) { 239 | return null; 240 | } 241 | return jsonObject; 242 | } catch (Exception e) { 243 | logger.error(e.getMessage(), e); 244 | } 245 | return null; 246 | } 247 | ``` 248 | 249 | 使用以上方法就可以得到微信用户的基础咯~,上面是作者已经封装好的sdk方法,具体的源码请见文末源码地址。 250 | 251 | 如果用户已经授权,第二次进入网页,这时候不需要再次授权,作者也提供了另一种获取用户信息的方法,通过access\_token和openid请求获取用户信息,代码如下: 252 | 253 | ```text 254 | /** 255 | * 通过access_token和openid请求获取用户信息 256 | * @return str 257 | * 258 | * @author yclimb 259 | * @date 2018/9/17 260 | */ 261 | @ApiOperation(value = "微信用户|通过access_token和openid请求获取用户信息", httpMethod = "POST", notes = "通过access_token和openid请求获取用户信息") 262 | @PostMapping("/getXxxUser/{access_token}/{openid}") 263 | public AppMessage getXxxUser(@PathVariable String access_token, @PathVariable String openid) { 264 | 265 | // 通过access_token和openid请求获取用户信息 266 | JSONObject jsonUserinfo = wxUtils.getJsapiUserinfo(access_token, openid); 267 | if (null == jsonUserinfo) { 268 | return AppMessage.error(-2); 269 | } 270 | 271 | // 判断用户是否在悦店系统中是一个用户 272 | String unionid = jsonUserinfo.getString("unionid"); 273 | if (StringUtils.isBlank(unionid)) { 274 | return AppMessage.error(61008); 275 | } 276 | 277 | // 存储用户信息到数据库 278 | 279 | // 用户名称解码 280 | user.setNickName(UserNickUtil.decodeNickName(user.getNickName())); 281 | 282 | return AppMessage.success(user); 283 | } 284 | ``` 285 | 286 | #### 结语 287 | 288 | 根据以上步骤实现,就可以完成微信授权-用户信息获取等操作,如果有问题,欢迎小伙伴随时交流~ 289 | 290 | 预告:下一篇文章,作者将讲 `统一下单接口`,敬请期待!!! 291 | 292 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: `https://github.com/YClimb/wxpay-sdk/blob/master/README.md` 293 | 294 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 295 | 296 | 到此本文就结束了,关注公众号查看更多推送!!! 297 | 298 | 299 | 300 | ![关注我的微信公众号](../.gitbook/assets/er-wei-ma.jpg) 301 | 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /pay/orderquery.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第七篇,主要讲解微信商户平台的订单查询和关闭接口的使用。 3 | --- 4 | 5 | # 查询订单和关闭订单 6 | 7 | 浅析微信支付系列已经更新六篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:支付结果通知](https://mp.weixin.qq.com/s/Zr3ldgsMIg_cBrtuS2c7_g) 10 | 11 | [浅析微信支付:统一下单接口](https://mp.weixin.qq.com/s/lYPiad1pyZ6ZdqH-sg66eg) 12 | 13 | [浅析微信支付:微信公众号网页授权](https://mp.weixin.qq.com/s/BQPn_r5pcwZO3tzqnoTiKg) 14 | 15 | 声明:这里的`查询订单`、`关闭订单`接口仅适用于 `小程序支付、公共号支付、扫码支付、APP支付`,`刷卡支付`方式此处并不适用。 16 | 17 | #### 1、查询订单 18 | 19 | 以下为微信官方的`查询订单`文档: 20 | 21 | ```text 22 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 23 | ``` 24 | 25 | **1.1. 应用场景** 26 | 27 | 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。 28 | 29 | ```text 30 | 需要调用查询接口的情况: 31 | ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知; 32 | ◆ 调用支付接口后,返回系统错误或未知交易状态情况; 33 | ◆ 调用刷卡支付API,返回USERPAYING的状态; 34 | ◆ 调用关单或撤销接口API之前,需确认支付状态; 35 | ``` 36 | 37 | **1.2. 接口链接** 38 | 39 | ```text 40 | https://api.mch.weixin.qq.com/pay/orderquery 41 | ``` 42 | 43 | **1.3. 是否需要证书** 44 | 45 | 不需要 46 | 47 | **1.4. 调用接口** 48 | 49 | 查询订单接口需要使用`微信订单号`或者`商户订单号`来查询,其他参数为商户平台信息的公共参数,为常量,此处省略解释。 50 | 51 | ```text 52 | 微信订单号:transaction_id(微信的订单号,建议优先使用) 53 | 商户订单号:out_trade_no(商户系统内部订单号) 54 | ``` 55 | 56 | 此两个参数必填其中之一,微信推荐使用`微信订单号`来查询,下面为实现代码: 57 | 58 | ```text 59 | private void doOrderQuery() { 60 | System.out.println("查询订单"); 61 | HashMap data = new HashMap(); 62 | // data.put("out_trade_no", out_trade_no); 63 | data.put("transaction_id", "4008852001201608221962061594"); 64 | try { 65 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 66 | Map r = wxPay.orderQuery(data); 67 | System.out.println(r); 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | ``` 73 | 74 | `wxPay.orderQuery`方法为封装的sdk方法,具体实现请参考作者github源码。 75 | 76 | 对于商户关键信息的写入,公共方法为`wxPay.fillRequestData`,实现如下: 77 | 78 | ```text 79 | /** 80 | * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign
81 | * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口 82 | * 83 | * @param reqData r 84 | * @return map 85 | * @throws Exception e 86 | */ 87 | public Map fillRequestData(Map reqData) throws Exception { 88 | reqData.put("appid", config.getAppID()); 89 | reqData.put("mch_id", config.getMchID()); 90 | reqData.put("nonce_str", WXPayUtil.generateNonceStr()); 91 | if (SignType.MD5.equals(this.signType)) { 92 | reqData.put("sign_type", WXPayConstants.MD5); 93 | } else if (SignType.HMACSHA256.equals(this.signType)) { 94 | reqData.put("sign_type", WXPayConstants.HMACSHA256); 95 | } 96 | reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType)); 97 | return reqData; 98 | } 99 | ``` 100 | 101 | 以上为查询微信订单的使用方式,具体的返回参数请参考官方文档。 102 | 103 | #### 2、关闭订单 104 | 105 | 以下为微信官方的`关闭订单`文档: 106 | 107 | ```text 108 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3 109 | ``` 110 | 111 | **2.1. 应用场景** 112 | 113 | 以下情况需要调用关单接口: 114 | 115 | ```text 116 | 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付; 117 | 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。 118 | ``` 119 | 120 | 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。 121 | 122 | **2.2. 接口链接** 123 | 124 | ```text 125 | https://api.mch.weixin.qq.com/pay/closeorder 126 | ``` 127 | 128 | **2.3. 是否需要证书** 129 | 130 | 不需要 131 | 132 | **2.4. 调用接口** 133 | 134 | 关闭订单接口需要使用`商户订单号`来查询,其他参数为商户平台信息的公共参数,为常量,此处省略解释。 135 | 136 | ```text 137 | 商户订单号:out_trade_no(商户系统内部订单号) 138 | ``` 139 | 140 | PS:关单接口只能使用`微信订单号`来查询,和查询接口不同,下面为实现代码: 141 | 142 | ```text 143 | private void doOrderClose() { 144 | System.out.println("关闭订单"); 145 | HashMap data = new HashMap(); 146 | data.put("out_trade_no", out_trade_no); 147 | try { 148 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 149 | Map r = wxPay.closeOrder(data); 150 | System.out.println(r); 151 | } catch (Exception e) { 152 | e.printStackTrace(); 153 | } 154 | } 155 | ``` 156 | 157 | 关单接口的公共参数设置和查询订单一致,这里就不重复解释了,具体的返回参数请参考微信官方文档。 158 | 159 | PS:关单接口可能会调用失败,已支付、已关闭等场景,所以需要开发者注意官方文档中的错误码,对异常情况进行处理。 160 | 161 | #### 结语 162 | 163 | 以上为`查询订单`、`关闭订单`的调用方式,如果是`刷卡支付`方式,他的关闭订单接口为`撤销订单:reverse`,在作者sdk源码中也有具体的实现方式。 164 | 165 | 预告:下一篇文章 `申请退款和退款回调接口`,敬请期待!!! 166 | 167 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: 168 | 169 | ```text 170 | ​https://github.com/YClimb/wxpay-sdk/blob/master/README.md 171 | ``` 172 | 173 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 174 | 175 | 到此本文就结束了,关注公众号查看更多推送!!! 176 | 177 | ![](../.gitbook/assets/er-wei-ma.jpg) 178 | 179 | -------------------------------------------------------------------------------- /pay/pay.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第五篇,主要讲解如何调用统一下单接口生成预支付单及调起支付页面。 3 | --- 4 | 5 | # 统一下单接口 6 | 7 | 浅析微信支付系列已经更新四篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:微信公众号网页授权](https://mp.weixin.qq.com/s/BQPn_r5pcwZO3tzqnoTiKg) 10 | 11 | [浅析微信支付:开发前的准备](https://mp.weixin.qq.com/s/mkxukWU4aMw92NxnL91dng) 12 | 13 | 上面是本文的前置文章,有前面几篇文章的基础以后看会更加明了,如果已经看过的小伙伴可以忽略。 14 | 15 | #### 1、什么是\[统一下单接口\]? 16 | 17 | 首先我们要明白这个问题,需要先行看一下微信的官方文档: [https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9\_1](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) 18 | 19 | 官方解释如下: 20 | 21 | ```text 22 | 除被扫支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按扫码、JSAPI、APP等不同场景生成交易串调起支付。 23 | ``` 24 | 25 | 什么意思?简单理解:就是说我们要在调起微信支付窗口之前,需要先生成一个 `预支付交易单`,这个单子相当于和我们自身系统的 `支付交易单` 一一对应,也就是我们每次支付需要记录的订单支付交易单。 26 | 27 | 从上面我们可以得到,在调用此接口之前,首先,我们系统中肯定已经需要有以下步骤:订单提交 -> 生成订单 -> 生成订单对应的支付单 -> 调用统一下单接口 28 | 29 | 好了,假设系统现在已经生成支付交易单,准备调用统一下单接口,我们来看一下具体的实现方式。 30 | 31 | PS:调用统一下单接口时,需要注意的是必须传入异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。示例如下: 32 | 33 | `https://xxx.com/v1/weixin/pay/wxnotify` 34 | 35 | #### 2、调用接口 36 | 37 | 示例代码: 38 | 39 | ```text 40 | /** 41 | * [微信支付统一下单] - 保存调用的相关记录

42 | * [微信支付小程序] - 返回支付唤醒参数 43 | * @param payment 付款对象 44 | * @param user 当前用户 45 | * @return map 46 | * @throws Exception e 47 | * 48 | * @author yclimb 49 | * @date 2018/6/15 50 | */ 51 | public Map saveWxPayUnifiedOrder(Payment payment, User user) throws Exception { 52 | if (payment == null || user == null) { 53 | return null; 54 | } 55 | 56 | // 1.调用微信统一下单接口 57 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 58 | Map resultMap = wxPay.unifiedOrder(...); 59 | 60 | // 1.1.记录付款流水 61 | ... 62 | 63 | // 下单失败,进行处理 64 | if (WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RETURN_CODE)) || 65 | WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RESULT_CODE))) { 66 | 67 | // 处理结果返回,无需继续执行 68 | resultMap.put(WXPayConstants.RESULT_CODE, WXPayConstants.FAIL); 69 | resultMap.put(WXPayConstants.ERR_CODE_DES, resultMap.get(WXPayConstants.RETURN_MSG)); 70 | return resultMap; 71 | } 72 | 73 | // 1.2.获取prepay_id、nonce_str 74 | String prepay_id = resultMap.get("prepay_id"); 75 | String nonce_str = resultMap.get("nonce_str"); 76 | 77 | // 2.根据微信统一下单接口返回数据组装微信支付参数,返回结果 78 | return wxPay.chooseWXPayMap(prepay_id, nonce_str); 79 | } 80 | ``` 81 | 82 | 以上为一个调用统一下单的接口代码,主要在于以下两句: 83 | 84 | ```text 85 | // 实例化一个微信支付对象,使用单例配置的方式 86 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 87 | 88 | // 直接调用统一下单接口 89 | Map resultMap = wxPay.unifiedOrder(...); 90 | ``` 91 | 92 | 微信支付对象`WXPay`统一下单接口: 93 | 94 | ```text 95 | /** 96 | * 作用:统一下单
97 | * 场景:商户在小程序中先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易后调起支付。 98 | * 接口链接:URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder 99 | * 是否需要证书:否 100 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 101 | * 102 | * @param notify_url 公众号用户openid 103 | * @param body 商品简单描述,该字段请按照规范传递,例:腾讯充值中心-QQ会员充值 104 | * @param out_trade_no 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一 105 | * @param total_fee 订单总金额,传入参数单位为:元 106 | * @param spbill_create_ip APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP 107 | * @param goods_tag 订单优惠标记,用于区分订单是否可以享受优惠 108 | * @param detail 商品详情 ,单品优惠活动该字段必传 109 | * @param timeStart 订单生成时间,格式为yyyyMMddHHmmss 110 | * @param timeExpire 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010 111 | * @return API返回数据 112 | * @throws Exception e 113 | */ 114 | public Map unifiedOrder(String notify_url, String openid, String body, String out_trade_no, String total_fee, String spbill_create_ip, String goods_tag, String detail, Date timeStart, Date timeExpire) throws Exception { 115 | 116 | /** 构造请求参数数据 **/ 117 | Map data = new HashMap<>(); 118 | 119 | // 字段名 变量名 必填 类型 示例值 描述 120 | // 标价币种 fee_type 否 String(16) CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 121 | data.put("fee_type", WXPayConstants.FEE_TYPE_CNY); 122 | // 通知地址 notify_url 是 String(256) http://www.weixin.qq.com/wxpay/pay.php 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 123 | data.put("notify_url", notify_url); 124 | // 交易类型 trade_type 是 String(16) JSAPI 小程序取值如下:JSAPI,详细说明见参数规定 125 | data.put("trade_type", WXPayConstants.TRADE_TYPE); 126 | // 用户标识 openid 否 String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。 127 | data.put("openid", openid); 128 | // 商品描述 body 是 String(128) 腾讯充值中心-QQ会员充值 商品简单描述,该字段请按照规范传递,具体请见参数规定 129 | data.put("body", body); 130 | // 商户订单号 out_trade_no 是 String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号 131 | data.put("out_trade_no", out_trade_no); 132 | // 标价金额 total_fee 是 Int 88 订单总金额,单位为分,详见支付金额 133 | // 默认单位为分,系统是元,所以需要*100 134 | data.put("total_fee", String.valueOf(new BigDecimal(total_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); 135 | // 终端IP spbill_create_ip 是 String(16) 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 136 | data.put("spbill_create_ip", spbill_create_ip); 137 | 138 | /** 以下参数为非必填参数 **/ 139 | // 订单优惠标记 goods_tag 否 String(32) WXG 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 140 | if (StringUtils.isNotBlank(goods_tag)) { 141 | data.put("goods_tag", goods_tag); 142 | } 143 | // 商品详情 detail 否 String(6000) 商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传,详见“单品优惠参数说明” 144 | if (StringUtils.isNotBlank(detail)) { 145 | data.put("detail", detail); 146 | // 接口版本号 新增字段,接口版本号,区分原接口,默认填写1.0。入参新增version后,则支付通知接口也将返回单品优惠信息字段promotion_detail,请确保支付通知的签名验证能通过。 147 | data.put("version", "1.0"); 148 | } 149 | // 设备号 device_info 否 String(32) 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" 150 | data.put("device_info", "WEB"); 151 | 152 | // 交易起始时间 time_start 否 String(14) 20091225091010 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 153 | data.put("time_start", DateTimeUtil.getTimeShortString(timeStart)); 154 | // 交易结束时间 time_expire 否 String(14) 20091227091010 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。 155 | // 订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则,建议:最短失效时间间隔大于1分钟 156 | data.put("time_expire", DateTimeUtil.getTimeShortString(timeExpire)); 157 | /*// 商品ID product_id 否 String(32) 12235413214070356458058 trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 158 | data.put("product_id", null); 159 | // 指定支付方式 limit_pay 否 String(32) no_credit 上传此参数no_credit--可限制用户不能使用信用卡支付 160 | data.put("limit_pay", null); 161 | // 附加数据 attach 否 String(127) 深圳分店 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 162 | data.put("attach", null);*/ 163 | 164 | /** 以下五个参数,在 this.fillRequestData 方法中会自动赋值 **/ 165 | /*// 小程序ID appid 是 String(32) wxd678efh567hg6787 微信分配的小程序ID 166 | data.put("appid", WXPayConstants.APP_ID); 167 | // 商户号 mch_id 是 String(32) 1230000109 微信支付分配的商户号 168 | data.put("mch_id", WXPayConstants.MCH_ID); 169 | // 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法 170 | data.put("nonce_str", nonce_str); 171 | // 签名类型 sign_type 否 String(32) MD5 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 172 | data.put("sign_type", WXPayConstants.MD5); 173 | // 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法 174 | data.put("sign", sign);*/ 175 | 176 | // 微信统一下单接口请求地址 177 | Map resultMap = this.unifiedOrder(data); 178 | 179 | WXPayUtil.getLogger().info("wxPay.unifiedOrder:" + resultMap); 180 | 181 | return resultMap; 182 | } 183 | ``` 184 | 185 | 以上代码详细说明了每个字段的含义,具体的代码可以看一下作者的github,文末有对应的地址。 186 | 187 | 下面说一个特殊情况,在我们支付的时候,有时候用户会取消支付,等一段时间再重新调起,这时候,需要用到另外一个方法,那就是二次支付,所以,在我们数据库中,必须保存两个字段,用于二次支付时使用:预支付ID`prepay_id`、随机字符串`nonce_str`,此两个参数可以生成微信支付调起时需要的验证签名。 188 | 189 | 以下为二次调用时的代码: 190 | 191 | ```text 192 | /** 193 | * 根据付款单号查询微信付款参数 194 | * @param relationId 交易编号 195 | * @param type 支付类型 196 | * @return payment 197 | * 198 | * @author yclimb 199 | * @date 2018/6/28 200 | */ 201 | public Map queryChooseWXPayMapByRelationId(Integer relationId, String type) throws Exception { 202 | 203 | // 微信支付对象 204 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 205 | 206 | // 1.查询付款对象 207 | Payment payment = this.queryPaymentByRelationId(relationId, type); 208 | 209 | // 2.根据微信统一下单接口返回数据组装微信支付参数,返回结果 210 | return wxPay.chooseWXPayMap(payment.getPrepayId(), payment.getNonceStr()); 211 | } 212 | ``` 213 | 214 | 此时我们已经调用微信统一下单接口成功,并为我们返回了需要的参数,下一步需要组装为微信支付调起时前端需要的参数。 215 | 216 | #### 生成支付签名 217 | 218 | 下面为组装支付签名的代码: 219 | 220 | ```text 221 | /** 222 | * 作用:生成微信支付所需参数,微信支付二次签名
223 | * 场景:根据微信统一下单接口返回的 prepay_id 生成微信支付所需的参数 224 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 225 | * 226 | * @param prepay_id 预支付id 227 | * @param nonce_str 随机字符串 228 | * @return 支付方法调用所需参数map 229 | * @throws Exception e 230 | */ 231 | public Map chooseWXPayMap(String prepay_id, String nonce_str) throws Exception { 232 | 233 | // 支付方法调用所需参数map 234 | Map chooseWXPayMap = new HashMap<>(); 235 | chooseWXPayMap.put("appId", config.getAppID()); 236 | chooseWXPayMap.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp())); 237 | chooseWXPayMap.put("nonceStr", nonce_str); 238 | chooseWXPayMap.put("package", "prepay_id=" + prepay_id); 239 | chooseWXPayMap.put("signType", WXPayConstants.MD5); 240 | 241 | WXPayUtil.getLogger().info("wxPay.chooseWXPayMap:" + chooseWXPayMap.toString()); 242 | 243 | // 生成支付签名 244 | String paySign = WXPayUtil.generateSignature(chooseWXPayMap, config.getKey()); 245 | chooseWXPayMap.put("paySign", paySign); 246 | 247 | WXPayUtil.getLogger().info("wxPay.paySign:" + paySign); 248 | 249 | return chooseWXPayMap; 250 | } 251 | ``` 252 | 253 | 组装好需要的参数以后,就可以调起微信支付窗口了,如果是微信公众号支付,需要使用以下的方式调起微信支付: 254 | 255 | ```text 256 | function weixinConfig(appid, timestamp, noncestr, signature) { 257 | wx.config({ 258 | debug : false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 259 | // debug : true, 260 | appId :appid, // 必填,公众号的唯一标识 261 | timestamp : timestamp, // 必填,生成签名的时间戳 262 | nonceStr : noncestr, // 必填,生成签名的随机串 263 | signature : signature,// 必填,签名,见附录1 264 | jsApiList: [ 265 | 'checkJsApi', 266 | 'chooseWXPay' // 必须增加此参数,用户微信支付功能 267 | ] 268 | // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 269 | }); 270 | } 271 | 272 | wx.chooseWXPay({ 273 | appId: appId, 274 | timestamp: timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 275 | nonceStr: nonceStr, // 支付签名随机串,不长于 32 位 276 | package: package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*) 277 | signType: signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5' 278 | paySign: paySign, // 支付签名 279 | success: function (res) { 280 | // 支付成功后的回调函数 281 | alert('支付成功!'); 282 | }, 283 | cancel: function (res) { 284 | // 支付取消 285 | }, 286 | fail: function (res) { 287 | // 支付失败 288 | alert("支付失败!"); 289 | } 290 | }); 291 | ``` 292 | 293 | PS:小程序调用方法类似,参数一致。 294 | 295 | #### 结语 296 | 297 | 以上就是微信支付统一下单接口的调用方式了,具体的源码可以参考作者github,最好在开发之前先通读一遍微信官方文档,此时再使用作者源码开发事半功倍,更易理解。 298 | 299 | 预告:下一篇文章,作者将讲 `支付结果通知`,敬请期待!!! 300 | 301 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​`https://github.com/YClimb/wxpay-sdk/blob/master/README.md` 302 | 303 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 304 | 305 | 到此本文就结束了,关注公众号查看更多推送!!! 306 | 307 | ![关注我的微信公众号](../.gitbook/assets/er-wei-ma.jpg) 308 | 309 | -------------------------------------------------------------------------------- /pay/wxnotify.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第六篇,主要讲解支付成功后,微信回调商户支付结果通知的处理。 3 | --- 4 | 5 | # 支付结果通知 6 | 7 | 浅析微信支付系列已经更新五篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:统一下单接口](https://mp.weixin.qq.com/s/lYPiad1pyZ6ZdqH-sg66eg) 10 | 11 | [浅析微信支付:微信公众号网页授权](https://mp.weixin.qq.com/s/BQPn_r5pcwZO3tzqnoTiKg) 12 | 13 | [浅析微信支付:开发前的准备](https://mp.weixin.qq.com/s/mkxukWU4aMw92NxnL91dng) 14 | 15 | 前面一章已经讲了如何调用统一下单接口和调起微信支付窗口,在调用下单接口时,我们会传入 `异步接收微信支付结果通知的回调地址`,顾名思义这个地址作用就是用来接收支付结果通知,当用户在前端支付成功后,微信服务器会自动调用此地址,然后商户再进行处理。 16 | 17 | #### 1、支付结果通知 18 | 19 | 以下为接口官方解释: 20 | 21 | ```text 22 | 支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。 23 | 24 | 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) 25 | 26 | 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 27 | 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 28 | 29 | 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。 30 | 技术人员可登进微信商户后台扫描加入接口报警群。 31 | ``` 32 | 33 | 支付结果通知接口文档地址: 34 | 35 | ```text 36 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8 37 | ``` 38 | 39 | 需要注意的事项有以下几点: 40 | 41 | ```text 42 | 1. 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。 43 | 2. 通知url必须为直接可访问的url,不能携带参数,也就是必须使用外网接口地址,不能使用本地调试地址 44 | 3. 商户需要接收处理,并返回应答。如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 45 | 4. 通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒 46 | 5. 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 47 | 6. 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。 48 | ``` 49 | 50 | PS:推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 51 | 52 | 关于具体的签名和接收通知代码如下: 53 | 54 | ```text 55 | package imall.controller.wx; 56 | package ... 57 | 58 | /** 59 | * 微信支付Controller 60 | * 61 | * @author yclimb 62 | * @date 2018/6/15 63 | */ 64 | @Api 65 | @RestController 66 | @RequestMapping("/weixin/pay") 67 | public class WXPayController extends BaseController { 68 | 69 | // 需要注入的一些service 70 | 71 | /** 72 | * 返回成功xml 73 | */ 74 | private String resSuccessXml = ""; 75 | 76 | /** 77 | * 返回失败xml 78 | */ 79 | private String resFailXml = ""; 80 | 81 | /** 82 | * 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。 83 | * 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” 84 | *

85 | * 支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。 86 | * 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 87 | * (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) 88 | * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 89 | * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 90 | * 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。 91 | * 92 | * @author yclimb 93 | * @date 2018/6/15 94 | */ 95 | @ApiOperation(value = "微信支付|支付回调接口", httpMethod = "POST", notes = "该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。") 96 | @RequestMapping("/wxnotify") 97 | public void wxnotify(HttpServletRequest request, HttpServletResponse response) { 98 | 99 | String resXml = ""; 100 | InputStream inStream; 101 | try { 102 | 103 | inStream = request.getInputStream(); 104 | ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 105 | byte[] buffer = new byte[1024]; 106 | int len = 0; 107 | while ((len = inStream.read(buffer)) != -1) { 108 | outSteam.write(buffer, 0, len); 109 | } 110 | 111 | WXPayUtil.getLogger().info("wxnotify:微信支付----start----"); 112 | 113 | // 获取微信调用我们notify_url的返回信息 114 | String result = new String(outSteam.toByteArray(), "utf-8"); 115 | WXPayUtil.getLogger().info("wxnotify:微信支付----result----=" + result); 116 | 117 | // 关闭流 118 | outSteam.close(); 119 | inStream.close(); 120 | 121 | // xml转换为map 122 | Map resultMap = WXPayUtil.xmlToMap(result); 123 | boolean isSuccess = false; 124 | if (WXPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get(WXPayConstants.RESULT_CODE))) { 125 | 126 | WXPayUtil.getLogger().info("wxnotify:微信支付----返回成功"); 127 | 128 | if (WXPayUtil.isSignatureValid(resultMap, WXPayConstants.API_KEY)) { 129 | 130 | // 订单处理 操作 orderconroller 的回写操作? 131 | WXPayUtil.getLogger().info("wxnotify:微信支付----验证签名成功"); 132 | 133 | // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了. 134 | resXml = resSuccessXml; 135 | isSuccess = true; 136 | 137 | } else { 138 | WXPayUtil.getLogger().error("wxnotify:微信支付----判断签名错误"); 139 | } 140 | 141 | } else { 142 | WXPayUtil.getLogger().error("wxnotify:支付失败,错误信息:" + resultMap.get(WXPayConstants.ERR_CODE_DES)); 143 | resXml = resFailXml; 144 | } 145 | 146 | // 付款记录修改 & 记录付款日志 147 | 148 | // 回调方法,处理业务 - 修改订单状态 149 | WXPayUtil.getLogger().info("wxnotify:微信支付回调:修改的订单===>" + resultMap.get("out_trade_no")); 150 | int updateResult = ...; 151 | if (updateResult > 0) { 152 | WXPayUtil.getLogger().info("wxnotify:微信支付回调:修改订单支付状态成功"); 153 | } else { 154 | WXPayUtil.getLogger().error("wxnotify:微信支付回调:修改订单支付状态失败"); 155 | } 156 | 157 | } catch (Exception e) { 158 | WXPayUtil.getLogger().error("wxnotify:支付回调发布异常:", e); 159 | } finally { 160 | try { 161 | // 处理业务完毕 162 | BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); 163 | out.write(resXml.getBytes()); 164 | out.flush(); 165 | out.close(); 166 | } catch (IOException e) { 167 | WXPayUtil.getLogger().error("wxnotify:支付回调发布异常:out:", e); 168 | } 169 | } 170 | 171 | } 172 | 173 | } 174 | ``` 175 | 176 | 验证是否本商户返回的正确通知: 177 | 178 | ```text 179 | // xml转换为map 180 | Map resultMap = WXPayUtil.xmlToMap(result); 181 | 182 | // 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。 183 | WXPayUtil.isSignatureValid(resultMap, WXPayConstants.API_KEY); 184 | ``` 185 | 186 | 从以上代码可以得知,微信是以流的方式来调用支付结果通知,流中数据格式为xml,所以需要先对流进行解析,然后再将xml转换为map,上面方法已经实现这个过程,有现成的代码可供参考,无需再重新编写代码。 187 | 188 | 需要注意的是,不论成功或者失败,必须向微信返回对应的返回值,如上方代码中的`resSuccessXml`、`resFailXml`,否则会引起重复调用的问题,在代码中我们应该规避风险。 189 | 190 | #### 2、对于支付单的处理 191 | 192 | 在收到支付结果后,我们会对系统中的支付单进行状态修改等操作,此时需要注意,如果微信返回失败,接口直接返回失败即可; 193 | 194 | 如果微信通知付款成功,返回时有一个 `out_trade_no` 参数,此参数为调用 `统一下单接口` 时传入微信的支付单号,可根据此参数取得对应的支付单,然后进行修改操作,若操作时出现异常,一定要向微信返回错误的xml代码,用微信的重试机制来二次回调,否则就需要记录通知信息,在本系统自动重试来解决。 195 | 196 | 在处理微信验证数据时,还需要注意微信最终返回的结果中是否使用了代金券,如果使用了代金券,还需要另行处理,此处先不详细描述,后面章节会详细解释代金券的操作。 197 | 198 | #### 结语 199 | 200 | 以上为 `微信支付结果通知接口` 的接收方式,它是一个微信服务自身控制的异步调用方法,在自身商户系统中需要处理很多异常,如网络抖动和服务异常等问题,所以尽量在商户系统中保存微信结果通知数据和增加失败重试机制,保证数据的正确性。 201 | 202 | 预告:下一篇文章 `查询订单和关闭订单`,敬请期待!!! 203 | 204 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: `https://github.com/YClimb/wxpay-sdk/blob/master/README.md` 205 | 206 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 207 | 208 | 到此本文就结束了,关注公众号查看更多推送!!! 209 | 210 | ![关注我的微信公众号](../.gitbook/assets/er-wei-ma.jpg) 211 | 212 | -------------------------------------------------------------------------------- /redpack/hong-bao.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十三篇,主要讲解在如何开通商户平台的红包功能和为用户发放红包,以及查询发送红包记录。 3 | --- 4 | 5 | # 商户平台开通现金红包、指定用户发放、红包记录查询 6 | 7 | 浅析微信支付系列已经更新十三篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:\(余额提现\)企业付款到微信用户零钱或银行卡账户](https://mp.weixin.qq.com/s/YZkrbYm5t8HJPT_S4W9zrQ) 10 | 11 | [浅析微信支付:支付验收示例和验收指引](https://mp.weixin.qq.com/s/YESU2V5byxfM8z9YQXgnuA) 12 | 13 | [浅析微信支付:如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 14 | 15 | [浅析微信支付:申请退款、退款回调接口、查询退款](https://mp.weixin.qq.com/s/IyWjWB__-VsqKO8SL0DL3Q) 16 | 17 | 上一篇文章我们说到,如果有`余额提现`、`返利福利`等需求时,就会用到商家向用户付款的操作,基于微信支付,上篇我们说了付款到用户余额和银行卡;本文来讲解如何使用现金红包的方式向用户发送`现金红包`,首先我们来了解什么是微信的现金红包。 18 | 19 | #### 现金红包 20 | 21 | 现金红包,是微信支付商户平台提供的营销工具之一,上线以来深受广大商户与用户的喜爱。商户可以通过本平台向微信支付用户发放现金红包。用户领取红包后,资金到达用户微信支付零钱账户,和零钱包的其他资金有一样的使用出口; 22 | 23 | 注意:若用户未领取,资金将会在24小时后退回商户的微信支付账户中。 24 | 25 | 官方文档地址: 26 | 27 | ```text 28 | https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_1 29 | ``` 30 | 31 | **现金红包意义** 32 | 33 | 微信支付现金红包因资金的承载方式为现金,一直以来深受用户的青睐,近年来的春晚中,现金红包都扮演着重要的角色;在日常运营中也为商户的营销活动带来热烈的反响。总的来说,现金红包在包括但不仅限于以下场景中发挥着重要意义: 34 | 35 | ◆ 为企业拉取新用户、巩固老用户关系、提升用户活跃度 36 | 37 | ◆ 结合巧妙的创意点子,辅以红包点缀,打造火爆的活动,提升企业与品牌知名度 38 | 39 | ◆ 结合企业运营活动,以红包作为奖品,使你的抽奖、满送等营销活动更便利进行 40 | 41 | ◆ 同时,除了营销之外,现金红包在企业日常的运营中也扮演着重要角色。如:为员工返福利、为供应商返利、会员积分/虚拟等级兑现等等。 42 | 43 | 什么意思? 简单点讲,就是现金红包具有特殊的营销属性,拿公众号来讲,我们可以建立活动,通过活动的方式为用户发送现金红包,而这个红包触达的消息是在公众号聊天窗口页面,这样也可以引导用户关注公众号、提升活跃度等等。 44 | 45 | **开通现金红包** 46 | 47 | 官方文档如下: 48 | 49 | ```text 50 | https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_3&index=2 51 | ``` 52 | 53 | 在使用现金红包之前,请前往开通现金红包功能。 操作路径:【登录微信支付商户平台——>产品中心——>现金红包——>开通】。 54 | 55 | 可以根据官方的声明来开通现金红包,这里说几个重要的点: 56 | 57 | 1. 入住时间超过90天; 58 | 59 | 2. 连续交易正常交易时间30天; 60 | 61 | 一定要注意:上面这两点是必要条件,很多新注册的公司很容易就着了道,入住时间不够、交易时间更不够,没搞明白,活活等了三个月时间;如果有小伙伴遇到这样的情况,可以换一个满足要求的主体公司来解决,我的github代码中也兼容不同主体的服务号使用微信支付相关功能,小伙伴可以看看源码`WXPayConstants`和`WXPay`这两个类,调用接口时扩展`WXPayConfigImpl`即可。 62 | 63 | 说明:在开通时请如实选择你的使用场景,且在红包的发放过程中如实上报你的场景,如有作假,微信支付将有权根据《微信支付商户平台使用协议》对你的商户号做出处理。 64 | 65 | #### 开发前的准备 66 | 67 | 具体的操作步骤这里就不描述了,小伙伴们可以查看官方文档: 68 | 69 | ```text 70 | https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_3&index=2 71 | ``` 72 | 73 | 上面文档中已经有详细的描述,我在这里简单描述一下重点注意项: 74 | 75 | 1. 下载API证书 76 | 77 | 2. 充值,保证商户余额有足够的钱(一定要注意基本账户和运营账户的区别,一般情况下,有运营账户的时候,都会从运营账户中扣款)操作路径:【登录商户平台——>交易中心——>资金管理——>充值】 78 | 79 | 3. 获取openid,指定用户发送红包必须先知道用户的标识openid,可以根据网页授权接口获得 80 | 81 | 4. 设置红包参数,操作路径:【登录商户平台——>产品中心——>现金红包——>产品设置】 82 | 83 | 对于第四点,可以设置和更改以下参数官方解释如下: 84 | 85 | 1. 调用IP地址:设置之后,仅有已设置的IP地址可以调用,其余的IP调用会报错; 86 | 87 | 2. 用户领取上限:限制同一openid同一日领取的个数; 88 | 89 | 3. 防刷等级:防刷是指微信风控针对微信小号、僵尸号、机器号等的拦截,你可以通过更改防刷等级控制防刷的强度; 90 | 91 | 4. 同时,你也可以申请更改红包额度。但是需要经过微信支付的审核,审核通过之后才会生效; 92 | 93 | 敲黑板!!!重点来了,以上第一点IP地址,就是我们调用现金红包发放的服务器IP地址了;第二点也要注意,每个用户可以领取的红包个数限制; 94 | 95 | 最需要注意的是,调用接口时,发放红包使用场景一定要慎重选择,查看一下每种场景对应的限制,比如在红包金额大于200或者小于1元时必传场景参数,这时就需要我们配置阀值。 96 | 97 | #### 发放方式(接口发放) 98 | 99 | 方式一:接口发放 商户根据开发文档进行开发,一次调用可以给一个指定用户发送一个指定金额的红包,满足多元化的运营需求。 100 | 101 | 方式二:通过上传openid文件发放 收集要发送红包对象的openid,将openid编辑成txt文件,登录微信支付商户平台,使用上传文件功能发放。一份文件对应一个红包模板,便于管理。 102 | 103 | 方式三:配置营销规则“满额送”发放 商户可以在商户平台配置自助规则:用户使用微信支付发生交易满足一定条件,立送现金红包 104 | 105 | 本文主要讲通过接口发放的方式。 106 | 107 | **接口链接** 108 | 109 | ```text 110 | https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack 111 | ``` 112 | 113 | **是否需要证书** 114 | 115 | 是 116 | 117 | **调用接口** 118 | 119 | 官方文档地址: 120 | 121 | ```text 122 | https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3 123 | ``` 124 | 125 | 首先是发放规则: 126 | 127 | 1. 发送频率限制------默认1800/min 128 | 129 | 2. 发送个数上限------按照默认1800/min算 130 | 131 | 3. 金额限制------默认红包金额为1-200元,如有需要,可前往商户平台进行设置和申请 132 | 133 | 4. 其他其他限制吗?------单个用户可领取红包上线为10个/天,如有需要,可前往商户平台进行设置和申请 134 | 135 | 5. 如果量上满足不了我们的需求,如何提高各个上限?------金额上限和用户当天领取次数上限可以在商户平台进行设置 136 | 137 | 注意1-红包金额大于200或者小于1元时,请求参数scene\_id必传。 138 | 139 | 注意2-根据监管要求,新申请商户号使用现金红包需要满足两个条件:1、入驻时间超过90天 2、连续正常交易30天。 140 | 141 | 注意3-移动应用的appid无法使用红包接口。 142 | 143 | PS:上面是官方介绍,不难理解,划重点!!!(注意3的含义,就是只能使用公众号的openid,小程序的openid不可用。) 144 | 145 | 消息触达规则参考官方文档: 146 | 147 | ![消息触达规则](../.gitbook/assets/xiao-xi-chu-da-gui-ze.png) 148 | 149 | 下面开始来干货,贴出源码吧,应用代码: 150 | 151 | ```text 152 | /** 153 | * 发送现金红包 154 | * 155 | * @author yclimb 156 | * @date 2018/9/18 157 | */ 158 | private void sendRedPack() throws Exception { 159 | WXPay wxPay = new WXPay(AsydWXPayConfigImpl.getInstance()); 160 | Map resultMap = wxPay.sendRedPack(WXPayUtil.getPayNo(), "obX_c0YRpT47zKcvq-ZYpjU6GFuA", "1", "活动名称", "红包祝福语", "备注", "127.0.0.1"); 161 | System.out.println("wxPay.sendRedPack:" + resultMap); 162 | } 163 | ``` 164 | 165 | 调用发送现金红包接口: 166 | 167 | ```text 168 | /** 169 | * 作用:企业向指定微信用户的openid发放指定金额红包
170 | * 场景:商户可以通过本平台向微信支付用户发放现金红包。用户领取红包后,资金到达用户微信支付零钱账户,和零钱包的其他资金有一样的使用出口;若用户未领取,资金将会在24小时后退回商户的微信支付账户中。 171 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3 172 | * 173 | * @param mch_billno 商户订单号 174 | * @param openid 用户openid 175 | * @param amount 企业付款金额 176 | * @param act_name 活动名称 177 | * @param wishing 红包祝福语 178 | * @param remark 备注 179 | * @param spbill_create_ip 该IP可传用户端或者服务端的IP 180 | * @return API返回数据 181 | * @throws Exception e 182 | */ 183 | public Map sendRedPack(String mch_billno, String openid, String amount, String act_name, String wishing, String remark, String spbill_create_ip) throws Exception { 184 | 185 | /** 构造请求参数数据 **/ 186 | Map data = new HashMap<>(); 187 | 188 | // 商户订单号 mch_billno 是 10000098201411111234567890 String(28) 商户订单号(每个订单号必须唯一。取值范围:0~9,a~z,A~Z)接口根据商户订单号支持重入,如出现超时可再调用。 189 | data.put("mch_billno", mch_billno); 190 | // 商户名称 send_name 是 天虹百货 String(32) 红包发送者名称 191 | data.put("send_name", "悦店"); 192 | // 用户openid re_openid 是 oxTWIuGaIt6gTKsQRLau2M0yL16E String(32) 接受红包的用户openid openid为用户在wxappid下的唯一标识(获取openid参见微信公众平台开发者文档:网页授权获取用户基本信息) 193 | data.put("re_openid", openid); 194 | // 付款金额 total_amount 是 1000 int 付款金额,单位分 195 | data.put("total_amount", String.valueOf(new BigDecimal(amount).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); 196 | // 红包发放总人数 total_num 是 1 int 红包发放总人数 total_num=1 197 | data.put("total_num", "1"); 198 | // 红包祝福语 wishing 是 感谢您参加猜灯谜活动,祝您元宵节快乐! String(128) 红包祝福语 199 | data.put("wishing", wishing); 200 | // Ip地址 client_ip 是 192.168.0.1 String(15) 调用接口的机器Ip地址 201 | data.put("client_ip", spbill_create_ip); 202 | // 活动名称 act_name 是 猜灯谜抢红包活动 String(32) 活动名称 203 | data.put("act_name", act_name); 204 | // 备注 remark 是 猜越多得越多,快来抢! String(256) 备注信息 205 | data.put("remark", remark); 206 | 207 | /** 以下参数为非必填参数 **/ 208 | /* 209 | * 场景id:scene_id 否 PRODUCT_8 String(32) 发放红包使用场景,红包金额大于200或者小于1元时必传 210 | * PRODUCT_1:商品促销 211 | * PRODUCT_2:抽奖 212 | * PRODUCT_3:虚拟物品兑奖 213 | * PRODUCT_4:企业内部福利 214 | * PRODUCT_5:渠道分润 215 | * PRODUCT_6:保险回馈 216 | * PRODUCT_7:彩票派奖 217 | * PRODUCT_8:税务刮奖 218 | */ 219 | //data.put("scene_id", "PRODUCT_1"); 220 | /* 221 | * 活动信息 risk_info 否 posttime%3d123123412%26clientversion%3d234134%26mobile%3d122344545%26deviceid%3dIOS String(128) 222 | * posttime:用户操作的时间戳 223 | * mobile:业务系统账号的手机号,国家代码-手机号。不需要+号 224 | * deviceid :mac 地址或者设备唯一标识 225 | * clientversion :用户操作的客户端版本 把值为非空的信息用key=value进行拼接,再进行urlencode urlencode(posttime=xx& mobile =xx&deviceid=xx) 226 | */ 227 | // 资金授权商户号 consume_mch_id 否 1222000096 String(32) 资金授权商户号 服务商替特约商户发放时使用 228 | 229 | /** 以下四个参数,在 this.redPackRequestData 方法中会自动赋值 **/ 230 | // 商户号 mch_id 是 10000098 String(32) 微信支付分配的商户号 231 | // 随机字符串 nonce_str 是 5K8264ILTKCH16CQ2502SI8ZNMTM67VS String(32) 随机字符串,不长于32位 232 | // 签名 sign 是 C380BEC2BFD727A4B6845133519F3AD6 String(32) 详见签名生成算法 233 | // 公众账号appid wxappid 是 wx8888888888888888 String(32) 微信分配的公众账号ID(企业号corpid即为此appId)。在微信开放平台(open.weixin.qq.com)申请的移动应用appid无法使用该接口。 234 | 235 | // 微信调用接口 236 | Map resultMap = this.sendRedPack(data); 237 | 238 | WXPayUtil.getLogger().info("wxPay.sendRedPack:" + resultMap); 239 | 240 | return resultMap; 241 | } 242 | ``` 243 | 244 | 以上为接口调用代码,对于接口调用的入参和出参,小伙伴看看官网文档哦,接口中的注释给大家一个参考。 245 | 246 | 官方还提供了一种发放`裂变红包`的接口,有需要的小伙伴可以了解一下,文档地址: 247 | 248 | ```text 249 | https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4 250 | ``` 251 | 252 | 裂变红包:一次可以发放一组红包。首先领取的用户为种子用户,种子用户领取一组红包当中的一个,并可以通过社交分享将剩下的红包给其他用户。裂变红包充分利用了人际传播的优势。 253 | 254 | #### 查询红包记录 255 | 256 | 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包。 257 | 258 | 这个接口很简单,就是查询已经发送的红包记录,根据商户发放红包的商户订单号查询即可。 259 | 260 | **接口链接** 261 | 262 | ```text 263 | https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo 264 | ``` 265 | 266 | **是否需要证书** 267 | 268 | 是 269 | 270 | **调用接口** 271 | 272 | 应用代码: 273 | 274 | ```text 275 | /** 276 | * 查询现金红包 277 | * 278 | * @author yclimb 279 | * @date 2018/9/18 280 | */ 281 | private void getHbInfo() throws Exception { 282 | WXPay wxPay = new WXPay(AsydWXPayConfigImpl.getInstance()); 283 | Map resultMap = wxPay.getHbInfo("1502348237482342342"); 284 | System.out.println("wxPay.getHbInfo:" + resultMap); 285 | } 286 | ``` 287 | 288 | 查询接口代码: 289 | 290 | ```text 291 | /** 292 | * 作用:查询红包记录
293 | * 场景:用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包。 294 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_6&index=5 295 | * 296 | * @param mch_billno 商户订单号 297 | * @return API返回数据 298 | * @throws Exception e 299 | */ 300 | public Map getHbInfo(String mch_billno) throws Exception { 301 | 302 | /** 构造请求参数数据 **/ 303 | Map data = new HashMap<>(); 304 | 305 | // 商户订单号 mch_billno 是 10000098201411111234567890 String(28) 商户订单号(每个订单号必须唯一。取值范围:0~9,a~z,A~Z)接口根据商户订单号支持重入,如出现超时可再调用。 306 | data.put("mch_billno", mch_billno); 307 | // 订单类型 bill_type 是 MCHT String(32) MCHT:通过商户订单号获取红包信息。 308 | data.put("bill_type", "MCHT"); 309 | 310 | /** 以下四个参数,在 this.fillRequestData 方法中会自动赋值 **/ 311 | // 商户号 mch_id 是 10000098 String(32) 微信支付分配的商户号 312 | // 随机字符串 nonce_str 是 5K8264ILTKCH16CQ2502SI8ZNMTM67VS String(32) 随机字符串,不长于32位 313 | // 签名 sign 是 C380BEC2BFD727A4B6845133519F3AD6 String(32) 详见签名生成算法 314 | // 公众账号appid appid 是 wx8888888888888888 String(32) 微信分配的公众账号ID(企业号corpid即为此appId)。在微信开放平台(open.weixin.qq.com)申请的移动应用appid无法使用该接口。 315 | 316 | // 微信调用接口 317 | Map resultMap = this.getHbInfo(data); 318 | 319 | WXPayUtil.getLogger().info("wxPay.getHbInfo:" + resultMap); 320 | 321 | return resultMap; 322 | } 323 | ``` 324 | 325 | 此接口通过商户订单号获取红包信息,很简单,对于返回参数小伙伴们可以查看官方文档,注意一下错误代码即可。 326 | 327 | #### 结语 328 | 329 | 以上为`商户平台开通现金红包、指定用户发放、红包记录查询`相关的解释和源码,小伙伴们一定要注意看看官方文档哦,具体的源码可以看作者的github,里面对每个方法有详细的注释。 330 | 331 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 332 | 333 | 预告:下一篇文章会讲发放奖励的另一种方式 `商户平台代金券或立减优惠开通、指定用户发放、查询等`,敬请期待!!! 334 | 335 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 336 | 337 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 338 | 339 | 到此本文就结束了,关注公众号查看更多推送!!! 340 | 341 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 342 | 343 | -------------------------------------------------------------------------------- /refund/downloadbill.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第九篇,主要讲解商户下载对账单接口和资金账单接口的实现和一些注意事项。 3 | --- 4 | 5 | # 下载对账单和资金账单 6 | 7 | 浅析微信支付系列已经更新九篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:申请退款、退款回调接口、查询退款](https://mp.weixin.qq.com/s/IyWjWB__-VsqKO8SL0DL3Q) 10 | 11 | [浅析微信支付:查询订单和关闭订单](https://mp.weixin.qq.com/s/SG4sTHsUKKJF-_Qgpjh0jA) 12 | 13 | [浅析微信支付:支付结果通知](https://mp.weixin.qq.com/s/Zr3ldgsMIg_cBrtuS2c7_g) 14 | 15 | 在商户平台中,商家也可以下载资金对账单,历史的交易清单,具体位置:商户平台 -> 交易中心 -> 账单管理。 16 | 17 | 如果要查看实时的流水记录,可以在微信APP中搜索小程序 `微信支付商户助手` 即可查看。 18 | 19 | #### 1、下载对账单 20 | 21 | 以下为微信官方的`下载对账单`文档: 22 | 23 | ```text 24 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6 25 | ``` 26 | 27 | 根据接口下载历史的交易账单,数据以文本表格的方式返回,第一行为表头,后面各行为对应的字段内容,字段内容跟查询订单或退款结果一致,具体字段说明可查阅相应接口。 28 | 29 | 此接口方便商家在自身系统中下载,不依赖于微信商户平台。 30 | 31 | **1.1. 应用场景** 32 | 33 | 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致,通过对账单核对后可校正支付状态。 34 | 35 | ```text 36 | 注意: 37 | 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致; 38 | 2、微信在次日9点启动生成前一天的对账单,建议商户10点后再获取; 39 | 3、对账单中涉及金额的字段单位为“元”。 40 | 4、对账单接口只能下载三个月以内的账单。 41 | 5、对账单是以商户号纬度来生成的,如一个商户号与多个appid有绑定关系,则使用其中任何一个appid都可以请求下载对账单。对账单中的appid取自交易时候提交的appid,与请求下载对账单时使用的appid无关。 42 | ``` 43 | 44 | **1.2. 接口链接** 45 | 46 | ```text 47 | https://api.mch.weixin.qq.com/pay/downloadbill 48 | ``` 49 | 50 | **1.3. 是否需要证书** 51 | 52 | 不需要 53 | 54 | **1.4. 调用接口** 55 | 56 | 调用参数: 57 | 58 | | 字段名称 | 变量名 | 必填 | 类型 | 描述 | 59 | | :--- | :--- | :--- | :--- | :--- | 60 | | 账单日期 | bill\_date | 是 | String\(8\) | 下载对账单的日期,格式:20140603 | 61 | | 账单类型 | bill\_type | 是 | String\(8\) | ALL,返回当日所有订单信息,默认值SUCCESS,返回当日成功支付的订单REFUND,返回当日退款订单RECHARGE\_REFUND,返回当日充值退款订单 | 62 | | 压缩账单 | tar\_type | 否 | String\(8\) | 非必传参数,固定值:GZIP,返回格式为.gzip的压缩包账单。不传则默认为数据流形式。 | 63 | 64 | 以下为调用示例代码: 65 | 66 | ```text 67 | /** 68 | * 对账单下载 69 | */ 70 | private void doDownloadBill() { 71 | HashMap data = new HashMap(); 72 | data.put("bill_date", "20161102"); 73 | data.put("bill_type", "ALL"); 74 | try { 75 | Map r = wxpay.downloadBill(data); 76 | System.out.println(r); 77 | } catch (Exception e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | ``` 82 | 83 | **1.5. 返回结果** 84 | 85 | 成功时,数据以文本表格的方式返回,第一行为表头,后面各行为对应的字段内容,字段内容跟查询订单或退款结果一致,具体字段说明可查阅相应接口。 86 | 87 | 第一行为表头,根据请求下载的对账单类型不同而不同\(由bill\_type决定\),目前有: 88 | 89 | 当日所有订单 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率 90 | 91 | 当日成功支付的订单 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,商品名称,商户数据包,手续费,费率 92 | 93 | 当日退款的订单 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率 94 | 95 | 从第二行起,为数据记录,各参数以逗号分隔,参数前增加\`符号,为标准键盘1左边键的字符,字段顺序与表头一致。 96 | 97 | 倒数第二行为订单统计标题,最后一行为统计数据 98 | 99 | 总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额 100 | 101 | 举例如下: 102 | 103 | ```text 104 | 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率 105 | `2014-11-1016:33:45,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1001690740201411100005734289,`1415640626,`085e9858e3ba5186aafcbaed1,`MICROPAY,`SUCCESS,`CFT,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60% 106 | `2014-11-1016:46:14,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1002780740201411100005729794,`1415635270,`085e9858e90ca40c0b5aee463,`MICROPAY,`SUCCESS,`CFT,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60% 107 | 总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额 108 | `2,`0.02,`0.0,`0.0,`0 109 | ``` 110 | 111 | #### 2、下载资金账单接口 112 | 113 | 以下为微信官方的`下载资金账单`文档: 114 | 115 | ```text 116 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_18&index=7 117 | ``` 118 | 119 | **2.1. 应用场景** 120 | 121 | 商户可以通过该接口下载自2017年6月1日起 的历史资金流水账单。 122 | 123 | 说明: 124 | 125 | ```text 126 | 1、资金账单中的数据反映的是商户微信账户资金变动情况; 127 | 2、当日账单在次日上午9点开始生成,建议商户在上午10点以后获取; 128 | 3、资金账单中涉及金额的字段单位为“元”。 129 | ``` 130 | 131 | **2.2. 接口链接** 132 | 133 | ```text 134 | https://api.mch.weixin.qq.com/pay/downloadfundflow 135 | ``` 136 | 137 | **2.3. 是否需要证书** 138 | 139 | 请求需要双向证书 140 | 141 | **2.4. 调用接口** 142 | 143 | 调用参数: 144 | 145 | | 字段名称 | 变量名 | 必填 | 类型 | 描述 | 146 | | :--- | :--- | :--- | :--- | :--- | 147 | | 签名类型 | sign\_type | 否 | String\(32\) | 签名类型,目前仅支持HMAC-SHA256 | 148 | | 资金账单日期 | bill\_date | 是 | String\(8\) | 下载对账单的日期,格式:20140603 | 149 | | 资金账户类型 | account\_type | 是 | String\(8\) | 账单的资金来源账户:Basic 基本账户、Operation 运营账户、Fees 手续费账户 | 150 | | 压缩账单 | tar\_type | 否 | String\(8\) | 非必传参数,固定值:GZIP,返回格式为.gzip的压缩包账单。不传则默认为数据流形式。 | 151 | 152 | 此接口不常用,推荐使用微信商户平台下载。具体的实现请参考上面的官方文档。 153 | 154 | #### 结语 155 | 156 | 以上为`下载对账单、资金账单`相关的解释和源码,特别需要注意的是`下载资金账单`接口需要特定的签名类型`HMAC-SHA256`,小伙伴们一定要注意哦,具体的源码可以看作者的github,里面对每个方法有详细的注释。 157 | 158 | 预告:下一篇文章 `如何使用沙箱环境测试`,敬请期待!!! 159 | 160 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 161 | 162 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 163 | 164 | 到此本文就结束了,关注公众号查看更多推送!!! 165 | 166 | ![关注我的公众号](../.gitbook/assets/er-wei-ma.jpg) 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /refund/refund.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第八篇,主要讲解商户如何处理微信申请退款、退款回调、查询退款接口,其中有一些坑的地方,会着重强调。 3 | --- 4 | 5 | # 申请退款、退款回调接口、查询退款 6 | 7 | 浅析微信支付系列已经更新七篇了哟~,没有看过的朋友们可以看一下哦。 8 | 9 | [浅析微信支付:查询订单和关闭订单](https://mp.weixin.qq.com/s/SG4sTHsUKKJF-_Qgpjh0jA) 10 | 11 | [浅析微信支付:支付结果通知](https://mp.weixin.qq.com/s/Zr3ldgsMIg_cBrtuS2c7_g) 12 | 13 | [浅析微信支付:统一下单接口](https://mp.weixin.qq.com/s/lYPiad1pyZ6ZdqH-sg66eg) 14 | 15 | 在实际场景中,申请退款和退款回调接口是比较常用到的微信支付接口,这里我们会讲`原路返回`方式的退款,还有的是使用直接为用户`付款到零钱`、`现金红包`等方式来退款,此种情况主要会出现在客服退款时,不是全部退款的情况,也有的会出现在使用了`微信代金券-单品券`的时候,因为单品券不能部分退款,所以只能走企业付款用户的方式,以下我们主要讲`原路返回`退款。 16 | 17 | PS:原路返回的意思就是,从你支付时的关联支付单中扣款,微信会记录相关数据,可以在客户端通知中展示。 18 | 19 | #### 1、申请退款接口 20 | 21 | 以下为微信官方的`申请退款`文档: 22 | 23 | ```text 24 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4 25 | ``` 26 | 27 | **1.1. 应用场景** 28 | 29 | 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。 30 | 31 | ```text 32 | 注意: 33 | 1、交易时间超过一年的订单无法提交退款 34 | 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号 35 | 3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次 36 | 错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次 37 | 4、每个支付订单的部分退款次数不能超过50次 38 | ``` 39 | 40 | PS:以上限制一般情况下不会出现,但我们也必须写入系统异常场景处理中,请求频率可以使用队列或增加延迟等方式来处理,部分退款此时不要超过微信的限制。 41 | 42 | **1.2. 接口链接** 43 | 44 | ```text 45 | https://api.mch.weixin.qq.com/secapi/pay/refund 46 | ``` 47 | 48 | **1.3. 是否需要证书** 49 | 50 | 请求需要双向证书。 51 | 52 | PS:关于微信证书,可以在 \[商户平台-账户中心-API安全\] 去下载,此证书很多支付接口均需要使用,请将证书地址配置为常量,具体实现可以参考作者github源码。 53 | 54 | **1.4. 调用接口** 55 | 56 | 先看源码,如下: 57 | 58 | ```text 59 | /** 60 | * [微信退款接口] - 保存调用的相关记录 61 | * @param refundPayment 退款订单的支付记录 62 | * @param tradePayment 历史付款单 63 | * @return map 64 | * @throws Exception e 65 | * 66 | * @author yclimb 67 | * @date 2018/6/21 68 | */ 69 | public Map saveWxPayRefund(Payment refundPayment, Payment tradePayment) throws Exception { 70 | if (refundPayment == null || tradePayment == null) { 71 | return null; 72 | } 73 | 74 | // 微信订单号/商户订单号,必须传入其中一个,此处默认传入商户订单号 75 | // 微信订单号,微信生成的订单号,在支付通知中有返回 76 | // String transaction_id = null; 77 | // 商户订单号,商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 78 | String out_trade_no = tradePayment.getFlowNumer(); 79 | // 商户退款单号,商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 80 | String out_refund_no = refundPayment.getFlowNumer(); 81 | // 订单总金额,传入参数单位为:元 82 | String total_fee = String.valueOf(tradePayment.getAmount()); 83 | // 退款总金额,订单总金额,传入参数单位为:元 84 | String refund_fee = String.valueOf(refundPayment.getAmount()); 85 | // 退款原因,若商户传入,会在下发给用户的退款消息中体现退款原因 86 | String refund_desc = refundPayment.getBody(); 87 | 88 | // 微信支付对象 89 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 90 | 91 | // 微信退款接口 92 | Map resultMap = wxPay.refund(refundUrl, null, out_trade_no, out_refund_no, total_fee, refund_fee, refund_desc); 93 | logger.info("saveWxPayRefund:resultMap:" + resultMap.toString()); 94 | 95 | // 记录付款流水 96 | 97 | 98 | // 下单失败,进行处理 99 | if (WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RETURN_CODE)) || 100 | WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RESULT_CODE))) { 101 | 102 | // 处理结果返回,无需继续执行 103 | resultMap.put(WXPayConstants.RESULT_CODE, WXPayConstants.FAIL); 104 | resultMap.put(WXPayConstants.ERR_CODE_DES, resultMap.get(WXPayConstants.RETURN_MSG)); 105 | return resultMap; 106 | } 107 | 108 | return resultMap; 109 | } 110 | ``` 111 | 112 | 以上为sdk退款调用示例代码,有几个参数需要我们注意: 113 | 114 | | 字段名 | 变量名 | 必填 | 类型 | 描述 | | 115 | | :--- | :--- | :--- | :--- | :--- | :--- | 116 | | 微信订单号 | transaction\_id | 是 | String\(32\) | 微信生成的订单号,在支付通知中有返回 | | 117 | | 商户订单号 | out\_trade\_no | 是 | String\(32\) | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母\_- | \*@ ,且在同一个商户号下唯一。 | 118 | | 商户退款单号 | out\_refund\_no | 是 | String\(64\) | 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母\_- | \*@ ,同一退款单号多次请求只退一笔。 | 119 | | 退款金额 | refund\_fee | 是 | Int | 退款总金额,订单总金额,单位为分,只能为整数 | | 120 | | 退款结果通知url | notify\_url | 否 | String\(256\) | 异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数,如果参数中传了notify\_url,则商户平台上配置的回调地址将不会生效。 | | 121 | 122 | PS:推荐以上的参数都必填,`notify_url`参数可配置为环境常量,根据环境的不同配置调用不会的回调地址。 123 | 124 | 下面为具体的实际sdk`wxPay.refund`调用代码: 125 | 126 | ```text 127 | /** 128 | * 作用:申请退款
129 | * 场景:当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家, 130 | * 微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。 131 | * 接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4 132 | * 133 | * @param notify_url 回调地址 134 | * @param transaction_id 微信生成的订单号,在支付通知中有返回 135 | * @param out_trade_no 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 136 | * @param out_refund_no 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 137 | * @param total_fee 订单总金额,传入参数单位为:元 138 | * @param refund_fee 退款总金额,订单总金额,传入参数单位为:元 139 | * @param refund_desc 退款原因,若商户传入,会在下发给用户的退款消息中体现退款原因 140 | * @return API返回数据 141 | * @throws Exception e 142 | */ 143 | public Map refund(String notify_url, String transaction_id, String out_trade_no, String out_refund_no, 144 | String total_fee, String refund_fee, String refund_desc) throws Exception { 145 | 146 | /** 构造请求参数数据 **/ 147 | Map data = new HashMap<>(); 148 | 149 | // 变量名 字段名 必填 类型 示例值 描述 150 | // 微信订单号 二选一 String(32) 1.21775E+27 微信生成的订单号,在支付通知中有返回 151 | if (transaction_id != null) { 152 | data.put("transaction_id", transaction_id); 153 | } 154 | // 商户订单号 String(32) 1.21775E+27 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 155 | data.put("out_trade_no", out_trade_no); 156 | // 商户退款单号 是 String(64) 1.21775E+27 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 157 | data.put("out_refund_no", out_refund_no); 158 | // 订单金额 是 Int 100 订单总金额,单位为分,只能为整数,详见支付金额 159 | data.put("total_fee", String.valueOf(new BigDecimal(total_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); 160 | // 退款金额 是 Int 100 退款总金额,订单总金额,单位为分,只能为整数,详见支付金额 161 | // 默认单位为分,系统是元,所以需要*100 162 | data.put("refund_fee", String.valueOf(new BigDecimal(refund_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); 163 | // 退款原因 否 String(80) 商品已售完 若商户传入,会在下发给用户的退款消息中体现退款原因 164 | data.put("refund_desc", refund_desc); 165 | // 货币种类 否 String(8) CNY 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 166 | data.put("refund_fee_type", WXPayConstants.FEE_TYPE_CNY); 167 | // 退款结果通知url 否 String(256) https://weixin.qq.com/notify/ 异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数,如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效。 168 | data.put("notify_url", notify_url); 169 | 170 | /** 以下参数为非必填参数 **/ 171 | // 退款资金来源 否 String(30) REFUND_SOURCE_RECHARGE_FUNDS 仅针对老资金流商户使用;REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款);REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款 172 | // data.put("refund_account", null); 173 | 174 | 175 | /** 以下五个参数,在 this.fillRequestData 方法中会自动赋值 **/ 176 | /*// 小程序ID appid 是 String(32) wxd678efh567hg6787 微信分配的小程序ID 177 | data.put("appid", WXPayConstants.APP_ID); 178 | // 商户号 mch_id 是 String(32) 1230000109 微信支付分配的商户号 179 | data.put("mch_id", WXPayConstants.MCH_ID); 180 | // 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法 181 | data.put("nonce_str", nonce_str); 182 | // 签名类型 sign_type 否 String(32) MD5 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 183 | data.put("sign_type", WXPayConstants.MD5); 184 | // 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法 185 | data.put("sign", sign);*/ 186 | 187 | // 微信退款接口 188 | Map resultMap = this.refund(data); 189 | 190 | WXPayUtil.getLogger().info("wxPay.refund:" + resultMap); 191 | 192 | return resultMap; 193 | } 194 | ``` 195 | 196 | 以上已经详细说明的具体的字段含义,有不明白的同学可以查看微信的官方文档,具体的源码可以查看作者的github。 197 | 198 | 这里有一个比较需要注意的点,在我们调用退款之后,会返回一些异常处理情况,官方文档中收录了一系列错误码code,我们可以在系统中对其进行处理,这里就不细说了。 199 | 200 | #### 2、退款回调接口 201 | 202 | 以下为微信官方的`退款结果通知`文档: 203 | 204 | ```text 205 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10 206 | ``` 207 | 208 | **2.1. 应用场景** 209 | 210 | 当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 211 | 212 | (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) 213 | 214 | 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 215 | 216 | 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容 217 | 218 | **2.2. 接口链接** 219 | 220 | 在申请退款接口中上传参数“notify\_url”以开通该功能 如果链接无法访问,商户将无法接收到微信通知。 通知url必须为直接可访问的url,不能携带参数。 221 | 222 | ```text 223 | 示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” 224 | ``` 225 | 226 | **2.3. 解密方式** 227 | 228 | ```text 229 | 解密步骤如下: 230 | (1)对加密串A做base64解码,得到加密串B 231 | (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台-->账户设置-->API安全-->密钥设置 ) 232 | (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding) 233 | ``` 234 | 235 | PS:特别注意,如果要进行微信AES解密,因为GJ的进口管制限制,Java发布的运行环境包中的加解密有一定的限制。默认不允许256位密钥的AES加解密,解决方法就是修改策略文件,我们需要从官方网站下载无限制权限策略文件,注意自己JDK的版本别下错了。 236 | 237 | jdk8的jce下载地址:`https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html` 238 | 239 | 将local\_policy.jar和US\_export\_policy.jar这两个文件替换%JRE\_HOME%\lib\security和%JDK\_HOME%\jre\lib\security下原来的文件,注意先备份原文件。 240 | 241 | 如果是jdk8,可能会遇到安全目录下有`policy`文件夹的情况,拿作者的电脑举例,jdk路径为`/opt/jdk1.8.0_152/jre/lib/security/policy`,此目录下有两个子文件夹`limited`、`limited`,需要替换`limited`文件夹下的文件 `local_policy.jar`、`US_export_policy.jar`这两个,最好先备份哦~!!!替换后重启项目即可。 242 | 243 | **2.4. 调用接口** 244 | 245 | 因为退款回调接口是咋们系统被动接收微信的消息,所以此处和支付回调接口一致,也是使用了流的方式,格式为xml,下面我们来看代码: 246 | 247 | ```text 248 | /** 249 | * 退款结果通知 250 | *

251 | * 在申请退款接口中上传参数“notify_url”以开通该功能 252 | * 如果链接无法访问,商户将无法接收到微信通知。 253 | * 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” 254 | *

255 | * 当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。 256 | * 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 257 | * (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) 258 | * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 259 | * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 260 | * 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容 261 | * @param request req 262 | * @param response resp 263 | * @return res xml 264 | * 265 | * @author yclimb 266 | * @date 2018/6/21 267 | */ 268 | @ApiOperation(value = "微信支付|微信退款回调接口", httpMethod = "POST", notes = "该链接是通过【微信退款API】中提交的参数notify_url设置,如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效。") 269 | @RequestMapping("/refund") 270 | public void refund(HttpServletRequest request, HttpServletResponse response) { 271 | 272 | String resXml = ""; 273 | InputStream inStream; 274 | try { 275 | 276 | inStream = request.getInputStream(); 277 | ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 278 | byte[] buffer = new byte[1024]; 279 | int len = 0; 280 | while ((len = inStream.read(buffer)) != -1) { 281 | outSteam.write(buffer, 0, len); 282 | } 283 | WXPayUtil.getLogger().info("refund:微信退款----start----"); 284 | 285 | // 获取微信调用我们notify_url的返回信息 286 | String result = new String(outSteam.toByteArray(), "utf-8"); 287 | WXPayUtil.getLogger().info("refund:微信退款----result----=" + result); 288 | 289 | // 关闭流 290 | outSteam.close(); 291 | inStream.close(); 292 | 293 | // xml转换为map 294 | Map map = WXPayUtil.xmlToMap(result); 295 | if (WXPayConstants.SUCCESS.equalsIgnoreCase(map.get(WXPayConstants.RETURN_CODE))) { 296 | 297 | WXPayUtil.getLogger().info("refund:微信退款----返回成功"); 298 | 299 | 300 | /** 以下字段在return_code为SUCCESS的时候有返回: **/ 301 | // 加密信息:加密信息请用商户秘钥进行解密,详见解密方式 302 | String req_info = map.get("req_info"); 303 | 304 | /** 305 | * 解密方式 306 | * 解密步骤如下: 307 | * (1)对加密串A做base64解码,得到加密串B 308 | * (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 ) 309 | * (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding) 310 | */ 311 | String resultStr = AESUtil.decryptData(req_info); 312 | 313 | // WXPayUtil.getLogger().info("refund:解密后的字符串:" + resultStr); 314 | Map aesMap = WXPayUtil.xmlToMap(resultStr); 315 | 316 | 317 | /** 以下为返回的加密字段: **/ 318 | // 商户退款单号 是 String(64) 1.21775E+27 商户退款单号 319 | String out_refund_no = aesMap.get("out_refund_no"); 320 | // 退款状态 是 String(16) SUCCESS SUCCESS-退款成功、CHANGE-退款异常、REFUNDCLOSE—退款关闭 321 | String refund_status = aesMap.get("refund_status"); 322 | // 商户订单号 是 String(32) 1.21775E+27 商户系统内部的订单号 323 | String out_trade_no = aesMap.get("out_trade_no"); 324 | /*// 微信订单号 是 String(32) 1.21775E+27 微信订单号 325 | String transaction_id = null; 326 | // 微信退款单号 是 String(32) 1.21775E+27 微信退款单号 327 | String refund_id = null; 328 | // 订单金额 是 Int 100 订单总金额,单位为分,只能为整数,详见支付金额 329 | String total_fee = null; 330 | // 应结订单金额 否 Int 100 当该订单有使用非充值券时,返回此字段。应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 331 | String settlement_total_fee = null; 332 | // 申请退款金额 是 Int 100 退款总金额,单位为分 333 | String refund_fee = null; 334 | // 退款金额 是 Int 100 退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 335 | String settlement_refund_fee = null;*/ 336 | 337 | // 退款是否成功 338 | if (!WXPayConstants.SUCCESS.equals(refund_status)) { 339 | resXml = resFailXml; 340 | } else { 341 | // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了. 342 | resXml = resSuccessXml; 343 | isSuccess = true; 344 | } 345 | 346 | // 根据付款单号查询付款记录 out_refund_no 347 | 348 | // 付款记录修改 & 记录付款日志 349 | if (payment != null) { 350 | WXPayUtil.getLogger().error("refund:微信支付回调:修改支付单"); 351 | } else { 352 | WXPayUtil.getLogger().error("refund:微信支付回调:找不到对应的支付单"); 353 | } 354 | 355 | 356 | } else { 357 | WXPayUtil.getLogger().error("refund:支付失败,错误信息:" + map.get(WXPayConstants.RETURN_MSG)); 358 | resXml = resFailXml; 359 | } 360 | 361 | } catch (Exception e) { 362 | WXPayUtil.getLogger().error("refund:微信退款回调发布异常:", e); 363 | } finally { 364 | try { 365 | // 处理业务完毕 366 | BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); 367 | out.write(resXml.getBytes()); 368 | out.flush(); 369 | out.close(); 370 | } catch (IOException e) { 371 | WXPayUtil.getLogger().error("refund:微信退款回调发布异常:out:", e); 372 | } 373 | } 374 | } 375 | ``` 376 | 377 | 以上代码详细解释了如何接收微信回调数据和解码数据,具体的`AESUtil.decryptData(req_info)`请参考作者源码,文末有地址,这里就不细讲了。 378 | 379 | 具体的退款接收参数请参考微信官方文档,需要注意的是`商户退款单号`和`微信退款单号`,此两个参数是修改和记录退款的必要凭证。 380 | 381 | #### 3、查询退款 382 | 383 | 以下为微信官方的`查询退款`文档: 384 | 385 | ```text 386 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5 387 | ``` 388 | 389 | **3.1. 应用场景** 390 | 391 | 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。 392 | 393 | 注意:如果单个支付订单部分退款次数超过20次请使用退款单号查询 394 | 395 | **3.2. 接口链接** 396 | 397 | ```text 398 | https://api.mch.weixin.qq.com/pay/refundquery 399 | ``` 400 | 401 | **3.3. 是否需要证书** 402 | 403 | 不需要 404 | 405 | **3.4. 调用接口** 406 | 407 | 注意:当一个订单部分退款超过10笔后,商户用微信订单号或商户订单号调退款查询API查询退款时,默认返回前10笔和`total_refund_count`(订单总退款次数)。商户需要查询同一订单下超过10笔的退款单时,可传入订单号及offset来查询,微信支付会返回offset及后面的10笔,以此类推。当商户传入的offset超过`total_refund_count`,则系统会返回报错`PARAM_ERROR`。 408 | 409 | 举例: 410 | 411 | ```text 412 | 一笔订单下的退款单有36笔,当商户想查询第25笔时,可传入订单号及offset=24,微信支付平台会返回第25笔到第35笔的退款单信息,或商户可直接传入退款单号查询退款 413 | ``` 414 | 415 | 以下为调用方式: 416 | 417 | ```text 418 | private void doRefundQuery() { 419 | // 四选一,微信订单号查询的优先级是: refund_id > out_refund_no > transaction_id > out_trade_no 420 | HashMap data = new HashMap(); 421 | // 商户订单号 422 | data.put("out_trade_no", out_trade_no); 423 | // 微信订单号 424 | data.put("transaction_id", out_trade_no); 425 | // 商户退款单号 426 | data.put("out_refund_no", out_trade_no); 427 | // 微信退款单号 428 | data.put("refund_id", out_trade_no); 429 | try { 430 | Map r = wxpay.refundQuery(data); 431 | System.out.println(r); 432 | } catch (Exception e) { 433 | e.printStackTrace(); 434 | } 435 | } 436 | ``` 437 | 438 | PS:微信订单号查询的优先级是: refund\_id > out\_refund\_no > transaction\_id > out\_trade\_no 439 | 440 | 需要注意的是,查询退款时,需要注意退款返回的错误码,如果出现错误,需要及时同步商户系统中的退款数据。 441 | 442 | #### 结语 443 | 444 | 以上为`申请退款、退款回调接口、查询退款`相关的解释和源码,特别需要注意的是接收退款时的解密方式和替换安全文件,小伙伴们一定要注意哦,具体的源码可以看作者的github,里面对每个方法有详细的注释。 445 | 446 | 预告:下一篇文章 `下载对账单和资金账单`,敬请期待!!! 447 | 448 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 449 | 450 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 451 | 452 | 到此本文就结束了,关注公众号查看更多推送!!! 453 | 454 | ![关注我的微信公众号](../.gitbook/assets/er-wei-ma.jpg) 455 | 456 | -------------------------------------------------------------------------------- /sandbox/sandboxnew.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十篇,主要讲解如何使用沙箱环境来测试微信支付。 3 | --- 4 | 5 | # 如何使用沙箱环境测试 6 | 7 | 浅析微信支付系列已经更新十篇了哟~,没有看过的朋友们可以看一下。 8 | 9 | [浅析微信支付:下载对账单和资金账单](https://mp.weixin.qq.com/s/XCR1Ts-uabuC573_vLb3Qg) 10 | 11 | [浅析微信支付:申请退款、退款回调接口、查询退款](https://mp.weixin.qq.com/s/IyWjWB__-VsqKO8SL0DL3Q) 12 | 13 | [浅析微信支付:查询订单和关闭订单](https://mp.weixin.qq.com/s/SG4sTHsUKKJF-_Qgpjh0jA) 14 | 15 | [浅析微信支付:支付结果通知](https://mp.weixin.qq.com/s/Zr3ldgsMIg_cBrtuS2c7_g) 16 | 17 | 在实际开发中,通常我们都是在开发环境中开发,本地环境也有很多限制,比如:微信支付无法调起、H5链接需要鉴权、支付结果通知需要外网等。 18 | 19 | 面对以上的问题,微信官方给出了解决的方法,就是咋们这篇文章的 `沙箱环境`,也就是微信支付的官方测试环境,这个环境能做些什么呢?我觉得最重要的一点就是我们可以实时根据官方的例子调用对应的接口,并且接口会马上返回结果,拿微信支付预支付单接口来说,调用以后会实时返回我们支付的相关信息,这样就免于开发时不知道返回结果而苦恼。 20 | 21 | #### 仿真测试系统 22 | 23 | 为降低商户测试门槛,微信支付团队开发了一套独立的仿真测试系统。该系统根据验收用例金额的不同返回不同的响应报文,以满足商户正常功能测试、安全/异常测试及性能测试的需求。 24 | 25 | ![微信支付仿真测试系统1](../.gitbook/assets/wei-xin-zhi-fu-fang-zhen-ce-shi-xi-tong-1.png) 26 | 27 | 图1为微信支付仿真测试系统(后简称仿真系统)的简化原理图。仿真系统的API协议与正式API完全相同(API接口文档)。商户开发者只需将正式API的调用URL增加一层`sandboxnew`路径,即可对接到仿真系统。 28 | 29 | 例如,刷卡支付URL:[https://api.mch.weixin.qq.com/pay/micropay](https://api.mch.weixin.qq.com/pay/micropay) 变更为:[https://api.mch.weixin.qq.com/sandboxnew/pay/micropay。](https://api.mch.weixin.qq.com/sandboxnew/pay/micropay。) 30 | 31 | 仿真系统与生产环境完全独立,包括存储层。商户在仿真系统所做的所有交易(如下单、支付、查询)均为无资金流的假数据,即:用户无需真实扣款,商户也不会有资金入账。代金券同理,沙箱环境中无需商户真实制券与发券,亦不会出现真实扣券情况。验收仿真测试系统的API验签密钥需从API获取: 32 | 33 | ![仿真测试系统的API验证签名](../.gitbook/assets/fang-zhen-ce-shi-xi-tong-de-api-yan-zheng-qian-ming.png) 34 | 35 | #### 源码&交互过程 36 | 37 | 以下为微信官方的`仿真测试系统`文档: 38 | 39 | ```text 40 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1 41 | ``` 42 | 43 | 上面说明具体的交互过程和相关的仿真测试系统的API验证签名,为什么需要这个签名接口呢?这是因为使用 `沙箱环境` 时使用的是真实的`商户号`、`小程序/公众号APP_ID`,但是 `API密钥`这个参数必须使用 `沙箱环境` 的 `sandbox_signkey`,此接口主要是取得这个参数。 44 | 45 | 注:仿真测试环境中的商户号(父子商户号)需使用真实商户号。 46 | 47 | 下面为取得 `sandbox_signkey`的示例: 48 | 49 | ```text 50 | /** 51 | * 获取沙盒 sandbox_signkey 52 | * 53 | * @author yclimb 54 | * @date 2018/9/18 55 | */ 56 | private void doGetSandboxSignKey() throws Exception { 57 | WXPayConfigImpl config = WXPayConfigImpl.getInstance(); 58 | HashMap data = new HashMap(); 59 | // 商户号 60 | data.put("mch_id", config.getMchID()); 61 | // 获取随机字符串 62 | data.put("nonce_str", WXPayUtil.generateNonceStr()); 63 | // 生成签名 64 | String sign = WXPayUtil.generateSignature(data, config.getKey()); 65 | data.put("sign", sign); 66 | 67 | // 得到 sandbox_signkey 68 | WXPay wxPay = new WXPay(config); 69 | String result = wxPay.requestWithoutCert("/sandboxnew/pay/getsignkey", data, 10000, 10000); 70 | System.out.println(result); 71 | } 72 | ``` 73 | 74 | 小伙伴可以根据 `result` 来获取具体的返回数据,解析之后获取 `sandbox_signkey`参数。 75 | 76 | 商户接入仿真系统的交互流程示例: 1. 商户发起刷卡支付请求,使用POST方式调用 [https://api.mch.weixin.qq.com/sandboxnew/pay/micropay](https://api.mch.weixin.qq.com/sandboxnew/pay/micropay) 2. 带sandboxnew 的https请求会被nginx路由到仿真系统。仿真系统根据支付金额(total\_fee字段)返回预期报文给商户。同时,落地该笔请求数据; 3. 商户发起查单,调用 [https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery,带上微信订单号(transaction\_id)或商户内部单号(out\_trade\_no);](https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery,带上微信订单号(transaction_id)或商户内部单号(out_trade_no);) 4. 仿真系统收到查单请求后,根据单号及金额返回预期的查单结果给商户; 5. 商户下载对账单,调用 [https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill](https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill) ,仿真系统返回固定的账单格式给商户。注:账单内容不一定与商户在仿真系统产生的交易完全相同。 77 | 78 | 沙箱说明:sandbox/sandboxnew 微信支付沙箱环境,是提供给微信支付商户的开发者,用于模拟支付及回调通知。以验证商户是否理解回调通知、账单格式,以及是否对异常做了正确的处理。 ◆ 如何对接沙箱环境? 1、修改商户自有程序或配置中,微信支付api的链接,如:被扫支付官网的url为:[https://api.mch.weixin.qq.com/pay/micropay](https://api.mch.weixin.qq.com/pay/micropay) 增加sandboxnew路径,变更为[https://api.mch.weixin.qq.com/sandboxnew/pay/micropay](https://api.mch.weixin.qq.com/sandboxnew/pay/micropay) , 即可接入沙箱验收环境,其它接口类似; 2、在微信支付开发调试站点(站点链接:[http://mch.weixin.qq.com/wiki/doc/api/index.php](http://mch.weixin.qq.com/wiki/doc/api/index.php) ),按接口文档填入正确的支付参数,发起微信支付请求,完成支付; 3、验收完成后,修改程序或配置中的api链接(重要!),去掉sandboxnew路径。对接现网环境。 79 | 80 | 说明地址: [https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23\_1](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1) [https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=21\_2](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=21_2) 81 | 82 | #### 结语 83 | 84 | 其实 `沙箱环境`主要是为了方便在开发时及时获得接口返回值和进行 `商户支付验收`使用,本文讲了如何获取 `sandbox_signkey`参数,然后如何进行模拟对接,在实际接口URL后增加 `sandboxnew` 即可,接口会实时返回结果参数,此点于正式环境不同(正式环境支付后是异步调用,沙箱环境是实时返回)。 85 | 86 | 注意:有的接口沙箱环境的接口并不只是在链接中增加 `sandboxnew`,整个链接都会改变,在实际操作中我们应该查看官方文档一一对照,如支付退款接口,正式线接口为:`/secapi/pay/refund`,而沙箱环境接口为:`/sandboxnew/pay/refund`,在沙箱环境中去掉了 `secapi` 这一路径,请小伙伴一定要注意。 87 | 88 | 预告:为了更好的验证微信支付安全性,我们需要接入微信的 `验收测试`,下一篇文章 `支付验收示例和验收指引` 为大家讲解,敬请期待!!! 89 | 90 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 91 | 92 | 加作者私人微信,作者微信号如下 `yclimb`,标明 `微信支付` 可拉入微信支付讨论群与小伙伴一起探讨哦,一定要标明 `微信支付` 哦~ 93 | 94 | 到此本文就结束了,关注公众号查看更多推送!!! 95 | 96 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 97 | 98 | -------------------------------------------------------------------------------- /sandbox/yanshou.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 本文是【浅析微信支付】系列文章的第十一篇,主要讲解支付验收示例和验收指引。 3 | --- 4 | 5 | # 支付验收示例和验收指引 6 | 7 | 浅析微信支付系列已经更新十一篇了哟~,没有看过的朋友们可以看一下。 8 | 9 | [浅析微信支付:如何使用沙箱环境测试](https://mp.weixin.qq.com/s/WmnsCnIrhN9STbvrNTQOiA) 10 | 11 | [浅析微信支付:下载对账单和资金账单](https://mp.weixin.qq.com/s/XCR1Ts-uabuC573_vLb3Qg) 12 | 13 | [浅析微信支付:申请退款、退款回调接口、查询退款](https://mp.weixin.qq.com/s/IyWjWB__-VsqKO8SL0DL3Q) 14 | 15 | [浅析微信支付:查询订单和关闭订单](https://mp.weixin.qq.com/s/SG4sTHsUKKJF-_Qgpjh0jA) 16 | 17 | 上一篇文章我们讲了 `如何使用沙箱环境测试`,文中有讲到沙箱环境不仅可以用来当开发环境使用,及时返回接口数据,还能当作微信支付的 `验收示例`,官方指出,为了安全考虑希望所有商户都接入验收,以下我们会结合官方文档为大家讲解如何接入及相关的验收用例。 18 | 19 | #### 验收指引 20 | 21 | 官方文档地址: 22 | 23 | ```text 24 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1 25 | ``` 26 | 27 | 本文阅读对象为:商户自有系统(包括但不限于:在线购物平台、人工收银系统、自动化智能收银系统、APP应用等)负责微信支付功能验收的测试及开发人员。 28 | 29 | 为保证商户接入质量,提升交易安全及用户体验,微信支付的合作服务商在正式上线交易前,必须先根据本文指引完成验收。验收完成后,服务商在验收公众平台(`微信号:WXPayAssist`)提交验收通过申请,审核通过后,才能开通相应的支付权限(如:刷卡支付)。否则,请根据审核驳回提示,重新完成验收。 30 | 31 | 注:仿真测试环境中的商户号(父子商户号)需使用真实商户号。 32 | 33 | **验收流程** 34 | 35 | ![图2 商户接入验收流程](../.gitbook/assets/tu-2-shang-hu-jie-ru-yan-shou-liu-cheng.png) 36 | 37 | 如图2,商户在收到微信支付审核通过的邮件后,即可用邮件中提供的开发者信息,启动测试验收工作。验收开始后,验收负责人可按照下表步骤操作: 38 | 39 | ![验收步骤](../.gitbook/assets/yan-shou-bu-zhou.png) 40 | 41 | 以上为验收的基本步骤,首先,我们需要接入 `沙箱环境`,不知道的小伙伴可以查看我的上一篇文章,有详细描述,这里就不细说了。 42 | 43 | **验收测试用例** 44 | 45 | 如果已经接入沙箱环境,我们就可以开始选择微信官方对应的验收用例进行测试了,官方提供了四种验收用例,如下: 46 | 47 | 请根据您需要开通的功能来选择相应的验收用例进行测试: 48 | 49 | ◆ [刷卡支付验收用例](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_11) 50 | 51 | ◆ [扫码支付验收用例](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_12) 52 | 53 | ◆ [公众号支付验收用例](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_13) 54 | 55 | ◆ [免充值券验收用例](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_15) 56 | 57 | 这里我们以 `公众号支付验收用例` 来做例子,下面为官方的验收流程: ![公众号支付验收用例](https://img-blog.csdnimg.cn/20181113200745632.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lDbGltYg==,size_16,color_FFFFFF,t_70) 58 | 59 | 流程我们已经知道了,重点来了,我们需要下载验收用例,下面是地址: 60 | 61 | ```text 62 | https://pay.weixin.qq.com/wiki/doc/api/download/jsapi_yanshou.zip 63 | ``` 64 | 65 | 首先,请关注上面图片中的二维码,如果遇到问题,可以查看官方的异常解答;下载验收用例后,我们会得到 4 个用例文档,需要根据文档中的描述来进行验收,`支付成功`、`支付失败`接口是必须验收的。 66 | 67 | **如何验收?** 68 | 69 | 简单讲,验收分为以下几个步骤: 1. 获取`sandbox_signkey` 2. 修改正常接口地址为沙箱环境地址,增加 `sandboxnew` 路径 3. 根据用例集标题中的金额传入参数,调用相应的接口 4. 查看返回值与用例集中是否一致,如果一致则成功,否则失败 70 | 71 | 需要注意的是,一定要根据用例集中的标题传入金额,比如`支付成功用例集`需要传入金额`1.01`元,那我们就必须传入这个金额,传入其他金额会导致失败。 72 | 73 | 以下为示例代码: 74 | 75 | ```text 76 | public static void main(String[] args) throws Exception { 77 | System.out.println("--------------->"); 78 | 79 | // 沙箱环境测试 80 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance(), true, true); 81 | 82 | Map resultMap = wxPay.unifiedOrder(notify_url, openid, body, out_trade_no, 83 | "1.01", spbill_create_ip, goods_tag, detail, 84 | timeStart, timeExpire); 85 | 86 | 87 | System.out.println(resultMap); 88 | 89 | /*Map resultMap = wxPay.refund(null, "10000", "10001", "1.01", "0.01", "测试微信退款"); 90 | System.out.println(WXPayUtil.isSignatureValid(resultMap, WXPayConstants.API_KEY));*/ 91 | 92 | 93 | System.out.println("<---------------"); 94 | } 95 | ``` 96 | 97 | 上面代码中是作者封装好的sdk方法,开启沙箱环境只需要实例化对象时传入参数即可: 98 | 99 | ```text 100 | // 沙箱环境测试 101 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance(), true, true); 102 | 103 | // 正式环境 104 | WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); 105 | ``` 106 | 107 | 具体源码见下面文末github地址。 108 | 109 | #### 结语 110 | 111 | 给小伙伴们分享点验收的经验,首先,一定要先看一遍官方文档,然后跟着官方文档一步步的操作,对于官方所讲的关键信息,必须仔细检查,比如上面所说的金额,还有官方标红的一些注释,本文主要目的是给大家一个分享和参考,比较方便的是作者已经封装好的sdk中有相关的 `沙箱环境` 切换示例,不需要大家再分析具体实现,关注如何应用即可。 112 | 113 | 如果小伙伴有遇到解决不了的问题,可以关注作者微信公众号,加入讨论群中发出疑问,和小伙伴们一起解决哦~ 114 | 115 | 预告:下一篇文章 `(余额提现)企业付款到微信用户零钱账户`,敬请期待!!! 116 | 117 | ​如果想要提前一览源码的小伙伴,可以先看看我的 github,地址如下: ​ ​`​https://github.com/YClimb/wxpay-sdk/blob/master/README.md ​` 118 | 119 | 关注作者微信公众号,点击下方`讨论群`,扫码即可加入`微信支付讨论群`与小伙伴一起探讨哦~ 120 | 121 | 到此本文就结束了,关注公众号查看更多推送!!! 122 | 123 | ![关注我的公众号](https://img-blog.csdn.net/20180130111432962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWUNsaW1i/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 124 | 125 | --------------------------------------------------------------------------------