银联开发平台官网: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; } }
支付流程图:
1.可见我们安卓端只需要访问后台的接口,拿订单号就行了
2.支付结果的确定(向后台确定为好)
可参考:http://blog.csdn.net/qq_33078541/article/details/50580102
效果图: