Java 微信支付分對接記錄 (先享後付)

微信支付分(先享後付)對接記錄:

微信支付分對接步驟

    1. 填寫開通支付分的申請表格 此步驟大概須要審覈 1-3 個工做日; (模板-服務信息配置表-【先享後付免確認】-【商戶名】.xls) 填寫商戶信息 和回調地址(支持三套環境)
    1. 拿到審覈結果 對技術而言 須要serviceId appid appSecret 和API密鑰(簽名用) apiclient_cert.xxx 證書文件(退款用)php

      升級 V3 證書html

    1. 仔細閱讀微信官方對接文檔 對接流程
    1. 微信支付分java

      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).jpggit

    4.2 接口說明github

    1 用戶下單叫車ajax

    用戶在商戶端下單叫車,通常交互方式爲:用戶輸入出發地/目的地呼叫車輛後,後臺查詢用戶是否可以使用服務,若否,則彈出開啓微信支付分說明頁或按鈕,用戶點擊後,調用開啓服務接口(跳轉至微信端,進行服務開啓),每一個用戶第一次使用微信支付分方式打車,均需進行一次開啓動做。(僅需一次) 微信官方推薦作法我的認爲流氓到極致. 咱們對此作了改變 引導用戶本身在產品中 開啓免密支付服務 下單(接到乘客開始服務)時只須要MQ 通知 支付系統 建立免確認支付分訂單便可.算法

    2 查詢用戶是否可以使用服務apache

    ●接口名稱:查詢用戶是否可以使用服務json

    ●文檔名稱:【先享後付-開啓支付分服務】小程序

    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 ... 打電話 電話是永遠打不通滴小老弟~

相關文章
相關標籤/搜索