solidus_paypal_braintree 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|