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.
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
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.
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.