solidus_paypal_braintree 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +196 -0
- data/Rakefile +30 -0
- data/app/assets/javascripts/spree/backend/solidus_paypal_braintree.js +66 -0
- data/app/assets/javascripts/spree/braintree_hosted_form.js +98 -0
- data/app/assets/javascripts/spree/checkout/braintree.js +60 -0
- data/app/assets/javascripts/spree/frontend/paypal_button.js +182 -0
- data/app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js +188 -0
- data/app/assets/stylesheets/spree/backend/solidus_paypal_braintree.scss +28 -0
- data/app/assets/stylesheets/spree/frontend/solidus_paypal_braintree.css +16 -0
- data/app/controllers/solidus_paypal_braintree/client_tokens_controller.rb +21 -0
- data/app/helpers/braintree_admin_helper.rb +18 -0
- data/app/models/application_record.rb +3 -0
- data/app/models/solidus_paypal_braintree/configuration.rb +5 -0
- data/app/models/solidus_paypal_braintree/customer.rb +4 -0
- data/app/models/solidus_paypal_braintree/gateway.rb +323 -0
- data/app/models/solidus_paypal_braintree/response.rb +52 -0
- data/app/models/solidus_paypal_braintree/source.rb +73 -0
- data/app/models/solidus_paypal_braintree/transaction.rb +30 -0
- data/app/models/solidus_paypal_braintree/transaction_address.rb +66 -0
- data/app/models/solidus_paypal_braintree/transaction_import.rb +92 -0
- data/app/models/spree/store_decorator.rb +11 -0
- data/app/overrides/admin_navigation_menu.rb +6 -0
- data/app/views/spree/shared/_braintree_hosted_fields.html.erb +26 -0
- data/config/initializers/braintree.rb +1 -0
- data/config/locales/en.yml +30 -0
- data/config/routes.rb +12 -0
- data/db/migrate/20160830061749_create_solidus_paypal_braintree_sources.rb +16 -0
- data/db/migrate/20160906201711_create_solidus_paypal_braintree_customers.rb +11 -0
- data/db/migrate/20161114231422_create_solidus_paypal_braintree_configurations.rb +11 -0
- data/db/migrate/20161125172005_add_braintree_configuration_to_stores.rb +9 -0
- data/db/migrate/20170203191030_add_credit_card_to_braintree_configuration.rb +6 -0
- data/db/migrate/20170505193712_add_null_constraint_to_sources.rb +30 -0
- data/db/migrate/20170508085402_add_not_null_constraint_to_sources_payment_type.rb +11 -0
- data/lib/controllers/backend/solidus_paypal_braintree/configurations_controller.rb +30 -0
- data/lib/controllers/frontend/solidus_paypal_braintree/checkouts_controller.rb +27 -0
- data/lib/controllers/frontend/solidus_paypal_braintree/transactions_controller.rb +61 -0
- data/lib/generators/solidus_paypal_braintree/install/install_generator.rb +37 -0
- data/lib/solidus_paypal_braintree.rb +10 -0
- data/lib/solidus_paypal_braintree/country_mapper.rb +35 -0
- data/lib/solidus_paypal_braintree/engine.rb +53 -0
- data/lib/solidus_paypal_braintree/factories.rb +18 -0
- data/lib/solidus_paypal_braintree/version.rb +3 -0
- data/lib/views/backend/solidus_paypal_braintree/configurations/_admin_tab.html.erb +3 -0
- data/lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb +30 -0
- data/lib/views/backend/spree/admin/payments/source_forms/_paypal_braintree.html.erb +16 -0
- data/lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb +34 -0
- data/lib/views/backend_v1.2/spree/admin/payments/source_forms/_paypal_braintree.html.erb +16 -0
- data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +52 -0
- metadata +350 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module SolidusPaypalBraintree
|
4
|
+
class Transaction
|
5
|
+
include ActiveModel::Model
|
6
|
+
|
7
|
+
attr_accessor :nonce, :payment_method, :payment_type, :address, :email, :phone
|
8
|
+
|
9
|
+
validates :nonce, presence: true
|
10
|
+
validates :payment_method, presence: true
|
11
|
+
validates :payment_type, presence: true
|
12
|
+
validates :phone, presence: true
|
13
|
+
validates :email, presence: true
|
14
|
+
|
15
|
+
validate do
|
16
|
+
unless payment_method.is_a? SolidusPaypalBraintree::Gateway
|
17
|
+
errors.add(:payment_method, 'Must be braintree')
|
18
|
+
end
|
19
|
+
if address && !address.valid?
|
20
|
+
address.errors.each do |field, error|
|
21
|
+
errors.add(:address, "#{field} #{error}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def address_attributes=(attributes)
|
27
|
+
self.address = TransactionAddress.new attributes
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module SolidusPaypalBraintree
|
4
|
+
class TransactionAddress
|
5
|
+
include ActiveModel::Model
|
6
|
+
include ActiveModel::Validations::Callbacks
|
7
|
+
include SolidusPaypalBraintree::CountryMapper
|
8
|
+
|
9
|
+
attr_accessor :country_code, :last_name, :first_name,
|
10
|
+
:city, :zip, :state_code, :address_line_1, :address_line_2
|
11
|
+
|
12
|
+
validates :first_name, :last_name, :address_line_1, :city, :zip,
|
13
|
+
:state_code, :country_code, presence: true
|
14
|
+
|
15
|
+
before_validation do
|
16
|
+
self.country_code = country_code.presence || "us"
|
17
|
+
end
|
18
|
+
|
19
|
+
validates :spree_country, presence: true
|
20
|
+
validates :spree_state, presence: true, if: :should_match_state_model?
|
21
|
+
|
22
|
+
def initialize(attributes = {})
|
23
|
+
country_name = attributes.delete(:country_name) || ""
|
24
|
+
if attributes[:country_code].blank?
|
25
|
+
attributes[:country_code] = iso_from_name(country_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
super(attributes)
|
29
|
+
end
|
30
|
+
|
31
|
+
def spree_country
|
32
|
+
country_code && (@country ||= Spree::Country.find_by(iso: country_code.upcase))
|
33
|
+
end
|
34
|
+
|
35
|
+
def spree_state
|
36
|
+
spree_country && state_code && ( @state ||= spree_country.states.where(
|
37
|
+
Spree::State.arel_table[:name].matches(state_code).or(
|
38
|
+
Spree::State.arel_table[:abbr].matches(state_code)
|
39
|
+
)
|
40
|
+
).first )
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_spree_address
|
44
|
+
address = Spree::Address.new first_name: first_name,
|
45
|
+
last_name: last_name,
|
46
|
+
city: city,
|
47
|
+
country: spree_country,
|
48
|
+
address1: address_line_1,
|
49
|
+
address2: address_line_2,
|
50
|
+
zipcode: zip
|
51
|
+
|
52
|
+
if spree_state
|
53
|
+
address.state = spree_state
|
54
|
+
else
|
55
|
+
address.state_name = state_code
|
56
|
+
end
|
57
|
+
address
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check to see if this address should match to a state
|
61
|
+
# model in the database.
|
62
|
+
def should_match_state_model?
|
63
|
+
spree_country.present? && spree_country.states.any?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module SolidusPaypalBraintree
|
4
|
+
class TransactionImport
|
5
|
+
class InvalidImportError < StandardError; end
|
6
|
+
|
7
|
+
include ActiveModel::Model
|
8
|
+
|
9
|
+
validate do
|
10
|
+
errors.add("Address", "is invalid") if address && !address.valid?
|
11
|
+
|
12
|
+
if !transaction.valid?
|
13
|
+
transaction.errors.each do |field, error|
|
14
|
+
errors.add(field, error)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
errors.none?
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :transaction, :order
|
21
|
+
|
22
|
+
def initialize(order, transaction)
|
23
|
+
@order = order
|
24
|
+
@transaction = transaction
|
25
|
+
end
|
26
|
+
|
27
|
+
def source
|
28
|
+
SolidusPaypalBraintree::Source.new nonce: transaction.nonce,
|
29
|
+
payment_type: transaction.payment_type,
|
30
|
+
payment_method: transaction.payment_method,
|
31
|
+
user: user
|
32
|
+
end
|
33
|
+
|
34
|
+
def user
|
35
|
+
order.user
|
36
|
+
end
|
37
|
+
|
38
|
+
def import!(end_state)
|
39
|
+
if valid?
|
40
|
+
order.email = user.try!(:email) || transaction.email
|
41
|
+
|
42
|
+
if address
|
43
|
+
order.shipping_address = order.billing_address = address
|
44
|
+
# work around a bug in most solidus versions
|
45
|
+
# about tax zone cachine between address changes
|
46
|
+
order.instance_variable_set("@tax_zone", nil)
|
47
|
+
end
|
48
|
+
|
49
|
+
payment = order.payments.new source: source,
|
50
|
+
payment_method: transaction.payment_method,
|
51
|
+
amount: order.total
|
52
|
+
|
53
|
+
order.save!
|
54
|
+
advance_order(payment, end_state)
|
55
|
+
else
|
56
|
+
raise InvalidImportError,
|
57
|
+
"Validation failed: #{errors.full_messages.join(', ')}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def address
|
62
|
+
transaction.address && transaction.address.to_spree_address.tap do |address|
|
63
|
+
address.phone = transaction.phone
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def state_before_current?(state)
|
68
|
+
steps = order.checkout_steps
|
69
|
+
steps.index(state) < (steps.index(order.state) || 0)
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def advance_order(payment, end_state)
|
75
|
+
return if state_before_current?(end_state)
|
76
|
+
|
77
|
+
until order.state == end_state
|
78
|
+
order.next!
|
79
|
+
update_payment_total(payment) if order.payment?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def update_payment_total(payment)
|
84
|
+
payment_total = order.payments.where(state: %w[checkout pending]).sum(:amount)
|
85
|
+
remaining_total = order.outstanding_balance - payment_total
|
86
|
+
|
87
|
+
if remaining_total > 0
|
88
|
+
payment.update!(amount: payment.amount + remaining_total)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Spree::Store.class_eval do
|
2
|
+
has_one :braintree_configuration, class_name: "SolidusPaypalBraintree::Configuration", dependent: :destroy
|
3
|
+
|
4
|
+
before_create :build_default_configuration
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def build_default_configuration
|
9
|
+
build_braintree_configuration
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
Deface::Override.new(
|
2
|
+
virtual_path: "spree/admin/shared/_settings_sub_menu",
|
3
|
+
name: "solidus_paypal_braintree_admin_navigation_configuration",
|
4
|
+
insert_bottom: "[data-hook='admin_settings_sub_tabs']",
|
5
|
+
partial: "solidus_paypal_braintree/configurations/admin_tab"
|
6
|
+
)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<% prefix = "payment_source[#{id}]" %>
|
2
|
+
|
3
|
+
<div class="hosted-fields">
|
4
|
+
<div class="field" data-hook="card_number">
|
5
|
+
<%= label_tag "card_number#{id}", Spree::CreditCard.human_attribute_name(:number), class: "required" %>
|
6
|
+
<div class="input" id="card_number<%= id %>"></div>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="field" data-hook="card_expiration">
|
10
|
+
<%= label_tag "card_expiry#{id}", Spree::CreditCard.human_attribute_name(:expiration), class: "required" %>
|
11
|
+
<div class="input" id="card_expiry<%= id %>"></div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="field" data-hook="card_code">
|
15
|
+
<%= label_tag "card_code#{id}", Spree::CreditCard.human_attribute_name(:card_code), class: "required" %>
|
16
|
+
<div class="input" id="card_code<%= id %>"></div>
|
17
|
+
|
18
|
+
<a href="/content/cvv" class="info cvvLink" target="_blank">
|
19
|
+
(<%= Spree.t(:what_is_this) %>)
|
20
|
+
</a>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<div class="clear"></div>
|
24
|
+
<input type="hidden" name="<%= prefix %>[payment_type]" value="<%= SolidusPaypalBraintree::Source::CREDIT_CARD %>">
|
25
|
+
<input type="hidden" id="payment_method_nonce" name="<%= prefix %>[nonce]">
|
26
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
Spree::Admin::PaymentsController.helper :braintree_admin
|
@@ -0,0 +1,30 @@
|
|
1
|
+
en:
|
2
|
+
activerecord:
|
3
|
+
models:
|
4
|
+
solidus_paypal_braintree/gateway: Braintree
|
5
|
+
spree:
|
6
|
+
admin:
|
7
|
+
tab:
|
8
|
+
braintree: Braintree
|
9
|
+
client_sdk_enabled: Client SDK enabled?
|
10
|
+
environment: Environment
|
11
|
+
merchant_currency_map: Merchant currency map
|
12
|
+
merchant_id: Merchant ID
|
13
|
+
paypal_payee_email_map: Paypal payee email map
|
14
|
+
private_key: Private key
|
15
|
+
public_key: Public key
|
16
|
+
token_generation_enabled: Token generation enabled?
|
17
|
+
solidus_paypal_braintree:
|
18
|
+
braintree: Braintree
|
19
|
+
nonce: Nonce
|
20
|
+
token: Token
|
21
|
+
payment_type:
|
22
|
+
label: Payment Type
|
23
|
+
apple_pay_card: Apple Pay
|
24
|
+
credit_card: Credit Card
|
25
|
+
pay_pal_account: PayPal
|
26
|
+
configurations:
|
27
|
+
title: Braintree Configurations
|
28
|
+
tab: Braintree
|
29
|
+
update_success: Successfully updated Braintree configurations.
|
30
|
+
update_error: An error occurred while updating Braintree configurations.
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
SolidusPaypalBraintree::Engine.routes.draw do
|
2
|
+
resource :checkout, only: [:update, :edit]
|
3
|
+
resource :client_token, only: [:create], format: :json
|
4
|
+
resource :transactions, only: [:create]
|
5
|
+
|
6
|
+
resources :configurations do
|
7
|
+
collection do
|
8
|
+
get :list
|
9
|
+
post :update
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateSolidusPaypalBraintreeSources < SolidusSupport::Migration[4.2]
|
2
|
+
def change
|
3
|
+
create_table :solidus_paypal_braintree_sources do |t|
|
4
|
+
t.string :nonce
|
5
|
+
t.string :token
|
6
|
+
t.string :payment_type
|
7
|
+
t.integer :user_id, index: true
|
8
|
+
t.references :customer, index: true
|
9
|
+
t.references :payment_method, index: true
|
10
|
+
|
11
|
+
t.timestamps null: false
|
12
|
+
end
|
13
|
+
|
14
|
+
add_foreign_key :solidus_paypal_braintree_sources, :spree_payment_methods, column: :payment_method_id
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateSolidusPaypalBraintreeCustomers < SolidusSupport::Migration[4.2]
|
2
|
+
def change
|
3
|
+
create_table :solidus_paypal_braintree_customers do |t|
|
4
|
+
t.references :user
|
5
|
+
t.string :braintree_customer_id
|
6
|
+
end
|
7
|
+
|
8
|
+
add_index :solidus_paypal_braintree_customers, :user_id, unique: true, name: "index_braintree_customers_on_user_id"
|
9
|
+
add_index :solidus_paypal_braintree_customers, :braintree_customer_id, unique: true, name: "index_braintree_customers_on_braintree_customer_id"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateSolidusPaypalBraintreeConfigurations < SolidusSupport::Migration[4.2]
|
2
|
+
def change
|
3
|
+
create_table :solidus_paypal_braintree_configurations do |t|
|
4
|
+
t.boolean :paypal, null: false, default: false
|
5
|
+
t.boolean :apple_pay, null: false, default: false
|
6
|
+
t.integer :store_id, null: false, index: true, foreign_key: { references: :spree_stores }
|
7
|
+
|
8
|
+
t.timestamps null: false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class AddNullConstraintToSources < SolidusSupport::Migration[4.2]
|
2
|
+
def up
|
3
|
+
payments = Spree::Payment.arel_table
|
4
|
+
sources = SolidusPaypalBraintree::Source.arel_table
|
5
|
+
join_sources = payments.join(sources).on(
|
6
|
+
payments[:source_id].eq(sources[:id]).and(
|
7
|
+
payments[:source_type].eq("SolidusPaypalBraintree::Source")
|
8
|
+
).and(
|
9
|
+
sources[:payment_method_id].eq(nil)
|
10
|
+
)
|
11
|
+
).join_sources
|
12
|
+
|
13
|
+
count = Spree::Payment.joins(join_sources).count
|
14
|
+
Rails.logger.info("Updating #{count} problematic sources")
|
15
|
+
|
16
|
+
Spree::Payment.joins(join_sources).find_each do |payment|
|
17
|
+
Rails.logger.info("Updating source #{payment.source_id} with payment method id #{payment.payment_method_id}")
|
18
|
+
SolidusPaypalBraintree::Source.where(id: payment.source_id).update_all(payment_method_id: payment.payment_method_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
# We use a foreign key constraint on the model,
|
22
|
+
# but it doesnt make sense to have this model exist without a payment method
|
23
|
+
# as two of its methods delegate to the payment method.
|
24
|
+
change_column_null(:solidus_paypal_braintree_sources, :payment_method_id, false)
|
25
|
+
end
|
26
|
+
|
27
|
+
def down
|
28
|
+
change_column_null(:solidus_paypal_braintree_sources, :payment_method_id, true)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddNotNullConstraintToSourcesPaymentType < SolidusSupport::Migration[4.2]
|
2
|
+
def change
|
3
|
+
reversible do |dir|
|
4
|
+
dir.up do
|
5
|
+
SolidusPaypalBraintree::Source.where(payment_type: nil).
|
6
|
+
update_all(payment_type: 'CreditCard')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
change_column_null(:solidus_paypal_braintree_sources, :payment_type, false)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SolidusPaypalBraintree
|
2
|
+
class ConfigurationsController < Spree::Admin::BaseController
|
3
|
+
helper Spree::Core::Engine.routes.url_helpers
|
4
|
+
|
5
|
+
def list
|
6
|
+
authorize! :list, SolidusPaypalBraintree::Configuration
|
7
|
+
|
8
|
+
@configurations = Spree::Store.all.map(&:braintree_configuration)
|
9
|
+
end
|
10
|
+
|
11
|
+
def update
|
12
|
+
authorize! :update, SolidusPaypalBraintree::Configuration
|
13
|
+
|
14
|
+
params = configurations_params[:configuration_fields]
|
15
|
+
if SolidusPaypalBraintree::Configuration.update(params.keys, params.values)
|
16
|
+
flash[:success] = t('update_success', scope: 'solidus_paypal_braintree.configurations')
|
17
|
+
else
|
18
|
+
flash[:error] = t('update_error', scope: 'solidus_paypal_braintree.configurations')
|
19
|
+
end
|
20
|
+
redirect_to action: :list
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def configurations_params
|
26
|
+
params.require(:configurations).
|
27
|
+
permit(configuration_fields: [:paypal, :apple_pay, :credit_card])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class SolidusPaypalBraintree::CheckoutsController < Spree::CheckoutController
|
2
|
+
PERMITTED_PAYMENT_PARAMS = [
|
3
|
+
:payment_method_id,
|
4
|
+
source_attributes: [
|
5
|
+
:nonce,
|
6
|
+
:payment_type
|
7
|
+
]
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
def update
|
11
|
+
@payment = Spree::PaymentCreate.new(@order, payment_params).build
|
12
|
+
|
13
|
+
if @payment.save
|
14
|
+
render plain: "ok"
|
15
|
+
else
|
16
|
+
render plain: "not-ok"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def payment_params
|
21
|
+
params.
|
22
|
+
require(:order).
|
23
|
+
require(:payments_attributes).
|
24
|
+
first.
|
25
|
+
permit(PERMITTED_PAYMENT_PARAMS)
|
26
|
+
end
|
27
|
+
end
|