In Getting Started with Stripe - Part 1, we saw how to properly set up your API routes which will be used by your application. In part 2, we explore how to integrate stripe into your iOS application using the routes provided by your API and the Stripe SDK.
Setting up Stripe in your iOS Project
There are many ways to include the Stripe framework into your iOS project including Cocoapods, manually, and Carthage. Here at Teeps we use Carthage as our dependency manager choice for many reasons, mainly because it seems to be less invasive than other methods and integrates seamlessly with our workflow. For more information on how to include the Stripe SDK into an iOS Project, see their Getting Started Guide.
Once you have successfully added the Stripe framework into your project, you will need to set up a Stripe account. This is completely free to do, and does not require any bank account information until your application is ready for production. To do this, head over to Stripe and sign up as a new user.
After registering your account, you will need to locate your publishable key. This key is used to link your application with your stripe account and is located in your dashboard under the API tab. Make certain that while testing you are using your test key and not the production key. Usage of either key requires configuration in your AppDelegate class. In this post, I am not going into the setup of Apple Pay but this is also where you would set up your Apple Pay merchant identifier.
import UIKit
import Stripe
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
STPPaymentConfiguration.shared().publishableKey = "YOUR_KEY_HERE"
return true
}
}
Since the AppDelegate is usually where you would configure the appearance of your application, this is also a good spot to configure the styling of Stripe’s provided card views. You can do that by accessing the default theme and changing its values provided to you in the Stripe standard integration guide.
primaryBackgroundColor // Background color for any views in this theme.
secondaryBackgroundColor // Background color for any supplemental views inside a view (for example the cells of a table view)
primaryForegroundColor // Color for any important labels in a view
secondaryForegroundColor //Color for any supplementary labels in a view
accentColor // For any buttons and other elements on a view that are important to highlight
errorColor // For rendering any error messages or views
font // Font to be used for all views
emphasisFont // Medium-weight font to be used for all bold text in views
After completion, your AppDelegate’s didFinishLaunchingWithOptions
should look something along these lines, give or take some configuration that you require.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
STPPaymentConfiguration.shared().publishableKey = "YOUR_KEY"
STPPaymentConfiguration.shared().requiredBillingAddressFields = .full
STPPaymentConfiguration.shared().companyName = "Teeps"
STPTheme.default().accentColor = UIColor.white
return true
}
API Adapter
The next step in this process is creating a way for your app to communicate with the API in a way that Stripe can understand. Thankfully, Stripe provides you with a protocol that defines just that. The STPBackendAPIAdapter protocol defines methods that perfectly align with the required API implementation which you saw in part 1. To set this up, we create a new class that conforms to the protocol.
import Stripe
final class StripeAdapter: NSObject, STPBackendAPIAdapter {
func retrieveCustomer(_ completion: @escaping STPCustomerCompletionBlock) {
}
func attachSource(toCustomer source: STPSourceProtocol, completion: @escaping STPErrorBlock) {
}
func selectDefaultCustomerSource(_ source: STPSourceProtocol, completion: @escaping STPErrorBlock) {
}
}
The first method retrieveCustomer
, enables you to get the customer object from Stripe that is linked to the user account in your API. You will notice that the method has a completion named STPCustomerCompletionBlock. This block is defined as (STPCustomer?, Error?) -> Void
where STPCustomer is the customer object returned by your API. Also, Stripe does make a deserializer that you can use to easily map the response to the customer object. This snippet does utilize BuckoNetworking to make the request. For more information on how to use BuckoNetworking to simplify your API calls, see our blog about it Protocol Oriented Networking in Swift.
func retrieveCustomer(_ completion: @escaping STPCustomerCompletionBlock) {
API.request(endpoint: StripeService.getCustomerInfo) { response in
if response.result.isSuccess && response.result.error != nil {
let customerDeserializer = STPCustomerDeserializer(jsonResponse: response.result.value!)
let customer = customerDeserializer.customer
completion(customer, nil)
}
else {
completion(nil, response.result.error)
}
}
}
The second method attachSource
is called when attempting to attach a new card to Stripe through their view provided. Stripe will generate a token for the card if it is valid, which is accessed on the source parameter in the method. It is required that you send this to your API in the format defined in part 1. The completion here STPErrorBlock is defined as (Error?) -> Void
, so in many cases you can pass your error returned from the server as the completion. The provided SDK from Stripe will handle the displaying of any errors that occur when the user passes an error in this completion.
func attachSource(toCustomer source: STPSourceProtocol, completion: @escaping STPErrorBlock) {
API.request(endpoint: StripeService.sendToken(token: source.stripeID)) { response in
// Handle success or failure here
}
}
The final method selectDefaultCustomerSource
is called when the user selects a new default card from the Stripe card view. It will relay the card token to your API which will set the logged-in user's primary payment method as that card. Similar to the previous method, you will need to pass in the card token to your API. This method also has a completion as an STPErrorBlock.
func selectDefaultCustomerSource(_ source: STPSourceProtocol, completion: @escaping STPErrorBlock) {
API.request(endpoint: StripeService.setDefaultCard(token: source.stripeID)) { response in
// Handle success or failure here
}
}
At this point, you have completed the Stripe API Adapter class and you are ready to move on to the implementation of your checkout screen.
Stripe Delegate and Checkout Screens
Now that your app is configured to handle the sending and retrieving of card/customer information, you will need to build your checkout screen to process payments and be notified when payments have been sent or when the context has changed. Implementing a basic checkout screen should at minimum have 4 key elements. A total cost, a product description, payment method, and a way to kick off the purchase.
Here we set up a simple model that has an id, price, and description.
class Product {
var id: Int
var price: Int // Stripe takes everything as cents
var description: String
init(id: Int, price: Int, name: String, description: String) {
self.id = id
self.price = price
self.description = description
}
}
Then, we need to set up the ViewController that will handle purchases and the updating of your PaymentContext
. The PaymentContext is the object that your screen will mainly revolve around. Everything from updating UI to retrieving the customer info can be done through PaymentContext. The payment context can be defined as:
var paymentContext \= STPPaymentContext(apiAdapter: StripeAdapter())
where StripeAdapter is the class from the last section that interfaces with your API. Your ViewController will also need to adopt to the STPPaymentContextDelegate
. Be sure to also set your desired ViewController as the paymentContext.hostViewController
Here is a basic ViewController that conforms to that delegate and sets the PaymentContext
import UIKit
import Stripe
class CheckoutViewController: UIViewController {
@IBOutlet weak var paymentMethodButton: UIButton!
@IBOutlet weak var productDescLabel: UILabel!
@IBOutlet weak var productCostLabel: UILabel!
var product: Product?
var paymentContext = STPPaymentContext(apiAdapter: StripeAdapter())
override func viewDidLoad() {
super.viewDidLoad()
configureView()
configurePaymentContext()
}
private func configureView() {
productDescLabel.text = product.description
productCostLabel.text = product.cost
}
private func configurePaymentContext() {
paymentContext.delegate = self
paymentContext.hostViewController = self
}
@IBAction func paymentMethodTapped(_ sender: Any) {
paymentContext.pushPaymentMethodsViewController()
}
@IBAction func makePaymentTapped(_ sender: Any) {
paymentContext.requestPayment()
}
}
extension ViewController: STPPaymentContextDelegate {
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
// Update your payment Context and change your UI here
self.paymentContext = paymentContext
paymentMethodButton.setTitle(paymentContext.selectedPaymentMethod?.label, for: .normal)
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
// Show an error here with failure message
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
API.request(endpoint: StripeService.makePurchase(id: self.product.id)) { response in
// Handle the response
}
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
// Handle the response
}
}
You will notice that in the paymentContextDidChange
method, you can make changes to your UI to reflect the new payment context. As shown above, I change the stripe button's text to reflect the card selected.
The didCreatePaymentResult
method gets called when a user makes a call to paymentContext.requestPayment()
. This is where you will notify your API of the product that is being purchased. Your API should then return a response that the payment was successful or has failed.
The last important method is didFinishWith
. This is where you can handle your UI that the purchase has been completed successfully or has failed.
Testing
Once you have completed your Checkout flow, you are ready to test! Stripe has made testing incredibly easy to perform. Stripe provides test cards of all different types, including: VISA, MASTERCARD, AMEX, and more. They also provide you with cards that are expected to fail with defined reasons. This is valuable for testing to see how thoroughly you have handled your errors. You can get those cards from Stripe Testing. The test payments are also viewable through Stripe's dashboard.
Need an app that would require Stripe or have a question? Fill out our Contact form. We would love to chat.