Article | Getting started with Stripe, Rails and React in three easy steps

Getting started with Stripe, Rails and React in three easy steps

A client recently requested to add the functionality to be able to accept credit card payments within a Rails application that we were building. Specifically, they wanted users to be able to purchase vouchers using credit card payments and pass on any processing fees. Once the payment was complete, the transaction had to be recorded within the app and an email needed to be sent out to both the purchaser and the giftee. Stripe came into the picture then.

Stripe offers payment processing software for websites and mobile applications. Their APIs and documentation make it super simple to set up and accept credit card payments whilst complying with regulations. Credit card information never touches our app and is directly sent to Stripe. The steps to setup Stripe in our app was pretty straightforward and I will cover it below.

Step One

We had to set up an endpoint within our Rails app - this would allow us to create what Stripe calls a “payment intent”. A payment intent represents a single customer session within Stripe. Stripe recommends you create a payment intent as soon as you know the payment amount. The API we created required the client to pass through an amount and some metadata used to populate information for our voucher. The Rails app then calculated the fee charged by Stripe, added it onto the total amount, created a payment intent within Stripe and returned back the payment intent’s client_secret. This is what the UI will now use to interface directly with Stripe’s APIs.

class CreatePaymentIntent
  class InvalidAmountError < StandardError; end
  class InvalidEmailError < StandardError; end
  include UseCase

  attr_reader :payment_intent, :metadata

  def initialize(amount:, from_name:, to_name:, from_email:, to_email:, note: nil)
    @amount = amount
    @from_name = from_name
    @to_name = to_name
    @from_email = from_email
    @to_email = to_email
    @note = note
  end

  def perform
    validate_emails
    validate_payment_amount
    determine_processing_fee_and_payment_amount
    determine_total_payment_amount_in_cents
    create_payment_intent!
  rescue InvalidEmailError
    nil
  rescue InvalidAmountError => e
    errors.add(:amount, e.message)
  rescue StandardError => e
    errors.add(:base, e.message)
  end

  private

  # Check if the from and to email addresses are valid
  def validate_emails
  end

  # Check the payment amount is between the minimum voucher amount and maximum voucher amount
  def validate_payment_amount
  end

  # Calculate the processing fee and payment amount
  def determine_processing_fee_and_payment_amount
    # See: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers
    @payment_amount = (@amount + ApplicationConfig::STRIPE_PROCESSING_FEE_CHARGE) / (1 - (ApplicationConfig::STRIPE_PROCESSING_FEE_PERCENTAGE / 100))
    @processing_fee = @payment_amount - @amount
  end

  # Calculate the payment amount in cents
  def determine_total_payment_amount_in_cents
  end

  def create_payment_intent!
    @payment_intent = Stripe::PaymentIntent.create(amount: @payment_amount_in_cents, currency: ApplicationConfig::STRIPE_CURRENCY, metadata: construct_metadata)
  end

  # Build a hash representation with the amount, processing fee, from_name, to_name, from_email, to_email and note
  def construct_metadata
  end
end

Step Two

After creating a payment intent, we set up a simple checkout form. This was done using the React Stripe.js library, the Elements provider and components and two hooks (useElements and useStripe). The form used the client_secret from step one as well as an API key which can be obtained from the Stripe console. You’re also able to style these components how you like.

Step Three

After setting up the checkout form, we set up a webhook endpoint within our Rails app. Once a payment has been successfully processed, Stripe sends a payment_intent.succeeded event. We configured the webhook endpoint within the Stripe dashboard to get Stripe to send events to. This webhook would issue a voucher and then email the purchaser and the recipient. Stripe also provides other events such as when a payment is created and fails.

class HandleStripeEvent
  class UnsupportedEventError < StandardError; end

  PAYMENT_INTENT_SUCCEEDED_EVENT_TYPE = 'payment_intent.succeeded'

  include UseCase

  attr_reader :event

  def initialize(payload:, signature:)
    @payload = payload
    @signature = signature
  end

  def perform
    construct_event
    construct_metadata
    construct_payment_provider_metadata
    add_job_to_queue
  rescue JSON::ParserError
    errors.add(:event, I18n.t('use_cases.handle_stripe_event.json_parse_error'))
  rescue Stripe::SignatureVerificationError
    errors.add(:event, I18n.t('use_cases.handle_stripe_event.invalid_signature_error'))
  rescue UnsupportedEventError => e
    errors.add(:event, e.message)
  end

  private

  def construct_event
    @event = Stripe::Webhook.construct_event(@payload, @signature, endpoint_secret)
  end

  # Build a hash representation of the metadata from the event data
  def construct_metadata
  end

  # Build payment provider metadata hash of Stripe event identifier (from the event id) and the Stripe payment intent identifier (from the event data object id)
  def construct_payment_provider_metadata
  end

  # Add payment intent succeeded events received to the IssueVoucherJob queue to process the event asynchronously with the basic event metadata and the payment provider metadata
  # Raise unsupported event error for any other event types
  def add_job_to_queue
  end

  # Stripe endpoint secret value
  def endpoint_secret
  end
end

Voila - that’s the Stripe checkout flow complete. From here, the Stripe dashboard offers excellent visibility over payments that have been processed, or are in progress. It also offers fantastic tools to visualise events that have been sent and the response received from your APIs.

We barely scratched the surface of what Stripe offers - Stripe has the ability to handle invoices, plans, quotes, subscriptions and more. We found that the combination of the payment intents API and their component library meant we could get this feature up and running fast, with minimal configuration.

Get in touch today, if you have an application that may need to accept credit card payments in future.

Message sent
Message could not be sent
|