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:



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

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



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

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:



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

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:



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

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:



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

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:



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

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



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

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:



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

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



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

def partial_name
  "braintree"
end

#payment_profiles_supported?Boolean

Returns:

  • (Boolean)


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

def payment_profiles_supported?
  true
end

#payment_source_classObject



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

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:



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

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



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

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



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

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:



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

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:



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

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