银联开发平台官网:https://open.unionpay.com/ajweb/product/detail?id=3 下载手机控件支付的sdk与代码:
下载解压如下:指导文档和jar包啥的都有 (测试账号银行卡也有)
集成步骤:
1.导入jar包与so文件:
2.清单文件的注册:
<!-- 银联支付需要的 application标签下-->
<uses-library
android:name="org.simalliance.openmobileapi"
android:required="false" />
<activity
android:name="com.unionpay.uppay.PayActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="com.unionpay.UPPayWapActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
3.支付类:MainActivity.java
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.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements Callback,
Runnable {
public static final String LOG_TAG = "PayDemo";
private Context mContext = null;
private int mGoodsIdx = 0;
private Handler mHandler = null;
private ProgressDialog mLoadingDialog = null;
public static final int PLUGIN_VALID = 0;
public static final int PLUGIN_NOT_INSTALLED = -1;
public static final int PLUGIN_NEED_UPGRADE = 2;
/*****************************************************************
* mMode参数解释: "00" - 启动银联正式环境 "01" - 连接银联测试环境
*****************************************************************/
private final String mMode = "01";
/**
* 获取订单号的网址 实际需要改为后台的接口
*/
private static final String TN_URL_01 = "http://101.231.204.84:8091/sim/getacptn";
/**
* 单击事件的回调,去支付
*/
private final View.OnClickListener mClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(LOG_TAG, " " + v.getTag());
mGoodsIdx = (Integer) v.getTag();
mLoadingDialog = ProgressDialog.show(mContext, // context
"", // title
"正在努力的获取tn中,请稍候...", // message
true); // 进度是否是不确定的,这只和创建进度条有关
/*************************************************
* 步骤1:从网络开始,获取交易流水号即TN(订单号)
************************************************/
new Thread(MainActivity.this).start();
}
};
/**
* 吊起支付
* @param activity 上下文对象
* @param tn 订单号
* @param mode 模式 // “00” – 银联正式环境 “01” – 银联测试环境,该环境中不发生真实交易
*/
public void doStartUnionPayPlugin(Activity activity, String tn,String mode){
// mMode参数解释:
// 0 - 启动银联正式环境
// 1 - 连接银联测试环境 tn:订单号
int ret = UPPayAssistEx.startPay(this, null, null, tn, mode);
if (ret == PLUGIN_NEED_UPGRADE || ret == PLUGIN_NOT_INSTALLED) {
// 需要重新安装控件
Log.e(LOG_TAG, "插件没有发现或需要升级");//需要去安装
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示");
builder.setMessage("完成购买需要安装银联支付控件,是否安装?");
builder.setNegativeButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UPPayAssistEx.installUPPayPlugin(MainActivity.this);
dialog.dismiss();
}
});
builder.setPositiveButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
}
Log.e(LOG_TAG, "" + ret);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
mHandler = new Handler(this);
setContentView(R.layout.activity_main);
Button btn0 = (Button) findViewById(R.id.btn0);
btn0.setTag(0);
btn0.setOnClickListener(mClickListener);//按钮,点击去支付
}
@Override
public boolean handleMessage(Message msg) {
Log.e(LOG_TAG, " " + "" + msg.obj);
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;
}
@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")) {
// 如果想对结果数据验签,可使用下面这段代码,但建议不验签,直接去商户后台查询交易结果
// 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);//下面直接返回true了
if (ret) {
// 验签成功,显示支付结果
msg = "支付成功!";
} else {
// 验签失败
msg = "支付失败!";
}
} catch (JSONException e) {
}
}
// 结果result_data为成功时,去商户后台查询一下再展示成功
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();
}
@Override
public void run() {
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);
}
int startpay(Activity act, String tn, int serverIdentifier) {
return 0;
}
private boolean verify(String msg, String sign64, String mode) {
// 此处的verify,商户需送去商户后台做验签 这里直接返回true了
return true;
}
}
4.工具类,RSAUtil.java
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import android.util.Base64;
public class RSAUtil {
public static final String RSA = "RSA";
public static final String RSA_PADDING_MODE = "RSA";
public static final String ALGORITHM_RSA_SIGN = "SHA1withRSA";
private static final String RSA_PKCS1PADDING = "RSA/ECB/PKCS1Padding";
private static final String RSA_NOPADDING = "RSA/ECB/NoPadding";
public static final int RSAKEYLEN = 2048;
/** key_lable:key_lable */
public static final String KEY_LABEL = "key_label";
/** data:data */
public static final String DATA = "data";
/** text:text */
public static final String TEXT = "text";
private static PrivateKey privateKey;
private static PublicKey publicKey;
public static PublicKey clientPublicKey;
public static PublicKey getPublicKey() {
return publicKey;
}
public static PrivateKey getPrivateKey() {
return privateKey;
}
/**
* RSA加密运算。
*
* @param data
* @param publicKey
* @return 加密结果
*/
public static byte[] encrypt(byte[] data, PublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_PADDING_MODE);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* RSA解密运算。
*
* @param data
* @param privateKey
* @return 解密成功则返回解密结果,否则返回null.
*/
public static byte[] decrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance(RSA_PADDING_MODE);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* RSA解密运算
*
* @param priKey
* @param data
* @param padding
* @return
*/
public static byte[] decrypt(PrivateKey priKey, byte[] data,
String padding, String provider) {
try {
Cipher cipher = Cipher.getInstance(padding, provider);
cipher.init(Cipher.DECRYPT_MODE, priKey);
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 根据模数和公钥指数生成公钥
*
* @param modulus
* @param publicExponent
* @return 公钥
*/
// public static PublicKey generateRSAPublicKey(String modulus,
// String publicExponent) {
// try {
// KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
// modulus), new BigInteger(publicExponent));
//
// PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
// return publicKey;
// // return keyFactory.generatePublic(pubKeySpec);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
/**
* 根据字节流产生公钥
*
* @param key
* @return 公钥
*/
public static PublicKey generateRSAPublicKey(byte[] key) {
try {
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(bobPubKeySpec);
return pubKey;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 根据字节流产生私钥
*
* @param key
* @return 私钥
*/
public static PrivateKey generateRSAPrivateKey(byte[] key) {
try {
PKCS8EncodedKeySpec pkcs8keyspec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(pkcs8keyspec);
return priKey;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 根据模和指数生成私钥
*
* @param modulus
* @param privateExponent
* @return 私钥
*/
public static PrivateKey generateRSAPrivateKey(String modulus,
String privateExponent) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
RSAPrivateKeySpec pubKeySpec = new RSAPrivateKeySpec(
new BigInteger(modulus), new BigInteger(privateExponent));
return keyFactory.generatePrivate(pubKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 使用公钥对数据进行加密,并返回byte[]类型
*
* @param publicKey
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptDataBytes(PublicKey publicKey, byte[] data)
throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int blockSize = cipher.getBlockSize();
int outputSize = cipher.getOutputSize(data.length);
int leavedSize = data.length % blockSize;
int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
: data.length / blockSize;
byte[] raw = new byte[outputSize * blocksSize];
int i = 0;
while (data.length - i * blockSize > 0) {
if (data.length - i * blockSize > blockSize) {
cipher.doFinal(data, i * blockSize, blockSize, raw, i
* outputSize);
} else {
cipher.doFinal(data, i * blockSize, data.length - i
* blockSize, raw, i * outputSize);
}
i++;
}
return raw;
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
public static PrivateKey getPrivateKey(String priKeyData) throws Exception {
/*
* n:512 e:512 d:512 p:256 q:256 dmp1:256 dmq1:256 iqmp:256
*/
BigInteger modulus = new BigInteger(priKeyData.substring(8, 512 + 8),
16);
BigInteger publicExponent = new BigInteger(priKeyData.substring(
512 + 8, 512 + 8 + 512), 16);
BigInteger privateExponent = new BigInteger(priKeyData.substring(
512 + 8 + 512, 512 + 8 + 512 + 512), 16);
BigInteger primeP = new BigInteger(priKeyData.substring(
512 + 8 + 512 + 512, 512 + 8 + 512 + 512 + 256), 16);
BigInteger primeQ = new BigInteger(priKeyData.substring(512 + 8 + 512
+ 512 + 256, 512 + 8 + 512 + 512 + 256 + 256), 16);
BigInteger primeExponentP = new BigInteger(
priKeyData.substring(512 + 8 + 512 + 512 + 256 + 256, 512 + 8
+ 512 + 512 + 256 + 256 + 256), 16);
BigInteger primeExponentQ = new BigInteger(priKeyData.substring(512 + 8
+ 512 + 512 + 256 + 256 + 256, 512 + 8 + 512 + 512 + 256 + 256
+ 256 + 256), 16);
BigInteger crtCoefficient = new BigInteger(priKeyData.substring(512 + 8
+ 512 + 512 + 256 + 256 + 256 + 256, 512 + 8 + 512 + 512 + 256
+ 256 + 256 + 256 + 256), 16);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateCrtKeySpec rsaPrivateKeySpec = new RSAPrivateCrtKeySpec(
modulus, publicExponent, privateExponent, primeP, primeQ,
primeExponentP, primeExponentQ, crtCoefficient);
return keyFactory.generatePrivate(rsaPrivateKeySpec);
}
public static PublicKey getPublicKey(String modulus, String publicExponent)
throws NoSuchAlgorithmException, InvalidKeySpecException {
BigInteger bigIntModulus = new BigInteger(modulus, 16);
BigInteger bigIntPublicExponent = new BigInteger(publicExponent, 16);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigIntModulus,
bigIntPublicExponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
/**
* 根据模数和公钥指数生成公钥
*
* @param modulus
* @param publicExponent
* @return 公钥
*/
public static PublicKey generateRSAPublicKey(String modulus,
String publicExponent) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
modulus), new BigInteger(publicExponent));
return keyFactory.generatePublic(pubKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static PublicKey getPublicKeyPM() {
// 请将此处的module换成PM环境商户验签的公钥模数
String modulus = "24870613246304283289263670822577417714537477136695312218046086562441084140352408862449003198972758030370375896331356438381534807815999481415930217971513079824183591552429779125222230389655838097565141139205829591128287005548898062000970767426912014994392229218979869216370190349843903870279325956661459861716847460988265260792970759967490015941772320263508330685602563839220027394572548955687677315821727057921756004005781874479358265172016335126486731385109336772938263090077762887508722625235251295041241798219236919770312254416281253815794530657627243362881204125234159183339122880098511453026644263131341899862471";
String publicExponent = "65537";
PublicKey publicKey = RSAUtil.generateRSAPublicKey(modulus,
publicExponent);
return publicKey;
}
public static PublicKey getPublicKeyProduct() {
// 请将此处的module换成生产环境商户验签的公钥模数
String modulus = "24882698307025187401768229621661046262584590315978248721358993520593720674589904440569546585666019820242051570504151753011145804842286060932917913063481673780509705461614953345565639235206110825500286080970112119864280897521494849627888301696007067301658192870705725665343356870712277918685009799388229000694331337917299248049043161583425309743997726880393752539043378681782404204317246630750179082094887254614603968643698185220012572776981256942180397391050384441191238689965500817914744059136226832836964600497185974686263216711646940573711995536080829974535604890076661028920284600607547181058581575296480113060083";
String publicExponent = "65537";
PublicKey publicKey = RSAUtil.generateRSAPublicKey(modulus,
publicExponent);
return publicKey;
}
public static boolean verifyPM(byte[] message, byte[] signature)
throws Exception {
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(getPublicKeyPM());
sig.update(message);
return sig.verify(signature);
}
public static boolean verifyProduct(byte[] message, byte[] signature)
throws Exception {
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(getPublicKeyProduct());
sig.update(message);
return sig.verify(signature);
}
public static String sha1(byte[] raw) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.reset();
messageDigest.update(raw);
byte[] bytes = messageDigest.digest();
return bytesToHex(bytes);
} catch (Exception e) {
return null;
}
}
public static boolean verify(String msg, String sign64, String mode) {
boolean ret = false;
try {
if ("01".equals(mode)) {
ret = RSAUtil.verifyPM(RSAUtil.sha1(msg.getBytes()).getBytes(),
Base64.decode(sign64, Base64.NO_WRAP));
} else if ("00".equals(mode)) {
ret = RSAUtil.verifyProduct(RSAUtil.sha1(msg.getBytes())
.getBytes(), Base64.decode(sign64, Base64.NO_WRAP));
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/**
* 将16进制的字符串转换成bytes
*
* @param hex
* @return 转化后的byte数组
*/
public static byte[] hexToBytes(String hex) {
return hexToBytes(hex.toCharArray());
}
/**
* 将16进制的字符数组转换成byte数组
*
* @param hex
* @return 转换后的byte数组
*/
public static byte[] hexToBytes(char[] hex) {
int length = hex.length / 2;
byte[] raw = new byte[length];
for (int i = 0; i < length; i++) {
int high = Character.digit(hex[i * 2], 16);
int low = Character.digit(hex[i * 2 + 1], 16);
int value = (high << 4) | low;
if (value > 127) {
value -= 256;
}
raw[i] = (byte) value;
}
return raw;
}
/**
* 将byte数组转换成16进制字符串
*
* @param bytes
* @return 16进制字符串
*/
public static String bytesToHex(byte[] bytes) {
String hexArray = "0123456789abcdef";
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
int bi = b & 0xff;
sb.append(hexArray.charAt(bi >> 4));
sb.append(hexArray.charAt(bi & 0xf));
}
return sb.toString();
}
public static String publicDecrypt(PublicKey key, byte[] enc) {
Cipher cipher = null;
String decText = "";
if (null == enc) {
return decText;
}
try {
cipher = Cipher.getInstance(RSA_NOPADDING);
// byte[] data = PBOCUtils.hexStringToBytes(message);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] enBytes = cipher.doFinal(enc);
decText = bytesToHex(enBytes);
// decText = new String(enBytes);
// Log.e("RSA", "encText:" + decText);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return decText;
}
}
View Code
支付流程图:
1.可见我们安卓端只需要访问后台的接口,拿订单号就行了
2.支付结果的确定(向后台确定为好)
可参考:
效果图: