如今移動支付比較火,尤其是在中國的市場。移動支付也稱為手機支付,就是允許用戶使用其移動終端(通常是手機)對所消費的商品或服務進行賬務支付的一種服務方式。單位或個人通過移動設備、互聯網或者近距離傳感直接或間接向銀行金融機構發送支付指令產生貨幣支付與資金轉移行為,從而實現移動支付功能。移動支付將終端設備、互聯網、應用提供商以及金融機構相融合,為用戶提供貨幣支付、繳費等金融業務。
談到移動支付,不得不說阿里旗下的螞蟻金融的支付以及騰訊旗下的微信支付。那么現在在就談談如何Android項目里集成調用支付寶支付開發的實現方式。首先訪問支付寶的官方平台螞蟻金服開放平台,網址為:https://open.alipay.com/platform/home.htm 。然后用自己的支付寶登錄並認證為開發者,接着在平台首頁依次點擊“文檔中心”進入查閱相關的Android集成的開發文檔,接着下載集成支付寶的SDK和demo,下載地址為: https://gw.alipayobjects.com/os/rmsportal/vPZJQoVJUyvrEUdlwYxl.zip 。
- Android項目集成調用支付寶的流程
流程說明(以Android平台為例):
第4步:調用支付接口:此消息就是本接口所描述的開發包提供的支付對象PayTask,將商戶簽名后的訂單信息傳進pay方法喚起支付寶收銀台。
第5步:支付請求:手機支付寶支付開發包將會按照商戶客戶端提供的請求參數發送支付請求。
第8步:接口返回支付結果:商戶客戶端在第4步中調用的支付接口,會返回最終的支付結果(即同步通知)。
第12步:異步發送支付通知:手機支付寶支付服務器端發送異步通知消息給商戶服務器端(備注:第12步一定發生在第6步之后,但不一定晚於7~11步)。
1.構造訂單數據並簽名
商戶服務器端根據手機支付寶支付開發包的接口規則,通過程序生成得到簽名結果及要傳輸給手機支付寶支付開發包的數據集合。簽名相關的公私鑰生成及配置規則。
2.發送請求數據
把構造完成的數據集合傳遞給手機支付寶支付開發包。
3.手機支付寶支付開發包對請求數據進行處理
手機支付寶支付開發包將請求數據根據業務規則包裝后傳遞給手機支付寶支付服務器端,服務器端得到這些集合后,會先進行安全校驗等驗證,一系列驗證通過后便會處理完成這次發送過來的數據請求。
4.返回處理的結果數據
(1)對於處理完成的交易,支付寶會以兩種方式把數據分別反饋給商戶客戶端和商戶服務器端。在手機客戶端上,手機支付寶支付開發包直接把處理的數據結果反饋給商戶客戶端;
(2)在服務器端上,手機支付寶支付服務器端主動發起通知,調用商戶在請求時設定好的頁面路徑。
5.商戶對獲取的返回結果數據進行處理
商戶在客戶端同步通知接收模塊或服務器端異步通知接收模塊獲取到支付寶返回的結果數據后,可以結合商戶自身業務邏輯進行數據處理(如:訂單更新、自動充值到會員賬號中等)。同步通知結果僅用於結果展示,入庫數據需以異步通知為准。
- 導入開發資源
將下載的alipaySDK-XXX.jar包放入商戶應用工程的libs目錄下,如下圖。
- 修改Manifest
在商戶應用工程的AndroidManifest.xml文件里面添加聲明:
<activity android:name="com.alipay.sdk.app.H5PayActivity" android:configChanges="orientation|keyboardHidden|navigation" android:exported="false" android:screenOrientation="behind" > </activity> <activity android:name="com.alipay.sdk.auth.AuthActivity" android:configChanges="orientation|keyboardHidden|navigation" android:exported="false" android:screenOrientation="behind" > </activity>
和權限聲明:
<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" />
- AlipayTask.java邏輯代碼如下:
package com.fukaimei.alipay.task;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.widget.Toast;
import com.alipay.sdk.app.PayTask;
import com.fukaimei.alipay.bean.AlipayConstants;
import com.fukaimei.alipay.bean.PayResult;
import com.fukaimei.alipay.util.SignUtils;
public class AlipayTask extends AsyncTask<String, Void, String> { private static final String TAG = "AlipayTask"; private Context mContext; private ProgressDialog dialog; private int mType; public AlipayTask(Context context, int type) { mContext = context; mType = type; } @Override protected void onPreExecute() { if (TextUtils.isEmpty(AlipayConstants.PARTNER) || TextUtils.isEmpty(AlipayConstants.RSA_PRIVATE) || TextUtils.isEmpty(AlipayConstants.SELLER)) { new AlertDialog.Builder(mContext).setTitle("警告").setMessage("需要配置PARTNER | RSA_PRIVATE| SELLER") .setPositiveButton("確定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialoginterface, int i) { } }).show(); cancel(true); } else { dialog = ProgressDialog.show(mContext, "提示", "正在啟動支付寶..."); } } @Override protected String doInBackground(String... params) { String orderInfo = getOrderInfo(params[0], params[1], params[2]); // 特別注意,這里的簽名邏輯需要放在服務端,切勿將私鑰泄露在代碼中! 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(); // 構造PayTask 對象 PayTask alipay = new PayTask((Activity) mContext); // 調用支付接口,獲取支付結果 String result = alipay.pay(payInfo, false); return result; } @Override protected void onPostExecute(String result) { if (dialog != null) { dialog.dismiss(); } PayResult payResult = new PayResult(result); // 支付寶返回此次支付結果及加簽,建議對支付寶簽名信息拿簽約時支付寶提供的公鑰做驗簽 String resultInfo = payResult.getResult(); Toast.makeText(mContext, "resultInfo=" + resultInfo, Toast.LENGTH_SHORT).show(); String resultStatus = payResult.getResultStatus(); // 判斷resultStatus 為“9000”則代表支付成功,具體狀態碼代表含義可參考接口文檔 if (TextUtils.equals(resultStatus, "9000")) { Toast.makeText(mContext, "支付寶支付成功", Toast.LENGTH_SHORT).show(); } else { // 判斷resultStatus 為非“9000”則代表可能支付失敗 // “8000”代表支付結果因為支付渠道原因或者系統原因還在等待支付結果確認,最終交易是否成功以服務端異步通知為准(小概率狀態) if (TextUtils.equals(resultStatus, "8000")) { Toast.makeText(mContext, "支付寶支付結果確認中", Toast.LENGTH_SHORT).show(); } else { // 其他值就可以判斷為支付失敗,包括用戶主動取消支付,或者系統返回的錯誤 Toast.makeText(mContext, "支付寶支付失敗" + payResult.getResult(), Toast.LENGTH_SHORT).show(); } } // if (mType == 1) { // Intent intent = new Intent(mContext, TaxResultActivity.class); // mContext.startActivity(intent); // } } private String getOrderInfo(String subject, String body, String price) { // 簽約合作者身份ID String orderInfo = "partner=" + "\"" + AlipayConstants.PARTNER + "\""; // 簽約賣家支付寶賬號 orderInfo += "&seller_id=" + "\"" + AlipayConstants.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; } 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; } private String sign(String content) { return SignUtils.sign(content, AlipayConstants.RSA_PRIVATE); } private String getSignType() { return "sign_type=\"RSA\""; } }
- PayResult.java邏輯代碼如下:
package com.fukaimei.alipay.bean; import android.text.TextUtils; public class PayResult { private String resultStatus; private String result; private String memo; public PayResult(String rawResult) { if (TextUtils.isEmpty(rawResult)) { return; } String[] resultParams = rawResult.split(";"); for (String resultParam : resultParams) { if (resultParam.startsWith("resultStatus")) { resultStatus = gatValue(resultParam, "resultStatus"); } if (resultParam.startsWith("result")) { result = gatValue(resultParam, "result"); } if (resultParam.startsWith("memo")) { memo = gatValue(resultParam, "memo"); } } } @Override public String toString() { return "resultStatus={" + resultStatus + "};memo={" + memo + "};result={" + result + "}"; } private String gatValue(String content, String key) { String prefix = key + "={"; return content.substring(content.indexOf(prefix) + prefix.length(), content.lastIndexOf("}")); } /** * @return the resultStatus */ public String getResultStatus() { return resultStatus; } /** * @return the memo */ public String getMemo() { return memo; } /** * @return the result */ public String getResult() { return result; } }
- 在AlipayConstants里配置相關與支付寶簽約的商家密鑰和賬號等,AlipayConstants.java邏輯代碼如下:
package com.fukaimei.alipay.bean; public class AlipayConstants { // 商戶PID public static final String PARTNER = "這里配置商戶PID"; // 商戶收款賬號 public static final String SELLER = "這里配置商戶收款賬號"; // 商戶私鑰,pkcs8格式 public static final String RSA_PRIVATE = "這里配置商戶私鑰"; // 支付寶公鑰 public static final String RSA_PUBLIC = "這里配置支付寶公鑰"; public static final int SDK_PAY_FLAG = 1; public static final int SDK_CHECK_FLAG = 2; }
- layout/activity_main.xml界面布局代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp"> <ImageView android:layout_width="match_parent" android:layout_height="150dp" android:scaleType="fitCenter" android:src="@drawable/dabaitu" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="商品名稱:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_goods_title" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/editext_selector" android:padding="5dp" android:text="小米MIX 2 " android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="商品描述:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_goods_desc" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/editext_selector" android:padding="5dp" android:text="全面屏2.0,驍龍835處理器,全陶瓷機身。" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="商品價格:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_goods_price" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/editext_selector" android:enabled="false" android:inputType="numberDecimal" android:padding="5dp" android:text="0.01" android:textColor="@color/black" android:textSize="17sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="元" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <Button android:id="@+id/btn_alipay" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="支付寶支付" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout>
- MainActivity.java邏輯代碼如下:
package com.fukaimei.alipay; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; import com.fukaimei.alipay.task.AlipayTask; public class MainActivity extends AppCompatActivity implements OnClickListener { private static final String TAG = "MainActivity"; private EditText et_goods_title; private EditText et_goods_desc; private EditText et_goods_price; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_goods_title = (EditText) findViewById(R.id.et_goods_title); et_goods_desc = (EditText) findViewById(R.id.et_goods_desc); et_goods_price = (EditText) findViewById(R.id.et_goods_price); findViewById(R.id.btn_alipay).setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.btn_alipay) { String title = et_goods_title.getText().toString(); String desc = et_goods_desc.getText().toString(); String price = et_goods_price.getText().toString(); new AlipayTask(this, 0).execute(title, desc, price); } } }
- Demo程序運行效果界面截圖如下: