Class: Effective::DeluxeApi

Inherits:
Object
  • Object
show all
Defined in:
app/models/effective/deluxe_api.rb

Constant Summary collapse

SCRUB =
/[^\w\d#,\s]/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environment: nil, client_id: nil, client_secret: nil, access_token: nil, currency: nil) ⇒ DeluxeApi

Returns a new instance of DeluxeApi.



18
19
20
21
22
23
24
# File 'app/models/effective/deluxe_api.rb', line 18

def initialize(environment: nil, client_id: nil, client_secret: nil, access_token: nil, currency: nil)
  self.environment = environment || EffectiveOrders.deluxe.fetch(:environment)
  self.client_id = client_id || EffectiveOrders.deluxe.fetch(:client_id)
  self.client_secret = client_secret || EffectiveOrders.deluxe.fetch(:client_secret)
  self.access_token = access_token || EffectiveOrders.deluxe.fetch(:access_token)
  self.currency = currency || EffectiveOrders.deluxe.fetch(:currency)
end

Instance Attribute Details

#access_tokenObject

Returns the value of attribute access_token.



13
14
15
# File 'app/models/effective/deluxe_api.rb', line 13

def access_token
  @access_token
end

#client_idObject

Returns the value of attribute client_id.



11
12
13
# File 'app/models/effective/deluxe_api.rb', line 11

def client_id
  @client_id
end

#client_secretObject

Returns the value of attribute client_secret.



12
13
14
# File 'app/models/effective/deluxe_api.rb', line 12

def client_secret
  @client_secret
end

#currencyObject

Returns the value of attribute currency.



14
15
16
# File 'app/models/effective/deluxe_api.rb', line 14

def currency
  @currency
end

#environmentObject

All required



10
11
12
# File 'app/models/effective/deluxe_api.rb', line 10

def environment
  @environment
end

#purchase_responseObject

Returns the value of attribute purchase_response.



16
17
18
# File 'app/models/effective/deluxe_api.rb', line 16

def purchase_response
  @purchase_response
end

Instance Method Details

#card_info(payment_intent) ⇒ Object

Takes a payment_intent and returns the card info we can store



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/models/effective/deluxe_api.rb', line 53

def card_info(payment_intent)
  token = extract_token(payment_intent)

  # Return the authorization params merged with the card info
  last4 = token['maskedPan'].to_s.last(4)
  card = token['cardType'].to_s.downcase
  date = token['expDate']
  cvv = token['cvv']

  active_card = "**** **** **** #{last4} #{card} #{date}" if last4.present?

  { 'active_card' => active_card, 'card' => card, 'expDate' => date, 'cvv' => cvv }.compact
end

#create_payment(order, payment_intent) ⇒ Object

Create Payment



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'app/models/effective/deluxe_api.rb', line 101

def create_payment(order, payment_intent)
  response = post('/payments', params: create_payment_params(order, payment_intent))

  # Sanity check response
  raise('expected responseCode') unless response.kind_of?(Hash) && response['responseCode'].present?

  # Sanity check response approved vs authorized
  valid = [0].include?(response['responseCode'])

  # We might be approved for an amount less than the order total. Not sure what to do here
  if valid && (amountApproved = response['amountApproved']) != (amountAuthorized = order.total_to_f)
    raise("expected complete payment amountApproved #{amountApproved} to be the same as the amountAuthorized #{amountAuthorized} but it was not")
  end

  # Generate the card info we can store
  card = card_info(payment_intent)

  # Return the response merged with the card info
  response.reverse_merge(card)
end

#decode_payment_intent_payload(payload) ⇒ Object

Decode the base64 encoded JSON object into a Hash



41
42
43
44
45
46
47
48
49
50
# File 'app/models/effective/deluxe_api.rb', line 41

def decode_payment_intent_payload(payload)
  raise('expected a string') unless payload.kind_of?(String)

  payment_intent = (JSON.parse(Base64.decode64(payload)) rescue nil)

  raise('expected payment_intent to be a Hash') unless payment_intent.kind_of?(Hash)
  raise('expected a token payment') unless payment_intent['type'] == 'Token'

  payment_intent
end

#generate_payment_intent(card: nil, expiry: nil, cvv: nil, encode: false) ⇒ Object

This is only used for testing



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'app/models/effective/deluxe_api.rb', line 183

def generate_payment_intent(card: nil, expiry: nil, cvv: nil, encode: false)
  card ||= '5555 5555 5555 4444'
  expiry ||= "12/#{Time.zone.now.year - 1998}"
  cvv ||= '123'

  card_info = { expiry: expiry, cvv: cvv }
  params = { paymentMethod: { card: { card: card.gsub(" ", '') }.merge(card_info) } }

  response = post('/paymentmethods/token', params: params)

  # Like the delayed_purchase form gives us
  retval = {
    type: "Token",
    status: "success",
    data: { expDate: card_info[:expiry], cardType: 'Visa', token: response.fetch('token') }
  }

  encode ? Base64.encode64(retval.to_json) : retval
end

#health_checkObject



26
27
28
# File 'app/models/effective/deluxe_api.rb', line 26

def health_check
  get('/')
end

#healthy?Boolean

Returns:

  • (Boolean)


30
31
32
33
34
35
36
37
38
# File 'app/models/effective/deluxe_api.rb', line 30

def healthy?
  response = health_check()

  return false unless response.kind_of?(Hash)
  return false unless response['appName'].present?
  return false unless response['environment'].present?

  true
end

#paymentObject

The response from last create payment request



123
124
125
126
# File 'app/models/effective/deluxe_api.rb', line 123

def payment
  raise('expected purchase response to be present') unless purchase_response.kind_of?(Hash)
  purchase_response
end

#purchase!(order, payment_intent) ⇒ Object

After we store a payment intent we can call purchase! immediately or wait till later. This calls the /payments Create Payment endpoint Returns true when purchased. Returns false when declined. The response is stored in api.payment() after this is run



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/effective/deluxe_api.rb', line 71

def purchase!(order, payment_intent)
  payment_intent = decode_payment_intent_payload(payment_intent) if payment_intent.kind_of?(String)
  raise('expected payment_intent to be a Hash') unless payment_intent.kind_of?(Hash)
  raise('expected a token payment') unless payment_intent['type'] == 'Token'

  # Start a purchase. Which is an Authorization and a Completion
  self.purchase_response = nil
  payment = create_payment(order, payment_intent)
  self.purchase_response = payment

  # Validate
  valid = [0].include?(payment['responseCode'])
  return false unless valid

  # Valid purchase. This is authorized and completed.
  true
end

#purchase_delayed_orders!(orders) ⇒ Object

Called by rake task



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'app/models/effective/deluxe_api.rb', line 129

def purchase_delayed_orders!(orders)
  now = Time.zone.now

  Array(orders).each do |order|
    puts "Trying order #{order.id}"

    begin
      raise('expected a delayed order') unless order.delayed?
      raise('expected a deferred order') unless order.deferred?
      raise('expected delayed payment intent') unless order.delayed_payment_intent.present?
      raise('expected a delayed_ready_to_purchase? order') unless order.delayed_ready_to_purchase?

      order.update_columns(delayed_payment_purchase_ran_at: now, delayed_payment_purchase_result: nil)

      purchased = if order.total.to_i > 0
        purchase!(order, order.delayed_payment_intent)
      elsif order.free?
        purchase_free!(order)
      else
        raise("Unexpected order amount: #{order.total}")
      end

      provider = (order.free? ? 'free' : order.payment_provider)
      payment = self.payment()
      card = payment["card"] || payment[:card]

      if purchased
        order.assign_attributes(delayed_payment_purchase_result: "success")
        order.purchase!(payment: payment, provider: provider, card: card, email: true, skip_buyer_validations: true)

        puts "Successfully purchased order #{order.id}"
      else
        order.assign_attributes(delayed_payment_purchase_result: "failed with message: #{Array(payment['responseMessage']).to_sentence.presence || 'none'}")
        order.decline!(payment: payment, provider: provider, card: card, email: true)

        puts "Failed to purchase order #{order.id} #{order.delayed_payment_purchase_result}"
      end

    rescue => e
      order.update_columns(delayed_payment_purchase_ran_at: now, delayed_payment_purchase_result: "error: #{e.message}")

      EffectiveLogger.error(e.message, associated: order) if defined?(EffectiveLogger)
      ExceptionNotifier.notify_exception(e, data: { order_id: order.id }) if defined?(ExceptionNotifier)

      puts "Error purchasing #{order.id}: #{e.message}"

      raise(e) if Rails.env.development? || Rails.env.test?
    end
  end

  true
end

#purchase_free!(order) ⇒ Object



89
90
91
92
93
94
95
96
97
98
# File 'app/models/effective/deluxe_api.rb', line 89

def purchase_free!(order)
  raise('expected a free order') unless order.free?

  self.purchase_response = nil
  payment = { card: "none", details: "free order. no payment required."}
  self.purchase_response = payment

  # Free is always valid
  true
end