Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dealing with payments

Dealing with payments

When things go right and our product starts making money everyone is happy, but sometimes this means the start of the nightmare for people working with payments.

Let's not sugar coat it. In this talk you'll learn about some where thing went terribly wrong, some of them involved loosing money. Stories of stuff that can easily get overlooked, about the most common mistakes when working with payments and things you probably won't consider until shit hits the fan. All of this so that you don't run into the same problems and don't have to learn those lessons the hard way.

Presented at: Rails Pacific 2016

Sebastian Sogamoso

May 19, 2016
Tweet

More Decks by Sebastian Sogamoso

Other Decks in Programming

Transcript

  1. class Transfer def create(user, trip) @response = PaymentGatewayClient.create_transfer( amount: trip.price,

    currency: user.currency, destination: user.account, description: trip.to_s ) end def successful? @response.status_code == "201" end end
  2. class Transfer def create(user, trip) @response = PaymentGatewayClient.create_transfer( amount: trip.price,

    currency: user.currency, destination: user.account, description: trip.to_s ) end def successful? @response.status_code == "201" end end
  3. class Transfer def create(user, trip) @response = PaymentGatewayClient.create_transfer( amount: trip.price,

    currency: user.currency, destination: user.account, description: trip.to_s ) end def successful? @response.status_code == "201" end end
  4. class Transfer def create(user, trip) @response = PaymentGatewayClient.create_transfer( amount: trip.price,

    currency: user.currency, destination: user.account, description: trip.to_s ) end def successful? @response.status_code == "201" end end user = User.last trip = User.trips.last transfer = Transfer.new.create(user, trip)
  5. class Transfer def create(user, trip) @response = PaymentGatewayClient.create_transfer( amount: trip.price,

    currency: user.currency, destination: user.account, description: trip.to_s ) end def successful? @response.status_code == "201" end end user = User.last trip = User.trips.last transfer = Transfer.new.create(user, trip) transfer.successful? # Not correct
  6. # Not idempotent def charge_not_idempotent(invoice) Stripe::Charge.create( amount: invoice.total, currency: invoice.currency,

    source: invoice.user.credit_card, description: invoice.to_s ) end # Idempotent def charge_idempotent(invoice) if invoice.charged? Stripe::Charge.retrieve(invoice.charge_id) else Stripe::Charge.create( amount: invoice.total, currency: invoice.currency, source: invoice.user.credit_card, description: invoice.to_s ) end end
  7. # Not idempotent def charge_not_idempotent(invoice) Stripe::Charge.create( amount: invoice.total, currency: invoice.currency,

    source: invoice.user.credit_card, description: invoice.to_s ) end # Idempotent def charge_idempotent(invoice) if invoice.charged? Stripe::Charge.retrieve(invoice.charge_id) else Stripe::Charge.create( amount: invoice.total, currency: invoice.currency, source: invoice.user.credit_card, description: invoice.to_s ) end end
  8. # Not idempotent def charge_not_idempotent(invoice) Stripe::Charge.create( amount: invoice.total, currency: invoice.currency,

    source: invoice.user.credit_card, description: invoice.to_s ) end # Idempotent def charge_idempotent(invoice) if invoice.charged? Stripe::Charge.retrieve(invoice.charge_id) else Stripe::Charge.create( amount: invoice.total, currency: invoice.currency, source: invoice.user.credit_card, description: invoice.to_s ) end end
  9. invoice = Invoice.last # It will charge the invoice 10

    times 10.times do charge_not_idempotent(invoice) end # It will charge the invoice 1 time 10.times do charge_idempotent(invoice) end
  10. • Hard to know which code led a state change

    • Previous data might be overwritten • Relevant code might not exist
  11. class Charge < ActiveRecord::Base belongs_to :user has_many :events, class_name: "ChargeEvent"

    end class ChargeEvent < ActiveRecord::Base EVENT_TYPES = %w(creation success failure refund) belongs_to :charge validates :charge, :data, presence: true validates :event_type, inclusion: { in: EVENT_TYPES} end
  12. class Charge < ActiveRecord::Base belongs_to :user has_many :events, class_name: "ChargeEvent"

    end class ChargeEvent < ActiveRecord::Base EVENT_TYPES = %w(creation success failure refund) belongs_to :charge validates :charge, :data, presence: true validates :event_type, inclusion: { in: EVENT_TYPES} end
  13. class Order < ActiveRecord::Base attr_reader :user, :items, :total def charge

    ActiveRecord.transation do charge = Charge.create!(user: user, total: total) charge.events.create!(data: data, event_type: "creation") self.update!(charge: charge) end end private def data {items: items.inspect, total: total, user: user} end end
  14. class Order < ActiveRecord::Base attr_reader :user, :items, :total def charge

    ActiveRecord.transation do charge = Charge.create!(user: user, total: total) charge.events.create!(data: data, event_type: "creation") self.update!(charge: charge) end end private def data {items: items.inspect, total: total, user: user} end end
  15. class Order < ActiveRecord::Base attr_reader :user, :items, :total def charge

    ActiveRecord.transation do charge = Charge.create!(user: user, total: total) charge.events.create!(data: data, event_type: "creation") self.update!(charge: charge) end end private def data {items: items.inspect, total: total, user: user} end end
  16. class PaymentGateway def initialize(client) @client = client end def charge(order)

    charge = order.charge response = @client.create_charge(order.charge) transaction_id = response.transaction_id if response.successful? charge.update!(transaction_id: response.transaction_id) charge.events.create!(data: transaction_id, event_type: "success") else charge.update!(transaction_id: response.transaction_id) charge.events.create!(data: transaction_id, event_type: "failure") end end end
  17. class PaymentGateway def initialize(client) @client = client end def charge(order)

    charge = order.charge response = @client.create_charge(order.charge) transaction_id = response.transaction_id if response.successful? charge.update!(transaction_id: response.transaction_id) charge.events.create!(data: transaction_id, event_type: "success") else charge.update!(transaction_id: response.transaction_id) charge.events.create!(data: transaction_id, event_type: "failure") end end end
  18. class PaymentGateway def initialize(client) @client = client end def charge(order)

    charge = order.charge response = @client.create_charge(order.charge) transaction_id = response.transaction_id if response.successful? charge.update!(transaction_id: response.transaction_id) charge.events.create!(data: transaction_id, event_type: "success") else charge.update!(transaction_id: response.transaction_id) charge.events.create!(data: transaction_id, event_type: "failure") end end end
  19. 5

  20. class ChargesController < ApplicationController def create @charge = current_user.charges.build.new(charge_params) if

    charge_user(@charge) redirect_to :charge_sucess else render :cart end end ...
  21. class ChargesController < ApplicationController def create @charge = current_user.charges.build.new(charge_params) if

    charge_user(@charge) redirect_to :charge_sucess else render :cart end end ...
  22. def charge_user(charge) result = Stripe::Charge.create( amount: charge.amount, currency: charge.currency, source:

    charge.credit_card_token, description: charge.product_name ) charge.status = result.status charge.save result.status == "succeeded" end
  23. def charge_user(charge) result = Stripe::Charge.create( amount: charge.amount, currency: charge.currency, source:

    charge.credit_card_token, description: charge.product_name ) charge.status = result.status charge.save result.status == "succeeded" end
  24. def charge_user(charge) result = Stripe::Charge.create( amount: charge.amount, currency: charge.currency, source:

    charge.credit_card_token, description: charge.product_name ) charge.status = result.status charge.save result.status == "succeeded" end
  25. class ChargeWorker def perform(charge_id) charge = ::Charge.find(charge_id) Stripe::Charge.create( amount: charge.amount,

    currency: charge.currency, source: charge.credit_card_token, description: charge.product_name ) end end
  26. class ChargeWorker include Sidekiq::Worker def perform(charge_id) charge = ::Charge.find(charge_id) Stripe::Charge.create(

    amount: charge.amount, currency: charge.currency, source: charge.credit_card_token, description: charge.product_name ) end end
  27. class ChargeWorker include Sidekiq::Worker def perform(charge_id) charge = ::Charge.find(charge_id) Stripe::Charge.create(

    amount: charge.amount, currency: charge.currency, source: charge.credit_card_token, description: charge.product_name ) end end
  28. class ChargeWorker include Sidekiq::Worker sidekiq_options retry: 3 def perform(charge_id) charge

    = ::Charge.find(charge_id) Stripe::Charge.create( amount: charge.amount, currency: charge.currency, source: charge.credit_card_token, description: charge.product_name ) end end
  29. class CreditCardsController < ApplicationController def destroy @credit_card = current_user.credit_cards.find(params[:id]) if

    current_user.charge_pending_trips @credit_card.destroy redirect_to credit_cards_url, notice: "Credit card deleted" else redirect_to @credit_card, notice: “Settlement failed" end end end
  30. class CreditCardsController < ApplicationController def destroy @credit_card = current_user.credit_cards.find(params[:id]) if

    current_user.charge_pending_trips @credit_card.destroy redirect_to credit_cards_url, notice: "Credit card deleted" else redirect_to @credit_card, notice: "Settlement failed" end end end
  31. class CreditCardsController < ApplicationController def destroy @credit_card = current_user.credit_cards.find(params[:id]) if

    current_user.charge_pending_trips @credit_card.destroy redirect_to credit_cards_url, notice: "Credit card deleted" else redirect_to @credit_card, notice: "Settlement failed" end end end
  32. class Charge def initialize(payment_gateway) @payment_gateway = payment_gateway end def create(user:,

    amount:) @payment_gateway.new(user) @payment_gateway.charge(amount) end end
  33. class PaymentGatewayOne def initialize(customer) @customer = customer end def charge

    ... end end class PaymentGatewayTwo def initialize(user) @user = user end def charge ... end end
  34. class SimulationPaymentGateway def initialize(user) @user = user end def charge

    Rails.logger.debug(“Charging $#{amount} to user ##{@user.id}”) end end
  35. class SimulationPaymentGateway def initialize(user) @user = user end def charge

    Rails.logger.debug(“Charging $#{amount} to user ##{@user.id}”) end end