在这个移动互联网高速发展的时代,手机已经成为人们生活或者出行之中不可缺少的设备了,现在很多城市的商户都可以采用支付宝,微信支付了,人们出门只需要随身携带带手机,不用带大量现金就可以放心购物了。现在的很多移动互联网产品都应用到移动支付功能,特别像电商平台,每天下单,支付就是他们处理的最复杂的业务逻辑。作为一位Android开发人员也必须应该懂得怎样在自己所开发的应用中接入当今互联网非常流行的移动支付(银联,支付宝,微信)功能。所以此篇文章就来记录一下Android对于移动支付的一些开发步骤。
银联支付
简介
银联手机支付控件(以下简称支付控件),主要为合作商户的手机客户端提供安全、便捷的支付服务。用户通过在支付控件中输入银行卡卡号、手机号、密码(借记卡和预付卡)或者CVN2(信用卡背面的数字)、有效期(信用卡)、验证码等要素完成支付。
支付流程介绍
其实银联,支付宝,微信的支付流程都差不多,这里先说银联的支付流程。
流程图说明:
(1)用户在客户端中点击购买商品,客户端发起订单生成请求到商户后台;
(2)商户后台收到订单生成请求后,按照《手机控件支付产品接口规范》组织并推送订单信息至银联后台;
(3)银联后台接收订单信息并检查通过后,生成对应交易流水号(即TN),并回复交易流水号至商户后台(应答要素:交易流水号等);
(4)商户后台接收到交易流水号,将交易流水号返回给客户端;
(5)客户端通过交易流水号(TN)调用支付控件;
(6)用户在支付控件中输入相关支付信息后,由支付控件向银联后台发起支付请求;
(7)支付成功后,银联后台将支付结果通知给商户后台;
(8)银联将支付结果通知支付控件;
(9)支付控件显示支付结果并将支付结果返回给客户端;
在客户端的操作是(1)首先将商品的id传递给后台服务器,步骤(2)-(4)是服务器端完成的,所有客户端不需要做其他操作。在本篇文章中直接产生一个模拟的流水号(TN),直接调用支付控件,完成步骤(5)-(9)的操作。下面的代码就是一个简单的使用银联支付的demo
package com.example.unionpay; 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.unionpay.UPPayAssistEx; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity implements Callback,Runnable{ /** * mMode参数解释: "00" - 启动银联正式环境 "01" - 连接银联测试环境 */ private final String mMode = "01"; //银联官方测试环境提供的访问接口 可供测试使用,在实际开发当中可以替换成自己的服务端的接口地址 用于生产流水号(TN) private static final String TN_URL_01 = "http://101.231.204.84:8091/sim/getacptn"; private Button btn; private Context mContext = null; private Handler mHandler = null; private ProgressDialog mLoadingDialog = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; mHandler = new Handler(this); btn = (Button) findViewById(R.id.submit); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //这里做具体的提交数据 访问网络的操作 mLoadingDialog = ProgressDialog.show(mContext,"", "正在努力的获取tn中,请稍候...",true); // 进度是否是不确定的,这只和创建进度条有关 // 步骤1:从网络开始,获取交易流水号即TN new Thread(MainActivity.this).start(); } }); } @Override public void run() { //这里主要做的是模拟将从商品ID传递给服务端 让服务端生产订单 最后银联后台生成支付的流水号 //此处直接返回流水号 String tn = null; InputStream is; try { String url = 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(); } Message msg = mHandler.obtainMessage(); msg.obj = tn; mHandler.sendMessage(msg); } @Override public boolean handleMessage(Message msg) { //callback的回调函数 if (mLoadingDialog.isShowing()) { mLoadingDialog.dismiss(); } String tn = ""; if (msg.obj == null || ((String) msg.obj).length() == 0) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("错误提示"); builder.setMessage("网络连接失败,请重试!"); builder.setNegativeButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.create().show(); } else { tn = (String) msg.obj; //步骤2:通过银联工具类启动支付插件 doStartUnionPayPlugin(this, tn, mMode); } return false; } public void doStartUnionPayPlugin(Activity activity, String tn, String mode) { UPPayAssistEx.startPay(activity, null, null, tn, mode); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { /************************************************* * 步骤3:处理银联手机支付控件返回的支付结果 ************************************************/ 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"); try { JSONObject resultJson = new JSONObject(result); String sign = resultJson.getString("sign"); String dataOrg = resultJson.getString("data"); // 验签证书同后台验签证书 // 此处的verify,商户需送去商户后台做验签 boolean ret = verify(dataOrg, sign, mMode); if (ret) { // 验证通过后,显示支付结果 msg = "支付成功!"; } else { // 验证不通过后的处理 // 建议通过商户后台查询支付结果 msg = "支付失败!"; } } catch (JSONException e) { } } else { // 未收到签名信息 // 建议通过商户后台查询支付结果 msg = "支付成功!"; } } else if (str.equalsIgnoreCase("fail")) { msg = "支付失败!"; } else if (str.equalsIgnoreCase("cancel")) { msg = "用户取消了支付"; } AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("支付结果通知"); builder.setMessage(msg); builder.setInverseBackgroundForced(true); // builder.setCustomTitle(); builder.setNegativeButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.create().show(); } private boolean verify(String msg, String sign64, String mode) { // 此处的verify,商户需送去商户后台做验签 return true; } }
开发步骤(只是我个人的习惯):
1.在xml文件中添加所需要的权限,这里直接复制粘贴就ok
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="org.simalliance.openmobileapi.SMARTCARD" /> <uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc.hce"/>
2.在Application节点下面添加下面代码,我也不知道为毛,照着做就ok了
<uses-library android:name="org.simalliance.openmobileapi" android:required="false"/>
3.添加银联sdk中需要用到的支付的activity,注册。直接ctrl+c,ctrl+v就ok
<activity android:name="com.unionpay.uppay.PayActivity" android:configChanges="orientation|keyboardHidden|keyboard" android:screenOrientation="portrait" android:excludeFromRecents="true" android:windowSoftInputMode="adjustResize"> </activity> <activity android:name="com.unionpay.UPPayWapActivity" android:configChanges="orientation|keyboardHidden|fontScale" android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize" > </activity>
4.上面步骤完成之后,你会看到manifest文件报错,提示要添加jar包,这就得去下载相应的jar添加响应的jar包和.so文件了,可以直接到我后面的demo里面拷贝。也可以到官方网址上面去下载。下面给了官网的步骤,好人做到底。别忘了还要将data.bin放入assets目录下面。具体这些请参考《中国银联手机支付控件接入指南》的操作
(1). 打开https://open.unionpay.com/
(2). 下载规范和开发包。帮助中心-下载-产品接口规范-手机控件支付产品接口规范,帮助中心-下载-产品接口规范-手机控件支付产品技术开发包(安卓版的)。
5.开发我们自己的activity,处理客户端应该处理的逻辑,例如上面的mainActivity。
支付宝支付
蚂蚁金服也的确很牛逼哈。支付宝支付的接入应该是在这三种支付接入方式中最简单的一种吧。有详细的文档和demo。文档,sdk,demo下载地址。就支付宝,微信支付而言我们都需要注册成为商家才能进行支付宝,微信支付方式的接入。一般的普通用户是没有这种功能的,估计是微信或者支付宝要对这一部分人收取额外的费用
吧,所以要走这样一个流程。支付宝商家支付中心的申请也需要一堆审核。呵呵 ,如果你真的想去试试可以按照官网上的步骤一步一步走下去,下面所写的例子中,我用的是一个实际当中的商户ID,所有会真实的支付0.01元。还是按照我们习惯的一般步骤来看:
(1)在manifest文件中假如相关的系统权限。如下,直接copy就ok。
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
(2)注册添加支付宝自己的接口,也就是支付的activity页面。
<!-- alipay sdk begin --> <activity android:name="com.alipay.sdk.app.H5PayActivity" android:configChanges="orientation|keyboardHidden|navigation" android:exported="false" android:screenOrientation="behind" android:windowSoftInputMode="adjustResize|stateHidden" > </activity> <!-- alipay sdk end -->
(3)此时当然会提示找不到上面的H5PayActivity,此时就需要导入相应的jar包了,直接到官方demo里面拷贝alipaySdk.jar放入libs文件夹下面就ok。其实此时别忘了将demo中的Base64.java,PayResult.java,SignUtils.java拷贝到我们的src目录下。免得我们重新写一些关于签名加解密的类
上面就直接写自己的生成订单,提交订单,支付结果处理的逻辑就ok。
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.alipay.sdk.app.PayTask; import com.tz.aplipay.utils.Constants; import com.tz.aplipay.utils.PayResult; import com.tz.aplipay.utils.SignUtils; public class PayActivity extends Activity { //商户ID public static final String PARTNER = Constants.PARTNER; //商户收款账号 public static final String SELLER = Constants.SELLER; //商户私钥 //私钥用于对数据进行签名加密 //公钥用于对签名进行验证 public static final String RSA_PRIVATE = Constants.RSA_PRIVATE; private String name; private String price; private String desc; private final static int MESSAGE_PAY = 1; private Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { switch (msg.what) { case MESSAGE_PAY: String result = (String) msg.obj; PayResult payResult = new PayResult(result); //支付状态码 String resultStatus = payResult.getResultStatus(); if (TextUtils.equals(resultStatus, "9000")) { Toast.makeText(PayActivity.this, "支付成功", Toast.LENGTH_SHORT).show(); }else if (TextUtils.equals(resultStatus, "8000")) { Toast.makeText(PayActivity.this, "支付结果确认中", Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(PayActivity.this, "支付失败", Toast.LENGTH_SHORT).show(); } break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pay); //获取商品的详细信息 Intent intent = getIntent(); name = intent.getStringExtra("name"); price = intent.getStringExtra("price"); desc = intent.getStringExtra("desc"); TextView product_name = (TextView) findViewById(R.id.product_name); TextView product_price = (TextView) findViewById(R.id.product_price); TextView product_desc = (TextView) findViewById(R.id.product_desc); product_name.setText(name); product_price.setText(price); product_desc.setText(desc); } //支付 public void pay(View btn){ //1.构建订单数据 String orderInfo = getOrderInfo(name, desc, price); //2.对订单进行签名 String sign = sign(orderInfo); try { //转码 sign = URLEncoder.encode(sign, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //3.构建一个符合支付宝参数规范的订单数据 final String payInfo = orderInfo + "&sign=\""+sign +"\"&sign_type=\"RSA\"" ; //4.调用支付接口 new Thread(){ public void run() { //支付任务 PayTask task = new PayTask(PayActivity.this); String result = task.pay(payInfo); //按照支付宝的规则进行支付结果的解析 Message msg = handler.obtainMessage(MESSAGE_PAY); msg.obj = result; handler.sendMessage(msg); } }.start(); //5.处理支付结果 } /** * 对订单进行签名 * @param orderInfo * @return */ private String sign(String orderInfo) { return SignUtils.sign(orderInfo, RSA_PRIVATE); } /** * 构建订单信息 * @param subject 商品名称 * @param body 商品详情 * @param price 商品金额 * @return */ public String getOrderInfo(String subject, String body, String price) { // 签约合作者身份ID String orderInfo = "partner=" + "\"" + PARTNER + "\""; // 签约卖家支付宝账号 orderInfo += "&seller_id=" + "\"" + 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; } /** * 产生一个包含时间的随机的订单号 * @return */ private String getOutTradeNo() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(); String key = sdf.format(date); Random random = new Random(); key = key + random.nextInt(); key = key.substring(0, 15); return key; } }
上述final类型的PARTNER对象就是在注册商家时,支付宝分配陪你的唯一ID。SELLER代表的是商家注册时候拥有交易的支付宝账号。RSA_PRIVATE是你注册时候支付宝分配给你的唯一私钥,配合支付宝的公钥实现数据的加解密操作。看看的我的constant文件。实际开发当中,必须将相应的值替换成你所申请的商户信息,不然以后打钱的时候都打到我这个支付宝当中了额。
public class Constants { public static final String PARTNER = "2088111984427922"; public static final String SELLER = "[email protected]"; public static final String RSA_PRIVATE = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANcUPyx/lkokBOM1UfnFF1UckLs2Z2nh7RPMyhiMrats6LqsEhkCi4CiWS4ha6GuZcDtKzK2bfzd61IS1l6Ae4Qt+VZcdSpG+5vu875awBgiBCnbCP6YBm81vcAdSJYlOpizgS4JCuIalUqqaWN3hSP4tThuQcRAeHFwDi/ImJyDAgMBAAECgYBo2MnjG19cTSrEyB1qMRYqu34ihWbsSuKToGV0ij+vLaxWM8OuxXrT/lCTGF+rtaSM5BEG67+6YURyAhTWhLOw2mcEakFfVY0uTGadtsFODwHPkmDzL/kIhi99rB2gg0ota+FNVfd2oAmc//UvgHAeMA2bW+kxt9Y73XQE8yjbgQJBAP4kEemsgUtMo4y9lP8lkmwfK7pAWBiT2zajM6m///kfPG3NLJdJ4E4R026RBcZmL9WVxs2isX4o6TgLXtAmHpMCQQDYpwaT4RpkXIiSw124TlwrJF786c/OOtXm30kUuqWdMnbb6NFXDFMeeAPNN0bLLquR5X+XCIsroltpmLRi+VBRAkAuJGhoL9ztygVr2UQDK1Qxc1tiHqqgE8BaZDlOGcEk/ynemcD92vjx08S6r3QH+Ke4tM/6qA5n5I+rkEzvp+wnAkB2tG1CMSAIxTp/T1PWW/jcGn2BDYqycEIq0UR1ex6q1q+RJistCq+wDgnnMtYzFUskER6rXh8CtV5oqSaM5BVBAkBUqxfm8ro7eNagEnnWZidMrUIwesp1TQsQjtLFZTC9mVnpgLMj0IIaggojEBTQR6SvW7Sm7MqgqzYNUxlP0eoG"; public static final String RSA_PUBLIC = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB"; }
在整个PayActivity中代码的132-181行实际上是构建订单信息的一个过程,其实在现实中这个构建订单信息的过程是由客户端将商品的id传递给服务器端,服务器根据商家的一些信息,构建订单数据发送给支付宝的服务器,支付宝验证确定订单信息之后,才能够开始支付。这里为了演示的简单,并且对后台服务器打搭建也不熟,所以就直接在客户端产生了订单信息。直接发送给支付宝。里面的参数的具体解释可以看下面的参数说明链接。
生成订单信息后就要对订单进行签名,最后构建符合支付宝参数规范的订单数据,之后调用支付接口PayTask。 如果手机上有支付宝客户端,就会直接打开支付宝客户端,如果么样安装支付宝客户端,就直接调用签名注册的H5PayActivity进行登录支付操作。代码中的handler就是处理支付之后的各种返回结果。
微信支付
目前没有实例来写微信支付,因为他也需要商家认证,所有先贴出微信支付的接入的 API