Class: SolidusBraintree::Gateway

Inherits:
Spree::PaymentMethod
  • Object
show all
Includes:
RequestProtection
Defined in:
app/models/solidus_braintree/gateway.rb

Defined Under Namespace

Classes: TokenGenerationDisabledError

Constant Summary collapse

NON_VOIDABLE_STATUS_ERROR_REGEXP =

Error message from Braintree that gets returned by a non voidable transaction

/can only be voided if status is authorized/
TOKEN_GENERATION_DISABLED_MESSAGE =
'Token generation is disabled. ' \
'To re-enable set the `token_generation_enabled` preference on the ' \
'gateway to `true`.'
ALLOWED_BRAINTREE_OPTIONS =
[
  :device_data,
  :device_session_id,
  :merchant_account_id,
  :order_id
].freeze
VOIDABLE_STATUSES =
[
  Braintree::Transaction::Status::SubmittedForSettlement,
  Braintree::Transaction::Status::SettlementPending,
  Braintree::Transaction::Status::Authorized
].freeze

Instance Method Summary collapse

Methods included from RequestProtection

#protected_request

Instance Method Details

#authorize(money_cents, source, gateway_options) ⇒ Response

Authorize a payment to be captured later.

Parameters:

  • money_cents (Number, String)

    amount to authorize

  • source (Source)

    payment source

Returns:



125
126
127
128
129
130
131
132
133
134
# File 'app/models/solidus_braintree/gateway.rb', line 125

def authorize(money_cents, source, gateway_options)
  protected_request do
    result = braintree.transaction.sale(
      amount: dollars(money_cents),
      **transaction_options(source, gateway_options)
    )

    Response.build(result)
  end
end

#braintreeObject



82
83
84
# File 'app/models/solidus_braintree/gateway.rb', line 82

def braintree
  @braintree ||= Braintree::Gateway.new(gateway_options)
end

#cancel(response_code) ⇒ Response

Will either refund or void the payment depending on its state.

If the transaction has not yet been settled, we can void the transaction. Otherwise, we need to issue a refund.

Parameters:

  • response_code (String)

    the transaction id of the payment to void

Returns:



189
190
191
192
193
194
195
196
197
198
# File 'app/models/solidus_braintree/gateway.rb', line 189

def cancel(response_code)
  transaction = protected_request do
    braintree.transaction.find(response_code)
  end
  if VOIDABLE_STATUSES.include?(transaction.status)
    void(response_code, nil, {})
  else
    credit(cents(transaction.amount), nil, response_code, {})
  end
end

#capture(money_cents, response_code, _gateway_options) ⇒ Response

Collect funds from an authorized payment.

Parameters:

  • money_cents (Number, String)

    amount to capture (partial settlements are supported by the gateway)

  • response_code (String)

    the transaction id of the payment to capture

Returns:



143
144
145
146
147
148
149
150
151
# File 'app/models/solidus_braintree/gateway.rb', line 143

def capture(money_cents, response_code, _gateway_options)
  protected_request do
    result = braintree.transaction.submit_for_settlement(
      response_code,
      dollars(money_cents)
    )
    Response.build(result)
  end
end

#create_profile(payment) ⇒ SolidusBraintree::Customer

Creates a new customer profile in Braintree

Parameters:

  • payment (Spree::Payment)

Returns:



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'app/models/solidus_braintree/gateway.rb', line 231

def create_profile(payment)
  source = payment.source

  return if source.token.present? || source.customer.present? || source.nonce.nil?

  result = braintree.customer.create(customer_profile_params(payment))
  fail ::Spree::Core::GatewayError, result.message unless result.success?

  customer = result.customer

  source.create_customer!(braintree_customer_id: customer.id).tap do
    if customer.payment_methods.any?
      source.token = customer.payment_methods.last.token
    end

    source.save!
  end
end

#credit(money_cents, _source, response_code, _gateway_options) ⇒ Response

Used to refeund a customer for an already settled transaction.

Parameters:

  • money_cents (Number, String)

    amount to refund

  • response_code (String)

    the transaction id of the payment to refund

Returns:



159
160
161
162
163
164
165
166
167
# File 'app/models/solidus_braintree/gateway.rb', line 159

def credit(money_cents, _source, response_code, _gateway_options)
  protected_request do
    result = braintree.transaction.refund(
      response_code,
      dollars(money_cents)
    )
    Response.build(result)
  end
end

#gateway_optionsObject



86
87
88
89
90
91
92
93
94
95
96
# File 'app/models/solidus_braintree/gateway.rb', line 86

def gateway_options
  {
    environment: preferred_environment.to_sym,
    merchant_id: preferred_merchant_id,
    public_key: preferred_public_key,
    private_key: preferred_private_key,
    http_open_timeout: preferred_http_open_timeout,
    http_read_timeout: preferred_http_read_timeout,
    logger: logger
  }
end

#generate_tokenString

Returns The token that should be used along with the Braintree js-client sdk.

Examples:

<script>
  var token = #{Spree::Braintree::Gateway.first!.generate_token}

  braintree.client.create(
    {
      authorization: token
    },
    function(clientError, clientInstance) {
      ...
    }
  );
</script>

Returns:

  • (String)

    The token that should be used along with the Braintree js-client sdk.

Raises:



269
270
271
272
273
274
275
# File 'app/models/solidus_braintree/gateway.rb', line 269

def generate_token
  unless preferred_token_generation_enabled
    raise TokenGenerationDisabledError, TOKEN_GENERATION_DISABLED_MESSAGE
  end

  braintree.client_token.generate
end

#partial_nameObject Also known as: method_type



73
74
75
# File 'app/models/solidus_braintree/gateway.rb', line 73

def partial_name
  "braintree"
end

#payment_profiles_supported?Boolean

Returns:

  • (Boolean)


277
278
279
# File 'app/models/solidus_braintree/gateway.rb', line 277

def payment_profiles_supported?
  true
end

#payment_source_classObject



78
79
80
# File 'app/models/solidus_braintree/gateway.rb', line 78

def payment_source_class
  Source
end

#purchase(money_cents, source, gateway_options) ⇒ Response

Create a payment and submit it for settlement all at once.

Parameters:

  • money_cents (Number, String)

    amount to authorize

  • source (Source)

    payment source

Returns:



106
107
108
109
110
111
112
113
114
115
# File 'app/models/solidus_braintree/gateway.rb', line 106

def purchase(money_cents, source, gateway_options)
  protected_request do
    result = braintree.transaction.sale(
      amount: dollars(money_cents),
      **transaction_options(source, gateway_options, submit_for_settlement: true)
    )

    Response.build(result)
  end
end

#reusable_sources(order) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
# File 'app/models/solidus_braintree/gateway.rb', line 286

def reusable_sources(order)
  if order.completed?
    sources_by_order(order)
  elsif order.user_id
    payment_source_class.where(
      payment_method_id: id,
      user_id: order.user_id
    ).with_payment_profile
  else
    []
  end
end

#sources_by_order(order) ⇒ Object



281
282
283
284
# File 'app/models/solidus_braintree/gateway.rb', line 281

def sources_by_order(order)
  source_ids = order.payments.where(payment_method_id: id).pluck(:source_id).uniq
  payment_source_class.where(id: source_ids).with_payment_profile
end

#try_void(payment) ⇒ Response|FalseClass

Will void the payment depending on its state or return false

Used by Solidus >= 2.4 instead of cancel

If the transaction has not yet been settled, we can void the transaction. Otherwise, we return false so Solidus creates a refund instead.

Parameters:

  • payment (Spree::Payment)

    the payment to void

Returns:



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'app/models/solidus_braintree/gateway.rb', line 210

def try_void(payment)
  transaction = braintree.transaction.find(payment.response_code)
  if transaction.status.in? SolidusBraintree::Gateway::VOIDABLE_STATUSES
    # Sometimes Braintree returns a voidable status although it is not voidable anymore.
    # When we try to void that transaction we receive an error and need to return false
    # so Solidus can create a refund instead.
    begin
      void(payment.response_code, nil, {})
    rescue ActiveMerchant::ConnectionError => e
      e.message.match(NON_VOIDABLE_STATUS_ERROR_REGEXP) ? false : raise(e)
    end
  else
    false
  end
end

#void(response_code, _source, _gateway_options) ⇒ Response

Used to cancel a transaction before it is settled.

Parameters:

  • response_code (String)

    the transaction id of the payment to void

Returns:



174
175
176
177
178
179
# File 'app/models/solidus_braintree/gateway.rb', line 174

def void(response_code, _source, _gateway_options)
  protected_request do
    result = braintree.transaction.void(response_code)
    Response.build(result)
  end
end