上家公司想要拓展自己在新加坡的市场,打算做一个新加坡本地的生活服务应用,其中少不了的就是支付了。国外支付这块一直是个头疼的问题。想用Google Wallet吧,但它是采用NFC接触式交易,想要进行线上服务时没法进行,后来就去整个贝宝PayPal支付。在这里想吐槽一下,PayPal支付做起来真是头疼,文档阅读起来很吃力,官方也没什么好的demo,无奈,在度娘里不断地搜索,找到了别人做的一个demo,我自己拿来集成了一下,终于弄好了。好了是好了,不过后来老板又重新定了需求,因为嫌PayPal支付的手续费太贵,所以果断弃用,不断打听了解,找到了这个Stripe支付。又是第一次接触这个支付,我呢就把Stripe支付的流程用博客记录下来,以备不时之需,有想要在google play上架自己带支付的应用的小伙伴们也可以参考一下这篇文章。
Stripe Android开发文档地址: https://stripe.com/docs/mobile/android
第一步,在build.gradle
中配置sdk
:
compile ‘com.stripe:stripe-android:3.0.1’
要在Eclipse上安装Stripe引用,你需要 :
1. 首先下载 stripe-android
库.
2. 确保你的 Android SDK
最低在 Level 17
以上并且有android-support-v4
包.
3. 导入 stripe
文件夹 到 Eclipse.
4. 在你项目设置里, 在“Android”
类别下的“Stripe
”部分添加项目依赖库。
如果说没有找到jar包的下载地址,可以用as添加主module依赖:
compile ‘com.stripe:stripe-android:3.0.1’ ,然后在在这里拷贝出来:
第二步,收集信用卡信息
这里需要注意的是,Stripe并没有支付界面,收集信用卡信息的界面需要自己设计。
使用前先导入stripe支付应用的类:
import com.stripe.android.*;
这里会用到两大主类:Card
和Stripe
我们使用用户的输入信息来初始化一个Card
:
Card card = new Card(cardNumber, //卡号cardExpMonth, //卡片过期月份cardExpYear, //卡片过期年份cardCVC //CVC验证码
);
card.validateNumber(); //检测卡号是否有效
card.validateCVC(); //检测CVC验证码是否有效
第三步,构建Stripe
生成token
:
if (card.validateCard()) {Stripe stripe = new Stripe();//调用创建token方法stripe.createToken(card,//传入card对象new TokenCallback() {//这里的token打印出来是一串json数据,其中的 token需要用getId()来得到public void onSuccess(Token token) {// 这里生成得到了token,你需要将它发送到自己服务器,然后服务器利用这个token和支付金额去向 Stripe请求扣费submitPaymentInfo(token.getId(),"12.20");//提交支付信息}public void onError(Exception error) {// 显示本地错误信息Toast.makeText(getContext(),error.getLocalizedString(getContext()),Toast.LENGTH_LONG).show();}})
}else {//卡号有误MToast.shortToast("The card number that you entered is invalid");} else if (!card.validateExpiryDate()) {//过期时间有误MToast.shortToast("The expiration date that you entered is invalid");} else if (!card.validateCVC()) {//CVC验证码有误MToast.shortToast("The CVC code that you entered is invalid");} else {//卡片详情有误MToast.shortToast("The card details that you entered are invalid");}}
至此,Stripe支付就接入完成了,具体的扣费则是需要自己服务器与stripe服务器交互,在提交完支付信息再得到支付结果过程中进行加载框的处理就看这里就不再多说了。
下面我把我这个类的完整代码放出来吧:
package com.anmu.wannasg.user.ui;import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.support.v4.app.DialogFragment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;import com.alibaba.fastjson.JSONObject;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.anmu.wannasg.R;
import com.anmu.wannasg.SwipeBack.SwipeBackLayout;
import com.anmu.wannasg.application.MyApplication;
import com.anmu.wannasg.autolayout.utils.AutoUtils;
import com.anmu.wannasg.commonactivity.BaseSwipeBackActivity;
import com.anmu.wannasg.net.Keys;
import com.anmu.wannasg.net.URLS;
import com.anmu.wannasg.utils.L;
import com.anmu.wannasg.utils.MToast;
import com.anmu.wannasg.utils.ScreenUtils;
import com.anmu.wannasg.widget.pickerview.NumberPickerView;
import com.ant.liao.GifView;
import com.stripe.android.Stripe;
import com.stripe.android.TokenCallback;
import com.stripe.android.model.Card;
import com.stripe.android.model.Token;import java.util.HashMap;
import java.util.Map;/*** Created by Maibenben on 2016/10/27.*/public class UEPaymentInputActivity extends BaseSwipeBackActivity {private RelativeLayout top;private ImageView btnBack;private LinearLayout middle;private EditText edtCardHolderName;private EditText edtCardNumber;private EditText edtCvv;// private EditText edtExpiryDate;private LinearLayout bottom;private NumberPickerView picker_year, picker_month;private TextView btnReset;private TextView btnNext;private TextView txt_price;String amount = "";String holdername = "", cardnumber = "", cvv = "", expirydate = "";Intent mIntent;String code;int type = 0;String[] months = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"};String[] years = {"2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030", "2031", "2032", "2033", "2034", "2035", "2036", "2037", "2038", "2039"};@Overridepublic void setContentView() {mIntent = getIntent();if (mIntent != null) {amount = mIntent.getStringExtra("amount");code = mIntent.getStringExtra("code");type = mIntent.getIntExtra("type", 0);}setContentView(R.layout.u_activity_payment_input);}@Overridepublic void initData() {picker_year.refreshByNewDisplayedValues(years);picker_month.refreshByNewDisplayedValues(months);initPopuptWindow();txt_price.setText("S$ " + amount);}@Overridepublic void initWidget() {SwipeBackLayout swipeBackLayout = getSwipeBackLayout();swipeBackLayout.setEdgeSize(ScreenUtils.getScreenWidth(this) / 10);swipeBackLayout.setEdgeTrackingEnabled(MyApplication.getInstance().getCloseModel());top = (RelativeLayout) findViewById(R.id.top);btnBack = (ImageView) findViewById(R.id.btn_back);middle = (LinearLayout) findViewById(R.id.middle);edtCardHolderName = (EditText) findViewById(R.id.edt_card_holder_name);edtCardNumber = (EditText) findViewById(R.id.edt_card_number);edtCvv = (EditText) findViewById(R.id.edt_cvv);
// edtExpiryDate = (EditText) findViewById(R.id.edt_expiry_date);bottom = (LinearLayout) findViewById(R.id.bottom);picker_year = (NumberPickerView) findViewById(R.id.picker_year);picker_month = (NumberPickerView) findViewById(R.id.picker_month);btnReset = (TextView) findViewById(R.id.btn_reset);btnNext = (TextView) findViewById(R.id.btn_next);txt_price = (TextView) findViewById(R.id.txt_price);btnBack.setOnClickListener(this);btnReset.setOnClickListener(this);btnNext.setOnClickListener(this);}@Overridepublic void widgetClick(View v) {switch (v.getId()) {case R.id.btn_back:scrollToFinishActivity();break;case R.id.btn_reset:edtCardHolderName.setText("");edtCardNumber.setText("");edtCvv.setText("");break;case R.id.btn_next:holdername = edtCardHolderName.getText().toString().trim();cardnumber = edtCardNumber.getText().toString().trim();cvv = edtCvv.getText().toString().trim();expirydate = picker_year.getContentByCurrValue() + picker_month.getContentByCurrValue();if (TextUtils.isEmpty(holdername)) {MToast.shortToast("please enter card holder name");return;}if (TextUtils.isEmpty(cardnumber)) {MToast.shortToast("please enter card number");return;}if (TextUtils.isEmpty(cvv)) {MToast.shortToast("please enter cvv");return;}if (TextUtils.isEmpty(expirydate)) {MToast.shortToast("please enter expiry date");return;}mLoadingPopupWindow.showAtLocation(loadingView, Gravity.CENTER, 0, 0);saveCreditCard(holdername, cardnumber, cvv, expirydate);break;}}@Overridepublic void getData() {}@Overridepublic void dealData(String response) {}String url = "";private void getMyData(final Token token) {url = URLS.USER_STRIPE_PAYMENT;StringRequest submitToken = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {dealSubmitResponse(response);}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError volleyError) {if (mLoadingPopupWindow.isShowing()) {mLoadingPopupWindow.dismiss();}}}) {@Overrideprotected Map<String, String> getParams() throws AuthFailureError {Map<String, String> params = new HashMap<>();params.put("stripeToken", token.getId());params.put("code", code);return params;}};MyApplication.getQueue().add(submitToken);}private void dealSubmitResponse(String response) {if (TextUtils.isEmpty(response)) {return;}JSONObject jsonObject = JSONObject.parseObject(response);int ret = jsonObject.getIntValue("ret");if (ret == 200) {//successMToast.shortToast("Payment Success");switch (type) {case 2://pickup进来的mIntent = new Intent(this, UPickUpWaitingDetailActivity.class);mIntent.putExtra("code", code);startActivity(mIntent);finish();overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);break;case 4://coupon进来的mIntent = new Intent(this, UCouponSuccessfulPurchaseActivity.class);mIntent.putExtra("code", code);mIntent.putExtra("title", "Successful Purchase");startActivity(mIntent);finish();overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);break;}} else {MToast.shortToast("sorry,network or server error");}if (mLoadingPopupWindow.isShowing()) {mLoadingPopupWindow.dismiss();}}public void saveCreditCard(String holdername, String cardnumber, String cvv, String expirydate) {if (expirydate.length() < 6) {MToast.shortToast("Please enter correct expiry date");return;}Card card = new Card(cardnumber, Integer.parseInt(expirydate.substring(4, expirydate.length())), Integer.parseInt(expirydate.substring(0, 4)), cvv);
// card.setCurrency(form.getCurrency());boolean validation = card.validateCard();if (validation) {new Stripe().createToken(card,Keys.Stripe_Publishable_Key,new TokenCallback() {public void onSuccess(Token token) {getMyData(token);L.i(token.toString() + "\n---------\n" + token.getId());}public void onError(Exception error) {L.i(error.toString());}});} else if (!card.validateNumber()) {if (mLoadingPopupWindow.isShowing()) {mLoadingPopupWindow.dismiss();}MToast.shortToast("The card number that you entered is invalid");} else if (!card.validateExpiryDate()) {if (mLoadingPopupWindow.isShowing()) {mLoadingPopupWindow.dismiss();}MToast.shortToast("The expiration date that you entered is invalid");} else if (!card.validateCVC()) {if (mLoadingPopupWindow.isShowing()) {mLoadingPopupWindow.dismiss();}MToast.shortToast("The CVC code that you entered is invalid");} else {if (mLoadingPopupWindow.isShowing()) {mLoadingPopupWindow.dismiss();}MToast.shortToast("The card details that you entered are invalid");}}GifView loadingGif;/*** popupwindow布局*/View loadingView;PopupWindow mLoadingPopupWindow;/*** 创建PopupWindow*/private void initPopuptWindow() {LayoutInflater layoutInflater = LayoutInflater.from(this);loadingView = layoutInflater.inflate(R.layout.u_popup_payemnt_loading, null);AutoUtils.auto(loadingView);//View loadingView ;//PopupWindow mLoadingPopupWindow;// 创建一个PopupWindow// 参数1:contentView 指定PopupWindow的内容// 参数2:width 指定PopupWindow的width// 参数3:height 指定PopupWindow的heightmLoadingPopupWindow = new PopupWindow(loadingView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);// 设置popwindow如果点击外面区域无法关闭。mLoadingPopupWindow.setOutsideTouchable(true);// 点击空白处时,隐藏掉pop窗口mLoadingPopupWindow.setFocusable(false);mLoadingPopupWindow.setBackgroundDrawable(new BitmapDrawable());// backgroundAlpha(0.5f);// 设置背景为半透明// mFilterPopupWindow.showAsDropDown(view_divide, 0, 0);loadingGif = (GifView) loadingView.findViewById(R.id.loading_gif);loadingGif.setGifImage(R.drawable.loadingview);}}
xml代码:
<?xml version="1.0" encoding="utf-8"?>
<com.anmu.wannasg.autolayout.AutoRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"android:fitsSystemWindows="true"android:orientation="vertical"><RelativeLayout
android:id="@+id/top"android:layout_width="match_parent"android:layout_height="81px"><ImageView
android:id="@+id/btn_back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:scaleType="fitXY"android:src="@mipmap/u_img_back_pink"/><TextView
android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:gravity="center"android:text="ePayment"android:textColor="@color/black"android:textSize="35px"/><View
android:layout_width="match_parent"android:layout_height="1px"android:layout_alignParentBottom="true"android:background="@color/lightgrayline"/></RelativeLayout><LinearLayout
android:id="@+id/middle"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@+id/top"android:orientation="vertical"android:paddingBottom="30px"android:paddingLeft="30px"android:paddingRight="30px"><TextView
android:layout_width="wrap_content"android:layout_height="85px"android:gravity="center_vertical"android:text="Card Holder Name:"android:textColor="@color/sixsix"android:textSize="30px"/><EditText
android:id="@+id/edt_card_holder_name"android:layout_width="match_parent"android:layout_height="70px"android:background="@drawable/u_edt_input_selector"android:maxEms="50"android:maxLines="1"android:paddingLeft="20px"android:paddingRight="20px"/><TextView
android:layout_width="wrap_content"android:layout_height="85px"android:gravity="center_vertical"android:text="Card Number:"android:textColor="@color/sixsix"android:textSize="30px"/><EditText
android:id="@+id/edt_card_number"android:layout_width="match_parent"android:layout_height="70px"android:background="@drawable/u_edt_input_selector"android:inputType="number"android:maxEms="50"android:maxLines="1"android:paddingLeft="20px"android:paddingRight="20px"/><TextView
android:layout_width="wrap_content"android:layout_height="85px"android:gravity="center_vertical"android:text="CVV:"android:textColor="@color/sixsix"android:textSize="30px"/><EditText
android:id="@+id/edt_cvv"android:layout_width="match_parent"android:layout_height="70px"android:background="@drawable/u_edt_input_selector"android:inputType="number"android:maxEms="50"android:maxLines="1"android:paddingLeft="20px"android:paddingRight="20px"/><TextView
android:layout_width="wrap_content"android:layout_height="85px"android:gravity="center_vertical"android:text="Expiry Date:"android:textColor="@color/sixsix"android:textSize="30px"/><LinearLayout
android:layout_width="match_parent"android:layout_height="100px"android:background="@drawable/u_edt_input_selector"><LinearLayout
android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center_vertical"><TextView
android:layout_width="wrap_content"android:layout_height="85px"android:layout_marginLeft="20px"android:layout_marginRight="40px"android:gravity="center_vertical"android:text="Month:"android:textColor="@color/sixsix"android:textSize="30px"/><com.anmu.wannasg.widget.pickerview.NumberPickerView
android:id="@+id/picker_month"android:layout_width="100px"android:layout_height="wrap_content"app:npv_DividerColor="@color/no_color"app:npv_ShowCount="1"app:npv_TextColorHint="@color/sixsix"app:npv_TextColorSelected="#1E64D2"app:npv_TextSizeHint="14sp"app:npv_TextSizeSelected="18sp"/></LinearLayout><LinearLayout
android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center_vertical"><TextView
android:layout_width="wrap_content"android:layout_height="85px"android:layout_marginLeft="20px"android:layout_marginRight="40px"android:gravity="center_vertical"android:text="Year:"android:textColor="@color/sixsix"android:textSize="30px"/><com.anmu.wannasg.widget.pickerview.NumberPickerView
android:id="@+id/picker_year"android:layout_width="100px"android:layout_height="wrap_content"app:npv_DividerColor="@color/no_color"app:npv_ShowCount="1"app:npv_TextColorHint="@color/sixsix"app:npv_TextColorSelected="#1E64D2"app:npv_TextSizeHint="14sp"app:npv_TextSizeSelected="18sp"/></LinearLayout></LinearLayout><!--<EditText--><!--android:id="@+id/edt_expiry_date"--><!--android:layout_width="match_parent"--><!--android:layout_height="70px"--><!--android:background="@drawable/u_edt_input_selector"--><!--android:hint="like 201601"--><!--android:inputType="number"--><!--android:maxEms="50"--><!--android:maxLines="1"--><!--android:paddingLeft="20px"--><!--android:paddingRight="20px"--><!--android:textColorHint="@color/CDCDCD"/>--></LinearLayout><RelativeLayout
android:layout_width="match_parent"android:layout_height="match_parent"android:layout_above="@id/bottom"android:layout_below="@+id/middle"android:background="@color/backgroudgray"><LinearLayout
android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_margin="40px"android:orientation="horizontal"android:visibility="gone"><TextView
android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginRight="20px"android:gravity="center"android:text="Need pay"android:textColor="@color/user_theme_color"android:textSize="35px"/><TextView
android:id="@+id/txt_price"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:text="S$ 00"android:textColor="@color/user_theme_color"android:textSize="40px"/></LinearLayout></RelativeLayout><!--<View--><!--android:layout_width="match_parent"--><!--android:layout_above="@id/bottom"--><!--android:layout_below="@+id/middle"--><!--android:background="@color/backgroudgray"--><!--android:layout_height="match_parent"/>--><LinearLayout
android:id="@+id/bottom"android:layout_width="match_parent"android:layout_height="120px"android:layout_alignParentBottom="true"android:background="@color/user_theme_color"android:orientation="horizontal"><TextView
android:id="@+id/btn_reset"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:text="Reset"android:textColor="@color/white"android:textSize="35px"/><View
android:layout_width="1px"android:layout_height="match_parent"android:layout_marginBottom="20px"android:layout_marginTop="20px"android:background="@color/white"/><TextView
android:id="@+id/btn_next"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:text="Next"android:textColor="@color/white"android:textSize="35px"/></LinearLayout></com.anmu.wannasg.autolayout.AutoRelativeLayout>