How To Accept Credit Cards In Your iOS Apps Using Stripe

Pietro Rea Pietro Rea
Learn how to add credit card processing into your app!

Learn how to add credit card processing into your app!

In this tutorial, you are going to learn how to accept credit cards in iOS using a service called Stripe, a pain-free, developer-centric way to handle purchases in your apps.

Almost all of us have used PayPal to send and receive online payments at one time or another. PayPal works fine but, believe it or not, the company is already 13 years old. Today there are newer and more exciting options available to developers and merchants.

In the old world of online payments, you would have to create a merchant account, sign a long legal agreement, and wait 3-10 days to have your business information verified.

No more! Stripe does away with all the bureaucracy while still ensuring transaction safety. Perhaps its best feature is that you can create a developer account right away, without verifying any business information. You can start exploring Stripe’s APIs literally minutes after signing up with them.

Moving to production is easy, too! This tutorial will walk you through the whole process, from creating your Stripe account to sending your customer a confirmation email! And of course, all those server transactions in between.

Getting Started: Creating Your Stripe Account

The first step is to get your API keys from Stripe. Head over to stripe.com and click on the blue button that says Get Started with Stripe. Notice that the phrase “Payments for developers” is stamped right on the homepage. So far, so good!

At this point, you are not actually going to create a full-fledged Stripe account, because this would require you to have your business details handy. Besides, one of the reasons Stripe is so developer-friendly is that it allows you test its APIs without providing any information at all! So just leave everything blank and click on skip this step.

You will be greeted with a dialog containing links to help you get started using Stripe. But you can skip reading those for now because you’ve got this tutorial! Just click Go straight to your dashboard or the X button to dismiss the dialog.

To find your keys, click on Your Account on the top-right of the dashboard, then choose Account Settings.

Your keys are located under the aptly-named API Keys tab.

As shown below, Stripe generates two pairs of secret/publishable API keys for your account. One pair is for testing and development while the other pair is for live production.

While you can already see your account’s live credentials, you won’t be able to actually use those until you verify your account with Stripe. For the purposes of this tutorial, and whenever you are just developing, you want to use the test secret and test publishable keys.

Note: Whenever you need your API keys, remember that this is where you’ll find them. If your keys ever become compromised, you can reset them using the circular arrow icons to their right.

RWPuppies: The Stripe Sample Project

Your project for this tutorial is going to be RWPuppies, a fully-functional mobile storefront that sells and ships puppies right to your door. Full disclosure: This app will not ship puppies right to your door. Sorry if that’s a disappointment!

The app will consist of the following three tabs:

  • Featured Puppy: Displays the puppy of the day. This tab contains the puppy’s detailed information such as name, breed, price, etc. There’s even an “Add to Cart” button right there on the view controller if you decide to buy the featured pup on a whim. :]
  • Puppy Inventory: Displays the entire list of puppies on sale in table format. Tapping on any cell takes you to that pup’s details page, which looks very similar to the featured puppy page.
  • Checkout Cart: Shows all the puppies that are currently in your cart, along with a running total for your order. Tapping on “Continue” takes you to a view controller where you can enter your credit card information and complete your purchase.

You don’t want to waste your time setting up table views or dragging out buttons in Interface Builder when you’re here to learn about Stripe. So you’ll be happy to hear this tutorial provides you with a starter project that has everything unrelated to Stripe already implemented for you.

You can download the starter project here.

Open RWPuppies.xcworkspace. Build and run RWPuppies to give it a whirl, and notice that the UI and most of the functionality is already in place.

Cute puppies in starter project!

Real-world e-commerce apps typically get their inventory data from a remote server. For simplicity, the ten puppies on display, including the featured puppy, are all read as data from a local file named dogs.json. The contents look something like this:

[
 
 {
 "id" : 12252012,
 "name" : "Penny",
 "breed" : "Dachshund",
 "photo-large" : "http://www.raywenderlich.com/downloads/puppies/dachshund.jpeg",
 "max_weight" : 28,
 "max_height" : 18,
 "cuddle_factor" : 5,
 "price" : 299.99
 },
 
 ...
]

The checkout cart is implemented as a singleton called RWCheckoutCart that can be conveniently accessed from anywhere in the app. You can add and remove any number of puppies to and from your checkout cart and all the changes will be reflected automatically in the third tab, which contains your order information.

Cart in RWPuppies

Your primary task is to implement RWStripeViewController.m. That’s the view controller where users fill out their credit card details.

Credit card info view controller

This view controller needs to talk to Stripe as well as a simple back-end script that you are going to write.

“But I’m a mobile developer. I don’t do back end!” Fear not. You’ll find it’s pretty straightforward. :]

As an optional step, you will send a confirmation email to your customers once the charge has succeeded. You can’t use iOS directly for automated emails, so if you chose to do this step, you’ll also be writing another back-end script.

But Wait A Second, You Might Be Wondering… What About In-App Purchases?

Credit card payments are not to be confused with In-App Purchases. In-App Purchases can only be used to sell digital goods that are going to be consumed within the app. If you are dealing with a physical good or service, not only should you not try to use an In-App Purchase, you must not. Apple would reject your app!

Say you developed a game like Mikey Shorts. If you want to offer more levels for 99 cents a pop, you have to use an In-App Purchase. If, on the other hand, you want to sell the official Mikey Shorts t-shirt inside the app, then you have to handle your own credit card payments with a service like Stripe or PayPal.

Time To Earn Your Stripes

The sequence of steps leading to a successful credit card charge looks like this:
Stripe Credit Card Transaction Sequence Diagram

  1. The app sends your customer’s credit card information to Stripe using a secure HTTPS POST request. The only required bits of information you need to send to Stripe are the credit card number, the expiration month and the expiration year. Not even the owner’s name is required!

    Sending additional information, such as the card’s CVC number and billing address, is not required but is highly recommended. This extra information helps decrease fraudulent transactions that the merchant (that means you or your client) would have to cover. You can instruct Stripe to decline transactions that fail their CVC and/or ZIP code verification in the General tab of your Account Settings.

  2. Stripe processes the credit card information and returns a one-time token to your app. Getting this token from Stripe does not mean that your customer’s card was charged. The actual charge is initiated later from your server.
  3. Assuming the previous step was successful, your app posts the one-time token to your server for the grand finale.
  4. Server-side, you must contact Stripe again using Stripe’s API. Luckily for you, the fine folks at Stripe have provided official libraries in several popular languages that wrap around their underlying HTTP API. You’ll be using Python for this tutorial.
  5. The charge succeeds (or not) on Stripe’s servers, and then Stripe sends the results back to your server.
  6. In the last step of what seems to be an endless game of telephone, your server notifies your iOS app about the final result of the transaction. If the charge succeeded, you’ll probably want to send a confirmation email to your customer. More about that a bit later.

Note: In theory, you could implement everything on the phone without a back end, but for security reasons this is not recommended. It would require you to put both public and private keys in your app – a requirement to submit a charge to Stripe. This means anyone with access to your app could reverse-engineer both keys and would be able to do anything with your Stripe account – ouch!

Getting Acquainted With Stripe’s iOS Library

There are two ways to add Stripe’s iOS library to your project. You can either download it from Github here or install it as a dependency using CocoaPods.

If you haven’t used CocaPods before, I encourage you to read Marcelo Fabri’s excellent Introduction To CocoaPods tutorial. It’s a real time-saver.

If you are using CocoaPods, simply add the following to your Podfile and run pod install:

pod 'Stripe', :git => 'https://github.com/stripe/stripe-ios.git'

If you prefer to do it the manual way, clone (or download and unzip) the Github repo and drag the folder named Stripe into your Frameworks folder:

Important Compatibility Notice: The Stripe code provided in the Git repo has changed since this tutorial was written, and may still be changing. You can always get the latest code from the above Git repo, but it may not match up exactly with what is in the tutorial. This may lead to problems you’ll have to work out on your own.

To make it easier for you to get through this tutorial, feel free to use the Stripe folder from this zip file. It includes a new version of the Stripe API that should work.

Download and unzip it, then drag the Stripe folder (and it’s new subfolders) into your project just like you did above. (XCode will complain if you already have a Stripe directly copied, which might be the case if you did the previous step before reading this note. Sorry!)

Now add QuartzCode.framework. To do so, select the RWPuppies project in the Project Navigator, choose the Summary tab, and then scroll down to the section named Linked Frameworks and Libraries. Click the + button and then add QuartzCore.framework.

Remember, when working in a real project, be sure to use the newest stable version of the Stripe API.

It’s time to start coding! You start by implementing the code for steps 1 and 2 above: submiting the credit card information to Stripe, and getting a token.

Open RWStripeViewController.m and add the following import statement at the top:

#import "Stripe.h"

Below the imports you will find a couple of #define statements. The first one is for the publishable API key that you got from Stripe when you signed up for an account. You learned how to find your API keys earlier in this tutorial.

Complete the definition of STRIPE_TEST_PUBLIC_KEY by adding your Test Publishable Key. It should be a random string of numbers and letters.

#define STRIPE_TEST_PUBLIC_KEY @"your_test_publishable_api_key"
#define STRIPE_TEST_POST_URL

Note: If you accidentally use your secret key instead of your publishable key, the Stripe API methods that you are going to access later will throw exceptions indicating you’ve made an error. Also – remember to use your test keys, not your live keys at this stage.

Ignore the STRIPE_TEST_POST_URL #define statement for now. You’ll get to that later in the tutorial.

Next, add the following property to the RWStripeViewController class extension, somewhere between @interface RWStripeViewController() and its matching @end:

@property (strong, nonatomic) STPCard* stripeCard;

As the name implies, an STPCard object represents your customer’s credit card. It also provides nifty client-side validation that you’ll use soon.

At the moment, tapping on the “Complete” button on the credit card details view controller does nothing.

Unimplemented complete button

Implement the IBAction method completeButtonTapped: to jumpstart the checkout process:

- (IBAction)completeButtonTapped:(id)sender {
 
    //1
    self.stripeCard = [[STPCard alloc] init];
    self.stripeCard.name = self.nameTextField.text;
    self.stripeCard.number = self.cardNumber.text;
    self.stripeCard.cvc = self.CVCNumber.text;
    self.stripeCard.expMonth = [self.selectedMonth integerValue];
    self.stripeCard.expYear = [self.selectedYear integerValue];
 
    //2
    if ([self validateCustomerInfo]) {
        [self performStripeOperation];
    } 
}

Here’s what happening in the code above:

  1. You allocate and initialize an instance of STPCard, and populate its properties using the information that the customer entered before tapping “Complete”. Note that the user might not have actually filled out all of this information.
  2. It’s a good idea to perform some validation on the device before going out to Stripe. That way, if there’s something missing or incorrect, you can save yourself a network call. The method validateCustomerInfo returns YES if everything checks out and NO otherwise.

Now implement validateCustomerInfo by adding the code shown below (still in RWStripeViewController.m):

- (BOOL)validateCustomerInfo {
 
    UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Please try again"
                                                     message:@"Please enter all required information"
                                                    delegate:nil
                                           cancelButtonTitle:@"OK"
                                           otherButtonTitles:nil];
 
    //1. Validate name & email
    if (self.nameTextField.text.length == 0 ||
        self.emailTextField.text.length == 0) {
 
        [alert show];
        return NO;
    }
 
    //2. Validate card number, CVC, expMonth, expYear
    NSError* error = nil;
    [self.stripeCard validateCardReturningError:&error];
 
    //3
    if (error) {
        alert.message = [error localizedDescription];
        [alert show];
        return NO;
    }
 
    return YES;
}

This method is doing the following:

  1. STPCard only provides validation for card-related fields, which don’t include the customer’s name or email address. To validate the name and email address, you simply check whether the appropriate UITextFields contain any text at all. You could add more robust validation code here if you’d like.
  2. STPCard’s instance method validateCardReturningError: validates the credit card number, CVC number and the expiration date. If the NSError* pointer is not nil after the method returns, there was an error that you have to handle.
  3. As per Apple’s guidelines, Stripe provides localized error messages that you can display in your app if something goes wrong. You can access these customer-facing error messages with NSError’s localizedDescription accessor method.

If your customer’s information passes offline validation, the next step is to make your first call to Stripe. Implement performStripeOperation like this (still in RWStripeViewController.m):

- (void)performStripeOperation {
 
    //1
    self.completeButton.enabled = NO;
 
    //2
/*
    [Stripe createTokenWithCard:self.stripeCard
                 publishableKey:STRIPE_TEST_PUBLIC_KEY
                        success:^(STPToken* token) {	
                            [self postStripeToken:token.tokenId];
                        } error:^(NSError* error) {
                            [self handleStripeError:error];
                        }];
*/
    [Stripe createTokenWithCard:self.stripeCard
                 publishableKey:STRIPE_TEST_PUBLIC_KEY
                     completion:^(STPToken* token, NSError* error) {
                         if(error)
                             [self handleStripeError:error];
                         else
                             [self postStripeToken:token.tokenId];
                         }];
}

The explanation for this code is for the commented-out method. The Stripe API changed after this tutorial was published, but rather than update all of the instructions and screen shots, we have just commented out the old code and added a call to a new version of the method createTokenWithCard:publishableKey:completion. The difference is just that the success and error handling blocks have been combined into a single completion block. Keep this in mind when following any instructions regarding this section of code.

This method does the following:

  1. It’s good practice to disable a button after it gets tapped so that its associated action is not duplicated. The last thing you want to do is to post your user’s credit card information multiple times!
  2. The class method createTokenWithCard:publishableKey:success:error posts your customer’s credit card details to Stripe. If it succeeds, the success handler is called and you get a brand-new STPToken object. This is the one-time token you read about earlier.

    If something went wrong, the error handler gets called and you get a somewhat disappointing reference to an NSError object to make sense of what happened.

Add breakpoints inside the success and failure handlers of createTokenWithCard:publishableKey:success:error. The methods postStripeToken: and handleStripeError: are currently empty stubs, but you’re going to fix that soon.

Note: To add a breakpoint, simply click the border area to the left of the line where you want the breakpoint and a blue arrow will appear indicating the breakpoint. You can later click an active breakpoint to disable it, or right-click on it to access the delete option.

Build and run what you’ve got so far to make sure everything’s implemented correctly. Go to the inventory and add any puppy to the cart. Perdita, the Dalmatian at the end of the inventory list, is only $9.99 if you feel queasy about buying a $700 puppy.

Perdita the Dalmation

Now go to the third tab to check out. Enter any name and email address, as well as the following credit card information:

  • Credit Card Number: 4242424242424242
  • Expiration Date : Any month & year in the future
  • CVC number : Any three- or four-digit number

That is a test credit card provided by Stripe for development and debugging. It only works with development API keys! That card is not going to buy you much of anything if you try to use it anywhere else. :]

When you’re done, hit the “Complete” button. A few seconds later the breakpoint you placed in the success handler should halt the Simulator.

Since the debugger is already open, inspect the STPToken returned by the success block. In the variable list, click on the right arrow next to the variable name token to display its instance variables as a drop-down list.

As you can see, STPToken contains a tokenID as well as some other properties. You’ll be using this information later when you implement postStripeToken:. But before you can do that, you need to set up the back-end script that is going to complete the credit card payment.

Setting Up Your Python Back End

Setting up a back-end development environment is beyond the scope of this tutorial, so you’ll keep it as simple as possible. The back end is going to be a simple CGI script that expects you to send the token ID you just saw, along with a few other parameters.

I used Bluehost‘s Linux shared hosting plan to host the script I’m about to show you. I like Bluehost in particular because virtually 100% of the setup required for this tutorial was already in place when I signed up:

  • Apache (the web server) was already configured and ready to receive requests.
  • A recent version of Python came installed by default.
  • The hosting account came ready with a workable CGI bin (where you put CGI scripts).
  • Setting up SSH access was as easy as clicking a button.

If any of those things are not in place in your current setup, you have to figure out how to make it so. If this is your first time setting up a server, it could take you a few hours to Google your way to a workable development environment.

For example, sites hosted by GoDaddy use an older version of Python that will not run the scripts you are about to write. If you plan on using GoDaddy, this blog post will get you up and running.

However, then you will need to modify the first line of each of the server scripts you write so that instead of just reading #!/usr/bin/env python, they include the full path to your custom python executable (probably installed in your home directory, in a subdirectory called bin). And instead of cgi-bin, your directory is probably called cgi, so you’d need to make that change where necessary, too.

If you’d prefer to run a local test server on your Mac, this tutorial or this project might help you get started. Or you can just do what I did and go with Bluehost, the lazy man’s way. :]

Note: If you want to read more about setting up your own back-end server, check out Ray’s guide to setting up a Linode server for this blog. That’s a good starting point.

Assuming everything is in place with your server, the first step is to verify that Python is indeed installed and working properly. SSH into your server using the credentials that your hosting provider created for you, type python in Terminal and hit Enter.

If you did everything correctly, you should see something like this in Terminal:

pietrore@pietrorea.com [~]# python
Python 2.6.6 (r266:84292, Sep 11 2012, 08:34:23) 
[GCC 4.4.6 20120305 (Red Hat 4.4.6-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

That’s Python’s handy REPL (read-eval-print loop)! If you saw an error message instead of the REPL, it typically means that Python is either not installed, or that the Python interpreter can’t be found in your current search path.

Assuming that you got Python’s REPL to start, type quit() and hit Enter to exit this interactive mode. The next step is to install Stripe’s official Python library so you can use it in your script. Type this command in Terminal:

sudo pip install --index-url https://code.stripe.com --upgrade stripe

pip is a Python package manager that often comes installed by default in many server configurations. After the package has finished installing, verify that the Stripe library is ready for use with Python’s REPL.

In Terminal, type python and hit Enter to start the interactive mode. Now type import stripe and hit Enter. You should see something like this:

pietrore@pietrorea.com [~]# python
Python 2.6.6 (r266:84292, Sep 11 2012, 08:34:23) 
[GCC 4.4.6 20120305 (Red Hat 4.4.6-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import stripe
>>> 

If there’s no “Traceback” error, it means Stripe’s library has been successfully installed and you can safely use it in your script.

The CGI Script

CGI stands for Common Interface Gateway. It’s a way for a web server to run an executable program upon receiving a web request. This is perfect for your case – as in steps 3-4 above, you want to be able to send a message to your server to make a charge, and then have some code (written by you) to submit the charge to Stripe’s servers.

Your CGI script is going to live in a special directory called cgi-bin. This directory is configured with special file permissions to provide added protection to your web server.

Navigate to your cgi-bin directory. In my Bluehost setup, I do this with the following Terminal command:

cd ~/www/cgi-bin

Now create a file called payment-test.cgi by entering the following into Terminal:

vim payment-test.cgi

You are using vim to create and edit the payments-test.cgi file because it comes pre-installed in most Linux distributions. Hit i to enter vim’s interactive mode and type in the following script:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
 
#1
import sys
import json
import cgi
import cgitb
import stripe
 
#2
cgitb.enable()
 
print 'Content-Type: text/json'     
print                               
 
#3
stripe.api_key = 'your-private-key-goes-here'
 
#4
json_data = sys.stdin.read()
json_dict = json.loads(json_data)
 
#5
stripeAmount = json_dict['stripeAmount']
stripeCurrency = json_dict['stripeCurrency']
stripeToken = json_dict['stripeToken']
stripeDescription = json_dict['stripeDescription']
 
#6
json_response = stripe.Charge.create(amount=stripeAmount, currency=stripeCurrency, card=stripeToken, description=stripeDescription)
 
print json_response

Following the numbered comments, here’s what you just did:

  1. You have to import several Python modules that your script is going to need. Since your iOS app is going to be doing a POST request, the parameters that you pass into your Python script are going to come from standard in. Import the sys module to give yourself access to these parameters.

    The POST request is going to arrive in JSON format, so you also need to import the json module to be able to convert the incoming JSON into a proper Python dictionary. The cgi and cgitb modules are used mainly for troubleshooting. See below for more about that.

  2. cgitb.enable() creates a formatted report every time there is an uncaught exception in your script. This is very helpful when you are first writing your back-end script. However, you should disable it in production since it will expose your implementation in its detailed traceback reports.
  3. Type in your Test Secret Key that Stripe provided earlier. This is the secret part of the secret/publishable pair of keys necessary to complete a transaction. If there is a mismatch between the secret and publishable keys, the charge will fail.
  4. Convert the data arriving through standard in into a Python dictionary by calling json.loads().
  5. Create placeholder variables to store the parameters that you’re going to post from your app. It’s a bit weird handling variables you haven’t even created in your app yet, but this is what they are going to do:

    • stripeAmount: The amount in cents that you’ll charge your customer.
    • stripeCurrency: A short string that identifies the currency used for the transaction (e.g., “usd” for U.S. dollars). At the time of writing, a Stripe account can only process payments in one currency, so you could choose to hard-code this string in the script.
    • stripeToken: The one-time token that you get back with the STPCard object (card.tokenID).
    • stripeDescription: A descriptive message that you can easily recognize when you log into the Stripe dashboard. This is a good place for an internal transaction ID.
  6. stripe.Charge.create() goes out to Stripe, charges the customer’s card and returns with a response formatted in JSON. If everything goes as planned, your Stripe account should have more $$. :]

    Keep in mind that errors can occur here as well. For example, you might try to charge a token that you already used. Whatever the outcome, you have to notify your app of what happened. You do this by printing the JSON response that you receive from Stripe.

Now press Esc to exit vim’s interactive mode. Type :wq and hit Enter to save and quit vim.

There’s one last thing you need to do before your CGI script can execute properly. In Terminal, type the following command while you are still in the cgi-bin directory:

chmod 755 payment-test.cgi

This changes the file permissions of your script to 755, which allows others – in particular, your web server – to read and execute it.

Back To Your App

With your script in place, it’s time to go back to the app to continue where you left off. You had just implemented the method performStripeOperation and were about to implement the success and failure blocks.

Before you do that, scroll to the top of RWStripeViewController.m and complete the second #define statement with your script’s URL:

#define STRIPE_TEST_PUBLIC_KEY @"your_test_publishable_api_key"
#define STRIPE_TEST_POST_URL @"http://pietrorea.com/cgi-bin/payment-test.cgi"

I’ve included my script’s URL just to show you what it should look like. Make sure to use your script’s URL instead of mine. Unfortunately, you can’t reuse mine because you and I have different Stripe API keys.

Now remove the two breakpoints you added earlier in performStripeOperation and implement postStripeToken like this:

- (void)postStripeToken:(NSString* )token {
 
    //1
    NSURL *postURL = [NSURL URLWithString:STRIPE_TEST_POST_URL];
    AFHTTPClient* httpClient = [AFHTTPClient clientWithBaseURL:postURL];
    httpClient.parameterEncoding = AFJSONParameterEncoding;
    [httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [httpClient setDefaultHeader:@"Accept" value:@"text/json"];
 
    //2    
    RWCheckoutCart* checkoutCart = [RWCheckoutCart sharedInstance];
    NSInteger totalCents = [[checkoutCart total] doubleValue] * 100;
 
    //3
    NSMutableDictionary* postRequestDictionary = [[NSMutableDictionary alloc] init];
    postRequestDictionary[@"stripeAmount"] = [NSString stringWithFormat:@"%d", totalCents];
    postRequestDictionary[@"stripeCurrency"] = @"usd";
    postRequestDictionary[@"stripeToken"] = token;
    postRequestDictionary[@"stripeDescription"] = @"Purchase from RWPuppies iOS app!";
 
    //4
    NSMutableURLRequest* request = [httpClient requestWithMethod:@"POST" path:nil parameters:postRequestDictionary];
 
    self.httpOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        [self chargeDidSucceed];
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        [self chargeDidNotSuceed];
    }];
 
    [self.httpOperation start];
 
    self.completeButton.enabled = YES;
}

That’s quite a bit of code. Let’s go through it step-by-step:

  1. You are going to use AFNetworking to make the POST request to your back-end script. AFNetworking already comes installed in the starter project so you don’t have to worry about adding it yourself. Create an AFHTTPClient and configure it as shown. To learn more about AFNetworking, check out our AFNetworking Crash Course.
  2. Invoke the singleton checkout cart by sending the message sharedInstance to the RWCheckoutCart class. Its class method total returns the running total for your order as an NSNumber. Compute the total number of cents by extracting the double value out of the NSNumber and multiplying it by 100.
  3. Create the parameter dictionary that you are going to pass into your Python script. Remember that all of the dictionary’s keys have to match what you handle in your script, otherwise your script is going to fail.
  4. Use AFNetworking to create an instance of NSMutableURLRequest. Instantiate the AFJSONRequestOperation property called httpOperation using the NSMutableURLRequest and the parameter dictionary you created a moment ago.

    The HTTP post request executes asynchronously so it doesn’t block the main thread. If it succeeds, you call the success handler chargeDidSucceed. Otherwise, you call the failure handler chargeDidNotSuceed. These two methods are currently empty stubs that you will implement later on.

Now implement handleStripeError:. You execute this method if your first call to Stripe fails. Implement it as follows:

- (void)handleStripeError:(NSError *) error {
 
    //1
    if ([error.domain isEqualToString:@"StripeDomain"]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
                                                        message:[error localizedDescription]
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];
        [alert show];
    }
 
    //2
    else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
                                                        message:@"Please try again"
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];
        [alert show];
    }
 
    self.completeButton.enabled = YES;
}
  1. If Stripe received your customer’s credit card information but was unable to proceed for whatever reason (e.g., insufficient funds), the NSError’s domain comes back as “StripeDomain”. Again, you use the localized error message provided by Stripe to populate your alert view.
  2. The error could have happened somewhere else (e.g., a network error). Handle this scenario by providing a standard error message in your alert view.

You’re ready to test the entire Stripe workflow! Place two breakpoints inside postStripeToken:, one pointing at chargeDidSucceed and another one pointing at chargeDidNotSucceed.

Build and run your app. Add a puppy to your checkout cart and fake-purchase the pup using the test card you used previously. Here it is again:

  • Credit Card Number: 4242424242424242
  • Expiration Date: Any month & year in the future
  • CVC number: Any three- or four-digit number

Tap on “Complete” after entering all of your customer and credit card information. If everything went well, your debugger should stop right before chargeDidSucceed:

More importantly, verify that the charge went through on your Stripe dashboard.

Congratulations! The test charge was successful. (Hopefully. If not, you’ll have to do some debugging to find the source of the problem.)

The methods chargeDidSucceed and chargeDidNotSucceed are called from postStripeToken:‘s success and failure blocks, respectively. These methods are responsible for notifying your customer about the transaction’s final result. Remove the two breakpoints from postStripeToken: and then implement these methods as follows:

- (void)chargeDidSucceed {
    //1
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success"
                                                    message:@"Please enjoy your new pup."
                                                   delegate:nil
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    [alert show];
 
    RWCheckoutCart* checkoutCart = [RWCheckoutCart sharedInstance];
    [checkoutCart clearCart];
 
    [self.navigationController popToRootViewControllerAnimated:YES];
}
 
- (void)chargeDidNotSuceed {
    //2
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Payment not successful"
                                                    message:@"Please try again later."
                                                   delegate:nil
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    [alert show];
}
  1. If the charge succeeds, present an alert view with a success message. Also make sure to reset the checkout cart by calling clearCart on RWCheckoutCart. It’s good to dismiss the payment page when a transaction is successful. You accomplish this by popping the navigation controller back to the root view controller.
  2. On the other hand, if the charge was not completed successfully, simply present an alert view with a failure message. The user can then decide to try again if they so desire (for example, if they entered some of their information incorrectly).

Build and run your app. Go through the entire checkout process one more time using the test credit card. After the transaction goes through, you should see this:

Thank You For Shopping With Us! (Optional Section)

Before you start planning your e-commerce empire, there’s one more thing you should consider implementing. Over the years, customers have come to expect a confirmation email after making any online purchase. I don’t know about you, but if I don’t get one, I get nervous!

In the last section of the tutorial, you are going to implement a Python script to provide your customers with email confirmations. This part is optional. Sit back and relax. :]

The client-side logic for sending the confirmation email is going to be encapsulated in the RWEmailManager class. However, you’ll be writing a separate back-end script named rw-confirmation-email.cgi that will be responsible for actually sending the confirmation email, as commanded by your app.

This second Python script is going to live in the same directory as the earlier payment-test.cgi script you implemented. So head over to RWEmailManager.m and complete the empty #define statement to look something like this:

#define kConfirmationEmailPostURL @"http://www.pietrorea.com/cgi-bin/rw-confirmation-email.cgi"

Remember to use a URL that will point to your script instead of mine.

Next, implement the rest of the class like this:

- (void)sendConfirmationEmail {
    [self sendConfirmationEmailWithSuccessBlock:nil failureBlock:nil];
}
 
- (void)sendConfirmationEmailWithSuccessBlock:(void(^)(void))successBlock failureBlock:(void(^)(void))failureBlock {
 
    NSURL* postURL = [NSURL URLWithString:kConfirmationEmailPostURL];
    AFHTTPClient* httpClient = [AFHTTPClient clientWithBaseURL:postURL];
    httpClient.parameterEncoding = AFJSONParameterEncoding;
    [httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [httpClient setDefaultHeader:@"Accept" value:@"text/json"];
 
    NSMutableDictionary* postRequestDictionary = [[NSMutableDictionary alloc] init];
 
    RWCheckoutCart* checkoutCart = [RWCheckoutCart sharedInstance];
    NSMutableArray* puppyArray = [[NSMutableArray alloc] init];
 
    for (RWPuppy* puppy in checkoutCart.puppiesInCart) {
        NSMutableDictionary* puppyDict = [[NSMutableDictionary alloc] init];
        puppyDict[@"puppyName"] = puppy.name;
        puppyDict[@"puppyPrice"] = puppy.price;
        [puppyArray addObject:puppyDict];
    }
 
    postRequestDictionary[@"recipientName"] = self.name;
    postRequestDictionary[@"recipientEmail"] = self.email;
    postRequestDictionary[@"recipientPuppies"] = puppyArray;
 
    NSMutableURLRequest* request = [httpClient requestWithMethod:@"POST" path:nil parameters:postRequestDictionary];
    self.httpOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        if (successBlock) successBlock();
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        if (failureBlock) failureBlock();
    }];
 
    [self.httpOperation start];
}

That’s quite a bit of code! The good news is that the structure of this POST request is almost identical to the POST request you implemented in RWStripeViewController.m. The main difference is the set of parameters that you send into the script.

As far as these parameters go, recipientName and recipientEmail come directly from the instance variables that are passed into RWEmailManager as it is instantiated.

recipientPuppies, on the other hand, is an array of dictionaries, where each element dictionary contains a purchased puppy’s name and price. The email script needs this information to generate an order summary for the confirmation email.

Now go back to RWStripeViewController.m and modify chargeDidSucceed like this:

- (void)chargeDidSucceed {
 
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success"
                                                    message:@"Please enjoy your new pup."
                                                   delegate:nil
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    [alert show];
 
    //Send confirmation email
    RWEmailManager* emailManager = [[RWEmailManager alloc] initWithRecipient:self.nameTextField.text
                                                              recipientEmail:self.emailTextField.text];
 
    [emailManager sendConfirmationEmail];
 
    RWCheckoutCart* checkoutCart = [RWCheckoutCart sharedInstance];
    [checkoutCart clearCart];
 
    [self.navigationController popToRootViewControllerAnimated:YES];
}

Insert the RWEmailManager after the alert view but before clearing out the checkout cart. Your app is now ready to tell a back-end script to send an automated email once the purchase is complete. The problem is that the script doesn’t exist yet!

SSH into the server that hosts payment-test.cgi and navigate to cgi-bin. Type the following command in Terminal and press Enter:

vim rw-confirmation-email.cgi

Like before, this command creates and opens a new file. Press i to start vim’s interactive mode and type in the following script:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
 
#1
import sys
import json
import cgi
import cgitb
import smtplib
 
cgitb.enable()
 
#2
json_data = sys.stdin.read()
json_dict = json.loads(json_data)
 
recipient_email = json_dict["recipientEmail"]
recipient_name = json_dict["recipientName"]
recipient_puppies = json_dict["recipientPuppies"]
 
#3
total = 0.0
details = ""
 
for puppy_dictionary in recipient_puppies:
	name = puppy_dictionary["puppyName"]
	price = puppy_dictionary["puppyPrice"]
	details = details + name + "        " + "$" + str(price) + "\n"
	total = total + price
 
#4
email_username = "ENTER_YOUR_EMAIL"
email_password = "ENTER_YOUR_PASSWORD"            #8
email_subject = "Your RWPuppies Order"
 
#5
email_body = "Hello " + recipient_name + ",\n\n"
email_body += "Thank you for your order! Here are your order details:\n\n"
email_body += details + "\n"
email_body += "TOTAL: $" + str(total) +"\n\n"
email_body += "If you have any questions or concerns regarding your order please visit our\n\
website www.raywenderlich.com or call us at...actually don't call us. Email\n\
works much better.\n\nWe hope you enjoy playing with your new puppies!\n\nRWPuppies Team"
 
#6
session = smtplib.SMTP('YOUR_MAIL_SERVER', 26)
session.ehlo()                                    #8
session.starttls()                                #8
session.login(email_username,email_password)      #8
 
#7
headers = "\r\n".join(["from: " + email_username,
                       "subject: " + email_subject,
                       "to: " + recipient_email,
                       "mime-version: 1.0",
                       "content-type: text/plain"])
 
content = headers + "\r\n\r\n" + email_body
session.sendmail(email_username, recipient_email, content)
session.quit()

Whew! That’s a big chunk of code. Fortunately, it is easy to follow what’s going on thanks to Python’s minimalist syntax. Let’s go over this script step-by-step:

  1. As with the previous script, you have to import several Python modules. The most important one here is smtplib, which you are going to use to talk to your SMTP server.
  2. Read data from standard in and convert it into a Python dictionary using the json module. Extract the name, email and puppies parameters that you passed in from iOS.
  3. Iterate through all the received puppies to create the order summary (a string). For every puppy you received from the app, create a line with the puppy’s name and price. You are also updating the total cost with the cost of each puppy.

Not that type of puppy string!

  1. email_username and email_password refer to the email account that is going to be sending the email. I originally wanted to use my Gmail account as an SMTP server, but Bluehost only allows outgoing SMTP connections to their own SMTP servers (to prevent spam, I suppose).
  2. Create the email body by concatenating different strings with the + operator. You have to interweave the variables relevant to that specific order in the right location (e.g., recipient_name, details and total).
  3. Connect to an SMTP server listening to port 26. As mentioned before, there may be some limitations that apply if you are using shared hosting. Some hosting providers don’t allow outgoing SMTP connections at all; others may use different ports; some don’t require you to specify the port. In other words, you may need to investigate and try out a few things to get this working. On the other hand, if you have a dedicated server from a company like Linode, you can typically open as many ports as you want.
  4. Without going too much into the SMTP specification, you are going to create the appropriate headers and send the email by specifying the sender’s email address, the recipient’s email address and the email’s content.
  5. The lines of code marked with the #8 are special in that they are very likely not necessary. Depending on how your server is set up, it may just check the email_username that you specified, and if it is a user from the same account that owns the script, then it will allow the connection. Give it a try without those lines; if it works, that’s one less file out there with your password in it! :]

Press Esc to exit vim’s interactive mode. Type :wq and hit Enter to save and quit vim. In Terminal, type the following command while you are still in the cgi-bin directory:

chmod 755 rw-confirmation-email.cgi

As with the previous script, changing file permissions to 755 makes rw-confirmation-email.cgi executable by others, which is what you want so that your server can access it.

Build and run your project one last time. Go through the motions of buying another puppy using the test card from before. Make sure you provide a valid email address so that the confirmation email can actually reach you!

When you complete the checkout process, wait a few seconds and then head over to your inbox. You should see an email similar to this one:

And that’s how to send confirmation emails using a back-end script!

But What If Something Went Wrong?

This section is also optional – it’s for people who completed the last section, or who want to know more about debugging server-side logic like this.

It can be a bit tricky to debug issues like a misconfigured server. It’s usually a good idea to isolate each component in the process and test them individually. Here is a command I used to help debug rw-confirmation-email.cgi:

echo -n '{"recipientEmail":"TEST_ADDRESS","recipientName":"You",\
"recipientPuppies":[{"puppyName":"Pup","puppyPrice":1000.99}]}' \
| python rw-confirmation-email.cgi

Note: The backslashes \ are there just to make the command more readable here. You should enter it without the backslashes and all on one line or else it will not work.

By running this command in your Terminal while SSHed into your server, you can execute your Python script directly without having to go through your app. Just replace TEST_ADDRESS with a valid email address and you should get a confirmation email if your script is working correctly. If there is an error, you should see an error message.

The error message may be formatted as HTML, so it might help to redirect the output to a file that you can view in your browser. For example, most web servers are set up with the cgi-bin (or cgi, or whatever it’s called on your server) located within the top level of your site’s html directory. So to redirect the output to a webpage that is viewable at www.yoursite.com/test_output.html, you could do:

echo -n '{"recipientEmail":"TEST_ADDRESS","recipientName":"You",\
"recipientPuppies":[{"puppyName":"Pup","puppyPrice":1000.99}]}' \
| python rw-confirmation-email.cgi > ../test_output.html

When you point your browser to that address, you should see a nicely formatted error message complete with stack traces that should help point you in the right direction.

Also note that if you had to do something like install a custom version of Python (I’m looking at you, GoDaddy!), then be sure you are running the python executable that you installed and not the default executable.

Every server environment could be different, so don’t get discouraged if it takes you a while to tailor your scripts. And feel free to ask for help in the comments section, because someone else may have already solved the same problem you’re having!

Where To Go From Here?

Here is the finished iOS project and finished server scripts.

Hooray! You’ve successfully implemented Stripe checkout in iOS from start to finish. Whenever you want to start charging your customers, verify your Stripe account and switch all the test API keys to the live API keys.

There are lots of cool things you can do with Stripe’s API that this tutorial didn’t cover. For example, if you know you are going to be charging the same person in the future, you may want to create a customer object. Stripe also lets you create subscription plans and invoices if that suits your business model.

I encourage you to peruse Stripe’s docs if you’re interested in implementing any of the features I mentioned above. If you have any tips or suggestions for using Stripe with iOS, or if you’ve implemented a checkout cart in the past, please share in the comments section.

Pietro Rea
Pietro Rea

Pietro is an iOS developer at Quidsi. He enjoys working on projects large and small and jokes about Objective-C being his native language.

You can find Pietro on Twitter.

User Comments

27 Comments

[ 1 , 2 ]
  • This is a possible solution to install a python module on a shared Web hosting account when you do not have a sudo account.

    https://my.bluehost.com/cgi/help/530

    So far I can "import stripe" in python. That seems to work.
    mageroski
  • I am trying to add a few features to this. I'd like to add a "remove from shopping cart" feature. Has anyone tried this?
    wallstcheater
  • Lovely tutorial, but something is just not working. I have adapted the code for an OS X app and I get this error:

    print json_response, NSErrorFailingURLKey=http://fees.nopali.com/payment/payment-test.cgi, AFNetworkingOperationFailingURLRequestErrorKey=<NSMutableURLRequest http://fees.nopali.com/payment/payment-test.cgi>, AFNetworkingOperationFailingURLResponseErrorKey=<NSHTTPURLResponse: 0x10b16be20>, NSLocalizedDescription=Expected content type {(
    "text/javascript",
    "application/json",
    "text/json"
    )}, got text/plain}

    So to work around this error, I changed:

    #pragma mark - AFHTTPRequestOperation

    + (NSSet *)acceptableContentTypes {

    //return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];

    // Ezat debugging version below
    return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/plain", @"text/html", nil];
    }


    and also in the code where I create a request operation:

    httpOperation.JSONReadingOptions = NSJSONReadingAllowFragments;

    [httpOperation start];


    and I get this error:

    Error Domain=NSCocoaErrorDomain Code=3840 "The data couldnt be read because it has been corrupted." (Invalid value around character 0.) UserInfo=0x1020901e0 {NSDebugDescription=Invalid value around character 0.}

    Not sure what to do at this point.
    ezathashim
  • [url=http://www.astir.pl]konserwacja kamienia[/url]
    [url=http://www.skrzynie-aluminiowe.pl]Walizki Peli[/url]
    [url=http://www.brukarstwo-pomorskie.com.pl]us?ugi brukarskie Wejcherowo[/url]
    TootWrese
  • Ok, I have a question. If I'm building an app that I plan to sell in the app store and I want to integrate stripe into my app, does that mean that every person who purchases my app is going to have to go to the hassle and difficulty of setting up the bakend on their own server to be able to use my app? Or, do I set it up on my server and that becomes the backend for everyone who buys my app?

    I can't seem to find an answer to this question anywhere.

    Bob
    Bob Hunt
  • You must support the back end on your own server. The users of your app should never need to know anything about Stripe.

    -Chris
    clapollo
  • When i downloaded the source files libPods.a is highlighted red in the Xcode project. Do you know what the issue could be?
    bport
  • Are you saying it's red under the Products folder in the Navigation pane? If so, that's not a problem. That just means it's something that gets built rather than something that already exists in the project.

    -Chris
    clapollo
  • I am getting this issues and not able to compile the source code --> library not found for -lPods
    Please let us know what i m missing to execute it?
    Tikam
  • Hi !
    Do you know where I can change the text "Remove from Cart" when I click on the button "add to cart" on the featured view controller or on the puppy view controller ?
    Yes, I know it's a basic question, but I'm trying to understand the code and I couldn't find this.
    Thanks
    Michel
  • So any idea where to find the text "Remove from Cart". It's impossible to find it ?
    Michel
  • I want to set up my backend in php .. can any one please tell me how do i do it ? its urgent :(
    Anirban123
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Tammy Coron
  • Kirill Muzykov

... 49 total!

Update Team

Editorial Team

... 23 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Heejun Han
  • Jesus Guerra
  • David Xie

... 33 total!

Subject Matter Experts

... 4 total!