移動支付
用戶使用移動的終端完成對所購買商品或者服務的支付功能;分為近場支付(藍牙支付,刷卡,滴卡),和遠程支付(網上支付,短信支付)
app支付模塊
常見的支付廠商-->常見的支付方式
- 支付寶:阿里公司
- 微信:騰訊公司
- 銀聯:聯合起來的結構
- 財付通:騰訊公司
- 支付寶錢包:阿里公司
- 百度錢包:百度公司
支付安全嗎?
都是比較安全.都是大公司的產品.而且這個和金錢之前掛鈎;
支付難不難?
支付不難.因為是第三方平台的東西.
支付集成大概需要多長時間?(如果之前做過)
- 支付寶:5-10分鍾
- 銀聯:5-10分鍾
- 微信:10-20分鍾
支付流程_從生活出發
- 1. 選擇商品-->goodName,goodId,price,count
- 2. 選擇支付方式-->payType:1,支付寶;2,銀聯;3:微信
- 3. 處理支付結果-->支付成功(購物流程),支付失敗(重試,放棄)
支付流程_從程序角度出發
- 選擇商品,組裝支付數據-->拼接請求的jsonString
- 把支付數據post到后台server-->發送一個請求request
- 后台server(支付寶的服務)生成支付串碼--->處理第二步的reponse
-
在客戶端使用第三方平台的api調用插件完成支付-->調用第三方平台jar包里面的方法(集成過程),這一步才用到支付寶sdk
- 處理支付結果-->利用沒有平台特有的通知機制處理支付結果
自己總結一下
支付串碼是啥?
支付方法需要的支付參數
支付流程_簡明說法
- 發起支付請求
- 拿到支付串碼
- 調用api支付
- 處理支付結果
- 同步返回:支付后通知我們自己的apk
- 異步通知:支付后通知我們的server
支付寶
- demo運行問題:需要ALIPAYPartnerID,ALIPAYSellerID,ALIPAYMD5KEY,ALIPAYPartnerPrivKey,ALIPAYPubKey才可以運行.但是如果直接下載的demo.這些字段都是"",無法看到效果,這些數據的獲得.是用公司運行去做;
- 支付寶demo的流程和實際開發不一致.實際開發.生成支付串碼的過程應該交給服務器.因為sign需要支付寶的privatekey,如果放到apk里面是會泄露privatekey的,所以最后簡化之后的支付寶集成就幾行代碼;
- RSA:RSA是一種公鑰加密算法。能夠抵抗到目前為止已知的絕大多數密碼攻擊,已被ISO推薦為公鑰數據加密標准。主要用於公鑰加密私鑰解密、私鑰簽名公鑰驗簽。
- 支付寶公鑰:開發者請求支付寶並獲得返回時,開發者用於驗簽使用的公鑰
- 我們自己要和支付寶簽約(商戶簽約).-->運營
- 秘鑰配置-->協助運營完成秘鑰的配置(公鑰互換),可能程序員會參與
- 集成支付寶-->必須是程序員去做.
MainActivity
public class MainActivity extends Activity {
private Button btn_zfb;
/**
* 支付寶通過hanlder機制發送支付結果出來,這個過程我們理解為"同步返回".因為只要支付寶支付成功.客戶端就可以通過handler收到消息
*/
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {/*
Result result = new Result((String) msg.obj);
switch (msg.what) {
case RQF_PAY:
case RQF_LOGIN: {
Toast.makeText(ExternalPartner.this, result.getResult(), Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
*/
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
btn_zfb = (Button) findViewById(R.id.btn1);
}
private void initListener() {
/**
* 支付寶支付
*/
btn_zfb.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//1. 選擇商品,把**支付數據**post到后台server-->發送一個請求
final String goodInfoJsonString = "{\"goodInfos\":[{\"goodCounts\":\"1\",\"goodExtInfo\":{},\"goodIDs\":\"965\",\"goodType\":\"1\"}],\"loginFlag\":\"0\",\"mobile\":\"18682036558\",\"orderId\":\"0\",\"otherInfo\":{\"agentID\":\"0-maizuo\",\"channelID\":\"31\",\"clientID\":\"31\"},\"payDatas\":{\"discountInfo\":{\"activeID\":\"0\",\"discountID\":\"0\",\"discountPrice\":\"\"},\"payInfo\":[{\"bankType\":\"7\",\"payCount\":\"3800\",\"payTicketCount\":\"1\",\"payType\":\"1\"}],\"payPass\":\"\",\"returnUrl\":\"\",\"totalPrice\":\"3800\"},\"processPath\":\"1\",\"sessionKey\":\"chfrlczgtomqsiurzzyo\",\"userID\":\"200394160\"}";
//2.把支付數據post到后台server-->發送一個請求
new Thread(new Runnable() {
@Override
public void run() {
// TODO
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost post = new HttpPost(
"http://mobileif.maizuo.com/version3/orderform/order?version=2");
post.addHeader("Content-Type", "application/json");
post.setEntity(new StringEntity(goodInfoJsonString));
HttpResponse response = httpClient.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
System.out.println(result);//-->bean-->getAlipayVerifyKey();
//3.在客戶端使用第三方平台的api調用插件完成支付
//獲取Alipay對象,構造參數為當前Activity和Handler實例對象
//mHandler就是一會處理我們支付結果的hanlder
AliPay alipay = new AliPay(MainActivity.this, handler);
//調用pay方法,將訂單信息傳入
String orderInfo = "_input_charset=\"UTF-8\"&body=\"賣座網電子影票\"&it_b_pay=\"1h\"¬ify_url=\"http%3A%2F%2Fpay.maizuo.com%2FmobileBack.htm\"&out_trade_no=\"201503283783092428\"&partner=\"2088411628331920\"&payment_type=\"1\"&seller_id=\"2088411628331920\"&service=\"mobile.securitypay.pay\"&subject=\"海岸影城(2D通兌票1張)\"&total_fee=\"38.00\"&sign=\"KDhXG0I8T1VZCgg3tfmYbnhF91I6marCQ0yWgmIe1ZGJ9z6MHFwwV7O156%2FkTecKikrIwRnrPNOI%0Ac8h6bUPRX9DIoHF3Yamj1NCi%2B5j0e16uRy5VtyhLFPx608stqjLlaepBsRZYPblyikuts67W9IJ%2B%0AyNrrG8cZ6ltgulZTFH4%3D\"&sign_type=\"RSA\"";//支付串碼
String payResult = alipay.pay(orderInfo);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
});
}
// “00” – 銀聯正式環境
// “01” – 銀聯測試環境,該環境中不發生真實交易
String serverMode = "00";
/**
* 銀聯支付
*/
public void uupay(View v) {
//通過支付請求.拿到支付串碼
String tn = "201503281059506568548";
//發起支付請求
int ret = UPPayAssistEx.startPay(MainActivity.this, null, null, tn, serverMode);
if (ret == UPPayAssistEx.PLUGIN_NOT_FOUND) {
//安裝Asset中提供的UPPayPlugin.apk
// 此處可根據實際情況,添加相應的處理邏輯
UPPayAssistEx.installUPPayPlugin(this);
}
}
/**
* uupay處理支付結果
*/
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data == null) {
return;
}
String str = data.getExtras().getString("pay_result");
String msg = "";
/*
* 支付控件返回字符串:success、fail、cancel 分別代表支付成功,支付失敗,支付取消
*/
if (str.equalsIgnoreCase("success")) {
msg = "支付成功!";
} else if (str.equalsIgnoreCase("fail")) {
msg = "支付失敗!";
} else if (str.equalsIgnoreCase("cancel")) {
msg = "用戶取消了支付";
}
//下面就是進行用戶提示
Toast.makeText(getApplicationContext(), msg, 0).show();
}
}
RSA密鑰生成命令
生成RSA私鑰
openssl>genrsa -out rsa_private_key.pem 1024
生成RSA公鑰
openssl>rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
將RSA私鑰轉換成PKCS8格式
openssl>pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
注意:“>”符號后面的才是需要輸入的命令。
E:\支付\支付寶\支付寶錢包支付接口開發包2.0標准版(20160120)\DEMO\openssl\bin\1目錄下有倆個文件
開發者將私鑰保留,將公鑰提交給支付寶網關,用於信息加密及解密。
銀聯
- demo運行問題:只需要tnno就可以
- 銀聯提供了測試環境和正式環境,而且還有測試賬號
-
關鍵方法:
//通過支付請求.拿到支付串碼 String tn = "201503281059506568548"; //發起支付請求 int ret = UPPayAssistEx.startPay(MainActivity.this, null, null, tn, serverMode); if (ret == UPPayAssistEx.PLUGIN_NOT_FOUND) { //安裝Asset中提供的UPPayPlugin.apk // 此處可根據實際情況,添加相應的處理邏輯 UPPayAssistEx.installUPPayPlugin(this); }
-
處理支付結果:在onactivityForResult中處理
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
initData();
if (data == null) {
return;
}
String uupayResult = data.getExtras().getString("pay_result");
Logger.i(TAG, "pay_result:" + uupayResult);
if (uupayResult != null && !"".equals(uupayResult)) {
Logger.i(TAG, "pay_result:" + uupayResult);
Message msg = new Message();
if (uupayResult.equalsIgnoreCase("success")) {
msg.what = UUPAYSUCCESS;
} else if (uupayResult.equalsIgnoreCase("fail")) {
msg.what = UUPAYFAIL;
}
/*else if (uupayResult.equalsIgnoreCase("cancel")) {
msg = "用戶取消了支付";
}*/
handler.sendMessage(msg);
}
super.onActivityResult(requestCode, resultCode, data);
}
微信
- 步驟
- 一、獲取 access_token
- 二、生成預支付訂單
- 三、調起微信支付
- 四、接收支付返回結果
-
關鍵方法:
private void sendPayReq(WXPayData info) { api = WXAPIFactory.createWXAPI(this, info.getAppid()); PayReq req = new PayReq(); req.appId = info.getAppid(); req.partnerId = info.getPartnerid(); req.prepayId = info.getPrepayid();//預支付id req.nonceStr = info.getNoncestr();//32位內的隨機串,防重發 req.timeStamp = String.valueOf(info.getTimestamp());//時間戳,為 1970 年 1 月 1 日 00:00 到請求發起時間的秒數 req.packageValue = info.getPackage1(); req.sign = info.getApp_signature(); // 在支付之前,如果應用沒有注冊到微信,應該先調用IWXMsg.registerApp將應用注冊到微信 api.sendReq(req); }處理支付結果:在WXPayEntryActivity.java
errCode = resp.errCode;
Logger.i(TAG, "微信支付結果:" + String.valueOf(resp.errCode));
if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
/*AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.app_tip);
builder.setMessage(getString(R.string.pay_result_callback_msg, String.valueOf(resp.errCode)));
builder.show();*/
}
Message msg = new Message();
if (errCode == 0) {
msg.what = PayActivity.WEIXINPAYSUCCESS;
} else {
msg.what = PayActivity.WEIXINPAYFAIL;
}
PayActivity.instance.handler.sendMessage(msg);
finish();
-
安全碼:
數字簽名+";"+包名; 輸入“安全碼”。安全碼的組成規則為:Android簽名證書的sha1值+“;”+packagename(即:數字簽名+分號+包名),例如: BB:0D:AC:74:D3:21:E1:43:67:71:9B:62:91:AF:A1:66:6E:44:5D:75;com.baidumap.dem
- 作用:保證app唯一性
現在的支付寶demo主代碼:PayDemoActivity
特別注意,這里的簽名邏輯需要放在服務端,切勿將私鑰泄露在代碼中!,所剩代碼就是上面的,把方法換成現在的即可
public class PayDemoActivity extends FragmentActivity {
// 商戶PID
public static final String PARTNER = "2016012501118845";
// 商戶收款賬號
public static final String SELLER = "6225757524716173";
// 商戶私鑰,pkcs8格式
public static final String RSA_PRIVATE = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALTam792ATycFFDXWg6VEQofa3lT4qWkmcyXLnbSZVHV/brDdDNfeHMJWwvsvuJYNxEDZOYL/AA7/WuBLI+KklXhFUnu/NjQWmXzndiQI15Mfq+2TDh1Cf9H7ypUHah8RrcTwaM9H1/SWP7f2o2QOucB2Y/bI4Faq3ISwONXztTvAgMBAAECgYEAjK4cRwOhFKeIehX6XKuB9LDaJielfxoZ9PaI0y74V38w/q15X1jdVgaqBw2ismjSdO6B9xzNatU/XPe/VO0CxHFZ3/5Qhc/b724MsTxGyVC8TMI/oHMgAlVE3cR4/fwj0fhsYUYbSy9yCTqyinpdLZcNkUpMBJaeaM4jQJZvaSECQQDm7TrKPyJ1mgkKZADco+/HzcX1OnLvGtjFnSxD4LUShFfpYW5bWthy+869Jt9iIbOVDkvrfANMKhOuk0sEany/AkEAyH2SUFJUA1r+csi6WDf694npi6gtY0MhcNgGmoVr3g1daWf3cbx81VUE9y4ffqH91mdxWlVMVsCQetNYywdD0QJAAKQsA5/FQrpYyBSbBAHYip+BqzqsUwmqDHJxSwb2ucRwUg+ZNNu9uiQE4PWYrTcWvpU5lL/VaoK7Z0K1dJ+vFQJBAKV78F7X9XxniQqZYCYc3sufS+P4Rq5d5KZNyPWWFvjLs0SjifyZBbjYWibkLR7K+sgTzd4v9bjNbPPUqr+6GWECQAk6JYzWuS8D7ns/JEbI1fuUzm2U8/Q2R60dq7EFtbw+Po1dxZzUJ+V5JhW9exvhrr7lVII/0aB8nv/LUE+2XCo=";
// 支付寶公鑰
public static final String RSA_PUBLIC = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB";
private static final int SDK_PAY_FLAG = 1;
private static final int SDK_CHECK_FLAG = 2;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
PayResult payResult = new PayResult((String) msg.obj);
/**
* 同步返回的結果必須放置到服務端進行驗證(驗證的規則請看https://doc.open.alipay.com/doc2/
* detail.htm?spm=0.0.0.0.xdvAU6&treeId=59&articleId=103665&
* docType=1) 建議商戶依賴異步通知
*/
String resultInfo = payResult.getResult();// 同步返回需要驗證的信息
String resultStatus = payResult.getResultStatus();
// 判斷resultStatus 為“9000”則代表支付成功,具體狀態碼代表含義可參考接口文檔
if (TextUtils.equals(resultStatus, "9000")) {
Toast.makeText(PayDemoActivity.this, "支付成功",
Toast.LENGTH_SHORT).show();
} else {
// 判斷resultStatus 為非"9000"則代表可能支付失敗
// "8000"代表支付結果因為支付渠道原因或者系統原因還在等待支付結果確認,最終交易是否成功以服務端異步通知為准(小概率狀態)
if (TextUtils.equals(resultStatus, "8000")) {
Toast.makeText(PayDemoActivity.this, "支付結果確認中",
Toast.LENGTH_SHORT).show();
} else {
// 其他值就可以判斷為支付失敗,包括用戶主動取消支付,或者系統返回的錯誤
Toast.makeText(PayDemoActivity.this, "支付失敗",
Toast.LENGTH_SHORT).show();
}
}
break;
}
case SDK_CHECK_FLAG: {
Toast.makeText(PayDemoActivity.this, "檢查結果為:" + msg.obj,
Toast.LENGTH_SHORT).show();
break;
}
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pay_main);
}
/**
* call alipay sdk pay. 調用SDK支付
*
*/
public void pay(View v) {
if (TextUtils.isEmpty(PARTNER) || TextUtils.isEmpty(RSA_PRIVATE)
|| TextUtils.isEmpty(SELLER)) {
new AlertDialog.Builder(this)
.setTitle("警告")
.setMessage("需要配置PARTNER | RSA_PRIVATE| SELLER")
.setPositiveButton("確定",
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialoginterface, int i) {
//
finish();
}
}).show();
return;
}
String orderInfo = getOrderInfo("測試的商品", "該測試商品的詳細描述", "0.01");
/**
* 特別注意,這里的簽名邏輯需要放在服務端,切勿將私鑰泄露在代碼中!
*/
String sign = sign(orderInfo);
try {
/**
* 僅需對sign 做URL編碼
*/
sign = URLEncoder.encode(sign, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
/**
* 完整的符合支付寶參數規范的訂單信息
*/
final String payInfo = orderInfo + "&sign=\"" + sign + "\"&"
+ getSignType();
System.out.println("payInfo===========" + payInfo);
Runnable payRunnable = new Runnable() {
@Override
public void run() {
// 構造PayTask 對象
PayTask alipay = new PayTask(PayDemoActivity.this);
// 調用支付接口,獲取支付結果
String result = alipay.pay(payInfo, true);
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
// 必須異步調用
Thread payThread = new Thread(payRunnable);
payThread.start();
}
/**
* check whether the device has authentication alipay account.
* 查詢終端設備是否存在支付寶認證賬戶
*
*/
public void check(View v) {
Runnable checkRunnable = new Runnable() {
@Override
public void run() {
// 構造PayTask 對象
PayTask payTask = new PayTask(PayDemoActivity.this);
// 調用查詢接口,獲取查詢結果
boolean isExist = payTask.checkAccountIfExist();
Message msg = new Message();
msg.what = SDK_CHECK_FLAG;
msg.obj = isExist;
mHandler.sendMessage(msg);
}
};
Thread checkThread = new Thread(checkRunnable);
checkThread.start();
}
/**
* get the sdk version. 獲取SDK版本號
*
*/
public void getSDKVersion() {
PayTask payTask = new PayTask(this);
String version = payTask.getVersion();
Toast.makeText(this, version, Toast.LENGTH_SHORT).show();
}
/**
* 原生的H5(手機網頁版支付切natvie支付) 【對應頁面網頁支付按鈕】
*
* @param v
*/
public void h5Pay(View v) {
Intent intent = new Intent(this, H5PayDemoActivity.class);
Bundle extras = new Bundle();
/**
* url是測試的網站,在app內部打開頁面是基於webview打開的,demo中的webview是H5PayDemoActivity,
* demo中攔截url進行支付的邏輯是在H5PayDemoActivity中shouldOverrideUrlLoading方法實現,
* 商戶可以根據自己的需求來實現
*/
String url = "http://m.taobao.com";
// url可以是一號店或者美團等第三方的購物wap站點,在該網站的支付過程中,支付寶sdk完成攔截支付
extras.putString("url", url);
intent.putExtras(extras);
startActivity(intent);
}
/**
* create the order info. 創建訂單信息
*
*/
private 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;
}
/**
* get the out_trade_no for an order. 生成商戶訂單號,該值在商戶端應保持唯一(可自定義格式規范)
*
*/
private String getOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss",
Locale.getDefault());
Date date = new Date();
String key = format.format(date);
Random r = new Random();
key = key + r.nextInt();
key = key.substring(0, 15);
return key;
}
/**
* sign the order info. 對訂單信息進行簽名,即加密
*
* @param content
* 待簽名訂單信息
*/
private String sign(String content) {
return SignUtils.sign(content, RSA_PRIVATE);
}
/**
* get the sign type we use. 獲取簽名方式
*
*/
private String getSignType() {
return "sign_type=\"RSA\"";
}
}



