微信支付分(先享后付)对接记录:

微信支付分对接步骤

    1. 填写开通支付分的申请表格 此步骤大概需要审核 1-3 个工作日; (模板-服务信息配置表-【先享后付免确认】-【商户名】.xls) 填写商户信息 和回调地址(支持三套环境)
    1. 拿到审核结果 对技术而言 需要serviceId appid appSecret 和API密钥(签名用) apiclient_cert.xxx 证书文件(退款用)

      升级 V3 证书

    1. 仔细阅读微信官方对接文档
    1. 微信支付分

      4.1 网约车场景接入流程图(非官方) http://tomas.test.upcdn.net/java/%E8%AE%A2%E5%8D%95%E7%8A%B6%E6%80%81%E6%B5%81%E7%A8%8B%E5%9B%BE1%20(1).jpg

    4.2 接口说明

    1 用户下单叫车

    用户在商户端下单叫车,一般交互方式为:用户输入出发地/目的地呼叫车辆后,后台查询用户是否可使用服务,若否,则弹出开启微信支付分说明页或按钮,用户点击后,调用开启服务接口(跳转至微信端,进行服务开启),每个用户第一次使用微信支付分方式打车,均需进行一次开启动作。(仅需一次) 微信官方推荐做法个人认为流氓到极致. 我们对此做了改变 引导用户自己在产品中 开启免密支付服务 下单(接到乘客开始服务)时只需要MQ 通知 支付系统 创建免确认支付分订单即可.

    2 查询用户是否可使用服务

    ●接口名称:查询用户是否可使用服务

    ●文档名称:【先享后付-开启支付分服务】

    3.1 请求创建订单

    ●接口名称:创建先享后付订单

    ●文档名称:【先享后付-创建订单】

    3.2 引导用户开启服务

    ●接口名称:小程序跳转接口(开启服务) //文档提供了两种跳转方法,商户自行选择一种即可,此接口支持小程序/app/H5

    ●文档名称:【先享后付-引导用户开通】

    4 行程开始

    商户请求创建订单后,若用户【开启服务成功】+【风险评估通过】则会收到微信支付的异步回调通知,此时可提供网约车服务给用户。

    ●接口名称:用户确认订单通知商户规则

    ●文档名称:【先享后付-用户确认订单通知】

    该步骤可能出现创建订单失败、用户确认订单失败、回调通知异常等情况,导致长时间接收不到微信支付的异步回调通知,此时可进行查询订单状态操作,主动查询确认订单状态。

    ●接口名称:查询先享后付订单

    ●文档名称:【先享后付-查询订单详情】

    5 行程结束

    用户完成打车服务,行程结束

    6 请求完结订单

    商户根据行程订单金额,请求完结订单,发起扣款请求

    ●接口名称:完结先享后付订单

    ●文档名称:【先享后付-完结订单发起扣款】

    7 扣款成功

    微信支付完成扣款,并将扣款结果异步回调通知给商户

    ●接口名称:收款成功通知商户规则

    ●文档名称:【先享后付-扣款成功回调】

    该步骤可能出现扣款失败、回调通知异常等情况,导致长时间接收不到微信支付的异步回调通知,此时可进行查询订单状态操作,避免重复扣款。

    ●接口名称:查询先享后付订单

    ●文档名称:【先享后付-查询订单详情】

    超级坑的技术文档: 签名规则统一以下面网页介绍的为准:

    https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-sheng-cheng

下面是Java 接入过程:

第一步: 阅读微信支付分给的接口规则 说明: https://wechatpay-api.gitbook.io/wechatpay-api-v3/

第二步: 微信支付API v3要用第三方CA的证书 所以涉及到 API证书升级

  • 新接入商户请参考什么是API证书?如何获取API证书?

  • 已经接入并使用微信支付颁发证书的商户请参考微信支付API证书升级指引(技术人员)
    API v3已不支持使用微信支付颁发的证书。
    商户升级API证书时,需要完成三个步骤:

    ①:商户号的超级管理员到商户平台升级证书,获取到权威CA颁发的API证书。 (查看指引

    ②:超级管理员将权威CA颁发的API证书(共包含三个文件: 证书pkcs12格式、证书pem格式、证书密钥pem格式)转交给技术人员。

    ③:技术人员用新证书文件替换服务器上原微信支付颁发的API证书,无需对现有系统进行代码修改。

    (注意)这里升级API证书不影响原有的 API 密钥 代码不需要做改动直接替换 apiclient_cert.p12文件即可

第三步: 拿到API证书和密钥文件.

第四步: 引入微信支付API v3的Apache HttpClient装饰器: GitHub 地址

​ 注意: 微信支付API v3Apache HttpClient扩展,实现了请求签名的生成和应答签名的验证。如不想使用次封装客户端 可自己实现 签名和应答解密过程.

​ Maven

​ 加入JitPack仓库

    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

加入以下依赖

    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-apache-httpclient</artifactId>
        <version>0.1.4-SNAPSHOT</version>
    </dependency>

第五步: 请求微信支付分API前准备

   //微信支付商户开通后 微信会提供appid
    public String appId;

    //微信支付商户开通后 微信会提供appSecret
    public String appSecret;

    //商户号
    public String mchId; 

    //32位的api密钥,微信商户平台-账户设置-安全设置-api安全 密钥 用于拉起支付签名
    public String partnerkey;

    //openId 是微信用户针对公众号的标识,授权的部分这里不解释
    public String openId;

    //微信支付成功后异步通知地址 必须要求80端口并且地址不能带参数
    public String notifyUrl;

    //微信支付成功后同步通知地址 必须要求80端口并且地址不能带参数
    public String returnUrl;

    //证书apiclient_cert.p12文件位置 可加载
    public String certPath;

    //微信支付分 分配的服务 ID
    public String serviceId;

    //v3接口 CA证书 apiclient_key.pem私钥内容
    public String privateKey;

    //v3接口 CA证书 apiclient_cert.pem证书内容
    public String certificate;

    // APIv3密钥 32 位
    public String AES_KEY = "xxx"; 

    //商户证书序列号 CA证书 可查看微信商户平台-账户设置-安全设置-api安全密钥
    public String  MC_HSERIAL_NO = "xxxxx";

第六步: 请求支付分 API : 签约 创建订单 确认订单

​ DEMO参见: https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/blob/master/src/test/java/com/wechat/pay/contrib/apache/httpclient/HttpClientBuilderTest.java

几个示例核心代码:

示例: 查询用户签约状态
    public static final String USER_SERVICE_STATE_URL = "https://api.mch.weixin.qq.com/payscore/user-service-state";

   PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
        new ByteArrayInputStream(privateKey.getBytes("utf-8")));
//privateKey= //v3接口 CA证书 apiclient_key.pem私钥内容 

    httpClient = WechatPayHttpClientBuilder.create()
        .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
        .withValidator(response -> true)
        .build();
  URIBuilder uriBuilder =   new URIBuilder(USER_SERVICE_STATE_URL);
                        uriBuilder.setParameter("service_id", yourServiceIdxxx);
                        uriBuilder.setParameter("appid", yourAppIdxxx);
                        uriBuilder.setParameter("openid", userOpenIdxxx);
 CloseableHttpResponse response=null;
        try {
            HttpGet httpGet = new HttpGet(uriBuilder.build());
            httpGet.addHeader("Accept", "application/json");
            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
            response = getHttpDefaultClient().execute(httpGet);
            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                String result = EntityUtils.toString(response.getEntity());// 返回json格式:
                return JSONObject.parseObject(result);
            }else {
                String result = EntityUtils.toString(response.getEntity());// 返回json格式:
                log.info("微信支付V3 url={} result={} responseEntity={}",uriBuilder.build(), result,JSON.toJSONString(response.getEntity()));
            }
        } catch (Exception e) {
            log.error("微信支付V3 请求url={}异常 ",uriBuilder.build());
        }finally {
            if(null!=response){
                response.close();
            }
        }

 示例: 创建支付分订单 

     public static final String PAYSCORE_PAYAFTER_ORDERS_URL = "https://api.mch.weixin.qq.com/v3/payscore/payafter-orders";

  //【先享后付-创建订单】参数 转化成javaBean/或者 JSONOjbect 设置参数 

 //注意我这里need_user_confirm 参数 如果传 false 表不需要用户确认
   PayAfterOrdersModel  payAfterOrdersModel=PayAfterOrdersModel.builder().appid(appId)
      .need_user_confirm(Boolean.FALSE).openid(recordModel.getOpenId()).risk_amount(PAY_SIGN_RISK_AMOUNT).out_order_no(model.getObjId())
          .service_start_time(DateTimeUtils.dateFormat(model.getServiceStartTime(),"yyyyMMddHHmmss"))
                  .service_start_location(WxPayUtil.subString(model.getServiceStartLocation(),20)).service_introduction(PAY_SIGN_FEE_DESC).service_id(config.getServiceId()).build();

 Fees fee=new Fees();
        fee.setFee_name("xx费");
        fee.setFee_amount(amount); //支付金额
        fee.setFee_desc("xxx");
        List<Fees> fees=new ArrayList<>();
        fees.add(fee);
payAfterOrdersModel.setFees(fees);
 //如果有优惠请设置优惠信息  此处不做设置

 //构造 Client
   PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
        new ByteArrayInputStream(privateKey.getBytes("utf-8")));
//privateKey= //v3接口 CA证书 apiclient_key.pem私钥内容 

    httpClient = WechatPayHttpClientBuilder.create()
        .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
        .withValidator(response -> true)
        .build();
 //请求支付分
  HttpPost httpPost = new HttpPost(PAYSCORE_PAYAFTER_ORDERS_URL);
        StringEntity reqEntity = new StringEntity(JSONObject.toJSONString(payAfterOrdersModel), ContentType.create("application/json", "utf-8"));
        httpPost.setEntity(reqEntity);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                String result = EntityUtils.toString(response.getEntity());// 返回json格式:
                log.info("微信支付V3 url={} result={} ",url,result);
                return JSONObject.parseObject(result);
            }else {
                String result = EntityUtils.toString(response.getEntity());// 返回json格式:
                log.info("微信支付V3 url={} result={} response.getEntity()={}",url,result,JSON.toJSONString(response.getEntity()));
            }
        } catch (Exception e) {
            log.error("微信支付V3 请求url={} 参数={} 异常 e={}",url, JSON.toJSONString(json),e.getMessage());
        }finally {
            response.close();
        }

  后续 完结订单   (注意请求完结订单的时有个finish_ticket 只有用户确认了订单才能拿到) 如果是创建的免确认订单 需要创建完订单后 查询订单详情 返回值中有 finish_ticket字段
 

第七步: 接收回调消息:

示例:

``

@Override
public ResultModel paidNotify(HttpServletRequest request) {
    try {
        //获取回调内容
        String bodyContent = RequestUtil.getBody(request);
        log.info("paidNotify bodyContent={}",bodyContent);
        if(StringUtils.isBlank(bodyContent)){
            return ResultModel.getError("XXX")
        }
        JSONObject jsonObject = JSONObject.parseObject(bodyContent);
        log.info("paidNotify getBody={} ",JSON.toJSONString(jsonObject));
        if(!jsonObject.containsKey("resource")){
            return  return ResultModel.getError("XXX")
        }else {
            JSONObject resource = jsonObject.getJSONObject("resource");
            //注意①: jdk版本
            //注意② aesgcmDecrypt解密算法 不要用引入jar包的AESUtil.aesgcmDecrypt解密算法  文章最后找源码
            String aesgcmDecrypt = WxPayUtil.aesgcmDecrypt(resource.getString("associated_data"), resource.getString("nonce"), resource.getString("ciphertext"));
            JSONObject coreDatax =JSONObject.parseObject(aesgcmDecrypt);
            log.info("coreDatax={}",JSON.toJSONString(coreDatax));
            if(coreDatax.containsKey("out_request_no")&& coreDatax.containsKey("state") && coreDatax.containsKey("pay_succ_time")){
                String outRequestNo = coreDatax.getString("out_request_no");

                if (USER_PAID.equals(coreDatax.getString("state"))){
                    //如果是支付成功  返回成功
                    //todo 自己的支付逻辑
                     return ResultModel.getSuccess();
                }else {
                   //失败返回 自定义失败 code msg 由 Controller 层处理返回值状态码200 还是500
                   return ResultModel.getError("xxx");
                }
            }
            return ResultModel.getResult(PAY_SIGN_OPEN_NOTIFY_CONTENT_ERROR.getCode(),PAY_SIGN_OPEN_NOTIFY_CONTENT_ERROR.getDesc(),null);
        }
    }catch (Exception e){
        log.info("paidNotify 回调异常={} ",e.getMessage());
        return ResultModel.getError("解析数据异常"+e.getMessage());
    }
}

/**
     * https://pay.api.olayc.cn/olayc/pay/v1/payscore/paidNotify
     * 回调通知-支付成功
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("payscore/paidNotify")
    public void paidNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("支付分订单完成 回调通知");
        Map<String,String> resMap=new HashMap<>();
        try {
            ResultModel resultModel = payScoreService.paidNotify(request);
            if (resultModel.isSuccess()){
                response.setStatus(SC_OK);
            }else {
                resMap.put("message",resultModel.getMsg());
                resMap.put("code",resultModel.getCode().toString());
                response.setStatus(SC_INTERNAL_SERVER_ERROR);
            }
        }catch (Exception e){
            log.error("支付分订单-支付成功 回调通知:{}",e.getMessage());
            resMap.put("code","999");
            resMap.put("message","解析数据异常"+e.getMessage());
            response.setStatus(SC_INTERNAL_SERVER_ERROR);
        }
       try {
            response.setContentType("application/json;charset=UTF-8");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            response.getWriter().write(content);
            response.getWriter().flush();
            response.getWriter().close();
            log.info("responseJson content={} " ,content);
        } catch (IOException e) {
           log.error("ajaxJson 异常信息={}",e.getMessage());
        }
    }

注意①:
使用Java加载密钥时,抛出异常InvalidKeyException: Illegal key size
受到美国法律的约束,早期Java的运行时限制了JCE支持的密钥长度,即默认不支持256位的AES。解决的方法有三个:
(推荐)升级Java 8u162+,默认使用ulimited policy
Java 8u151和8u152,可以在你的程序中直接放开策略
Security.setProperty("crypto.policy", "unlimited");
其他版本,下载无限强度权限策略文件补丁包,并使用其中的文件覆盖$JAVA_HOME/lib/security目录下的对应的local_policy.jar 和 US_export_policy.jar
Java9及以上,均无限制。
https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/api-v3-mi-yao-xiang-guan#shi-yong-java-jia-zai-mi-yue-shi-pao-chu-yi-chang-invalidkeyexception-illegal-key-size

注意②:
  WxPayUtil.java  aesgcmDecrypt方法

  private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;
    private static final int NONCE_LENGTH_BYTE = 12;
    private static final String AES_KEY = "xxxx"; // APIv3密钥
    public  static final  String MC_HSERIAL_NO = "xxx"; // 商户证书序列号

    public static String aesgcmDecrypt(String aad, String iv, String cipherText) throws Exception {
        Security.setProperty("crypto.policy", "unlimited");
        final Cipher cipher = Cipher.getInstance(ALGORITHM, "SunJCE");
        SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(), "AES");
        GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        cipher.updateAAD(aad.getBytes());
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }

对接过程中遇到一般技术问题去这里找下: https://developers.weixin.qq.com/community/pay/doc/0004060fa7c65855d698166145b808
API 和密钥 升级链接问题: http://kf.qq.com/product/wechatpaymentmerchant.html#hid=2874
对接支付的一些文档: http://kf.qq.com/product/wechatpaymentmerchant.html#hid=2805

如果还是没有答案: 可以发邮件 wepayTS@tencent.com; 注意: 邮件主题 商户登录手机+商户支付邮箱+开发问题(Internet mail)
还可以加微信技术支持: 微信号: WePayTS3 询问支付分相关问题
其实我也不知道微信有多少个 WePayTSx 都可以加下试试 WePayTS1 WePayTS2 WePayTS3 WePayTS4 ...
打电话 电话是永远打不通滴小老弟~

Java 微信支付分对接记录 (先享后付)的更多相关文章

  1. JAVA微信支付~

    1,简单说明 现在好多项目上都需要用到微信支付接口,官方文档上也是简单的描述了下,技术不高深的真的难以理解(我自己看官方文档就看不懂),还是需要自己收集,总结, 网上看了好多 有些照着弄最后还是没法成 ...

  2. JAVA 微信支付 native方式

    最近做了一个微信native方式支付的demo,整理一下. 首先到微信公众号官网阅读开发文档,虽然文档对于java没有例子,但是也可以作参考.https://pay.weixin.qq.com/wik ...

  3. JAVA微信支付代码(WeChatPay.java 才是调用类)

    微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html MD5Util.java package weixin; import java.se ...

  4. Java+微信支付(下预购单+回调+退款+查询账单)

    前言: 现在的APP的离不开微信支付, 现在项目里接入微信支付 , 微信支付的官方文档是:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chap ...

  5. JAVA微信支付——微信公众号内支付 代码

    官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 微信PC二维码支付方式参考:https://www.cnblogs. ...

  6. JAVA微信支付——企业付款(企业向微信用户个人付款、转账)

    本地开发环境支付回调调试方法可以参考:https://www.cnblogs.com/pxblog/p/11623053.html 需要自行引入相关依赖 官方文档地址:https://pay.weix ...

  7. JAVA微信支付接口开发——支付

    微信支付接口开发--支付 这几天在做支付服务,系统接入了支付宝.微信.银联三方支付接口.个人感觉支付宝的接口开发较为简单,并且易于测试. 关于数据传输,微信是用xml,所以需要对xml进行解析. 1. ...

  8. 170327、Java微信支付中的扫码支付

    微信支付现在已经变得越来越流行了,随之也出现了很多以可以快速接入微信支付为噱头的产品,不过方便之余也使得我们做东西慢慢依赖第三方,丧失了独立思考的能力,这次打算分享下我之前开发过的微信支付. 一 H5 ...

  9. 微信支付重复回调,java微信支付回调问题

    这几天一直在研究微信支付回调这个问题,发现之前微信支付回调都是正常的也没怎么在意,今天在自己项目上测试的时候发现相同的代码在我这个项目上微信支付回调老是重复执行导致支付成功之后的回调逻辑一直在执行,很 ...

随机推荐

  1. Java生鲜电商平台-电商促销业务分析设计与系统架构

    Java生鲜电商平台-电商促销业务分析设计与系统架构 说明:Java开源生鲜电商平台-电商促销业务分析设计与系统架构,列举的是常见的促销场景与源代码下载 左侧为享受促销的资格,常见为这三种: 首单 大 ...

  2. 苹果审核之遇到IPV6问题被拒的解决方法

    情景: 等待苹果审核上线时,发现因为IPV6被拒了.这是悲剧,以下是苹果审核给我的理由: We discovered one or more bugs on Wi-Fi connected to an ...

  3. Can't toast on a thread that has not called Looper.prepare()

    Android开发中Can't toast on a thread that has not called Looper.prepare()问题 说一下问题出现场景: 在一个Android项目中,利用 ...

  4. Struts2 运行流程

    Struts2运行流程 1.在web.xml中使用Struts的核心过滤器拦截所有请求. <filter> <filter-name>struts2</filter-na ...

  5. vue定时器+弹框 跳到登陆页面

    1.做一个请求拦截,并弹框提示几秒后,跳转到登陆首页或是点击确定之后直接跳转拦截用了this.$axios.interceptors.response页面上的弹框组件用了vux的组件vux地址:htt ...

  6. Django forms 主要的标签介绍

    修改 forms.py from django import forms as DForms from django.forms import fields from django.forms imp ...

  7. lf 前后端分离 (3) 中间建跨域

    一.关于中间建跨域 为了减少跨域代码冗余,采用中间件 from django.utils.deprecation import MiddlewareMixin class CorsMiddleware ...

  8. JS高阶---闭包面试题

    [面试题1] 答案:The Window 分析: 本案例里,不存在闭包. 条件: .函数嵌套(满足) .内部函数调用外部函数变量(没有) 综上所述,该例中不存在闭包 [面试题2] 答案:My Obje ...

  9. python-新建文件夹

    import tensorflow as tf import os categories = ['folder1', 'folder2'] for folderName in categories: ...

  10. Linux下的SVN服务器搭建(八)

    1. 通过yum命令安装svnserve yum -y install subversion #查看svn安装位置 rpm -ql subversion 2. 创建版本库目录(此仅为目录,为后面创建版 ...