当前位置: 代码迷 >> Android >> android平台接入服务器小结(一)腾讯qq应用宝接入
  详细解决方案

android平台接入服务器小结(一)腾讯qq应用宝接入

热度:67   发布时间:2016-04-28 06:04:24.0
android平台接入服务器总结(一)腾讯qq应用宝接入

腾讯开放平台的接入是非常麻烦的, open.qq.com,腾讯开放平台的文档很多很杂,社交功能的api接口也很多还有。我现在只接了他的登录跟支付。

一、登录。

登录相对来讲还是比较简单的,首先前端sdk要正确接入获取access_token  跟 openid ,然后需要一个https  方式的get请求来取得进一步的信息。

url :https://graph.qq.com/user/get_simple_userinfo?oauth_consumer_key=%s&access_token=%s&openid=%s&clientip=&oauth_version=2.a&scope=all  

填写好自己应用的所有内容。https协议的java实现

import java.io.BufferedReader;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.URL;import java.util.Map;import java.util.Map.Entry;import javax.net.ssl.HostnameVerifier;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLSession;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.ByteArrayEntity;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.params.HttpConnectionParams;import org.apache.http.params.HttpParams;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class MyhttpService {    private  int read_time_out = 10000;    private Logger logger = LoggerFactory.getLogger(MyhttpService.class);        public MyhttpService() {        super();    }        public MyhttpService(int time_out) {        read_time_out = time_out;    }        public String doPost(String url, Map<String,String> params){        StringBuilder postData = new StringBuilder();        for(Entry<String,String> entry:params.entrySet()){            if(postData.length()!=0){                postData.append("&");            }            postData.append(entry.getKey()).append("=").append(entry.getValue());        }        return service(false, url, postData.toString(), "POST", null);    }    public String doPost(String url, Map<String,String> params,Map<String,String> headers){        StringBuilder postData = new StringBuilder();        for(Entry<String,String> entry:params.entrySet()){            if(postData.length()!=0){                postData.append("&");            }            postData.append(entry.getKey()).append("=").append(entry.getValue());        }        return service(false, url, postData.toString(), "POST", headers);    }    public String doPost(String url,String body){        return service(false, url, body, "POST", null);    }        public String doPost(String url, String postData, Map<String,String> headers){        return service(false, url, postData, "POST", headers);    }        public String doGet(String url, Map<String,String> headers){        return service(false, url, null, "GET", headers);    }        public String doGet(String url){        return service(false, url, null, "GET", null);    }        public String doHttpsPost(String url, String postData) {        return service(true, url, postData, "POST",null);    }    public String doHttpsPost(String url, Map<String,String> params){        return  doHttpsPost(url,params,null);    }        public String doHttpsPost(String url, Map<String,String> params,Map<String,String> headers){        StringBuilder postData = new StringBuilder();        for(Entry<String,String> entry:params.entrySet()){            if(postData.length()!=0){                postData.append("&");            }            postData.append(entry.getKey()).append("=").append(entry.getValue());        }        return service(true, url, postData.toString(), "POST", headers);    }    public String doHttpsGet(String url) {        return service(true, url, null, "GET",null);    }        private String service(boolean isHttps, String url, String postData, String method, Map<String,String> headers){                HttpURLConnection conn = null;        try {            boolean doOutput = postData != null && postData.equals("");            conn = isHttps ? createHttpsConn(url, method, doOutput) : createHttpConn(url, method, doOutput);            fillProperties(conn, headers);            if(doOutput) writeMsg(conn, postData);            String msg = readMsg(conn);            logger.debug(msg);            return msg;        } catch (Exception ex) {            logger.error(ex.getMessage(), ex);        } finally {            if (conn != null) {                conn.disconnect();                conn = null;            }        }        return null;    }    private HttpURLConnection createHttpConn(String url, String method, boolean doOutput) throws IOException {        URL dataUrl = new URL(url);        HttpURLConnection conn = (HttpURLConnection) dataUrl.openConnection();        conn.setReadTimeout(read_time_out);        conn.setRequestMethod(method);        conn.setDoOutput(doOutput);        conn.setDoInput(true);        return conn;    }        public static void main(String[] args) {//      System.out.println(DigestUtils.md5DigestAsHex("19a98d31-4652-4b94-b7cd-129e8ddaliji11899CNY68appstoreQY7road-16-WAN-0668ddddSHEN-2535-7ROAD-shenqug-lovedede77".getBytes()));    }    private String readMsg(HttpURLConnection conn) throws IOException {        return readMsg(conn, "UTF-8");    }    private String readMsg(HttpURLConnection conn, String charSet) throws IOException {        BufferedReader reader = null;        try{            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), charSet));            StringBuilder sb = new StringBuilder();            String line = null;            while ((line = reader.readLine()) != null) {                sb.append(line);            }            return sb.toString();        } finally {            if(reader != null){                reader.close();            }        }    }    private void writeMsg(HttpURLConnection conn, String postData) throws IOException {        DataOutputStream dos = new DataOutputStream(conn.getOutputStream());        dos.write(postData.getBytes());        dos.flush();        dos.close();    }    private void fillProperties(HttpURLConnection conn, Map<String,String> params) {        if(params == null||params.isEmpty()){            return;        }        for (Entry<String,String> entry: params.entrySet()) {            conn.addRequestProperty(entry.getKey(), entry.getValue());        }    }            public String httpsPost(String url, String postData) {        HttpURLConnection conn = null;        try {            boolean doOutput = (postData != null && postData.equals(""));//!Strings.isNullOrEmpty(postData);            conn = createHttpsConn(url, "POST", doOutput);            if (doOutput)                writeMsg(conn, postData);            return readMsg(conn);        } catch (Exception ex) {            // ingore            // just print out            logger.error(ex.getMessage(), ex);        } finally {            if (conn != null) {                conn.disconnect();                conn = null;            }        }        return null;    }        private HttpURLConnection createHttpsConn(String url, String method, boolean doOutput) throws Exception {        HostnameVerifier hv = new HostnameVerifier() {            public boolean verify(String urlHostName, SSLSession session) {                return true;            }        };        HttpsURLConnection.setDefaultHostnameVerifier(hv);        trustAllHttpsCertificates();        URL dataUrl = new URL(url);                HttpURLConnection conn = (HttpURLConnection) dataUrl.openConnection();        conn.setReadTimeout(read_time_out);        conn.setRequestMethod(method);        conn.setDoOutput(doOutput);        conn.setDoInput(true);        return conn;    }        private static void trustAllHttpsCertificates() throws Exception {        //  Create a trust manager that does not validate certificate chains:        javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];                javax.net.ssl.TrustManager tm = new miTM();        trustAllCerts[0] = tm;        javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");        sc.init(null, trustAllCerts, null);        javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(                sc.getSocketFactory());    }        public static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {        public java.security.cert.X509Certificate[] getAcceptedIssuers() {            return null;        }        public boolean isServerTrusted(                java.security.cert.X509Certificate[] certs) {            return true;        }        public boolean isClientTrusted(                java.security.cert.X509Certificate[] certs) {            return true;        }        public void checkServerTrusted(                java.security.cert.X509Certificate[] certs, String authType) throws                java.security.cert.CertificateException {            return;        }        public void checkClientTrusted(                java.security.cert.X509Certificate[] certs, String authType) throws                java.security.cert.CertificateException {            return;        }    }    /**      * 执行一个HTTP POST请求,返回请求响应的内容     * @param url        请求的URL地址      * @param params 请求的查询参数,可以为null      * @return 返回请求响应的内容      */     public static String doPostforUC(String url, String body) {         StringBuffer stringBuffer = new StringBuffer();        HttpEntity entity = null;        BufferedReader in = null;        HttpResponse response = null;        try {            DefaultHttpClient httpclient = new DefaultHttpClient();            HttpParams params = httpclient.getParams();            HttpConnectionParams.setConnectionTimeout(params, 20000);            HttpConnectionParams.setSoTimeout(params, 20000);            HttpPost httppost = new HttpPost(url);            httppost.setHeader("Content-Type", "application/x-www-form-urlencoded");            httppost.setEntity(new ByteArrayEntity(body.getBytes("UTF-8")));            response = httpclient.execute(httppost);                        entity = response.getEntity();            in = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));            String ln;            while ((ln = in.readLine()) != null) {                stringBuffer.append(ln);                stringBuffer.append("\r\n");            }            httpclient.getConnectionManager().shutdown();        } catch (ClientProtocolException e) {            e.printStackTrace();        } catch (IOException e1) {            e1.printStackTrace();        } catch (IllegalStateException e2) {            e2.printStackTrace();        } catch (Exception e) {            e.printStackTrace();        } finally {            if (null != in) {                try {                    in.close();                    in = null;                } catch (IOException e3) {                    e3.printStackTrace();                }            }        }        return stringBuffer.toString();    } }
内容的返回是json格式的,可以从里面找自己需要的内容来解析

JSONParser jsonParser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);                    JSONObject obj;                    obj = (JSONObject) jsonParser.parse(doHttpsGet);                    String code = String.valueOf(obj.get("ret"));                    if(code.equals("0")){                        String nickName = String.valueOf(obj.get("nickname"));
后面就是自己服务器的逻辑了。登录相对来讲还是很简单的。

二、支付
1、腾讯的支付接口不知道是新开发的,还是涉及太多,总之非常乱,他们的开放平台的wiki上面有,四个服务,应用接入,移动接入,网站接入,腾讯云接入。因为我们是移动游戏所以,应该按照移动接入来接,但是实际上还要涉及应用接入这边的文档。只看一边的文档会发现少很多东西。按照文档接入出现问题。正题。

我手中的文档是 移动接入的sdk下载里面的

,腾讯的支付有两种:第一种是由腾讯来管理我们的支付,举例就是玩家充值的元宝在腾讯服务器上面,这个蛋疼的地方是,以后你所有的元宝操作都要跟腾讯交互,增加、扣除、赠送等等都要写协议去腾讯云处理。所以我们用了另外一种模式,道具购买模式。道具购买模式是直接花q点或者q币购买我们的道具,这个道具就是元宝。

2、这里有一个问题是,sdk里面自带的文档跟wiki上面的不一致,调用的接口也不是一个

这个对我们的影响在于后面的发货接口。发货接口的文档又再wiki上面,所以后面我们回到wiki的时候发现两份文档对不上。sdk文档包括腾讯托管跟我们自己管理元宝两种,第一种因为接口多,所以大部分是将第一种方式的。

3、道具购买服务器需要实现两个接口。购买道具下订单接口。购买结束回调接口。

下单接口需要客户端在登录时候取得  paytoken  openkey  pf  pfkey  然后按照文档以  http  方式连接开放api就可以了。

//qq直接购买道具下单界面	public String qq_buy_items(String appid,String sessionId ,String openid,String pay_token,String openkey ,String amount,String pf,String pfkey){        String appkey = PlatformUtil.QQ_APPKEY;        String apiaddress = "119.147.19.43";//qq测试地址//        String apiaddress = "openapi.tencentyun.com";//qq正式        //pf = "qq_m_qq-10000144-android-10000144-1111";        //pfkey = "pfkey";        OpenApiV3 openApiV3 = new OpenApiV3(appid, appkey, apiaddress);        String zoneid="1";                Map<String,String> params = new HashMap<String, String>();        params.put("openid", openid);        params.put("openkey", openkey);        params.put("pf", pf);        params.put("pfkey",pfkey);        params.put("ts", String.valueOf(System.currentTimeMillis()/1000));        params.put("pay_token", pay_token);        params.put("zoneid", zoneid);        params.put("appmode", "1");        params.put("appid", appid);        int iamount = SCUtils.calcScCount(amount+".0");        String payitem = String.format("100*1*%s",String.valueOf(iamount));        String goodsmeta = "元宝*元宝";        String goodsurl = "http://dragon.dl.hoolaigames.com/other/CH.png";        String app_metadata = String.format("%s-%s-",sessionId,String.valueOf(amount));        params.put("payitem", payitem);        params.put("goodsmeta", goodsmeta);        params.put("goodsurl", goodsurl);        params.put("app_metadata",app_metadata);    //这个在最终透传时候会增加腾讯的内容,*qdqd*qq 告诉我们是用什么方式支付的//        params.put("qq_m_qq",String.format("%s,%s,%s", appid,openid,openkey) );        try {            Map<String,String> cookies = new HashMap<String, String>();            cookies.put("session_id", SnsSigCheck.encodeUrl("openid"));            cookies.put("session_type", SnsSigCheck.encodeUrl("kp_actoken"));            cookies.put("org_loc ",SnsSigCheck.encodeUrl("/mpay/buy_goods_m"));            String api = openApiV3.api("/mpay/buy_goods_m", params,null ,"http");            return api;        } catch (OpensnsException e) {            log.error("openApiV3.api invoke failed",e);            return "error";        }    }
其中的  OpenApiV3 其实可以从开放平台下载,是  http://wiki.open.qq.com/wiki/SDK%E4%B8%8B%E8%BD%BD  里面其实是一些验证以及http的访问。实际接入时候可以下载一个最新的看看。返回值也是一个json

JSONParser jsonParser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);                    JSONObject obj;                    obj = (JSONObject) jsonParser.parse(payurl);                    int ret = (Integer) obj.get("ret");                    String msg = (String) obj.get("msg");                    String token_id = (String) obj.get("token");                    String url_params = (String) obj.get("url_params");
关于返回值里面参数,文档跟实际的返回有些出入,不一致,token 文档中写的是token_id  但实际返回的是token,这个可以实际debug看下再接收参数。得到的这些值需要发送给前端的sdk,前端的sdk会用这个返回的url 处理剩下的逻辑。

4、支付回调。客户端拿到刚才的url 会回传给腾讯,然后腾讯会调用我们在后台配置的回调接口,来通知支付结果,同时我们也要处理道具发放逻辑。这里有一个非常困难的问题 https协议的证书问题。 腾讯的证书最变态的一点是绑定ip地址,当然也是为了安全考虑。腾讯的后台我没有登录,但是应该是配置回调的ip地址,填写回调url  然后腾讯会生成一个绑定ip地址的证书,你需要安装这个证书在那台服务器上面,

发货URL用来给腾讯计费后台回调。用户付费成功后,腾讯计费后台将回调该URL给用户发货。在9001端口后可以是一个cgi或者php的路径。

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端口(外网端口)。
-必须填写发货服务器所在运营商(电信/联通)。

这是腾讯官方文档对于这个的解释,总之很麻烦的一个东西,中间遇到什么问题,建议找他们的企业支持。

5、证书的安装。

证书安装很坑爹的一个没有官方文档,官方有一个window浏览器的导入文档,没有linux的。这太无语了。

证书的安装可以安装在apache 或者 nginx 下面,我没有直接安装在tomcat下面,应该也是可以的吧,用apache或者nginx 可以做转发,转发到本地debug什么的。所以,我们用的是nginx做转发。首先腾讯后台下载一个这样的证书包。

这个里面带钥匙的那个需要密码,密码在readme里面,但是其实linux下面并没有用到,这个我估计是原始的密钥文件,可以和那个key生成 crt 文件,但是这里已经是生成好了的 crt 文件所以还是直接用比较好,先给第一个最长的那个起个别的名字。然后上传到 nginx 服务器的 conf 目录下面 ,nginx 在安装服务的时候应该是默认为支持https的 ssl 的,所以一般是不需要重新编译的,如果需要重新编译,可以去网上找找相关资料。如果你的  nginx  支持,那么就剩下一步,修改配置文件。 同样是conf目录下面的  nginx.conf   

server {        listen       443;        server_name  xxxxxxx;        ssl                       on;        ssl_certificate           oem.crt;        ssl_certificate_key       oem.key;        ssl_verify_client         off;        ssl_session_timeout       5m;        ssl_protocols             SSLv2 SSLv3 TLSv1;        ssl_ciphers               ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;        ssl_prefer_server_ciphers on;        ssl_client_certificate      ca.crt;        ssl_verify_depth            1;        location ~  ^/xxxxxr/* {                proxy_pass http://xxxxxx3;                index  index.jsp index.html index.htm;                proxy_set_header        Host $host;                proxy_set_header  X-Real-IP  $remote_addr;                proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;        }    }
里面的
ssl_client_certificate
ssl_certificate
对应证书里面的名字,修改完成后记得reload    ./nginx -s reload  一下应该就生效了,我是做了转发本地处理的,当然也可以转发到任意服务器或者本机。如果下订单成功但是收不到回调,多半是这个证书的问题,可以看 nginx的log日志看看有没有访问到。如果没有80%都是证书的问题,询问下腾讯的支持让他们帮你查下日志吧,不过等他们反馈,估计你已经找到原因了。

6、支付回调的验证,当你终于能收到回调了,恭喜你你就要成功了。

对于支付回调的处理,其实很简单,但是腾讯的就很蛋疼。这就是上面说的蛋疼的问题,没有文档。sdk里面的文档说去看 wiki ,wiki里面的文档貌似不是这一版的,而且sdk文档里面的连接还是去 wiki 的主页,哎。  这里忍不住吐槽太多太乱,大家看上去都差不多,我哪知道是我需要的接口。最终我看到这个貌似像 :

http://wiki.open.qq.com/wiki/%E5%9B%9E%E8%B0%83%E5%8F%91%E8%B4%A7URL%E7%9A%84%E5%8D%8F%E8%AE%AE%E8%AF%B4%E6%98%8E_V3

这个文档上面的参数回调,大部分都是正确的。注意是大部分,因为收到的所有参数都要参与 HmacSHA1 签名,所以一个参数错误就悲剧了,你都不知道去哪里找,贴一下我最终的回调处理。

//qq支付回调接口,根据http://wiki.open.qq.com/wiki/%E5%9B%9E%E8%B0%83%E5%8F%91%E8%B4%A7URL%E7%9A%84%E5%8D%8F%E8%AE%AE%E8%AF%B4%E6%98%8E_V3   编写    protected void processRequest(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException  {        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        Map<String, Object> obj = new HashMap<String, Object>();        try {            String openid = request.getParameter("openid");     //根据APPID以及QQ号码生成,即不同的appid下,同一个QQ号生成的OpenID是不一样的。            String appid = request.getParameter("appid");       //应用的唯一ID。可以通过appid查找APP基本信息。            String ts = request.getParameter("ts");             //linux时间戳。 注意开发者的机器时间与腾讯计费开放平台的时间相差不能超过15分钟。            String payitem = request.getParameter("payitem");   //接收标准格式为ID*price*num   G001*10*1            String token = request.getParameter("token");       //应用调用v3/pay/buy_goods接口成功返回的交易token            String billno = request.getParameter("billno");     //支付流水号(64个字符长度。该字段和openid合起来是唯一的)。            String version = request.getParameter("version");   //协议版本 号,由于基于V3版OpenAPI,这里一定返回“v3”。            String zoneid = request.getParameter("zoneid");     //在支付营销分区配置说明页面,配置的分区ID即为这里的“zoneid”。  如果应用不分区,则为0。            String providetype = request.getParameter("providetype");//发货类型   0表示道具购买,1表示营销活动中的道具赠送,2表示交叉营销任务集市中的奖励发放。            //Q点/Q币消耗金额或财付通游戏子账户的扣款金额。可以为空 若传递空值或不传本参数则表示未使用Q点/Q币/财付通游戏子账户。注意,这里以0.1Q点为单位。即如果总金额为18Q点,则这里显示的数字是180。            String amt = request.getParameter("amt");                       String payamt_coins = request.getParameter("payamt_coins");//扣取的游戏币总数,单位为Q点。            String pubacct_payamt_coins = request.getParameter("pubacct_payamt_coins");//扣取的抵用券总金额,单位为Q点。            String appmeta = request.getParameter("appmeta");            String clientver = request.getParameter("clientver");            String sig = request.getParameter("sig");                        String url = "/xxx/xxxx";                        Map<String, String> params = createCallbackParamsMap(openid, appid, ts, payitem, token, billno, version, zoneid,                    providetype, amt, payamt_coins, pubacct_payamt_coins, appmeta,clientver);            if(SnsSigCheck.verifySig(request.getMethod(), url,params, PlatformUtil.QQ_APPKEY+"&", sig)){                                if(ok){                                    }else{                                        obj.put("ret", 0);                    obj.put("msg", "ok");                }            }else{                log.info("qqPayCallback SnsSigCheck fail.");                obj.put("ret", -5);                obj.put("msg", "签名错误");            }                String resp = JSONObject.toJSONString(obj);                out.println(resp);        } catch (SQLException | DbException | ProtocolException | NumberFormatException | OpensnsException e) {            e.printStackTrace();        } finally {            out.close();        }
中间标红的地方都是有问题的地方,都是坑。首先 
pubacct_payamt_coins
是有可能传空的,因为你没有用抵用券对吧,但是记住这个也需要加入签名。  clientver  神坑。我最终也没再文档或者哪里找到这个参数为什么给我传过来,但是你就是传过来了,而且你还必须接收,必须加入签名中去,也许我水平太菜,反正我是没找到这个参数在那个文档上面写了。鄙视

createCallbackParamsMap  字面意思就是把参数弄到 map里面 

SnsSigCheck.verifySig  这也是上面下载的那个工具项目中自带的功能,其实就是一个   HmacSHA1  的 utf 格式的签名。可以下载,有兴趣的也可以自己写写。

  (三)总结

腾讯的支付接口应该做的不难,困难在于没有一个明确的文档。




  相关解决方案