一:准备工作

使用微信支付需要先开通服务号,然后还要开通微信支付,最后还要配置一些开发参数,过程比较多。

  1. 申请服务号(企业)
  2. 开通微信支付
  3. 开发配置 具体准备工作请参考Spring Boot入门教程(三十九):微信支付集成-申请服务号和微信支付

二:支付方式

  • 刷卡支付(MICROPAY) :刷卡支付是用户展示微信钱包内的“刷卡条码/二维码”给商户系统扫描后直接完成支付的模式。主要应用线下面对面收银的场景。相当于支付宝的条码支付
  • 扫码支付:扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。相当于支付宝的电脑网站支付
  • H5支付:H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。相当于支付宝的手机网站支付
  • 公众号支付(JSAPI):商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。
  • App支付:APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。
  • 小程序支付:在小程序中使用

H5支付: 是应用在微信客户端外,是一种WAP支付。 公众号支付(JSAPI): 是应用在微信内的一种支付。 两种方式的应用场景不一样。

三:刷卡支付场景介绍

1. 场景介绍

  • 步骤1:用户选择刷卡支付付款并打开微信,进入“我”->“钱包”->“收付款”条码界面;
  • 步骤2:收银员在商户系统操作生成支付订单,用户确认支付金额;
  • 步骤3:商户收银员用扫码设备扫描用户的条码/二维码,商户收银系统提交支付;
  • 步骤4:微信支付后台系统收到支付请求,根据验证密码规则判断是否验证用户的支付密码,不需要验证密码的交易直接发起扣款,需要验证密码的交易会弹出密码输入框。支付成功后微信端会弹出成功页面,支付失败会弹出错误提示。

2. 验证密码规则:

  • 支付金额>1000元的交易需要验证用户支付密码
  • 用户账号每天最多有5笔交易可以免密,超过后需要验证密码
  • 微信支付后台判断用户支付行为有异常情况,符合免密规则的交易也会要求验证密码

3. 免密支付流程:

(1)收银员在商户收银台生成支付订单,向用户展示支付金额;

(2)用户打开微信客户端,点击“我的钱包”,选择“刷卡”,进入条码界面;

(3)收银员使用扫码设备读取用户手机屏幕上的条码;

(4)扫码设备将读取的信息上传给门店收银台;

(5)门店收银台得到支付信息后,向商户收银后台发起支付请求。

(6)商户后台对门店收银台的支付请求进行处理,生成签名后调用【提交刷卡支付API】向微信支付系统发起支付请求。

(7)微信支付系统得到商户侧的支付请求之后会对请求进行验证,验证通过之后会对请求数据进行处理,最后将处理后的支付结果返回给商户收银后台。如果支付成功,微信支付系统会将支付结果返回给商户,同时把支付结果通知给用户(以短信、微信消息的形式通知)。

(8)商户收银后台对得到的支付结果进行签名验证和处理,再将支付结果返回给门店收银台。

(9)收银员看到门店收银台的支付结果后给用户发货。

4. 验密支付流程

验密支付流程文档

在商户调用【提交刷卡支付API】发起支付请求之后,微信支付后台提示用户输入密码确认支付,接口同步返回USERPAYING状态,商户系统再轮询调用查询订单接口来确认当前用户是否已经支付成功。

以下时序图说明验密支付流程:

由于在商户收银后台向微信支付系统发起支付请求之前的流程是完全一样的,所以这里只介绍商户发起支付请求之后的逻辑。

(1)商户门店生成订单后,收银台向后台系统发起支付请求。

(2)后台调用微信支付【提交刷卡支付API】生成支付交易。

(3)微信支付系统对商户请求进行验证,验证通过后判断当前用户需要输入密码。

(4)微信支付系统返回USERPAYING状态,商户后台系统将应答结果返回给商户门店收银台。

(5)微信支付系统通知用户微信客户端输入密码。

(6)用户得到输入密码提示后,确认支付并输入密码。

(7)完成密码输入,提交微信支付。

(8)微信客户端在用户完成支付后提示微信支付后台系统返回的支付结果,而且微信支付系统会通过短信、微信消息给用户发送支付结果提醒。

(9)商户收银台得到USERPAYING状态后,经过商户后台系统调用【查询订单API】查询实际支付结果。

(10)如果支付结果仍为USERPAYING,则每隔5秒循环调用【查询订单API】判断实际支付结果,如果用户取消支付或累计30秒用户都未支付,商户收银台退出查询流程后继续调用【撤销订单API】撤销支付交易。

5. 异常处理

用户遇到支付异常,请按如下说明处理

(1)用户微信端弹出系统错误提示框,用户可在交易列表查看交易情况,如果未找到订单,需要商户重新发起支付交易;如果订单显示成功支付,商户收银系统再次调用【查询订单API】查询实际支付结果;

(2)用户微信端弹出支付失败提示,例如:余额不足,信用卡失效。需要重新发起支付;

(3)当交易超时或支付交易失败,商户收银系统必须调用【撤销订单API】,撤销此交易。

(4)由于银行系统异常、用户余额不足、不支持用户卡种等原因使当前支付交易失败,商户收银系统应该把错误提示明确展示给收银员。

(5)根据返回的错误码,判断是否需要撤销交易,具体详见API返回错误码列表

public Map microPayWithPos(Map reqData) throws Exception {    return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());}/** * 提交刷卡支付,针对软POS,尽可能做成功 * 内置重试机制,最多60s * @param reqData * @param connectTimeoutMs * @return * @throws Exception */public Map microPayWithPos(Map reqData, int connectTimeoutMs) throws Exception {    int remainingTimeMs = 60*1000;    long startTimestampMs = 0;    Map lastResult = null;    Exception lastException = null;    while (true) {        startTimestampMs = WXPayUtil.getCurrentTimestampMs();        int readTimeoutMs = remainingTimeMs - connectTimeoutMs;        if (readTimeoutMs > 1000) {            try {                lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);                String returnCode = lastResult.get("return_code");                if (returnCode.equals("SUCCESS")) {                    String resultCode = lastResult.get("result_code");                    String errCode = lastResult.get("err_code");                    if (resultCode.equals("SUCCESS")) {                        break;                    }                    else {                        // 看错误码,若支付结果未知,则重试提交刷卡支付                        if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {                            remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs);                            if (remainingTimeMs <= 100) {                                break;                            }                            else {                                WXPayUtil.getLogger().info("microPayWithPos: try micropay again");                                if (remainingTimeMs > 5*1000) {                                    Thread.sleep(5*1000);                                }                                else {                                    Thread.sleep(1*1000);                                }                                continue;                            }                        }                        else {                            break;                        }                    }                }                else {                    break;                }            }            catch (Exception ex) {                lastResult = null;                lastException = ex;            }        }        else {            break;        }    }    if (lastResult == null) {        throw lastException;    }    else {        return lastResult;    }}

看Demo中的方法,整个方法也没有查询订单,也没有撤销订单的操作,而是不停的重复调用microPay,这和上面文档的(9)、(10)条逻辑不一样

注意:微信的刷卡支付并没有支付通知接口,只有退款通知接口;支付宝的条码支付是有支付通知接口的,两家是不一样的。

四:开发文档

在集成之前,一定要熟悉业务,熟悉文档

微信支付开发文档

刷卡文档,开发前请详细认证的看完该文档。

刷卡支付流程图 必须看

刷卡文档中有demo,SDK与DEMO下载,需要下载下来,熟悉一下项目结构,以及READEME.md

五:集成步骤

1. 引入SDK

  • wxpay-sdk 项目中的src就是要引入的sdk,可以直接将src的所有7个源文件拖入到自己项目中
  • wxpay-sdk 的READEME.md中说可以通过maven来引入sdk

现在有两种方式,选择其中一种,究竟选哪一种?

wxpay-sdk的src下面有7个java文件,通过maven引入可以看到的sdk中就4个java文件,两种方式文件个数不一致,多出来的3个文件是demo中用到的文件,并不是原始sdk中的文件,通过查看sdk中的源码可以看到,这4个文件其实就是使用httpclient来调用微信支付的支付接口,其中很多重要的逻辑并没有按照官方文档中说的那样缺少重要的逻辑实现,这个需要自己去完善一些逻辑

开发中发现WXPay这个类和maven中的类并不完全一样,多了两个方法microPayWithPos,其中这两个方法是demo作者自己基于内部方法microPay的一个封装,完善了部分逻辑, 但是完善的逻辑和官网文档的描述不一致,有重大逻辑问题

查看demo中的test发现竟然有一个类叫test,命名不清晰,语义太笼统,而且还是小写字母开头,不符合Java的基本命名规范,关于java中的测试的命名规则一般类名以Test作为后缀,而该demo以Test作为前缀,一般测试类方法的命名以test作为前缀,但是demo中的测试并没有什么test,一般测试类都要使用测试框架如Junit等,但是demo中的测试并没有使用测试框架,而是使用main方法来运行的。

对wxpay-sdk的评价:wxpay-sdk只是使用httpclient来调用微信支付的接口,只管调用微信支付接口,然后解析一下响应,并不处理支付中的业务逻辑,尽管一些和支付密切相关的重要逻辑也不会处理,甚至解密的工具方法都没有现成的,需要开发者自己去处理。看demo可以知道,写的不是一般的烂,没想到微信那么大的厂竟然sdk写的这么懒,demo写的这么low,真是丢人,和支付宝的sdk比一个地下一个天上。

骂完了,还是要集成的,这里我选择不相信demo的源文件,使用README.md中的maven来引入sdk,一个是demo写的太烂,多出来的文件也不是不要不行的,多出来的方法有重大逻辑缺陷,万一哪天微信哪天良心发现完善sdk了,更新了maven版本,自己只需要修改一下maven的版本号就行了。

com.github.wxpay    wxpay-sdk    0.0.3

2. application.yml

配置微信支付参数,其中appID、mchID、key、certPath是必须的,notifyUrl是可选的,sandboxKey和useSandbox是自己配置的,便于正式环境和沙箱环境的切换,关于参数的值去微信的开发配置中查看

  • useSandbox:true表示使用沙箱环境,如果为false为正式环境
  • sandboxKey: 沙箱环境API秘钥,需要通过下面的WXPayClient#getSignKey方法获取
pay:    wxpay:        appID: xxx        mchID: xxx        key: xxx        sandboxKey: xxx        certPath: /var/local/cert/apiclient_cert.p12        notifyUrl: http://65ta5j.natappfree.cc/wxpay/refund/notify        useSandbox: false

3. MyWXPayConfig

配置微信参数,其实就是一种Properties类,需要实现WXPayConfig中的方法 注意:demo中的WXPayConfig是一种抽象类abstract class,可以看到maven与demo的不统一

/** * 微信支付的参数配置 * * @author mengday zhang */@Data@Slf4j@ConfigurationProperties(prefix = "pay.wxpay")public class MyWXPayConfig implements WXPayConfig {    /** 公众账号ID */    private String appID;    /** 商户号 */    private String mchID;    /** API 密钥 */    private String key;    /** API 沙箱环境密钥 */    private String sandboxKey;    /** API证书绝对路径 */    private String certPath;    /** 退款异步通知地址 */    private String notifyUrl;    private Boolean useSandbox;    /** HTTP(S) 连接超时时间,单位毫秒 */    private int httpConnectTimeoutMs = 8000;    /** HTTP(S) 读数据超时时间,单位毫秒 */    private int httpReadTimeoutMs = 10000;    /**     * 获取商户证书内容     *     * @return 商户证书内容     */    @Override    public InputStream getCertStream()  {        File certFile = new File(certPath);        InputStream inputStream = null;        try {            inputStream = new FileInputStream(certFile);        } catch (FileNotFoundException e) {            log.error("cert file not found, path={}, exception is:{}", certPath, e);        }        return inputStream;    }    @Override    public String getKey(){        if (useSandbox) {            return sandboxKey;        }        return key;    }}

4. WXPayClient

WXPayClient 是对WXPay的一个封装,增加了microPayWithPOS方法,内部调用WXPay#microPay,但是sdk中的microPay并没有处理当微信支付时微信提示用户输入密码,这时sdk直接返回的错误,这里处理的逻辑就是在指定时间内去轮询支付结果,然后将轮询的结果返回出去,而不是就直接返回错误了,demo项目也有个microPayWithPOS实现,但是它的逻辑是当用户输入密码的情况会轮询的去下单去调用WXPay#microPay,这种做法是不符合微信的官方文档的。另外增加了获取沙箱环境API秘钥和解密退换通知的方法。

吐槽一下:感觉像这种解密方法WXPayUtil中竟然没有,这是必须的不可少的啊,怎么会没有呢,这也太烂了吧,这还称为sdk啊

/** * WXPayClient * 

* 对WXPay的简单封装,处理支付密切相关的逻辑. * * @author Mengday Zhang * @version 1.0 * @since 2018/6/16 */@Slf4jpublic class WXPayClient extends WXPay { /** 密钥算法 */ private static final String ALGORITHM = "AES"; /** 加解密算法/工作模式/填充方式 */ private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding"; /** 用户支付中,需要输入密码 */ private static final String ERR_CODE_USERPAYING = "USERPAYING"; private static final String ERR_CODE_AUTHCODEEXPIRE = "AUTHCODEEXPIRE"; /** 交易状态: 未支付 */ private static final String TRADE_STATE_NOTPAY = "NOTPAY"; /** 用户输入密码,尝试30秒内去查询支付结果 */ private static Integer remainingTimeMs = 10000; private WXPayConfig config; public WXPayClient(WXPayConfig config, WXPayConstants.SignType signType, boolean useSandbox) { super(config, signType, useSandbox); this.config = config; } /** * * 刷卡支付 * * 对WXPay#microPay(Map)增加了当支付结果为USERPAYING时去轮询查询支付结果的逻辑处理 * * 注意:该方法没有处理return_code=FAIL的情况,暂时不考虑网络问题,这种情况直接返回错误 * * @param reqData * @return * @throws Exception */ public Map microPayWithPOS(Map reqData) throws Exception { // 开始时间(毫秒) long startTimestampMs = System.currentTimeMillis(); Map responseMapForPay = super.microPay(reqData); log.info(responseMapForPay.toString()); // // 先判断 协议字段返回(return_code),再判断 业务返回,最后判断 交易状态(trade_state) // 通信标识,非交易标识 String returnCode = responseMapForPay.get("return_code"); if (WXPayConstants.SUCCESS.equals(returnCode)) { String errCode = responseMapForPay.get("err_code"); // 余额不足,信用卡失效 if (ERR_CODE_USERPAYING.equals(errCode) || "SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode)) { Map orderQueryMap = null; Map requestData = new HashMap<>(); requestData.put("out_trade_no", reqData.get("out_trade_no")); // 用户支付中,需要输入密码或系统错误则去重新查询订单API err_code, result_code, err_code_des // 每次循环时的当前系统时间 - 开始时记录的时间 > 设定的30秒时间就退出 while (System.currentTimeMillis() - startTimestampMs < remainingTimeMs) { // 商户收银台得到USERPAYING状态后,经过商户后台系统调用【查询订单API】查询实际支付结果。 orderQueryMap = super.orderQuery(requestData); String returnCodeForQuery = orderQueryMap.get("return_code"); if (WXPayConstants.SUCCESS.equals(returnCodeForQuery)) { // 通讯成功 String tradeState = orderQueryMap.get("trade_state"); if (WXPayConstants.SUCCESS.equals(tradeState)) { // 如果成功了直接将查询结果返回 return orderQueryMap; } // 如果支付结果仍为USERPAYING,则每隔5秒循环调用【查询订单API】判断实际支付结果 Thread.sleep(1000); } } // 如果用户取消支付或累计30秒用户都未支付,商户收银台退出查询流程后继续调用【撤销订单API】撤销支付交易。 String tradeState = orderQueryMap.get("trade_state"); if (TRADE_STATE_NOTPAY.equals(tradeState) || ERR_CODE_USERPAYING.equals(tradeState) || ERR_CODE_AUTHCODEEXPIRE.equals(tradeState)) { Map reverseMap = this.reverse(requestData); String returnCodeForReverse = reverseMap.get("return_code"); String resultCode = reverseMap.get("result_code"); if (WXPayConstants.SUCCESS.equals(returnCodeForReverse) && WXPayConstants.SUCCESS.equals(resultCode)) { // 如果撤销成功,需要告诉客户端已经撤销订单了 responseMapForPay.put("err_code_des", "用户取消支付或尚未支付,后台已经撤销该订单,请重新支付!"); } } } } return responseMapForPay; } /** * 解密退款通知 * * 退款结果通知文档 * @param reqInfo * @return * @throws Exception */ public Map decodeRefundNotify(String reqInfo) throws Exception { //(1)对加密串A做base64解码,得到加密串B byte[] bytes = new BASE64Decoder().decodeBuffer(reqInfo); //(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 ) Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING); SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(config.getKey()).toLowerCase().getBytes(), ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); // (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding) // java.security.InvalidKeyException: Illegal key size or default parameters // https://www.cnblogs.com/yaks/p/5608358.html String responseXml = new String(cipher.doFinal(bytes),"UTF-8"); Map responseMap = WXPayUtil.xmlToMap(responseXml); return responseMap; } /** * 获取沙箱环境验签秘钥API * 获取验签秘钥API文档 * @return * @throws Exception */ public Map getSignKey() throws Exception { Map reqData = new HashMap<>(); reqData.put("appid", config.getAppID()); reqData.put("mch_id", config.getMchID()); reqData.put("nonce_str", WXPayUtil.generateNonceStr()); String sign = WXPayUtil.generateSignature(reqData, config.getKey(), WXPayConstants.SignType.MD5); reqData.put("sign", sign); String responseXml = this.requestWithoutCert("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey", reqData, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs()); Map responseMap = WXPayUtil.xmlToMap(responseXml); return responseMap; }}

5. WXPayConfiguration

/** * 微信支付配置 * * @author mengday zhang */@Configuration@EnableConfigurationProperties(MyWXPayConfig.class)public class WXPayConfiguration {    @Autowired    private MyWXPayConfig wxPayConfig;    /**     * useSandbox true为沙盒环境     * @return     */    @Bean    public WXPay wxPay() {        return new WXPay(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox() );    }    @Bean    public WXPayClient wxPayClient() {        return new WXPayClient(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox());    }}

6. WXPayMicroPayController

/** * 微信支付-刷卡支付. * 

* detailed description * * @author Mengday Zhang * @version 1.0 * @since 2018/6/18 */@Slf4j@RestController@RequestMapping("/wxpay/microPay")public class WXPayMicroPayController { @Autowired private WXPayClient wxPayClient; /** * 刷卡支付(类似支付宝的条码支付) * * 和支付宝的好像不一样,支付宝有支付通知,但是微信好像没,微信有退款通知 * * 微信支付后台系统收到支付请求,根据验证密码规则判断是否验证用户的支付密码,不需要验证密码的交易直接发起扣款, * 需要验证密码的交易会弹出密码输入框。支付成功后微信端会弹出成功页面,支付失败会弹出错误提示 * 注意该接口有可能返回错误码为USERPAYING用户支付中 * * 验证密码规则 * ◆ 支付金额>1000元的交易需要验证用户支付密码 * ◆ 用户账号每天最多有5笔交易可以免密,超过后需要验证密码 * ◆ 微信支付后台判断用户支付行为有异常情况,符合免密规则的交易也会要求验证密码 * * 用户刷卡条形码规则:18位纯数字,以10、11、12、13、14、15开头 */ @PostMapping("") public Object microPay(String authCode) throws Exception { Map reqData = new HashMap<>(); // 商户订单号 reqData.put("out_trade_no", String.valueOf(System.nanoTime())); // 订单总金额,单位为分,只能为整数 reqData.put("total_fee", "2"); // 授权码 reqData.put("auth_code", authCode); // 商品描述 reqData.put("body", "测试"); Map resultMap = wxPayClient.microPayWithPOS(reqData); log.info(resultMap.toString()); return resultMap; }}

7. WXPayController

/** * 微信支付 - 通用API. * * 

* 类似支付宝中的条码支付. * * @author Mengday Zhang * @version 1.0 * @since 2018/6/15 */@Slf4j@RestController@RequestMapping("/wxpay")public class WXPayController { @Autowired private WXPay wxPay; @Autowired private WXPayClient wxPayClient; @Autowired private MyWXPayConfig wxPayConfig; /** * 订单查询 * @param orderNo * @return * @throws Exception */ @GetMapping("/orderQuery") public Object orderQuery(String orderNo) throws Exception { Map data = new HashMap<>(); data.put("out_trade_no", orderNo); Map result = wxPay.orderQuery(data); return result; } /** * 退款 * 注意:调用申请退款、撤销订单接口需要商户证书 * 注意:沙箱环境响应结果可能会是"沙箱支付金额不正确,请确认验收case",但是正式环境不会报这个错误 * 微信支付的最小金额是0.1元,所以在测试支付时金额必须大于0.1元,否则会提示微信支付配置错误,可以将microPay的total_fee大于1再退款 */ @PostMapping("/refund") public Object refund(String orderNo) throws Exception { Map reqData = new HashMap<>(); // 商户订单号 reqData.put("out_trade_no", orderNo); // 授权码 reqData.put("out_refund_no", orderNo); // 订单总金额,单位为分,只能为整数 reqData.put("total_fee", "2"); //退款金额 reqData.put("refund_fee", "2"); // 退款异步通知地址 reqData.put("notify_url", wxPayConfig.getNotifyUrl()); reqData.put("refund_fee_type", "CNY"); reqData.put("op_user_id", wxPayConfig.getMchID()); Map resultMap = wxPay.refund(reqData); log.info(resultMap.toString()); return resultMap; } /** * 退款结果通知 * * 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容 * @param request * @throws Exception */ @RequestMapping("/refund/notify") public String refundNotify(HttpServletRequest request) throws Exception {// Map notifyMap = new HashMap<>();// notifyMap.put("nonce_str", "9b4e428ae262d5dca96178027e849fa9");// notifyMap.put("req_info", "VKGj8c81RwQ45LcyWEVBE9/HsNfADGbgmbIAQZ2ydcbIFhIIcJFKFQwGfcSGgFGtQlWvg6KDNsRjmCjN+PvipJ3roynJ7cME0LOFG50VGtk4EYHqdjFzUVANI7GpT+i6Ok+ZWivH0MwoGK2fsz3WG+bYs2XJBwav/K89tKjFhZGitCKKBeGqcP99fa/gAL0swNXXNQHmL806Zi+QcACzL3E89BtP9FlXM2Gi+wPQafvPr+/iE+LrPdMlNUa5LiZnenZXUF24kMdhaTafczcKL4sZjRXQHEfEyc/pIZPbIjcNIETvHsskyzKuHVr/SAFkxaM6RR1Kl9pyWZGUjkH5SOeqsT8uL7YQmTlDXrnXmno3AvZdnepTGL5w69yIOmQNPeMqsd01ES9WX36GZYOigfi2+BJ9RRXjIffmpB/MFF+zryyvLTaJE2obCwFSHrmOD8YbaJqrZXOUvWZQrn7wIQgaCypo8V57cD3w5d2RSgIHNrdnEDYlbRcLNYgKuL+T9+1HPhU/frowZgwPN9IB53OahZV3p1Yvos23kvhqPCLn3BYgUihRbey6QhEtL2QyifiQ9e8WVLzWpRZ+DOa8VrhYvSuTfjRdjoNanqHFvXGP6uEsEa+DETqnexpB7xOS9m/CdmlNCwbdUplPEVzNQQdzYT4kybi00Y8A+EdairxfVyK9A7MAYAMtAO9yxV2ht0bn3SofFyZe/YSzdJgxdtcxBf1CVYN6x+yHcSueCSgq4cM/2VCwh4J1+pUVmNpEm0OVcdKbV5USkaxJR0h7Yd+n5FTz5Q2S/qvyDo202cUzLFPI5UqQm5X+FOrWDAkmmr5yVcDQIm3dAdb31jkz0X2TPYt5g7ciQ1h9heyVxJ67FexKvEM4pKCCubtWx6nyxcOUghHMrh8DSoBtewtNjbnwGVIbLsSb6X9MIYAkWIDbqNVP1f63GiZU+cJlhBmvcb8aeQUdTTj7EX5pOTIVSVv5D6SkKmpGU4FGvV+WjufuGX4ZRZo+01p6xl0sfZVmucG1UtxhX6bMCJb06yDwxpv7tGwkwS4TCK4SQp40Xe0=");// notifyMap.put("appid", "xxx");// notifyMap.put("mch_id", "xxx");// notifyMap.put("return_code", "SUCCESS"); // 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 // 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。 // 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 // TODO 处理业务 Map requstInfoMap = wxPayClient.decodeRefundNotify(request); // 商户处理退款通知参数后同步返回给微信参数 Map responseMap = new HashMap<>(); responseMap.put("return_code", "SUCCESS"); responseMap.put("return_msg", "OK"); String responseXml = WXPayUtil.mapToXml(responseMap); return responseXml; } /** * 退款查询 * @param orderNo * @return * @throws Exception */ @GetMapping("/refundQuery") public Object refundQuery(String orderNo) throws Exception { Map reqData = new HashMap<>(); reqData.put("out_trade_no", orderNo); Map result = wxPay.refundQuery(reqData); return result; } /** * 下载对账单 * 注意: * 微信在次日9点启动生成前一天的对账单,建议商户10点后再获取; * 对账单接口只能下载三个月以内的账单。 * @throws Exception */ @PostMapping("/downloadBill") public Object downloadBill(String billDate) throws Exception { Map reqData = new HashMap<>(); reqData.put("bill_date", billDate); reqData.put("bill_type", "ALL"); Map resultMap = wxPay.downloadBill(reqData); return resultMap; } /** * 获取沙箱环境API秘钥, * * 这里只是为了获取,可以放在main方法下运行,这里作为api来运行的,实际情况下不应该暴露出来 * @return * @throws Exception */ @GetMapping("/sandbox/getSignKey") public Object getSignKey() throws Exception { Map signKey = wxPayClient.getSignKey(); log.info(signKey.toString()); return signKey; }}

获取源码

关注并私信“微信刷卡支付”获取源代码。

在sdk中添加源文件_实用干货 | 一步一步教你在SpringBoot中集成微信刷卡支付相关推荐

  1. h5封装去底部_干货分享 | 一步一步教你在SpringBoot中集成微信支付H5支付

    一:开发文档场景介绍 H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付. 主要用于触屏版的手机浏览器请求微信支付的场景. ...

  2. 微擎支付返回商户单号_一步一步教你在SpringBoot中集成微信扫码支付

    一:准备工作 使用微信支付需要先开通服务号,然后还要开通微信支付,最后还要配置一些开发参数,过程比较多. 申请服务号(企业) 开通微信支付 开发配置 具体准备工作请参考Spring Boot入门教程( ...

  3. 微信刷卡 sdk java_微信支付 Java SDK

    微信支付 Java SDK 对微信支付开发者文档中给出的API进行了封装. com.github.wxpay.sdk.WXPay类下提供了对应的方法: 方法名 说明microPay 刷卡支付 unif ...

  4. zynq中mgtx应用_【干货分享】ZYNQ常用外设设计 (上)

    2. IO的电压为1.8V. 3. 默认使用的参数为:115200波特率以及其他对应参数,见图 21. Step2: 在Vivado的Block Design中配置ZYNQ7 Processing S ...

  5. ise仿真添加信号_「干货」推荐一款FPGA仿真调试鸟枪换炮的工具

    俗话说,隔行如隔山.非微电子专业没有做过芯片的同学,是不知道这个世界上还有效率更高的用来对Verilog/VHDL代码进行仿真的工具的.具体来讲,对于做FPGA开发的同学而言,需要知道除了ModelS ...

  6. mysql怎样在bat脚本中添加日志_如何在windows下用bat脚本定时备份mysql

    作/译者:叶金荣(Email: ),来源:http://imysql.cn,转载请注明作/译者和出处,并且不能用于商业用途,违者必究. 并不是所有MySQL都运行在Linux下,windows下也需要 ...

  7. 杂散干扰解决办法_实用干货——6种常见杂散问题的成因分析及解决办法

    原标题:实用干货--6种常见杂散问题的成因分析及解决办法 虽然目前的高分辨率SAR ADC和Σ-Δ ADC可提供高分辨率和低噪声,但可能难以实现数据手册上的额定SNR性能.而要达到最佳SFDR,也就是 ...

  8. 实用干货:7个实例教你从PDF、Word和网页中提取数据

    导读:本文的目标是介绍一些Python库,帮助你从类似于PDF和Word DOCX 这样的二进制文件中提取数据.我们也将了解和学习如何从网络信息源(web feeds)(如RSS)中获取数据,以及利用 ...

  9. threejs添加立方体_前端图形学(三十)——从源码去看threejs中的光照模型

    欢迎来到[畅哥聊技术]前端图形学相关技术文章,更多精彩内容持续更新中,敬请关注. 上章节回顾 熟悉了threejs中内置的几何图形的渲染原理就是通过顶点渲染 传入自定义顶点渲染自定义的几何图形 本章目 ...

最新文章

  1. java 短信平台_Java通过SMS短信平台实现发短信功能
  2. LeetCode 456 132 Pattern
  3. 将功补过 树形动态规划
  4. VS2012和XE2013的关联和设置问题
  5. django request对象和HttpResponse对象
  6. Java 多线程(六)——进程间通信与线程间通信
  7. 半数以上国产手游曾使用他开源的引擎:Cocos和王哲的故事 | 二叉树视频
  8. JQuery:视频+实战总结
  9. Unity字节序问题
  10. 安装wordpress时候报错:Parse error: syntax error, unexpected '.', expecting '' or variable (T_VARIABLE)
  11. 拓端tecdat|Python高维统计建模变量选择:SCAD平滑剪切绝对偏差惩罚、Lasso惩罚函数比较
  12. java发送邮件代码
  13. 好看的思维导图案例,你想做出来吗
  14. 史上最全的Android基础教程+入门实战训练+处理技巧(建议收藏)|寻找C站宝藏
  15. 最小割的必须割边和可行割边
  16. 前端追梦人CSS教程
  17. SSD固态盘 _测试总结
  18. 台式计算机如何安装6个系统?,联想台式机重装系统图文教程
  19. 科学计算机 分数计算公式,Z分数(标准分数,Z-Score)公式与在线计算器_三贝计算网_23bei.com...
  20. OSG第三方库编译之二十五:webp编译(Windows、Linux、Macos环境下编译)

热门文章

  1. VMWARE错误-“VirtualInfrastructure.Utils.ClientsXml“的类型初始值设定项引发异常
  2. 解决windows文件在linux系统中显示乱码的问题
  3. Bash中的管道输出和捕获退出状态
  4. HTML表中的自动换行
  5. Scala vs. Groovy vs. Clojure [已结束]
  6. jQuery / JavaScript:访问iframe的内容
  7. 以太坊私链搭建、truffle项目开发
  8. 职场疑问:如何进行技术面试
  9. webpack+gulp实现自动构建部署
  10. EIGRP的AD(管理距离)、AD(宣告距离)、FD(可行距离)