Intermediate TDD in Rails : Encapsulating the Stripe Response

Objective


To encapsulate the parsing of the Stripe response in it's own class.

Steps


Step 1

I would like to make a call in the subscriptions_controller.rb create action like this:

Subscription.save_details(stripe_customer, current_user, plan_name) 

This should save the stripe_customer_token, plan_name, last 4 digits, expiration_month and expiration_month of credit card.

Step 2

In order to accomplish that, we need to navigate the stripe customer JSON response from the Stripe servers and retrieve the customer id and the credit card details. The Stripe API docs shows the structure of the customer object:

  {
   "object": "customer",
   "created": 1426623031,
   "id": "cus_5tDSlyr30eqCTa",
   "livemode": false,
   "description": null,
   "email": null,
   "delinquent": false,
   "metadata": {
   },
   "subscriptions": {
     "object": "list",
     "total_count": 0,
     "has_more": false,
     "url": "/v1/customers/cus_5tDSlyr30eqCTa/subscriptions",
     "data": [

     ]
   },
   "discount": null,
   "account_balance": 0,
   "currency": "usd",
   "sources": {
     "object": "list",
     "total_count": 1,
     "has_more": false,
     "url": "/v1/customers/cus_5tDSlyr30eqCTa/sources",
     "data": [
       {
         "id": "card_15hR1H2eZvKYlo2CpmgFpXLh",
         "object": "card",
         "last4": "4242",
         "brand": "Visa",
         "funding": "credit",
         "exp_month": 3,
         "exp_year": 2017,
         "fingerprint": "Xt5EWLLDS7FJjR1c",
         "country": "US",
         "name": null,
         "address_line1": null,
         "address_line2": null,
         "address_city": null,
         "address_state": null,
         "address_zip": null,
         "address_country": null,
         "cvc_check": null,
         "address_line1_check": null,
         "address_zip_check": null,
         "dynamic_last4": null,
         "metadata": {
         },
         "customer": "cus_5tDSlyr30eqCTa"
       }
     ]
   },
   "default_source": "card_15hR1H2eZvKYlo2CpmgFpXLh"
 }

Step 3

Copy the above JSON to spec/fixtures/customer.json.

Step 4

Let's write a stripe_customer_mapper_spec.rb that will encapsulate the navigation of the JSON document.

require 'rails_helper'

describe StripeCustomerMapper do

  it 'should map credit card expiration month' do
    h = JSON.parse(File.read("spec/fixtures/customer.json"))
    customer = Stripe::Customer.construct_from(h)    

    mapper = StripeCustomerMapper.new(customer)

    expect(mapper.credit_card_expiration_month).to eq(3)
  end

end

Look at the fixture file, customer.json to find the credit card expiration date. It is 3, so we have 3 in the expect method. Run the test. It fails.

Step 5

Playing in the irb:

h = JSON.parse(File.read("spec/fixtures/customer.json"))
customer = Stripe::Customer.construct_from(h)   

We find that :

customer.sources.data[0].exp_month

will retrieve the expiration date of the credit card.

Step 6

Create the app/models/stripe_customer_mapper.rb and implement the method as follows:

class StripeCustomerMapper
  def initialize(customer)
    @customer = customer
  end

  def credit_card_expiration_month
    @customer.sources.data[0].exp_month
  end

end

Step 7

Here is the tests for expiration year and last 4 digits:

  it 'should map credit card expiration year' do
    h = JSON.parse(File.read("spec/fixtures/customer.json"))
    customer = Stripe::Customer.construct_from(h)    

    mapper = StripeCustomerMapper.new(customer)

    expect(mapper.credit_card_expiration_year).to eq(2017)
  end

  it 'should map credit card last 4 digits' do
    h = JSON.parse(File.read("spec/fixtures/customer.json"))
    customer = Stripe::Customer.construct_from(h)    

    mapper = StripeCustomerMapper.new(customer)

    expect(mapper.credit_card_last4digits).to eq('4242')
  end

Step 8

Here is the implementation that passes it. It is similar to our first test.

  def credit_card_expiration_year
    @customer.sources.data[0].exp_year
  end

  def credit_card_last4digits
    @customer.sources.data[0].last4
  end

Summary


In this lesson we encapsulated the logic to navigate the Stripe JSON response in StripeCustomerMaper class. This localizes the changes that may be needed in the future due to Stripe API changes to the mapper class.


Related Articles