Android开发笔记(一百零六)支付缴费SDK

第三方支付

第三方支付指的是第三方平台与各银行签约,在买方与卖方之间实现中介担保,从而增强了支付交易的安全性。国内常用的支付平台主要是支付宝和微信支付,其中支付宝的市场份额为71.5%,微信支付的市场份额为15.99%,也就是说这两家垄断了八分之七的支付市场(2015年数据)。除此之外,还有几个app开发会用到的支付平台,包括:银联支付,主要用于公共事业缴费,如水电煤、有线电视、移动电信等等的充值;易宝支付,主要用于各种报名考试的缴费,特别是公务员与事业单位招考;快钱,被万达收购,主要用于航空旅行、教育培训、游戏娱乐等网站的支付;京东支付,主要用于京东商城的支付;百度钱包,主要用于百度系的电商平台。

因为第三方支付只是个中介,交易流程要多次确认,所以app若要集成支付sdk,得进行以下处理:

1、除了作为买方的用户自己拥有支付账号,开发者还得申请作为卖方的商户账号。

2、支付过程中,虽然允许app直接与第三方支付平台通信,但是最好app要有自己的后台服务器,由自己的后台与第三方平台进行通信。这样做的好处是,一方面自己后台掌握了用户交易记录,做账有依据,管理也方便;另一方面,关键交易在自己后台处理,也减少了恶意篡改的风险。

3、为保证信息安全,需对关键数据进行加密处理,如支付宝采用RSA+BASE64算法,微信支付采用MD5算法,银联支付采用RSA算法。有关数据加密算法的说明参见《Android开发笔记(七十二)数据加密算法》。

支付宝支付

交易流程

支付宝支付的交易流程大致如下:

1、按照指定格式封装好交易信息;

2、对交易信息进行RSA加密与URL编码;

3、调用支付接口,传入加密好的信息串;(这步要另开线程处理,不能放在UI线程中)

4、支付宝sdk在界面下方弹出支付窗口,用户输入支付帐号信息,提交支付;

5、收到支付完成的结果,判断支付状态是成功还是失败;

集成步骤

支付宝sdk的集成比较简单,除了必要的权限外,无需再修改AndroidManifest.xml,jar包也只要导入alipaySdk-20160516.jar。

代码方面,支付宝官方给的demo采用了Thread+Handler的异步处理模式,不过该模式要把代码写在Activity中,不便管理和维护,因此我的测试代码将它改造为Android自带的异步任务处理即AsyncTask方式,有关AsyncTask的说明参见《Android开发笔记(四十九)异步任务处理AsyncTask》。

测试帐号

支付宝官方demo没有给出测试的商户账号,下面是我在网上找到的测试帐号:

	// 商户PID
	public static final String PARTNER = "2088811977704990";
	// 商户收款账号
	public static final String SELLER = "[email protected]";
	// 商户私钥,pkcs8格式
	public static final String RSA_PRIVATE =
		                "MIICXQIBAAKBgQDlQ468L1A7Q+GG80/Z8f3IsSiiFIluSxfTTSuJ/XSPzvYS+bMZ"
		               +"AQLMqq/nGhkp+1Q5pHF9LAQtQS3gL2pqzbKdtvZSsy/tNFFQcGCsgK2ygMl+MW/F"
		               +"g/ufx7c1jy1kZAeDyl1m302dnRrtSgDalkgH7FKRcmDxbXPTnFGHbg9zMQIDAQAB"
		               +"AoGAa28wGQF28H7L1Yh5V+FtkrlqGCHVkQjBfnRAPea205kheRzoD4SIwk4OJhb1"
		               +"ydWLz4M+53BT+Lz9eXveu3PvCdQe9zMIVC5dKUNVYCvvcHZ+Ot8HriiuwGPb3Quu"
		               +"twbnLGM5gxxPDo0yUyWrfaVn/qR35mS6TDfmgowVG8CmBpECQQDzuhodR/Jgxrtn"
		               +"tka+88alyy+BfjUZqNloPuE7JfXrpOxH5lodk7Y4lTki/dlo5BrK+hrismLFr9Du"
		               +"ueAJ7G9dAkEA8M8C6VnpUMAK5+rYcjKnQssDqcMfurKYEil1BD/TUdSbLI6v8p02"
		               +"mv1ApuTVtQQypZJKIFfurGk0g0QlvzLZ5QJAGfY38+iHDAH/UnPbI1oKTfzPyaZs"
		               +"95fB2NXh3hAUGw7NUHdcIAxs+6gBlxWdRAwQQpDTrlQ8KzyoL9XC5Ku3zQJBALO8"
		               +"j5vEtFTFQl6f9zYlgJpmFTHcpg4fx0mnD+RAD2aAneHADquzlFJSvLLVEn2tyG+0"
		               +"pQdHGqotTDi94L65IdECQQDb1h+5kugCu47IxsDkrLRsKVcr8dSDMORyeT1L0HWR"
		               +"ctramBu+2PBz2UKC6+9dQ+ZQH4XTKpBSvkyZH4mYi1de";
//	// 支付宝公钥
//	public static final String RSA_PUBLIC = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB";

下面是支付宝支付的效果截图

下面是支付宝支付的示例代码

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;

import com.alipay.sdk.app.PayTask;
import com.example.exmpay.alipay.bean.AlipayConstants;
import com.example.exmpay.alipay.bean.PayResult;
import com.example.exmpay.alipay.util.SignUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.widget.Toast;

public class AlipayTask extends AsyncTask<Void, Void, String> {

	private static final String TAG = "AlipayTask";
	private Context context;
	private ProgressDialog dialog;

	public AlipayTask(Context context) {
		this.context = context;
	}

	@Override
	protected void onPreExecute() {
		if (TextUtils.isEmpty(AlipayConstants.PARTNER)
				|| TextUtils.isEmpty(AlipayConstants.RSA_PRIVATE)
				|| TextUtils.isEmpty(AlipayConstants.SELLER)) {
			new AlertDialog.Builder(context).setTitle("警告").setMessage("需要配置PARTNER | RSA_PRIVATE| SELLER")
					.setPositiveButton("确定", new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialoginterface, int i) {
						}
					}).show();
			cancel(true);
		} else {
			dialog = ProgressDialog.show(context, "提示", "正在启动支付宝...");
		}
	}

	@Override
	protected String doInBackground(Void... params) {
		String orderInfo = getOrderInfo("测试的商品", "该测试商品的详细描述", "0.01");

		//特别注意,这里的签名逻辑需要放在服务端,切勿将私钥泄露在代码中!
		String sign = sign(orderInfo);
		try {
			//仅需对sign 做URL编码
			sign = URLEncoder.encode(sign, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

		//完整的符合支付宝参数规范的订单信息
		final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
		// 构造PayTask 对象
		PayTask alipay = new PayTask((Activity) context);
		// 调用支付接口,获取支付结果
		String result = alipay.pay(payInfo, false);
		return result;
	}

	@Override
	protected void onPostExecute(String result) {
		if (dialog != null) {
			dialog.dismiss();
		}

		PayResult payResult = new PayResult(result);

		// 支付宝返回此次支付结果及加签,建议对支付宝签名信息拿签约时支付宝提供的公钥做验签
		String resultInfo = payResult.getResult();
		Toast.makeText(context, "resultInfo="+resultInfo, Toast.LENGTH_SHORT).show();

		String resultStatus = payResult.getResultStatus();
		// 判断resultStatus 为“9000”则代表支付成功,具体状态码代表含义可参考接口文档
		if (TextUtils.equals(resultStatus, "9000")) {
			Toast.makeText(context, "支付宝缴费成功", Toast.LENGTH_SHORT).show();
		} else {
			// 判断resultStatus 为非“9000”则代表可能支付失败
			// “8000”代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态)
			if (TextUtils.equals(resultStatus, "8000")) {
				Toast.makeText(context, "支付宝缴费结果确认中", Toast.LENGTH_SHORT).show();
			} else {
				// 其他值就可以判断为支付失败,包括用户主动取消支付,或者系统返回的错误
				Toast.makeText(context, "支付宝缴费失败"+payResult.getResult(), Toast.LENGTH_SHORT).show();
			}
		}
	}

	private String getOrderInfo(String subject, String body, String price) {
		// 签约合作者身份ID
		String orderInfo = "partner=" + "\"" + AlipayConstants.PARTNER + "\"";
		// 签约卖家支付宝账号
		orderInfo += "&seller_id=" + "\"" + AlipayConstants.SELLER + "\"";
		// 商户网站唯一订单号
		orderInfo += "&out_trade_no=" + "\"" + getOutTradeNo() + "\"";
		// 商品名称
		orderInfo += "&subject=" + "\"" + subject + "\"";
		// 商品详情
		orderInfo += "&body=" + "\"" + body + "\"";
		// 商品金额
		orderInfo += "&total_fee=" + "\"" + price + "\"";
		// 服务器异步通知页面路径
		orderInfo += "¬ify_url=" + "\"" + "http://notify.msp.hk/notify.htm" + "\"";
		// 服务接口名称, 固定值
		orderInfo += "&service=\"mobile.securitypay.pay\"";
		// 支付类型, 固定值
		orderInfo += "&payment_type=\"1\"";
		// 参数编码, 固定值
		orderInfo += "&_input_charset=\"utf-8\"";

		// 设置未付款交易的超时时间,默认30分钟,一旦超时,该笔交易就会自动被关闭。
		// 取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
		// 该参数数值不接受小数点,如1.5h,可转换为90m。
		orderInfo += "&it_b_pay=\"30m\"";

		// extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付
		// orderInfo += "&extern_token=" + "\"" + extern_token + "\"";

		// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
		orderInfo += "&return_url=\"m.alipay.com\"";

		// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
		// orderInfo += "&paymethod=\"expressGateway\"";

		return orderInfo;
	}

	private String getOutTradeNo() {
		SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
		Date date = new Date();
		String key = format.format(date);

		Random r = new Random();
		key = key + r.nextInt();
		key = key.substring(0, 15);
		return key;
	}

	private String sign(String content) {
		return SignUtils.sign(content, AlipayConstants.RSA_PRIVATE);
	}

	private String getSignType() {
		return "sign_type=\"RSA\"";
	}

}

微信支付

交易流程

微信支付的流程分为六个步骤:

1、使用开发者申请到的APP_ID和APP_SECRET向微信平台请求获取access_token;

2、封装订单信息(使用开发者申请到的PARTNER_ID和PARTNER_KEY),并对订单信息进行MD5摘要处理;

3、把加密后的订单与access_token发给微信平台,生成预支付订单,返回预付订单id;

4、重新封装订单信息,加上预付订单id,向微信平台发起支付交易;

5、微信sdk跳到微信支付页面,用户输入支付帐号信息,提交支付;

6、支付完成,进行回调操作;

集成步骤

微信支付的集成与微信分享类似,有关微信分享的介绍参见《Android开发笔记(一百零五)社会化分享SDK》。下面是集成微信支付时的注意点:

1、要导入专门用于支付的jar包libammsdk.jar,注意这里不能直接用微信分享的jar包,得用官方demo里面的jar包;

2、申请的appid要与工程名对应,appid也要与app打包的签名相对应;

3、回调代码WXPayEntryActivity.java必须放在“包名.wxapi”这个package下面,另外AndroidManifest.xml也要补充该Activity的配置;

测试帐号

下面是微信开放平台官方demo给的测试帐号:

    // APP_ID 替换为你的应用从官方网站申请到的合法appId
    public static final String APP_ID = "wxd930ea5d5a258f4f";
    public static final String APP_SECRET = "db426a9829e4b49a0dcac7b4162da6b6";
    // wxd930ea5d5a258f4f 对应的支付密钥
    public static final String APP_KEY = "L8LrMqqeGRxST5reouB0K66CaYAWpqhAVsq7ggKkxHCOastWksvuX1uvmvQclxaHoYd3ElNBrNO2DHnnzgfVG9Qs473M3DTOZug5er46FhuGofumV8H2FVR9qkjSlC5K";

    /** 商家向财付通申请的商家id */
    public static final String PARTNER_ID = "1900000109";
    public static final String PARTNER_KEY = "8934e7d15453e97507ef794cf7b0519d";

下面是微信支付的效果截图

下面是微信支付的示例代码

1、获取入口token

import com.example.exmpay.wechat.bean.WechatConstants;
import com.example.exmpay.wechat.bean.GetAccessTokenResult;
import com.example.exmpay.wechat.bean.LocalRetCode;
import com.example.exmpay.wechat.util.WechatUtil;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

public class GetAccessTokenTask extends AsyncTask<Void, Void, GetAccessTokenResult> {

	private static final String TAG = "GetAccessTokenTask";
	private Context context;
	private ProgressDialog dialog;

	public GetAccessTokenTask(Context context) {
		this.context = context;
	}

	@Override
	protected void onPreExecute() {
		dialog = ProgressDialog.show(context, "提示", "正在获取access token...");
	}

	@Override
	protected GetAccessTokenResult doInBackground(Void... params) {
		GetAccessTokenResult result = new GetAccessTokenResult();
		String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
				WechatConstants.APP_ID, WechatConstants.APP_SECRET);
		Log.d(TAG, "get access token, url = " + url);

		byte[] buf = WechatUtil.httpGet(url);
		if (buf == null || buf.length == 0) {
			result.localRetCode = LocalRetCode.ERR_HTTP;
			return result;
		}

		String content = new String(buf);
		result.parseFrom(content);
		return result;
	}

	@Override
	protected void onPostExecute(GetAccessTokenResult result) {
		if (dialog != null) {
			dialog.dismiss();
		}

		if (result.localRetCode == LocalRetCode.ERR_OK) {
			Toast.makeText(context, "获取access token成功, accessToken = " + result.accessToken, Toast.LENGTH_LONG).show();
			GetPrepayIdTask getPrepayId = new GetPrepayIdTask(context, result.accessToken);
			getPrepayId.execute();
		} else {
			Toast.makeText(context, "获取access token失败,原因: " + result.localRetCode.name(), Toast.LENGTH_LONG).show();
		}
	}
}

2、获取预支付订单与进行支付

import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

import com.alibaba.fastjson.JSONObject;
import com.example.exmpay.wechat.bean.WechatConstants;
import com.example.exmpay.wechat.bean.GetPrepayIdResult;
import com.example.exmpay.wechat.bean.LocalRetCode;
import com.example.exmpay.wechat.util.MD5;
import com.example.exmpay.wechat.util.WechatUtil;
import com.tencent.mm.sdk.modelpay.PayReq;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.WXAPIFactory;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

public class GetPrepayIdTask extends AsyncTask<Void, Void, GetPrepayIdResult> {

	private static final String TAG = "GetPrepayIdTask";
	private Context context;
	private ProgressDialog dialog;
	private String accessToken;

	public GetPrepayIdTask(Context context, String accessToken) {
		this.context = context;
		this.accessToken = accessToken;
	}

	@Override
	protected void onPreExecute() {
		dialog = ProgressDialog.show(context, "提示", "正在获取预支付订单...");
	}

	@Override
	protected GetPrepayIdResult doInBackground(Void... params) {
		String url = String.format("https://api.weixin.qq.com/pay/genprepay?access_token=%s", accessToken);
		String entity = genProductArgs();
		Log.d(TAG, "doInBackground, url = " + url + ", entity = " + entity);

		GetPrepayIdResult result = new GetPrepayIdResult();
		byte[] buf = WechatUtil.httpPost(url, entity);
		if (buf == null || buf.length == 0) {
			result.localRetCode = LocalRetCode.ERR_HTTP;
			return result;
		}

		String content = new String(buf);
		Log.d(TAG, "doInBackground, response content = " + content);
		result.parseFrom(content);
		return result;
	}

	@Override
	protected void onPostExecute(GetPrepayIdResult result) {
		if (dialog != null) {
			dialog.dismiss();
		}
		if (result.localRetCode == LocalRetCode.ERR_OK) {
			Toast.makeText(context, "获取prepayid成功", Toast.LENGTH_LONG).show();
			payWithWechat(result);
		} else {
			Toast.makeText(context, "获取prepayid失败,原因"+result.localRetCode.name(), Toast.LENGTH_LONG).show();
		}
	}

	private IWXAPI mWeixinApi;

//	// 如果获取token和预付标识在服务器实现,只留下支付动作在客户端实现,那么下面要异步调用
//	private void payWithWechat() {
//		final String payInfo = "";
//
//		Runnable payRunnable = new Runnable() {
//			@Override
//			public void run() {
//				sendWXPayReq(payInfo);
//			}
//		};
//
//		Thread payThread = new Thread(payRunnable);
//		payThread.start();
//	}

	private String genPackage(List<NameValuePair> params) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < params.size(); i++) {
			sb.append(params.get(i).getName());
			sb.append('=');
			sb.append(params.get(i).getValue());
			sb.append('&');
		}
		sb.append("key=");
		sb.append(WechatConstants.PARTNER_KEY); // 注意:不能hardcode在客户端,建议genPackage这个过程都由服务器端完成

		// 进行md5摘要前,params内容为原始内容,未经过url encode处理
		String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase(Locale.getDefault());
		return URLEncodedUtils.format(params, "utf-8") + "&sign=" + packageSign;
	}

	private String genNonceStr() {
		Random random = new Random();
		return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
	}

	private long genTimeStamp() {
		return System.currentTimeMillis() / 1000;
	}

	private String getTraceId() {
		return "crestxu_" + genTimeStamp();
	}

	private String genOutTradNo() {
		Random random = new Random();
		return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
	}

	private long timeStamp;
	private String nonceStr, packageValue; 

	private String genSign(List<NameValuePair> params) {
		StringBuilder sb = new StringBuilder();

		int i = 0;
		for (; i < params.size() - 1; i++) {
			sb.append(params.get(i).getName());
			sb.append('=');
			sb.append(params.get(i).getValue());
			sb.append('&');
		}
		sb.append(params.get(i).getName());
		sb.append('=');
		sb.append(params.get(i).getValue());

		String sha1 = WechatUtil.sha1(sb.toString());
		Log.d(TAG, "genSign, sha1 = " + sha1);
		return sha1;
	}

	private String genProductArgs() {
		JSONObject json = new JSONObject();

		try {
			json.put("appid", WechatConstants.APP_ID);
			String traceId = getTraceId();  // traceId 由开发者自定义,可用于订单的查询与跟踪,建议根据支付用户信息生成此id
			json.put("traceid", traceId);
			nonceStr = genNonceStr();
			json.put("noncestr", nonceStr);

			List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
			packageParams.add(new BasicNameValuePair("bank_type", "WX"));
			packageParams.add(new BasicNameValuePair("body", "千足金箍棒"));
			packageParams.add(new BasicNameValuePair("fee_type", "1"));
			packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));
			packageParams.add(new BasicNameValuePair("notify_url", "http://weixin.qq.com"));
			packageParams.add(new BasicNameValuePair("out_trade_no", genOutTradNo()));
			packageParams.add(new BasicNameValuePair("partner", WechatConstants.PARTNER_ID));
			packageParams.add(new BasicNameValuePair("spbill_create_ip", "196.168.1.1"));
			packageParams.add(new BasicNameValuePair("total_fee", "1"));
			packageValue = genPackage(packageParams);

			json.put("package", packageValue);
			timeStamp = genTimeStamp();
			json.put("timestamp", timeStamp);

			List<NameValuePair> signParams = new LinkedList<NameValuePair>();
			signParams.add(new BasicNameValuePair("appid", WechatConstants.APP_ID));
			signParams.add(new BasicNameValuePair("appkey", WechatConstants.APP_KEY));
			signParams.add(new BasicNameValuePair("noncestr", nonceStr));
			signParams.add(new BasicNameValuePair("package", packageValue));
			signParams.add(new BasicNameValuePair("timestamp", String.valueOf(timeStamp)));
			signParams.add(new BasicNameValuePair("traceid", traceId));
			json.put("app_signature", genSign(signParams));

			json.put("sign_method", "sha1");
		} catch (Exception e) {
			Log.e(TAG, "genProductArgs fail, ex = " + e.getMessage());
			return null;
		}

		return json.toString();
	}

	private void payWithWechat(GetPrepayIdResult result) {
		PayReq req = new PayReq();
		req.appId = WechatConstants.APP_ID;
		req.partnerId = WechatConstants.PARTNER_ID;
		req.prepayId = result.prepayId;
		req.nonceStr = nonceStr;
		req.timeStamp = String.valueOf(timeStamp);
		req.packageValue = "Sign=" + packageValue;

		List<NameValuePair> signParams = new LinkedList<NameValuePair>();
		signParams.add(new BasicNameValuePair("appid", req.appId));
		signParams.add(new BasicNameValuePair("appkey", WechatConstants.APP_KEY));
		signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
		signParams.add(new BasicNameValuePair("package", req.packageValue));
		signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
		signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
		signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));
		req.sign = genSign(signParams);

		mWeixinApi = WXAPIFactory.createWXAPI(context, WechatConstants.APP_ID);
		// 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
		mWeixinApi.sendReq(req);
	}
}

3、完成支付后的回调

import net.sourceforge.simcpux.R;

import com.example.exmpay.wechat.bean.WechatConstants;
import com.tencent.mm.sdk.constants.ConstantsAPI;
import com.tencent.mm.sdk.modelbase.BaseReq;
import com.tencent.mm.sdk.modelbase.BaseResp;
import com.tencent.mm.sdk.modelbase.BaseResp.ErrCode;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.sdk.openapi.WXAPIFactory;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {

	private static final String TAG = "WXPayEntryActivity";
	private IWXAPI api;
	private TextView tv_result;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.pay_result);
		tv_result = (TextView) findViewById(R.id.tv_result);
		api = WXAPIFactory.createWXAPI(this, WechatConstants.APP_ID);
		api.handleIntent(getIntent(), this);
	}

	@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) {
		Log.d(TAG, "onResp, errCode = " + resp.errCode);
		String result = "";
		if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
			switch (resp.errCode) {
			case ErrCode.ERR_OK:
				result = "微信支付成功";
				break;
			case ErrCode.ERR_COMM:
				result = "微信支付失败:"+resp.errCode+","+resp.errStr;
				break;
			case ErrCode.ERR_USER_CANCEL:
				result = "微信支付取消:"+resp.errCode+","+resp.errStr;
				break;
			default:
				result = "微信支付未知异常:"+resp.errCode+","+resp.errStr;
				break;
			}
		}
		Toast.makeText(this, result, Toast.LENGTH_LONG).show();
		tv_result.setText(result);
	}

}

银联支付

交易流程

银联支付的流程步骤见下:

1、向银联平台申请一个交易流水号;

2、向商户自己的后台发起付费交易(如果有自己后台的话);

3、使用交易流水号向银联平台发起支付请求;

4、银联sdk跳到银联支付页面,用户输入银行卡号、手机号码及验证码,提交支付;

5、支付完成,进行回调处理,返回串里有签名信息,app要传回商户后台进行验证,并确认交易;

集成步骤

银联支付的集成略微复杂,注意点如下:

1、libs目录下加入jar包UPPayAssistEx.jar和UPPayPluginExPro.jar,以及so库文件;

2、assets目录下加入data.bin文件;

3、AndroidManifest.xml加入两个银联页面的定义,示例如下:

    	<!-- 银联支付 -->
        <uses-library android:name="org.simalliance.openmobileapi" android:required="false"/>

    	<!-- 银联支付页面 -->
        <activity
            android:name="com.unionpay.uppay.PayActivity"
            android:configChanges="orientation|keyboardHidden|keyboard"
            android:screenOrientation="portrait">
        </activity>

        <activity
            android:name="com.unionpay.UPPayWapActivity"
            android:configChanges="orientation|keyboardHidden|fontScale"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustResize" >
        </activity>

4、在启动银联支付的Activity代码中,重写方法onActivityResult,对支付结果的返回包进行验证处理;

测试帐号

银联支付的官方demo下载页面是https://open.unionpay.com/ajweb/help/file,下面是官方demo的测试帐号:

银行卡号:6226090000000048
手机号:18100000000
短信验证码:123456(先点获取验证码之后再输入)

下面是银联支付的效果截图

输入银行卡号

输入手机号码与验证码

下面是银联支付的示例代码

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import org.json.JSONException;
import org.json.JSONObject;

import com.example.exmpay.unionpay.bean.UnionpayConstants;
import com.unionpay.UPPayAssistEx;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

public class UnionpayTask extends AsyncTask<Void, Void, String> {

	private static final String TAG = "UnionpayTask";
	private Context context;
	private ProgressDialog dialog;

    public static final int PLUGIN_VALID = 0;
    public static final int PLUGIN_NOT_INSTALLED = -1;
    public static final int PLUGIN_NEED_UPGRADE = 2;

	public UnionpayTask(Context context) {
		this.context = context;
	}

	@Override
	protected void onPreExecute() {
		dialog = ProgressDialog.show(context, "提示", "正在启动银联支付...");
	}

	@Override
	protected String doInBackground(Void... params) {
		Log.d(TAG, "doInBackground");
        String tn = null;
        InputStream is;
        try {

            String url = UnionpayConstants.TN_URL_01;

            URL myURL = new URL(url);
            URLConnection ucon = myURL.openConnection();
            ucon.setConnectTimeout(120000);
            is = ucon.getInputStream();
            int i = -1;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((i = is.read()) != -1) {
                baos.write(i);
            }

            tn = baos.toString();
            is.close();
            baos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

		Log.d(TAG, "tn="+tn);
		return tn;
	}

	@Override
	protected void onPostExecute(String tn) {
		if (dialog != null) {
			dialog.dismiss();
		}
		startpay(tn);
        UPPayAssistEx.startPay(context, null, null, tn, UnionpayConstants.MODE);
	}

	public static void dealResult(Context context, Intent data) {
        if (data == null) {
            return;
        }

        String msg = "";
        //支付控件返回字符串:success、fail、cancel 分别代表支付成功,支付失败,支付取消
        String str = data.getExtras().getString("pay_result");
        if (str.equalsIgnoreCase("success")) {
            // 支付成功后,extra中如果存在result_data,取出校验
            // result_data结构见c)result_data参数说明
            if (data.hasExtra("result_data")) {
                String result = data.getExtras().getString("result_data");
                Log.d(TAG, "result="+result);
                try {
                    JSONObject resultJson = new JSONObject(result);
                    String sign = resultJson.getString("sign");
                    String dataOrg = resultJson.getString("data");
                    // 验签证书同后台验签证书
                    // 此处的verify,商户需送去商户后台做验签
                    boolean ret = verify(dataOrg, sign, UnionpayConstants.MODE);
                    if (ret) {
                        // 验证通过后,显示支付结果
                        msg = "银联支付成功";
                    } else {
                        // 验证不通过后的处理
                        // 建议通过商户后台查询支付结果
                        msg = "银联支付失败:验证失败";
                    }
                } catch (JSONException e) {
                }
            } else {
                // 未收到签名信息
                // 建议通过商户后台查询支付结果
                msg = "银联支付成功,但未收到签名信息";
            }
        } else if (str.equalsIgnoreCase("fail")) {
            msg = "银联支付失败";
        } else if (str.equalsIgnoreCase("cancel")) {
            msg = "银联支付取消";
        }
		Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
	}

	private int startpay(String tn) {
		//商户后台要保存交易流水号
        return 0;
    }

    private static boolean verify(String msg, String sign64, String mode) {
        // 此处的verify,商户需送去商户后台做验签
        return true;
    }

}

点此查看Android开发笔记的完整目录

时间: 2024-10-25 09:17:18

Android开发笔记(一百零六)支付缴费SDK的相关文章

【转】Android开发笔记(序)写在前面的目录

原文:http://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经验教训,与网友互相切磋,从而去芜存菁进一步提升自己的水平.因此博主就想,入门的东西咱就不写了,人不能老停留在入门上:其次是想拾缺补漏,写写虽然小众却又用得着的东西:另外就是想以实用为主,不求大而全,但求小而精:还有就是有的知识点是java的,只是Android开发也会经常遇上,所以蛮记下来.个人的经

《ArcGIS Runtime SDK for Android开发笔记》

开发笔记之基础教程 ArcGIS Runtime SDK for Android 各版本下载地址 <ArcGIS Runtime SDK for Android开发笔记>——(1).Android Studio下载与安装 <ArcGIS Runtime SDK for Android开发笔记>——(2).Android Studio基本配置与使用 <ArcGIS Runtime SDK for Android开发笔记>——(3).ArcGIS Runtime SDK概述

Android开发笔记(一百零三)地图与定位SDK

集成地图SDK 国内常用的地图SDK就是百度和高德了,二者的用法大同小异,可按照官网上的开发指南一步步来.下面是我在集成地图SDK时遇到的问题说明: 1.点击基本地图功能选项,不能打开地图,弹出"key验证出错!请在AndroidManifest.xml文件中检查key设置的"的红色字提示.查看日志提示"galaxy lib host missing meta-data,make sure you know the right way to integrate galaxy&

Android开发笔记(一百零九)利用网盘实现云存储

网盘存储 个人开发者往往没有自己的后台服务器,但同时又想获取app的运行信息,这就要借助于第三方的网络存储(也叫网盘.云盘.微盘等等).通过让app自动在网盘上存取文件,可以间接实现后台服务器的存储功能,同时开发者也能及时找到app的用户信息. 曾几何时,各大公司纷纷推出免费的个人网盘服务,还开放了文件管理api给开发者调用,一时间涌现了网盘提供商的八大金刚:百度网盘.阿里云.华为网盘.腾讯微云.新浪微盘.360云盘.金山快盘.115网盘.可是好景不长,出于盈利.监管等等因素,各大网盘开放平台要

Android开发笔记(一百零七)统计分析SDK

APP统计分析 用户画像 对程序员来说,用户画像就是用户的属性和行为:通俗地说,用户画像是包括了个人信息.兴趣爱好.日常行为等血肉丰满的客户实体.用户画像是精准营销的产物,企业通过收集用户的行为,然后分析出用户的特征与偏好,进而挖掘潜在的商业价值,实现企业效益的最大化. 用户画像的一个具体应用是电商app的"猜你喜欢"栏目,电商平台通过对用户购买过的商品进行统计,可以分析用户日常生活用的是什么物品:电商平台还可以对用户的搜索行为.浏览行为进行统计,从中分析用户感兴趣的商品,或者说考虑购

Android开发笔记(一百零四)消息推送SDK

推送的集成 常用概念 推送:从服务器把消息实时发到客户端app上,这就是推送,推送可用于发送系统通知.发送推荐信息.发送聊天消息等等. 别名:用于给移动设备取个好记的名字,比如电脑有计算机名,可以把别名理解为开发者给移送设备起的外号.不过,多个移动设备可以起一样的别名,这几个设备就会同时收到发给该别名的消息. 标记:用于给移动设备打标签,可以理解为分类,比如超市里的泰国大米既可以打上"粮食制品"的标签,也可以打上"进口商品"的标签.服务器可以统一给某个种类的移动设备

Android开发笔记(一百零一)滑出式菜单

可移动页面MoveActivity 滑出式菜单从界面上看,像极了一个水平滚动视图HorizontalScrollView,当然也可以使用HorizontalScrollView来实现侧滑菜单.不过今天博主要说的是利用线性布局LinearLayout来实现,而且是水平方向上的线性布局. 可是LinearLayout作为水平展示时有点逗,因为如果下面有两个子视图的宽度都是match_parent,那么LinearLayout只会显示第一个子视图,第二个子视图却是怎么拉也死活显示不了.倘若在外侧加个H

Android开发笔记(一百二十)两种侧滑布局

SlidingPaneLayout SlidingPaneLayout是Android在android-support-v4.jar中推出的一个可滑动面板的布局,在前面<Android开发笔记(一百零一)滑出式菜单>中,我们提到水平布局时的LinearLayout无法自动左右拉伸,必须借助于手势事件才能拉出左侧隐藏的布局,现在SlidingPaneLayout便是为了解决LinearLayout无法自动拉伸的缺陷.只要我们在布局文件的SlidingPaneLayout节点下定义两个子布局,那么

Android开发笔记(一百三十四)协调布局CoordinatorLayout

协调布局CoordinatorLayout Android自5.0之后对UI做了较大的提升,一个重大的改进是推出了MaterialDesign库,而该库的基础即为协调布局CoordinatorLayout,几乎所有的design控件都依赖于该布局.协调布局的含义,指的是内部控件互相之前的动作关联,比如在A视图的位置发生变化之时,B视图的位置也按照某种规则来变化,仿佛弹钢琴有了协奏曲一般. 使用CoordinatorLayout时,要注意以下几点:1.导入design库:2.根布局采用androi