1 Time Payments with Stripe and Vapor (Stripe Checkout)

289 Views

This tutorial will go over how to create a 1 time (non-recurring) payment using Stripe and vapor. It assumes you already have signed up for a stripe account and have your secret key and publishable key.

First, download the starter project from here. It is a bare-bones project with Leaf installed, and a few (mostly empty) classes that we will fill out. These classes are:

  • StripeConfiguration: This is where we will reference out secret and publishable keys needed to communicate with Stripe.
  • StripeAPIManager: Where we will store out API call to Stripe to create the charge.
  • PaymentController: Our controller to centrally manage payments. A few routes are already set up. We will implement the payment logic.

At this point you have a few project errors. We'll start by fixing that.

Let's begin in StripeConfiguration. This class conforms to the ConfigInitializable protocol, which, as you may guess, means we can initialize it from values we store under our Config folder. Add the following to the StripeConfiguration class:

    let publishableKey: String
    let secretKey: String
    
    init(publishableKey: String, secretKey: String) {
        self.publishableKey = publishableKey
        self.secretKey = secretKey
    }
    
    convenience init(config: Config) throws {
        let pubKey = config["stripe", "publishableKey"]?.string ?? ""
        let secKey = config["stripe", "secretKey"]?.string ?? ""
        self.init(publishableKey: pubKey, secretKey: secKey)
    }

This simply initializes 2 properties, a secret key and publishable key, that we will use when communicating with Stripe. In:

    config["stripe", "publishableKey"]?.string

the "stripe" is telling Vapor under which file to look for, and the "publishableKey" is telling it which key to look for in the json. .string tells us the expected type is string. We haven't created a stripe.json file yet, let's do it now.

Create a file under Config/secrets, and call it stripe.json. In that file, add your secret and publishable keys from stripe, as follows:

    {
      "secretKey": "sk_test_YOUR KEY",
      "publishableKey": "pk_test_YOUR KEY"
    }

It's very important to keep these under /secrets, and never expose them to version control! As of now, you should be able to run your project, go to /pay route, and see a blank screen. Now, go to pay.leaf. Inside the empty <div></div> tags, copy and paste:

    <form action="/your-method-here" method="POST">
      <script
        src="https://checkout.stripe.com/checkout.js" class="stripe-button"
        data-key=<yourPublishableKeyHere>
        data-amount="999"
        data-name="your name"
        data-description="Widget"
        data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
        data-locale="auto">
      </script>
    </form>

This code is just from the stripe documentation, here: https://stripe.com/docs/checkout/tutorial. It is the form widget managed by Stripe, so that we don't have to take custom input from users. At the top of the form, change action="/your-method-here" to: action="pay". This is because, in order to complete the payment, we will need to do some work on our server, and we will do so in the pay method in our PaymentController class.

Next, let's change <yourPublishableKeyHere> to be your publishable key. In PaymentController.swift, under the showPaymentPage function, change this:

     return try view.make("payments/pay", ["publishableKey": ""], for: request)

to be this:

    return try view.make("payments/pay", ["publishableKey": stripeConfig.publishableKey], for: request)

Then, in pay.leaf, change data-key=<yourPublishableKeyHere> to be: data-key=#(publishableKey).

The code snippet we copy and pasted above is a Stripe managed form, and when the user enters their card details, the card details are sent to stripe, and then stripe returns to us a token. At this point, the code snippet should now look like:

    <form action="pay" method="POST">
     <script
       src="https://checkout.stripe.com/checkout.js" class="stripe-button"
       data-key=#(publishableKey)
       data-amount="999"
       data-name="Your Name!"
       data-description="Widget"
       data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
       data-locale="auto">
      </script>
     </form>

The flow is like:

1. We include the above code snippet on our webpage 2. The above code will put a button on our screen that says "Pay with Card." 3. The user presses the button, and the Stripe-managed form is brought up 4. The user enters their card details 5. The card details are sent to Stripe. Stripe returns to us a token, in the format of tok_abcdefghijklmnop. 6. Inside our custom method, we will obtain this token, and then use this token to create a charge while calling Stripe.

We use the token so we don't have to handle and store fragile credit card information directly on our server.

At this point, you can restart your server, navigate to /pay, and see the "Pay with Card" button. Fill out the form with your email, and the dummy data: Card number of 4242 4242 4242 4242, and expiration of 01 / 20, 3 digit code as 111, and zip code as 12345.

Click "Pay 9.99 US", and after a few seconds of verification, you should be redirected to a screen that simply says `implement payment here`. Good! That means it's hitting our custom pay method.

So we know that when the user correctly enters their details, Stripe returns to us a token. Now, in our pay method, let's get that token to use for creating a charge. Change the pay method in `PaymentManager.swift` to read the following:

    func pay(request: Request) throws -> ResponseRepresentable {
        if let token = request.data["stripeToken"]?.string { // (1)
            return try Response.async { responder in // (2)
                StripeAPIManager.chargeCard(withAmount: 999, withDescription: "first charge!", fromSourceToken: token, andConfig: self.stripeConfig, completion: { (completed, errorMessage) in
                    if errorMessage == "" { // (3)
                        responder.close(with: "success!")
                    } else { responder.close(with: errorMessage) }
                })
            }
        } else { return "no token in params" }
    }

We haven't created the chargeCard method yet, so this will throw some errors (we'll fix that next). First, to explain to code:

(1) We retrieve the token in the params that Stripe ends back to us. This token is a 1 time use token, and represents the user's card details. (2) We need to make an async call with a closure, and this is the syntax to return something (either success or error, for example), based off a value we receive in a closure. In (3), if the errorMessage is an empty string, we know the call is a success. Otherwise, we return the error.

Next, let's create the chargeCard method. In the StripeAPIManager, add the following:

    static let chargesUrl = "https://api.stripe.com/v1/charges" // (1)
    
    class func chargeCard(withAmount amount: Int, withDescription descr: String, fromSourceToken token: String, andConfig stripeConfig: StripeConfiguration, completion: @escaping (_ completed: Bool, _ errorMessage: String) -> Void) {
        let session = URLSession.shared
        var request = URLRequest(url: URL(string: StripeAPIManager.chargesUrl)!)
        
       // (2)
        let myParams = "amount=\(amount)¤cy=usd&description=\(descr)&source=\(token)"
        let postData = myParams.data(using: String.Encoding.ascii, allowLossyConversion: true)
        request.httpBody = postData
        request.httpMethod = "POST"

       // (3)
        let postLength = String(format: "%d", postData!.count)
        request.setValue(postLength, forHTTPHeaderField: "Content-Length")
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

       // (4)
        let authBearer = "Bearer " + stripeConfig.secretKey
        request.setValue(authBearer, forHTTPHeaderField: "Authorization")
        
       // (5)
        let task = session.dataTask(with: request) { (data, response, error) in
            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments)
                print(json)
                if let dict = json as? NSDictionary, let stripeError = dict["error"] as? NSDictionary, let errorMessage = stripeError["message"] as? String {
                    completion(false, errorMessage)
                } else {
                    completion(true, "")
                }
            }catch { completion(false, "Could not serialize JSON") }
        }
        task.resume()
    }

This is a POST request to the stripe API. Let's go through this, piece by piece:

(1) is the URL string we're going to POST to for creating a charge.

At (2), we create the params from the data we passed in. Note, the params must be x-www-form-urlencoded (this is mysteriously missing from the Stripe docs). Encoding the data as JSON will not work and you'll get a 400 response (I should know--i spent about an hour trying to figure out why it wasn't working!). We then create Data from the params string, and assign that as the httpBody of the request.

At (3) we set values for the Content-Length and Content-Type HTTP headers.

In (4), we use the passed in stripeConfig object to set the Authorization header. it must be exactly in this format, and it must be the secret key. Using the publishable key will not work.

(5) is a typical dataTaskWithUrl request in swift. Depending on whether or not an error is returned, we call the appropriate completion block with either true or false.

That's it! now navigate to /pay, press 'Pay with Card', enter valid details, and you should be redirected to a screen that says "success!". Try removing the Authorization header to test the failing case as well. You will get a screen with the appropriate error message. You can [find the completed tutorial here](https://github.com/JoeyBodnar/StripeTutorial/tree/CompletedTutorial)



Enjoy this article? Consider supporting VaporForums by following us on Twitter! Get the latest Vapor articles, tutorials, and news.