Stripe is one of the most widely used payment gateways currently. It is also one of the best for developers because it allows very easy and smooth integration of code in multiple languages. Here we are going to look at accepting stripe payments in an Android app.
It is assumed that there is already a stripe account available for integration. If not then you can sign up for a free Stripe account and use it in test mode.
CONCEPTS
Before we look at the code, a few concepts need to be understood .
API Keys
Stripe can run in two modes – Live and Test mode. Test mode is useful while testing integration or for any scenario which allows you to test payment integration without actually charging live credit cards. When in test mode, Stripe has some predefined card numbers to use, as shown below. You can use these numbers with any CVC/CVV number and any expiry month/year set in the future.
For API integration, Stripe provides two keys – one Public and one Private. These can be found in your Stripe account settings. The Public key will have the prefix of pk and the Private Key will have the prefix of sk . You will have different pairs of Public/Private keys for Test mode and Live Mode.
So if you are going to use the Stripe gateway in Test mode then use the Test keys , otherwise use the Live keys.
Customer
A Customer Entity in Stripe is the person who makes payments into the account.
Card
A Card entity contains the credit card details of a Customer. A Customer may have multiple Cards.
Charge
An actual payment made in Stripe is called a Charge. A Charge is always linked to a Card and a Customer.
There are more entities in Stripe like Subscription, Plans, Coupons etc. but here we are just dealing with a simple payment, so we will not look into the advanced concepts.
PROCESS
In the sample code, the following logic is in place.
1.Create a Card entity in Stripe
2.Create a Customer entity
3.Create a Charge entity and link it with the Card and Customer entity.
Each of these entities returns a unique id which has to be handled by your backend code. For instance, in your database you should store the Stripe customer id, so that the next time this customer does a payment you can retrieve the Customer Entity from Stripe and then do a Charge, instead of creating a duplicate Customer entity again.
You can do a similar thing for the Card. A Card entity returns a Card token which can be stored and reused in your code. The advantage of this is that the actual card details are stored securely in Stripe and you can use the card token to tell Stripe which card to use.
Note that amounts are to be sent in the lowest currency denomination. Eg. if you are using USD and the amount to be charged is $50.00 then the amount should not be sent to Stripe as 50.00 but 50*100 cents which is 5000 .
THE APPLICATION
For your app to use the Stripe Android library, you need to link
'com.stripe:stripe-android'
This is shown in the build.gradle listing below.
We have a single Activity screen which takes in the payment details, validates the data and then updates Stripe with the transaction details.
LISTINGS
build.gradle
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "stripe.test.com.stripetest" minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.4.0' compile 'com.android.support:design:23.4.0' compile 'com.stripe:stripe-android:+' }
Android.Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="stripe.test.com.stripetest"> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" /> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:orientation="vertical" tools:context="stripe.test.com.stripetest.MainActivity" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> <TextView android:id="@+id/textStripeLabel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_horizontal" android:text="Transaction Details" android:textAllCaps="false" android:textAlignment="center" android:textSize="@dimen/font_size_medium" android:textStyle="bold" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="@drawable/sharp_rectangle_border" android:layout_marginBottom="20dp"> <EditText android:id="@+id/editStripeCardNumber1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.20" android:padding="5dp" android:singleLine="true" android:hint="XXXX" android:inputType="number" android:maxLength="4" android:textColor="#000000" android:textCursorDrawable="@null"/> <EditText android:id="@+id/editStripeCardNumber2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.20" android:padding="5dp" android:singleLine="true" android:hint="XXXX" android:inputType="number" android:maxLength="4" android:textColor="#000000" android:textCursorDrawable="@null"/> <EditText android:id="@+id/editStripeCardNumber3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.20" android:padding="5dp" android:singleLine="true" android:hint="XXXX" android:inputType="number" android:maxLength="4" android:textColor="#000000" android:textCursorDrawable="@null"/> <EditText android:id="@+id/editStripeCardNumber4" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.20" android:padding="5dp" android:singleLine="true" android:hint="XXXX" android:inputType="number" android:maxLength="4" android:textColor="#000000" android:textCursorDrawable="@null"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="20dp"> <EditText android:id="@+id/editStripeCVV" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.15" android:padding="5dp" android:hint="CVC" android:maxLength="3" android:singleLine="true" android:inputType="number" android:background="@drawable/sharp_rectangle_border" android:textColor="#000000" android:layout_marginRight="10dp" android:textCursorDrawable="@null"/> <LinearLayout android:layout_width="0dp" android:layout_weight="0.80" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="5dp" android:background="@drawable/sharp_rectangle_border" > <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.20" android:text="MM/YYYY" android:textAllCaps="true" android:textSize="@dimen/font_size_small" /> <Spinner android:layout_width="0dp" android:padding="2dp" android:layout_height="wrap_content" android:layout_weight="0.25" android:id="@+id/spinnerStripeExpMonth" android:layout_marginRight="5dp" /> <Spinner android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.25" android:padding="2dp" android:id="@+id/spinnerStripeExpYear" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="@drawable/sharp_rectangle_border" android:layout_marginBottom="10dp"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.60" android:text="$" android:layout_gravity="end" android:textAlignment="textEnd" android:textSize="@dimen/font_size_medium" android:textAllCaps="true" /> <EditText android:id="@+id/editStripeAmount" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.35" android:padding="2dp" android:editable="true" android:inputType="numberDecimal" android:singleLine="true" android:textAlignment="textEnd" android:textSize="@dimen/font_size_medium" android:textColor="#000000" android:textCursorDrawable="@null"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> <EditText android:id="@+id/editCustName" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:editable="true" android:singleLine="true" android:hint="Customer Name" android:textSize="@dimen/font_size_small" android:textColor="#000000" android:textCursorDrawable="@null"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> <EditText android:id="@+id/editCustEmail" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:editable="true" android:inputType="textEmailAddress" android:singleLine="true" android:hint="Customer Email" android:textSize="@dimen/font_size_small" android:textColor="#000000" android:textCursorDrawable="@null"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> <EditText android:id="@+id/editCustAddress" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:editable="true" android:singleLine="true" android:hint="Customer Address" android:textSize="@dimen/font_size_small" android:textColor="#000000" android:textCursorDrawable="@null"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="5dp"> <Button style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel" android:id="@+id/btnStripeCancel" android:layout_weight="0.45" android:onClick="showInvoices" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0.10" /> <Button style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Save" android:id="@+id/btnStripeSave" android:layout_weight="0.45" android:onClick="doPayment" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="10dp"> <TextView android:id="@+id/textStripeMode" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_horizontal" android:text="" android:textAllCaps="true" android:textAlignment="center" android:textSize="@dimen/font_size_small" /> </LinearLayout> </LinearLayout>
drawable/sharp_rectangle_border.xml
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="rectangle"> <solid android:color="#d4d4d4"/> <corners android:radius="0dp" /> </shape> </item> <item android:left="1dp" android:right="1dp" android:top="1dp" android:bottom="1dp"> <shape android:shape="rectangle"> <solid android:color="@android:color/white"/> <corners android:radius="0dp" /> </shape> </item> </layer-list>
values/dimens.xml
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp: https://developer.android.com/design/patterns/navigation-drawer.html --> <dimen name="navigation_drawer_width">240dp</dimen> <dimen name="font_size_verysmall">10sp</dimen> <dimen name="font_size_small">12sp</dimen> <dimen name="font_size_average">14sp</dimen> <dimen name="font_size_medium">20sp</dimen> <dimen name="font_size_large">30sp</dimen> <dimen name="font_size_verylarge">50sp</dimen> </resources>
MainActivity.java
package stripe.test.com.stripetest; import android.app.ProgressDialog; import android.os.AsyncTask; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.stripe.android.Stripe; import com.stripe.android.TokenCallback; import com.stripe.android.model.Card; import com.stripe.android.model.Token; import com.stripe.exception.AuthenticationException; import com.stripe.exception.CardException; import com.stripe.model.Charge; import com.stripe.model.Customer; import com.stripe.net.RequestOptions; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.Map; public class MainActivity extends AppCompatActivity { ProgressDialog mProgressBar; private String mStripeCardToken; private String mPublicKey = ""; private String mPrivateKey = ""; private Token mToken; private Stripe mStripe = null; private String mStripeCustId; private boolean mCustWasAdded = false; private String mCustomerName; private String mCustomerEmail; private String mCustomerAddress; private BigDecimal mAmount; private String mTxnRef; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = new ProgressDialog(this); // populate spinners String []months = new String[12]; for (int i =1; i <=12; i++) { String val = ""; if (i < 10) val = "0"; months[i-1] = val + String.valueOf(i); } Spinner s = (Spinner) findViewById(R.id.spinnerStripeExpMonth); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, months); s.setAdapter(adapter); String []years = new String[12]; for (int i =1; i <=12; i++) { years[i-1] = String.valueOf(i+2015); } Spinner s2 = (Spinner) findViewById(R.id.spinnerStripeExpYear); ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, years); s2.setAdapter(adapter2); // set auto jump to next field for CC entry EditText card1 = (EditText)findViewById(R.id.editStripeCardNumber1); card1.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { EditText card1 = (EditText) findViewById(R.id.editStripeCardNumber1); if (card1.getText().toString().length() == 4) { EditText card2 = (EditText) findViewById(R.id.editStripeCardNumber2); card2.requestFocus(); } } }); EditText card2 = (EditText)findViewById(R.id.editStripeCardNumber2); card2.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { EditText card2 = (EditText)findViewById(R.id.editStripeCardNumber2); if (card2.getText().toString().length() == 4) { EditText card3 = (EditText)findViewById(R.id.editStripeCardNumber3); card3.requestFocus(); } } }); EditText card3 = (EditText)findViewById(R.id.editStripeCardNumber3); card3.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { EditText card3 = (EditText) findViewById(R.id.editStripeCardNumber3); if (card3.getText().toString().length() == 4) { EditText card4 = (EditText) findViewById(R.id.editStripeCardNumber4); card4.requestFocus(); } } }); // set stripe key values // this info would actually come from another place in the app or from the backend. I am just putting hardcoded values here mPublicKey = "pk_test_apikey_comes_here"; mPrivateKey = "sk_test_apikey_comes_here"; // if you already have the customer details and have his stripe customer id, then this is the time to retrieve and // assign it to mStripeCustId } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } public void doPayment(View view) { boolean isValid = true; Spinner s = (Spinner) findViewById(R.id.spinnerStripeExpMonth); Spinner s2 = (Spinner) findViewById(R.id.spinnerStripeExpYear); String cardNumber = ""; EditText textCard1 = (EditText) findViewById(R.id.editStripeCardNumber1); EditText textCard2 = (EditText) findViewById(R.id.editStripeCardNumber2); EditText textCard3 = (EditText) findViewById(R.id.editStripeCardNumber3); EditText textCard4 = (EditText) findViewById(R.id.editStripeCardNumber4); cardNumber = textCard1.getText().toString(); cardNumber += textCard2.getText().toString(); cardNumber += textCard3.getText().toString(); cardNumber += textCard4.getText().toString(); EditText textAmount = (EditText) findViewById(R.id.editStripeAmount); EditText textName = (EditText) findViewById(R.id.editCustName); EditText textEmail = (EditText) findViewById(R.id.editCustEmail); EditText textAddress = (EditText) findViewById(R.id.editCustAddress); try { mAmount = new BigDecimal(Double.parseDouble(textAmount.getText().toString())); } catch (NumberFormatException nex) { Toast.makeText(getApplicationContext(), "Invalid amount entered", Toast.LENGTH_LONG).show(); isValid = false; return; } if (cardNumber.length() < 16) { Toast.makeText(getApplicationContext(), "Card number looks invalid", Toast.LENGTH_LONG).show(); isValid = false; return; } EditText textCVV = (EditText) findViewById(R.id.editStripeCVV); if (textCVV.getText().toString().length() < 3) { Toast.makeText(getApplicationContext(), "CVC looks invalid", Toast.LENGTH_LONG).show(); isValid = false; return; } int month = Integer.parseInt(s.getSelectedItem().toString()); int year = Integer.parseInt(s2.getSelectedItem().toString()); Card card = new Card(cardNumber, month, year, textCVV.getText().toString()); if ( !card.validateCard() ) { Toast.makeText(getApplicationContext(), "Credit card validation failed!", Toast.LENGTH_LONG).show(); isValid = false; return; } mCustomerAddress = textAddress.getText().toString(); mCustomerEmail = textEmail.getText().toString(); mCustomerName = textName.getText().toString(); mProgressBar.setCancelable(false); mProgressBar.setMessage("Validating payment.."); mProgressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressBar.show(); if (mStripeCardToken == null || mStripeCardToken.equals("")) { try { mStripe = new Stripe(mPublicKey); mStripe.createToken( card, new TokenCallback() { public void onSuccess(Token token) { mProgressBar.hide(); mToken = token; mStripeCardToken = mToken.getId(); checkCustomerStatus(); } public void onError(Exception error) { mProgressBar.hide(); // Show localized error message Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG ).show(); return; } } ); } catch (AuthenticationException aex) { Toast.makeText(getApplicationContext(), aex.getMessage(), Toast.LENGTH_LONG).show(); } } else { checkCustomerStatus(); } } void checkCustomerStatus() { if (mStripeCustId == null || mStripeCustId.equals("")) { mProgressBar.setCancelable(false); mProgressBar.setMessage("Adding customer to stripe account.."); mProgressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressBar.show(); new CheckCustomerTask().execute(0); } else { chargeTransaction(); } } private void chargeTransaction() { mProgressBar.setCancelable(false); mProgressBar.setMessage("Charging credit card.."); mProgressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressBar.show(); new MakeChargeTask().execute(0); } public void savePayment() { // this is where you update the payment to your backend servers or database // and then take the user to the next screen Toast.makeText(getApplicationContext(), "Payment has been updated.", Toast.LENGTH_LONG).show(); } public void updateCustomer() { mProgressBar.setCancelable(false); mProgressBar.setMessage("Updating customer details.."); mProgressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressBar.show(); new UpdateCustomerTask().execute(0); } ////////////////////////////////////////////////////////////////////////////// // // The params are dummy and not used // class UpdateCustomerTask extends AsyncTask<Integer, Integer, String> { String mError = null; @Override protected String doInBackground(Integer... params) { try { // update the customer data in your backend or database Thread.sleep(200); } catch (InterruptedException iex) {} return "Task Completed."; } @Override protected void onPostExecute(String result) { mProgressBar.hide(); if (mError != null && mError != "") Toast.makeText(getApplicationContext(), mError, Toast.LENGTH_LONG).show(); else { chargeTransaction(); } } @Override protected void onPreExecute() { } @Override protected void onProgressUpdate(Integer... values) { } } ////////////////////////////////////////////////////////////////////////////// // // The params are dummy and not used // class CheckCustomerTask extends AsyncTask<Integer, Integer, String> { String mError = null; @Override protected String doInBackground(Integer... params) { String mError = ""; try { if (mStripe == null) mStripe = new Stripe(mPublicKey); RequestOptions requestOptions = (new RequestOptions.RequestOptionsBuilder()).setApiKey(mPrivateKey).build(); Map<String, Object> customerParams = new HashMap<String, Object>(); customerParams.put("description", mCustomerName); customerParams.put("email", mCustomerEmail); customerParams.put("source", mStripeCardToken); // obtained with Stripe Customer cust = Customer.create(customerParams, requestOptions); mCustWasAdded = true; mStripeCustId = cust.getId(); } catch (Exception cx) { mError = cx.getMessage(); } return "Task Completed."; } @Override protected void onPostExecute(String result) { mProgressBar.hide(); if (mError != null && mError != "") Toast.makeText(getApplicationContext(), mError, Toast.LENGTH_LONG).show(); else { if (mCustWasAdded) updateCustomer(); else chargeTransaction(); } } @Override protected void onPreExecute() { } @Override protected void onProgressUpdate(Integer... values) { } } ////////////////////////////////////////////////////////////////////////////// // // The params are dummy and not used // class MakeChargeTask extends AsyncTask<Integer, Integer, String> { String mError = null; @Override protected String doInBackground(Integer... params) { DecimalFormat decimalFormat = new DecimalFormat("0.00"); String sAmount = decimalFormat.format(mAmount); sAmount = sAmount.replace(".", ""); // Create the charge on Stripe's servers - this will charge the user's card try { if (mStripe == null) mStripe = new Stripe(mPublicKey); RequestOptions requestOptions = (new RequestOptions.RequestOptionsBuilder()).setApiKey(mPrivateKey).build(); Map<String, Object> chargeParams = new HashMap<String, Object>(); chargeParams.put("amount", Integer.parseInt(sAmount)); // amount in cents, again chargeParams.put("currency", "usd"); chargeParams.put("customer", mStripeCustId); chargeParams.put("description", "Payment description will come here"); Charge charge = Charge.create(chargeParams, requestOptions); mTxnRef = charge.getId(); } catch (Exception e) { // The card has been declined mError = e.getMessage(); } return "Task Completed."; } @Override protected void onPostExecute(String result) { mProgressBar.hide(); if (mError != null && mError != "") Toast.makeText(getApplicationContext(), mError, Toast.LENGTH_LONG).show(); else { savePayment(); } } @Override protected void onPreExecute() { } @Override protected void onProgressUpdate(Integer... values) { } } }
SCREENSHOTS
App Screen
Stripe Transaction Details
Stripe Customer Details
how to integrate stripe on android?please provide me a latest article.