第三方支付
第三方支付指的是第三方平台与各银行签约,在买方与卖方之间实现中介担保,从而增强了支付交易的安全性。国内常用的支付平台主要是支付宝和微信支付,其中支付宝的市场份额为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; } }