1. 程式人生 > >應用寶-ysdk-米大師 對接道具直購伺服器端下單模式 java服務端開發日誌

應用寶-ysdk-米大師 對接道具直購伺服器端下單模式 java服務端開發日誌

用於接收回調請求的linux伺服器證書配置:

回撥伺服器配置分三種,前兩種是當服務部署在騰訊雲上時的配置方式,第三種為服務部署在自己伺服器上是的配置方式: hosting應用on CVM(即應用部署在騰訊CVM伺服器上): -發貨URL只需HTTP協議即可,不需要使用SSL安全協議。 -必須使用9001埠(內網埠,需開發者主動啟用,用apache iis或nginx做一個web監聽,埠改成9001)。 hosting應用on CEE_V2(即應用部署在騰訊CEE_V2伺服器上): -發貨URL只需HTTP協議即可,不需要使用SSL安全協議。 -必須使用9001埠(內網埠,需開發者主動啟用,用apache iis或nginx做一個web監聽,埠改成9001)。 -路徑必須以ceecloudpay開頭,即支付相關程式碼必須都放到應用根目錄下的“ceecloudpay”目錄下。 -對於CEE其發貨URL的IP只能填寫為10.142.11.27或者10.142.52.17(詳見:CEE_V2訪問雲支付)。 non-hosting應用(即應用部署在開發者自己的伺服器上) -發貨URL必須使用HTTPS協議。 -必須使用443埠(外網埠)。 這裡採用non-hosting應用配置方式,方式為在linux伺服器的nginx/conf/nginx.conf檔案下新增如下配置(nginx版本1.14.0):

# HTTPS server
server {
    listen                      443 ssl;
    server_name                 localhost;
    #"/usr/.../"為自己放證書檔案的目錄地址
    ssl_certificate             /usr/.../1**_**6.crt;
    ssl_certificate_key         /usr/.../1**_**6.key;
    ssl_client_certificate      /usr/.../ca.crt;
     
    ssl_session_cache           shared:SSL:1m;
    ssl_session_timeout         5m;

    ssl_ciphers                 HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers   on;
}

配置完後重新載入nginx使配置生效。

伺服器下單:

伺服器請求下單引數:

HashMap<String, String> params = new HashMap<>();//params型別不能是Map<String,String>會報錯,需要改為HsahMap<String,String>
//必填引數
params.put("openid", "");//app登陸獲取
params.put("openkey", "");//app登陸獲取
params.put("appid", APP_ID);//應用id
params.put("ts", String.valueOf(System.currentTimeMillis() / 1000L));//時間戳(單位:秒)
params.put("payitem", product_id + "*" + price * 10 + "*1");//使用x*p*num的格式,x表示物品ID,p表示單價(以Q點為單位,1Q幣=10Q點,單價的制定需遵循騰訊定價規範),num表示預設的購買數量
params.put("goodsmeta", "充值*金幣充值");//物品資訊,格式必須是name*des,批量購買套餐時也只能有1個道具名稱和1個描述,即給出該套餐的名稱和描述
params.put("goodsurl", "http://.../coin.png");//物品的圖片url(長度<512字元)
params.put("pf", "");//app登陸獲取
params.put("pfkey", "");//app登陸獲取
params.put("zoneid", "1");//應用如果沒有分割槽:傳zoneid=1
//可選引數
params.put("amt", "1");//道具總價值。應用寶支援的qq支付和微信支付方式會有折扣(qq點和qq卡一般沒有折扣),導致回撥的amt數值與請求下單時的amt值不一致,所以回撥避免用amt做金額驗證,如有必要,可以在回撥程式碼裡採用Double.parseDouble(payitem.split("\\*")[1])的值做金額驗證
params.put("appmode", "1");//(可選)1表示使用者不可以修改物品數量,2 表示使用者可以選擇購買物品的數量
params.put("format", "json");//json、jsonp_$func。預設json。如果jsonp,字首為:$func 例如:format=jsonp_sample_pay,返回格式字首為:sample_pay()
params.put("userip", "");//使用者ip
params.put("app_metadata", "");//發貨時透傳給應用。長度必須<=128字元,可以傳商戶id,便於回撥後續做驗證

生成簽名:

//SnsSigCheck為騰訊開放平臺提供的簽名方法:[SDK下載](http://qzonestyle.gtimg.cn/qzone/vas/opensns/res/doc/Java_SDK_V3.0.6.zip)
params.put("sig", SnsSigCheck.makeSig("POST", "/v3/r/mpay/buy_goods_m", params,APP_KEY + "&"));//java呼叫簽名方法必須用post請求,APP_KEY為app金鑰

伺服器下單請求cookie:

//不必自己生成cookie直接呼叫官方工具包的方法,所以直接傳入HashMap<String,String>作為cookie的配置即可
HashMap<String, String> cookies = new HashMap<>();
if ("qq".equalsIgnoreCase(txOpenPayOrderPost.getLoginType())) {//這裡的wx/qq需前端傳入
    cookies.put("session_id", "openid");
    cookies.put("session_type", "kp_actoken");
    cookies.put("org_loc", SnsSigCheck.encodeUrl("/v3/r/mpay/buy_goods_m"));//org_loc的值需用官方工具類呼叫SnsSigCheck.encodeUrl方法做urlEncode,java自帶的URLEncoder.encode方法因為不符合騰訊的規範,不建議使用
} else if ("wx".equalsIgnoreCase(txOpenPayOrderPost.getLoginType())) {
    cookies.put("session_id", "hy_gameid");
    cookies.put("session_type", "wc_actoken");
    cookies.put("org_loc", SnsSigCheck.encodeUrl("/v3/r/mpay/buy_goods_m"));
}

伺服器下單請求方法:

//https://ysdktest.qq.com/mpay/buy_goods_m為測試伺服器
String orderInfo = SnsNetwork.postRequest("https://ysdktest.qq.com/mpay/buy_goods_m", params, cookies,"http");

請求正確的返回值為:

{
"ret": 0,
"token": "7***2",
"url_params": "/**/***/**/mobile_goods_info?token_id=7**2",
"attach": ""
}

請求錯誤的返回值為:

{
"ret": **,//不為0的值
"msg": ***
}

最好做一個判斷:

JSONObject jsonObject = JSON.parseObject(orderInfo);
if (jsonObject.getIntValue("ret") == 0) {
    logger.error("下單成功");
} else {
    logger.error("下單失敗");
}

支付回撥:

//支付回撥的介面必須部署在前面配置了證書的linux伺服器上,並在應用寶配置介面做配置
@RequestMapping(value = "/notify")
@ResponseBody
public String verify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HashMap<String, String> params = (HashMap<String, String>) RequestParamsUtil.parseParams(request);
    logger.error(params.toString());
    String appid = params.get("appid");//應用的唯一ID。可以通過appid查詢APP基本資訊。
    String appmeta = params.get("appmeta");//透傳引數 可以用來獲取商戶訂單號
    String billno = params.get("billno");//支付流水號(64個字元長度。該欄位和openid合起來是唯一的)。
    String token = params.get("token");//應用呼叫v3/pay/buy_goods介面成功返回的交易token
    String payitem = params.get("payitem");//接收標準格式為ID*price*num   G001*10*1
    String sig = params.get("sig");//伺服器簽名
    Long orderId = Long.parseLong(appmeta.split("\\*")[0]);//從appmeta中擷取的透傳引數商戶支付流水號
    Double amount = Double.parseDouble(payitem.split("\\*")[1]);
    boolean signVerified = SnsSigCheck.verifySig(request.getMethod(), "/v3/r/mpay/buy_goods_m", params,
            APP_KEY + "&", sig);//此處嘗試呼叫官方工具類驗證失敗
    if(signVerified){
        logger.error("驗證成功");
        //業務程式碼,自定義資訊驗證及資料庫資訊回寫等
    }else{
        logger.error("驗證失敗");
        return JSONObject.toJSONString(mkResultMap(-2, "簽名錯誤"));//這裡的ret值會影響之後騰訊給前端回撥的錯誤碼值,計算方式為500000-ret.例如此處ret=-2,回撥錯誤碼即為499998,ret=2,回撥錯誤碼為500002,利用這一點可以寫出分類比較詳細的錯誤驗證
    }
}
/**
* 生成成功回覆結果map
*
* @param ret 結果code
* @param msg 結果msg
*/
private Map<String, Object> mkResultMap(int ret, String msg) {
    Map<String, Object> map = new HashMap<>();
    map.put("ret", ret);
    map.put("msg", msg);
    return map;
}

其中RequestParamsUtil工具類程式碼:

    /**
    * 根據請求資料獲取引數值map
    *
    * @param request HttpServletRequest請求物件
    * @return HttpServletRequest內封裝的請求引數構建的map(key(引數名):value(引數值))
    */
    public static Map<String, String> parseParams(HttpServletRequest request) {
        Map<String, String[]> requestParams = request.getParameterMap();
        Map<String, String> params = new HashMap<>();
        for (String name : requestParams.keySet()) {
            String[] values = requestParams.get(name);
            StringBuilder valueStr = new StringBuilder();
            for (int i = 0; i < values.length; i++) {
                valueStr.append(
                        (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",");
            }
            params.put(name, valueStr.toString());
        }
        return params;
    }