在这个移动互联网快速发展的时代,手机已经成为人们生活或者出行之中必不可少的设备了,如今非常多城市的商户都能够採用支付宝,微信支付了。人们出门仅仅须要随身携带带手机。不用带大量现金就能够放心购物了。如今的非常多移动互联网产品都应用到移动支付功能。特别像电商平台。每天下单。支付就是他们处理的最复杂的业务逻辑。
作为一位Android开发者也必须应该懂得如何在自己所开发的应用中接入当今互联网非常流行的移动支付(银联,支付宝,微信)功能。所以此篇文章就来记录一下Android对于移动支付的一些开发步骤。
银联支付
简单介绍
银联手机支付控件(下面简称支付控件),主要为合作商户的手机client提供安全、便捷的支付服务。
用户通过在支付控件中输入银行卡卡号、手机号、password(借记卡和预付卡)或者CVN2(信用卡背面的数字)、有效期(信用卡)、验证码等要素完毕支付。
支付流程介绍
事实上银联,支付宝,微信的支付流程都差点儿相同,这里先说银联的支付流程。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" width="400" height="400" alt="">
流程图说明:
(1)用户在client中点击购买商品。client发起订单生成请求到商户后台;
(2)商户后台收到订单生成请求后,依照《手机控件支付产品接口规范》组织并推送订单信息至银联后台;
(3)银联后台接收订单信息并检查通过后。生成相应交易流水号(即TN),并回复交易流水号至商户后台(应答要素:交易流水号等);
(4)商户后台接收到交易流水号。将交易流水号返回给client;
(5)client通过交易流水号(TN)调用支付控件;
(6)用户在支付控件中输入相关支付信息后。由支付控件向银联后台发起支付请求。
(7)支付成功后,银联后台将支付结果通知给商户后台。
(8)银联将支付结果通知支付控件;
(9)支付控件显示支付结果并将支付结果返回给client;
在client的操作是(1)首先将商品的id传递给后台server,步骤(2)-(4)是server端完毕的。全部client不须要做其它操作。在本篇文章中直接产生一个模拟的流水号(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,处理client应该处理的逻辑。比如上面的mainActivity。
支付宝支付
蚂蚁金服也的确非常牛逼哈。支付宝支付的接入应该是在这三种支付接入方式中最简单的一种吧。有具体的文档和demo。
文档。sdk,demo下载地址。就支付宝,微信支付而言我们都须要注冊成为商家才干进行支付宝。微信支付方式的接入。一般的普通用户是没有这样的功能的,预计是微信或者支付宝要对这一部分人收取额外的费用
吧。所以要走这样一个流程。支付宝商家支付中心的申请也须要一堆审核。呵呵 ,假设你真的想去试试能够依照官网上的步骤一步一步走下去,以下所写的样例中。我用的是一个实际其中的商户ID。全部会真实的支付0.01元。还是依照我们习惯的一般步骤来看:
<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文件夹下。免得我们又一次写一些关于签名加解密的类
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 + "\""; // server异步通知页面路径
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 = "zf@159cai.com";
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行实际上是构建订单信息的一个过程,事实上在现实中这个构建订单信息的过程是由client将商品的id传递给server端,server依据商家的一些信息,构建订单数据发送给支付宝的server,支付宝验证确定订单信息之后,才可以開始支付。
这里为了演示的简单,而且对后台server打搭建也不熟,所以就直接在client产生了订单信息。直接发送给支付宝。里面的參数的具体解释可以看以下的參数说明链接。
微信支付
chapter=8_1">API