Last active
May 10, 2020 10:43
-
-
Save joeljunstrom/96f212ce7240cd70d65affa32d60417f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Payment < ApplicationRecord | |
end | |
class PaymentEventMessagesController < ApplicationController | |
# The controller does as little as possible. Not ethat this should probably verify that the payload is legit somehow | |
# We offload the heavy lifting to a job to we answer the gateway asap and not bog down our servers | |
def create | |
message = PaymentEventMessage.create!(gateway: gateway, payload: params[:payload]) | |
ProcessPaymentEventMessageJob.perform_later(message.id) | |
head :ok | |
end | |
def gateway | |
raise ArgumentError, "unknown gateway #{params[:gateway]}" unless PaymentGateways.supported?(params[:gateway]j) | |
params[:gateway] | |
end | |
end | |
# The job can be responsible for tying togheter the pieces and call the correct objects | |
# You can also ofc push more work further down | |
class ProcessPaymentEventJob < ApplicationJob | |
def perform(payment_event_message_id) | |
message = PaymentEventMessage.find_by(id: payment_event_message_id) | |
return unless message | |
return if message.processed? | |
return if message.failed? | |
gateway = PaymentGateways.resolve(message.gateway) | |
event = gateway.interpret_message(message.payload) | |
payment = Payment.find(event.payment_id) | |
Payments.receive_event(payment, event) | |
rescue Payments::UnknownEventError => e | |
MyErrorHandlerService.report(e) | |
event.failed! | |
end | |
end | |
# A gateway is expected to return an object which signature is known and can | |
# be handled in a similar fashion regardless of which gateway was used. | |
# The job here is to convert foreign data structures to something that is owned by our system. | |
module PaymentGateways | |
GATEWAYS = {stripe: StripeGateway} | |
def self.resolve(name) | |
GATEWAYS.fetch(name).new | |
end | |
NoopEvent = Class.new do | |
def type | |
:noop | |
end | |
end | |
BillingEvent = Struct.new(:payment_id, :amount, :type, :failure) | |
class StripeGateway | |
def interpret_message(message) | |
event = Stripe::Event.construct_from(message) | |
case event.type | |
when "order.payment_succeeded" | |
BillingEvent.new(event.metadata["order_id"], event.amount, :billing_success, nil) | |
when "order.payment_failed" | |
# … | |
else | |
NoopEvent.new | |
else | |
end | |
end | |
end | |
# Could recide in app/lib/payments or something | |
# Makes changes to your system using known types of objects | |
module Payments | |
class UnknownEventError < StandardError; end | |
def self.receive_event(payment, event) | |
case event.type | |
when :noop | |
return | |
when :billing_success | |
Payments::PaymentReceived.call(payment, event.amount) | |
when :billing_failed | |
Payments::PaymentFailed.call(payment, event.failure) | |
else | |
raise UnknownEventError, "#{event.type} cannot be handled by #{self.class}" | |
end | |
event.processed! | |
end | |
# This could be where the knowledge about how an event changes our system lies | |
# Alter the db, call out to other services, log stuff etc. | |
PaymentReceived -> (payment, amount) { … } | |
PaymentFailed -> (payment, reason) { … } | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment