Skip to content

Instantly share code, notes, and snippets.

@nightcrawler-
Created January 16, 2022 09:59
Show Gist options
  • Save nightcrawler-/97cac5dc0241dcd70ac5266594bef9c3 to your computer and use it in GitHub Desktop.
Save nightcrawler-/97cac5dc0241dcd70ac5266594bef9c3 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
# == Schema Information
#
# Table name: contributions
#
# id :bigint not null, primary key
# aasm_state :string
# amount :integer default(0)
# error_message :string
# message :text
# mpesa_fees :decimal(, ) default(0.0)
# phone_number :string
# platform_fees :decimal(, ) default(0.0)
# source :string
# created_at :datetime not null
# updated_at :datetime not null
# campaign_id :bigint
# contributor_id :bigint
#
class Contribution < ApplicationRecord
include AASM
include PhoneNumberSanitizer
scope :completed, -> { where(aasm_state: :completed) }
FIXED_PLATFORM_FEES_RATE = 0.05
#################### Enums ##############################
#################### Associations #######################
belongs_to :campaign
belongs_to :contributor, class_name: :User
has_one :payment_online_payment_request, class_name: 'Payment::OnlinePaymentRequest'
has_one :payment_paybill_confirmation, class_name: 'Payment::PaybillConfirmation'
#################### Validations ########################
validates :amount, :source, presence: true
validates :amount, numericality: { greater_than_or_equal_to: 1, only_integer: true }
validates :phone_number, telephone_number: { country: proc { |_record| 'KE' }, types: %i[fixed_line mobile] }
#################### Callbacks ##########################
after_create :process_contribution
#################### AASM State Machine #########################
aasm do
state :pending, initial: true
state :processing
state :completed
state :failed
event :process, after: :send_payment_request do
transitions from: :pending, to: :processing
end
event :complete, after: :send_notifications do # Sending notifications after transition and charges applied, resource saved.
transitions from: :processing, to: :completed, after: :apply_charges # Apply charges doesnt need to call save, it will be done after this transition before persisting the state
end
event :failure do
transitions from: :processing, to: :failed, after: proc { |args| apply_failure_reason(args) }
end
after_all_transitions :log_status_change
end
#################### Object methods### ##################
# AASM Callbacks
def apply_charges
rate = CalistoDefault.first&.platform_fees_rate
rate ||= FIXED_PLATFORM_FEES_RATE
self.platform_fees = amount * rate
self.mpesa_fees = MpesaCharge.new(amount, campaign.fees_regime).call
self
end
def send_notifications
puts 'Sending contribution complete notifications'
UserMailer.send_donation_processed(self).deliver_later
UserMailer.send_donation_received(self).deliver_later
end
# dont forget to call comlete on the contribution!
def self.from_paybill_confirmation(params)
# Only create if corresponding campaign exists
# Create user from phone number
# Apply campaign and user to contribution
# Will include user name
campaign = Campaign.find_by(account_number: params['bill_ref_number'])
if campaign.present?
user = User.from_phone_number({ phone_number: params['msisdn'], name: "#{params['first_name']} #{params['middle_name']} #{params['last_name']}" })
contribution = Contribution.create!(
campaign: campaign,
contributor: user,
phone_number: params['msisdn'],
amount: params['trans_amount'].to_i,
source: 'mpesa-paybill',
aasm_state: 'processing' # Doing this so that on save doesnt trigger a callback to request online payment
)
contribution.complete!
contribution
end
end
private
def process_contribution
ContributionWorker.perform_async({ phone: phone_number, amount: amount, id: id })
end
######################### AASM State change callbacks #################
def apply_failure_reason(message)
puts "That failed: #{message}"
self.error_message = message
save
end
def send_payment_request
# Guess this is where the online payment happens? ama?
puts "Sending payment request...#{inspect}"
end
def log_status_change
puts "Changed from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment