銀聯開發平台官網: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
效果圖: