var.rb 7.27 KB
require 'var/version'
require 'conekta'
require 'paypal-sdk-rest'

# Main Module
module Var
  # TODO: add paypal
  VALID_SERVICES = [:conekta]
  @@var_classes = []

  def self.valid_services
    VALID_SERVICES
  end

  def self.var_classes
    @@var_classes
  end

  def self.add_var_class(class_name)
    @@var_classes << class_name unless @@var_classes.include? class_name
  end

  def self.create_charge(service, object, options = {})
    return { error_message: 'Service is not supported' } unless VALID_SERVICES.include? service
    return { error_message: "#{object.class} doesn't support charges" } unless object.respond_to?(:charge_with)
    charge = object.charge_with(service, options)
    charge
  end

  def self.conekta_webhook(params)
    payment = params[:data][:object]
    time = Time.strptime(payment[:paid_at].to_s, '%s')
    object = Var.find_charge payment[:id]
    object.update({var_status: payment[:status], var_fee: payment[:fee], var_paid_amount: payment[:amount], var_payment_method: payment[:payment_method][:object])
    object.update_columns(var_payment_at: time) if object.var_payed?
    object
  rescue Exception => exception
    puts exception
    false
  end

  def self.find_charge(id)
    Rails.application.eager_load! if Rails.env.development?
    @@var_classes.map do |class_name|
      class_name.where(var_id: id)
    end.flatten.first
  end
end
# Module for models
module ActsAsChargeable
  extend ActiveSupport::Concern
  # Class Methods
  module ClassMethods
    def acts_as_chargeable(keys = {})
      include ChargeableInstanceMethods
      cattr_accessor :sync_attributes
      self.sync_attributes = keys
      Var.add_var_class(self)
    end
  end
  # Instance Methods
  module ChargeableInstanceMethods
    def charge_with(service, options)
      unless instance_support?(service)
        error_message = "#{self.class} doesn't support" \
                        " charges with #{service}"
        return { error_message: error_message }
      end
      send("charge_with_#{service}", options)
    rescue Exception => exception
      { error_message: exception.message }
    end

    def charge_with_conekta(options)
      charge = conekta_charge(options)
      update_columns(var_status: charge.status, var_id: charge.id,
                     var_service: 'conekta')
      update_conekta_barcode(charge) if options[:conekta_type] == 'oxxo'
      update_columns(var_payment_at: Time.zone.now) if var_payed?
      charge
    rescue Conekta::ParameterValidationError, Conekta::ProcessingError,
           Conekta::Error => e
      update_columns(var_status: 'failed')
      { error_message: e.message }
    end

    def conekta_charge(options)
      @charge ||= Conekta::Charge.create({
        description: sync(:conekta, 'description'),
        amount: sync(:conekta, 'amount'), currency: 'MXN',
        reference_id: sync(:conekta, 'reference_id'),
        details: {
          name: sync(:conekta, 'name'), email: sync(:conekta, 'email'),
          line_items: [{
            description: sync(:conekta, 'description'), quantity: 1,
            unit_price: sync(:conekta, 'amount'), name: sync(:conekta, 'name')
          }] }
      }.merge(conekta_type_of_charge(options)))
    end

    def conekta_type_of_charge(options)
      if options[:conekta_type] == 'card'
        { card: options[:card_token] }
      elsif options[:conekta_type] == 'oxxo'
        { cash: { type: 'oxxo',
                  expires_at: (Time.zone.today + 3.days).strftime('%Y-%m-%d') }
        }
      end
    end

    def update_conekta_barcode(charge)
      method = charge.payment_method
      update_columns(var_barcode: method.barcode,
                     var_barcode_url: method.barcode_url,
                     var_payment_expires_at: Time.at(method.expires_at))
    end

    def manual_charge
      update_columns(var_service: 'manual', var_status: 'paid',
                     var_payment_at: Time.zone.now)
      # TODO: Create a new table with transaction
    end

    def manual_discharge
      update_columns(var_service: 'manual', var_status: 'pending',
                     var_payment_at: nil)
      # TODO: Create a new table with transaction
    end

    # def charge_with_paypal(options)
    #   if(!options.include? :card)
    #     error_message = "Paypal needs a card sent as a third paramater"
    #     return { error_message: error_message}
    #   end
    #   @payment = PayPal::SDK::REST::Payment.new({
    #     intent: "sale",
    #     payer: {
    #       payer_info: {
    #         email: self.sync(:paypal, 'email')},
    #       payment_method: "credit_card",
    #       funding_instruments: [{
    #         credit_card: {
    #           type: options[:card][:type],
    #           number: options[:card][:number],
    #           expire_month: options[:card][:expire_month],
    #           expire_year: options[:card][:expire_year],
    #           cvv2: options[:card][:cvv2]}}]},
    #     transactions: [{
    #       item_list: {
    #         items: [{
    #           name: self.sync(:paypal, 'name'),
    #           sku: self.sync(:paypal, 'sku'),
    #           price: self.sync(:paypal, 'price'),
    #           currency: "MXN",
    #           quantity: 1 }]},
    #       amount: {
    #         total: self.sync(:paypal, 'price'),
    #         currency: "MXN" },
    #       description: self.sync(:paypal, 'description') }]})
    # end

    def find_charge
      return { error_message: 'Not charged yet' } unless var_service
      send("find_#{var_service}_charge")
    rescue Exception => exception
      { error_message: exception.message }
    end

    def charged?(service)
      charge = find_charge(service)
      charge.any? && !charge.include?(:error_message)
    end

    def find_conekta_charge
      Conekta::Charge.find(var_id)
    end

    def instance_support?(service)
      sync_attributes.include?(service)
    end

    def sync(service, key)
      service_attributes = send("#{service}_attributes")
      return send(key) unless service_attributes.include? key.to_sym
      send(service_attributes[key.to_sym])
    end

    def conekta_attributes
      sync_attributes[:conekta] || {}
    end

    def paypal_attributes
      sync_attributes[:paypal] || {}
    end

    def var_payed?
      var_status == 'paid'
    end

    def cancel_oxxo_payment
      return { error_message: 'Already paid' } if var_payed?
      return { error_message: 'Not charged yet' } unless var_service
      return { error_message: 'Not charged with oxxo' } unless var_barcode
      charge = find_charge
      if charge.status == 'paid'
        update_columns(var_status: 'paid',
                       var_payment_at: Time.zone.at(charge.paid_at))
        return { error_message: 'Already paid' }
      end
      clean_var_variables
    end

    def var_expired_by(time)
      return false unless var_payment_expires_at
      return var_payment_expires_at + time < Time.zone.now && !var_payed?
    end

    private

    def clean_var_variables
      if update_columns(var_status: nil, var_barcode: nil, var_barcode_url: nil,
                        var_id: nil, var_service: nil, var_payment_at: nil,
                        var_payment_expires_at: nil)
        { object: code }
      else
        { error_message: 'Something went wrong' }
      end
    end
  end
end

if defined? ActiveRecord::Base
  ActiveRecord::Base.send(:include, ActsAsChargeable)
end