博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 微信支付分对接记录 (先享后付)
阅读量:5357 次
发布时间:2019-06-15

本文共 12297 字,大约阅读时间需要 40 分钟。

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

微信支付分对接步骤

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

      升级 V3 证书

    1. 仔细阅读 对接流程
    1. 微信支付分-网约车场景接入流程图(非官方)

      4.1img

    4.2 接口说明

    1 用户下单叫车

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

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

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

    ●文档名称:

    3.1 请求创建订单

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

    ●文档名称:

    3.2 引导用户开启服务

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

    ●文档名称:

    4 行程开始

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

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

    ●文档名称:

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

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

    ●文档名称:

    5 行程结束

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

    6 请求完结订单

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

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

    ●文档名称:

    7 扣款成功

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

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

    ●文档名称:

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

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

    ●文档名称:

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

下面是Java 接入过程:

第一步: 阅读微信支付分给的接口规则 说明:

第二步:

  • 新接入商户请参考。

  • 已经接入并使用微信支付颁发证书的商户请参考。

    API v3已不支持使用微信支付颁发的证书。
    商户升级API证书时,需要完成三个步骤:

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

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

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

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

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

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

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

​ Maven

​ 加入JitPack仓库

jitpack.io
https://jitpack.io

加入以下依赖

com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.1.4-SNAPSHOT

第五步: 请求微信支付分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参见:

几个示例核心代码:

示例: 查询用户签约状态    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=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字段

第七步: 接收回调消息:

示例:

``

@Overridepublic 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
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 policyJava 8u151和8u152,可以在你的程序中直接放开策略Security.setProperty("crypto.policy", "unlimited");其他版本,下载无限强度权限策略文件补丁包,并使用其中的文件覆盖$JAVA_HOME/lib/security目录下的对应的local_policy.jar 和 US_export_policy.jarJava9及以上,均无限制。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注意②: 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))); }

对接过程中遇到一般技术问题去这里找下:

API 和密钥 升级链接问题:
对接支付的一些文档:

如果还是没有答案: 可以发邮件 wepayTS@tencent.com; 注意: 邮件主题 商户登录手机+商户支付邮箱+开发问题(Internet mail)

还可以加微信技术支持: 微信号: WePayTS3 询问支付分相关问题
其实我也不知道微信有多少个 WePayTSx 都可以加下试试 WePayTS1 WePayTS2 WePayTS3 WePayTS4 ...
打电话 电话是永远打不通滴小老弟~

转载于:https://www.cnblogs.com/wangdaijun/p/11194690.html

你可能感兴趣的文章
Centos8 新功能记录笔记
查看>>
The "Go" Learning Trip -- 0. Base build
查看>>
English trip EM3-LP 2A Around The World Teacher: Olivia
查看>>
[转] CVE-2019-1367 脚本引擎内存损坏漏洞
查看>>
English trip EM3-PE 2A Teacher:Lamb
查看>>
English trip EM3 LP 1B Have a good weekend Teacher:Ryan
查看>>
(转)CentOS / RHEL 7/6 自动更新安全补丁
查看>>
English trip EM3 LP4A ROOMS Teacher:Lamb
查看>>
English trip EM3-PE 4A Rooms Teacher:Taylor
查看>>
English trip EM3 LP 4B Home Design Relationship Teacher:Ryan
查看>>
I4-6 Surprise Teacher:Lamb Talk about Holidays and Festives People do what
查看>>
我追求的是什么
查看>>
AJAX的出现与跨域处理
查看>>
C++报错:error C3874
查看>>
U盘安装Ubuntu14.04&配置远程win10远程连接
查看>>
读取经纬度坐标并存储为字典格式,即key为ID,value为轨迹点
查看>>
解决:error LNK1169: 找到一个或多个多重定义的符号
查看>>
ubuntu下安装软件时报错解决:Unmet dependencies. Try 'apt-get -f install' with no packages
查看>>
关于集合的相似度测量方法
查看>>
postman
查看>>