編輯:關於Android編程
騰訊開放平台的接入是非常麻煩的, 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 params){
StringBuilder postData = new StringBuilder();
for(Entry 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 params,Map headers){
StringBuilder postData = new StringBuilder();
for(Entry 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 headers){
return service(false, url, postData, "POST", headers);
}
public String doGet(String url, Map 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 params){
return doHttpsPost(url,params,null);
}
public String doHttpsPost(String url, Map params,Map headers){
StringBuilder postData = new StringBuilder();
for(Entry 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 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 params) {
if(params == null||params.isEmpty()){
return;
}
for (Entry 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下載裡面的
vcrHo6zS1LrzxOPL+dPQtcTUqrGmstnX97a80qq4+sza0ba9u7ulo6zU9rzToaK/27P9oaLU+cvNtci1yLa80qrQtNCt0unIpcza0bbUxrSmwO2ho8v50tTO0sPH08PBy8HtzeLSu9bWxKPKvaOstcC+37m6wvLEo8q9oaO1wL7fubrC8sSjyr3Kx9axvdO7qHG147vy1d9xsdK5usLyztLDx7XEtcC+36Os1eK49rXAvt++zcrH1KqxpqGjPC9wPgo8cD4yoaLV4sDv09DSu7j2zsrM4srHo6xzZGvA78Pm19S0+LXEzsS1tbj6d2lracnPw+a1xLK70rvWwqOstffTw7XEvdO/2tKysrvKx9K7uPY8L3A+CjxwPjxpbWcgc3JjPQ=="" alt="\">這個對我們的影響在於後面的發貨接口。發貨接口的文檔又再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 params = new HashMap();
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 cookies = new HashMap();
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 obj = new HashMap();
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 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 格式的簽名。可以下載,有興趣的也可以自己寫寫。
(三)總結
騰訊的支付接口應該做的不難,困難在於沒有一個明確的文檔。
[HyBrid]HyBrid混編初嘗:原生和第三方JsBridge的使用
最近研究HyBrid的兩種方式:一、直接原生WebView1)初始化WebView: //啟動javascript webView = (WebView)
紅米手機快捷鍵使用技巧匯總
紅米手機快捷鍵使用技巧匯總。紅米手機,在市場的位置也慢慢變重要了,價格低,又實惠,又好用。那朋友們,你們知道它有那些快捷鍵的嗎?那就讓小編來跟大家詳細的介紹
56.EasyLikeArea
EasyLikeArea Easy like area in the circle of friends or QQ qzone
Android編程之SurfaceView實例詳解
本文實例講述了Android編程之SurfaceView用法。分享給大家供大家參考,具體如下:關於surfaceView相關知識:View和SurfaceView主要區別