Android版-微信APP支付


首發地址: Android版-微信APP支付

歡迎留言、轉發
微信極速開發系列文章(微信支付、授權獲取用戶信息等):點擊這里

目錄
1、注冊賬號、開發者認證
2、添加應用
3、申請微信支付
4、技術開發功能實現步驟介紹
5、代碼實例

此項目已開源歡迎Start、PR、發起Issues一起討論交流共同進步
https://github.com/Javen205/IJPay
http://git.oschina.net/javen205/IJPay

微信APP支付接入商戶服務中心 官方介紹文檔

1、注冊賬號、開發者認證

開放平台直接注冊,注冊郵箱不能與微信其他的產品同號。

比較坑的是微信公眾號中的支付(微信買單、刷卡、公眾號支付、wap支付)以及微信app支付都需要進行微信認證而不是公用一個微信商戶平台(需要交兩次認證的費用)。

微信認證這個時間比較短(畢竟交了300大洋)一般一個工作日就會有人聯系你核查公司的資料。

微信認證(開發者資質認證)通過之后就可以在開放平台添加應用了(這個需要審核),應用通過之后就可以申請微信支付了(也需要審核)

2、添加應用

這個比較簡單,按照提示操作就行 上圖

添加應用-填寫基本信息1

添加應用-填寫基本信息2

添加應用-上傳應用圖片

添加應用-填寫平台信息

應用包名只定義,應用簽名可以使用資源下載中心的簽名生成工具。務必記住包名以及簽名keystore文件的密碼,如果包名或者簽名文件不對打包是喚不起微信支付的。

資源下載

下載的資源截圖

應用簽名工具

3、申請微信支付

如果添加的應用審核通過了(一個工作日),就可以直接申請微信支付了(7個工作日之內)。

應用審核通過-申請微信支付

審核通過之后將會收到審核通過的郵件,里面有登錄商戶平台的登錄賬戶、密碼、商戶號以及一些操作指引的說明。服務端生成預付訂單的簽名需要密鑰 設置方法可以參考這里

4、技術開發功能實現

微信APP支付介紹【文檔
APP端開發步驟說明 【文檔

這里主要聊聊Android微信支付,主要包括以下幾個步驟
1、商戶服務端生成訂單並在微信平台生成預付訂單
2、客戶端調起微信支付進行支付
3、客戶端回調支付結果
4、服務端接收支付通知

1、商戶服務端生成訂單並在微信平台生成預付訂單

調起微信支付前需要服務器生成支付訂單再調用【統一下單API】生成預付訂單prepayId,再生成簽名sign【調起支付API

以上兩個步驟建議都在服務端完成,客戶端(Android)通過接口獲取對應的參數即可

2、客戶端調起微信支付進行支付

通過微信提供的jar 喚起微信支付

調起微信支付

3、客戶端回調支付結果

參照微信SDK Sample,在net.sourceforge.simcpux.wxapi包路徑中實現WXPayEntryActivity類【包名或類名不一致會造成無法回調】
栗子說明:認真反復讀了幾遍,感覺這句話有歧義是一個坑,測試的時候一直不回調。這里他想說的意識如下:
比如你申請應用包名為:javen.com 那么回調的WXPayEntryActivity類必須放到javen.com.wxapi 的包下面

客戶端回調支付結果

4、服務端接收支付通知

支付結果通知【官方文檔

代碼實現參考開源項目 【點擊這里

5、代碼實例

服務端代碼:根據商戶訂單生成微信預付訂單並返回喚起微信支付需要的參數。Demo中參數寫成固定了僅供參考

此項目已開源 【點擊這里】 如果對你有幫助請點擊Start告訴我 hahaha 。以下代碼對應的目錄在 com.javen.weixin.controller.WeixinPayController.java

/**
	 * 微信APP支付
	 */
	public void appPay(){
		//不用設置授權目錄域名
		//統一下單地址 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1#
		Map<String, String> params = new HashMap<String, String>();
		params.put("appid", appid);
		params.put("mch_id", partner);
		params.put("nonce_str", System.currentTimeMillis() / 1000 + "");
		params.put("body", "Javen微信公眾號極速開發");
		String out_trade_no=System.currentTimeMillis()+"";
		params.put("attach", "custom json");
		params.put("out_trade_no", out_trade_no);
		int price=10000;
		params.put("total_fee", price+"");
		
		String ip = IpKit.getRealIp(getRequest());
		if (StrKit.isBlank(ip)) {
			ip = "127.0.0.1";
		}
		
		params.put("spbill_create_ip", ip);
		params.put("notify_url", notify_url);
		params.put("trade_type", "APP");

		String sign = PaymentKit.createSign(params, paternerKey);
		params.put("sign", sign);
		
		String xmlResult = PaymentApi.pushOrder(params);
		
System.out.println(xmlResult);
		Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
		
		String return_code = result.get("return_code");
		String return_msg = result.get("return_msg");
		if (StrKit.isBlank(return_code) || !"SUCCESS".equals(return_code)) {
			ajax.addError(return_msg);
			renderJson(ajax);
			return;
		}
		String result_code = result.get("result_code");
		if (StrKit.isBlank(result_code) || !"SUCCESS".equals(result_code)) {
			ajax.addError(return_msg);
			renderJson(ajax);
			return;
		}
		// 以下字段在return_code 和result_code都為SUCCESS的時候有返回
		String prepay_id = result.get("prepay_id");
		//封裝調起微信支付的參數 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12
		Map<String, String> packageParams = new HashMap<String, String>();
		packageParams.put("appid", appid);
		packageParams.put("partnerid", partner);
		packageParams.put("prepayid", prepay_id);
		packageParams.put("package", "Sign=WXPay");
		packageParams.put("noncestr", System.currentTimeMillis() + "");
		packageParams.put("timestamp", System.currentTimeMillis() / 1000 + "");
		String packageSign = PaymentKit.createSign(packageParams, paternerKey);
		packageParams.put("sign", packageSign);
		
		String jsonStr = JsonUtils.toJson(packageParams);
System.out.println("最新返回apk的參數:"+jsonStr);
		renderJson(jsonStr);
	}
客戶端代碼實現

使用單例模式統一入口,首先判斷微信客戶端是否安裝,如果有安裝再從商戶服務器獲取調起支付的參數

public class IPay {
	private static  IPay mIPay;
	private Context mContext;
	
	private IPay(Context context) {
		mContext = context;
	}
	
	public static IPay getIntance(Context context){
		if (mIPay == null) {
			synchronized(IPay.class){
				if (mIPay == null) {
					mIPay = new IPay(context);
				}
			}
		}
		return mIPay;
	}
	
	//支付結果回調
	public interface IPayListener{
		void onPay(int code);
	}
	
	public void toTestPay(Order order,IPayListener listener){
		if (order != null) {
			if (IPayLogic.getIntance(mContext.getApplicationContext()).isWeixinAvilible()) {
				Constants.payListener = listener;
				new TestPayPrepay(mContext).execute();
			}else {
				Toast.makeText(mContext, "未安裝微信", Toast.LENGTH_LONG).show();
			}
		}else {
			Toast.makeText(mContext, "參數異常 order is null", Toast.LENGTH_LONG).show();
		}
	}
	
}

調起微信支付、獲取調取微信支付參數、判斷微信是否安裝邏輯實現


public class IPayLogic {
	private static  IPayLogic mIPayLogic;
	private Context mContext;
	
	private IPayLogic(Context context) {
		mContext = context;
	}
	
	public static IPayLogic getIntance(Context context){
		if (mIPayLogic == null) {
			synchronized(IPayLogic.class){
				if (mIPayLogic == null) {
					mIPayLogic = new IPayLogic(context);
				}
			}
		}
		return mIPayLogic;
	}
	
	//測試
	public String testPay(){
		return HttpKit.get(Constants.TESTPAY_URL);
	}
	
	/**
	 * 調起支付
	 * @param appId
	 * @param partnerId
	 * @param prepayId
	 * @param nonceStr
	 * @param timeStamp
	 * @param sign
	 */
	public void startWXPay(String appId,String partnerId,String prepayId,
			String nonceStr,String timeStamp,String sign){

		IWXAPI api= WXAPIFactory.createWXAPI(mContext, null);
		api.registerApp(appId);
		
		boolean isPaySupported = api.getWXAppSupportAPI() >= Build.PAY_SUPPORTED_SDK_INT;
		if (!isPaySupported) {
			Toast.makeText(mContext, "請更新微信客戶端", Toast.LENGTH_SHORT).show();
			return;
		}
		
		PayReq request = new PayReq();
		request.appId = appId;
		request.partnerId = partnerId;
		request.prepayId= prepayId;
		request.packageValue = "Sign=WXPay";
		request.nonceStr=nonceStr;
		request.timeStamp= timeStamp;
		request.sign= sign;
		api.sendReq(request);
	}

	
	/**
	 * 判斷微信是否安裝
	 * @param context
	 * @return
	 */
	 public  boolean isWeixinAvilible() {
	        final PackageManager packageManager = mContext.getPackageManager();// 獲取packagemanager
	        List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);// 獲取所有已安裝程序的包信息
	        if (pinfo != null) {
	            for (int i = 0; i < pinfo.size(); i++) {
	                String pn = pinfo.get(i).packageName;
	                if (pn.equals("com.tencent.mm")) {
	                    return true;
	                }
	            }
	        }
	        return false;
	    }

}

HttpKit MD5 工具類


/**
 * HttpKit
 */
public class HttpKit {
	
	private HttpKit() {}
	
	/**
	 * https 域名校驗
	 */
	private class TrustAnyHostnameVerifier implements HostnameVerifier {
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	}
	
	/**
	 * https 證書管理
	 */
	private class TrustAnyTrustManager implements X509TrustManager {
		public X509Certificate[] getAcceptedIssuers() {
			return null;  
		}
		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
		}
		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
		}
	}
	
	private static final String GET  = "GET";
	private static final String POST = "POST";
	private static String CHARSET = "UTF-8";
	
	private static final SSLSocketFactory sslSocketFactory = initSSLSocketFactory();
	private static final TrustAnyHostnameVerifier trustAnyHostnameVerifier = new HttpKit().new TrustAnyHostnameVerifier();
	
	private static SSLSocketFactory initSSLSocketFactory() {
		try {
			TrustManager[] tm = {new HttpKit().new TrustAnyTrustManager() };
			SSLContext sslContext = SSLContext.getInstance("TLS");	// ("TLS", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			return sslContext.getSocketFactory();
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public static void setCharSet(String charSet) {
		if (charSet.isEmpty()) {
			throw new IllegalArgumentException("charSet can not be blank.");
		}
		HttpKit.CHARSET = charSet;
	}
	
	private static HttpURLConnection getHttpConnection(String url, String method, Map<String, String> headers) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
		URL _url = new URL(url);
		HttpURLConnection conn = (HttpURLConnection)_url.openConnection();
		if (conn instanceof HttpsURLConnection) {
			((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory);
			((HttpsURLConnection)conn).setHostnameVerifier(trustAnyHostnameVerifier);
		}
		
		conn.setRequestMethod(method);
		conn.setDoOutput(true);
		conn.setDoInput(true);
		
		conn.setConnectTimeout(19000);
		conn.setReadTimeout(19000);
		
		conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
		conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");
		
		if (headers != null && !headers.isEmpty())
			for (Entry<String, String> entry : headers.entrySet())
				conn.setRequestProperty(entry.getKey(), entry.getValue());
		
		return conn;
	}
	
	/**
	 * Send GET request
	 */
	public static String get(String url, Map<String, String> queryParas, Map<String, String> headers) {
		HttpURLConnection conn = null;
		try {
			conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), GET, headers);
			conn.connect();
			return readResponseString(conn);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
		finally {
			if (conn != null) {
				conn.disconnect();
			}
		}
	}
	
	public static String get(String url, Map<String, String> queryParas) {
		return get(url, queryParas, null);
	}
	
	public static String get(String url) {
		return get(url, null, null);
	}
	
	/**
	 * Send POST request
	 */
	public static String post(String url, Map<String, String> queryParas, String data, Map<String, String> headers) {
		HttpURLConnection conn = null;
		try {
			conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), POST, headers);
			conn.connect();
			
			OutputStream out = conn.getOutputStream();
			out.write(data.getBytes(CHARSET));
			out.flush();
			out.close();
			
			return readResponseString(conn);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
		finally {
			if (conn != null) {
				conn.disconnect();
			}
		}
	}
	
	public static String post(String url, Map<String, String> queryParas, String data) {
		return post(url, queryParas, data, null);
	}
	
	public static String post(String url, String data, Map<String, String> headers) {
		return post(url, null, data, headers);
	}
	
	public static String post(String url, String data) {
		return post(url, null, data, null);
	}
	
	private static String readResponseString(HttpURLConnection conn) {
		StringBuilder sb = new StringBuilder();
		InputStream inputStream = null;
		try {
			inputStream = conn.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, CHARSET));
			String line = null;
			while ((line = reader.readLine()) != null){
				sb.append(line).append("\n");
			}
			return sb.toString();
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
		finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}
	
	/**
	 * Build queryString of the url
	 */
	private static String buildUrlWithQueryString(String url, Map<String, String> queryParas) {
		if (queryParas == null || queryParas.isEmpty())
			return url;
		
		StringBuilder sb = new StringBuilder(url);
		boolean isFirst;
		if (url.indexOf("?") == -1) {
			isFirst = true;
			sb.append("?");
		}
		else {
			isFirst = false;
		}
		
		for (Entry<String, String> entry : queryParas.entrySet()) {
			if (isFirst) isFirst = false;	
			else sb.append("&");
			
			String key = entry.getKey();
			String value = entry.getValue();
			if (!value.isEmpty())
				try {value = URLEncoder.encode(value, CHARSET);} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}
			sb.append(key).append("=").append(value);
		}
		return sb.toString();
	}
}

public class MD5 {
    public static String MD5sign(String s) {
        char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        try {
            byte[] btInput = s.getBytes("UTF-8");
            // 獲得MD5摘要算法的 MessageDigest 對象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字節更新摘要
            mdInst.update(btInput);
            // 獲得密文
            byte[] md = mdInst.digest();
            // 把密文轉換成十六進制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str).toLowerCase();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


	public static void main(String[] args) {
		System.out.println(MD5sign("Hello world"));
	}
}

使用AsyncTask異步獲取調起微信支付的相關參數。當然你也可以使用其他的異步網絡請求開源框架


public class TestPayPrepay extends AsyncTask<Object, Integer, String> {
	private Context mContext;
	public TestPayPrepay(Context context) {
		this.mContext = context;
	}
	
	@Override
	protected String doInBackground(Object... params) {
		System.out.println("TestPayPrepay doInBackground");
		return  IPayLogic.getIntance(mContext).testPay();
	}
	
	@Override
	protected void onPostExecute(String result) {
		try {
			if (result!=null) {
				System.out.println("TestPayPrepay result>"+result);
				JSONObject data = new JSONObject(result);
				if(!data.has("code")){
					String sign = data.getString("sign");
					String timestamp = data.getString("timestamp");
					String noncestr = data.getString("noncestr");
					String partnerid = data.getString("partnerid");
					String prepayid = data.getString("prepayid");
					String appid = data.getString("appid");
					Toast.makeText(mContext, "正在調起支付", Toast.LENGTH_SHORT).show();
					
					Constants.APP_ID = appid;
					
					IPayLogic.getIntance(mContext).startWXPay(appid, partnerid, prepayid, noncestr, timestamp, sign);
				}else{
					String message = data.getString("message");
		        	Log.d("PAY_GET", "返回錯誤"+message);
		        	Toast.makeText(mContext, "返回錯誤:"+message, Toast.LENGTH_SHORT).show();
				}
			}else {
				System.out.println("get  prepayid exception, is null");
			}
		} catch (Exception e) {
			Log.e("PAY_GET", "異常:"+e.getMessage());
        	Toast.makeText(mContext, "異常:"+e.getMessage(), Toast.LENGTH_SHORT).show();
        }
		super.onPostExecute(result);
	}

}

支付結果回調

 <activity
            android:name="[應用的包名].wxapi.WXPayEntryActivity"
            android:exported="true"
            android:theme="@android:style/Theme.Translucent"
            android:launchMode="singleTop" >
 </activity>

封裝的是SDK 所以這里設置了一個透明的主題


public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler{
    private IWXAPI api;
	private IPayListener payListener;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        requestWindowFeature(Window.FEATURE_NO_TITLE);//隱藏標題
        LinearLayout ll = new LinearLayout(this);
        setContentView(ll);
    	api = WXAPIFactory.createWXAPI(this, Constants.APP_ID);
        api.handleIntent(getIntent(), this);
        finish();
    }

	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		setIntent(intent);
        api.handleIntent(intent, this);
	}

	@Override
	public void onReq(BaseReq req) {
	}

	@Override
	public void onResp(BaseResp resp) {
		payListener = Constants.payListener;
		int code = resp.errCode;  
		System.out.println("onResp errCode>"+code);
		if (payListener!=null) {
			payListener.onPay(code);
			System.out.println("payListener callback");
		}else {
			System.out.println("payListener not callback");
		}
	}
}

注意:如果回調的code一直返回-1
1、請檢查應用包名以及apk 的簽名是否與你提交到微信開放平台的一致
2、請檢查返回調取微信支付的參數是否正確
大部分原因是第一種

微信APP支付.png

遺留問題:由於支付應用的包名不固定WXPayEntryActivity無法封裝到jar中,需要單獨在支付應用添加.wxapi 這個包名並復制 WXPayEntryActivity到此包中。如果有好的解決方案歡迎留言

微信開發系列文章 http://www.jianshu.com/p/a172a1b69fdd

推薦閱讀
極速開發微信公眾號之微信買單
極速開發微信公眾號之公眾號支付
極速開發微信公眾號之掃碼支付
極速開發微信公眾號之刷卡支付
極速開發微信公眾號之現金紅包
極速開發微信公眾號之模板消息

如果此文章對你有幫助請點擊喜歡告訴我

服務端源碼地址:http://git.oschina.net/javen205/weixin_guide
客戶端源碼地址:https://github.com/Javen205/HelloAndroid


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM