solidus_paypal_braintree 0.2.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +40 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.gitignore +18 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +76 -0
  8. data/CHANGELOG.md +258 -0
  9. data/Gemfile +41 -0
  10. data/LICENSE +2 -2
  11. data/README.md +208 -48
  12. data/Rakefile +4 -28
  13. data/app/assets/config/solidus_paypal_braintree_manifest.js +1 -0
  14. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_blue_button_280x48.svg +19 -0
  15. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_blue_button_320x48.svg +19 -0
  16. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_blue_button_375x48.svg +19 -0
  17. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_white_button_280x48.svg +19 -0
  18. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_white_button_320x48.svg +19 -0
  19. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_white_button_375x48.svg +19 -0
  20. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_acceptance_mark.svg +15 -0
  21. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_button_280x48.svg +19 -0
  22. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_button_320x48.svg +19 -0
  23. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_button_375x48.svg +19 -0
  24. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_logo.svg +18 -0
  25. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_acceptance_mark.svg +20 -0
  26. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_button_280x48.svg +19 -0
  27. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_button_320x48.svg +19 -0
  28. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_button_375x48.svg +19 -0
  29. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_logo.svg +18 -0
  30. data/app/assets/javascripts/solidus_paypal_braintree/checkout.js +32 -3
  31. data/app/assets/javascripts/solidus_paypal_braintree/client.js +58 -5
  32. data/app/assets/javascripts/solidus_paypal_braintree/constants.js +36 -5
  33. data/app/assets/javascripts/solidus_paypal_braintree/frontend.js +2 -0
  34. data/app/assets/javascripts/solidus_paypal_braintree/hosted_form.js +15 -5
  35. data/app/assets/javascripts/solidus_paypal_braintree/paypal_button.js +90 -26
  36. data/app/assets/javascripts/solidus_paypal_braintree/paypal_messaging.js +22 -0
  37. data/app/assets/javascripts/solidus_paypal_braintree/venmo_button.js +86 -0
  38. data/app/assets/javascripts/spree/backend/solidus_paypal_braintree.js +2 -2
  39. data/app/assets/javascripts/spree/frontend/paypal_button.js +15 -13
  40. data/app/assets/stylesheets/spree/frontend/solidus_paypal_braintree.css +12 -0
  41. data/app/decorators/controllers/solidus_paypal_braintree/admin_payments_controller_decorator.rb +11 -0
  42. data/app/decorators/controllers/solidus_paypal_braintree/checkout_controller_decorator.rb +11 -0
  43. data/app/decorators/controllers/solidus_paypal_braintree/client_tokens_controller.rb +41 -0
  44. data/app/decorators/controllers/solidus_paypal_braintree/orders_controller_decorator.rb +11 -0
  45. data/app/decorators/models/solidus_paypal_braintree/spree/store_decorator.rb +20 -0
  46. data/app/decorators/models/solidus_paypal_braintree/spree/user_decorator.rb +13 -0
  47. data/app/helpers/solidus_paypal_braintree/braintree_admin_helper.rb +23 -0
  48. data/app/helpers/solidus_paypal_braintree/braintree_checkout_helper.rb +60 -0
  49. data/app/models/application_record.rb +2 -0
  50. data/app/models/solidus_paypal_braintree/address.rb +64 -0
  51. data/app/models/solidus_paypal_braintree/avs_result.rb +69 -0
  52. data/app/models/solidus_paypal_braintree/configuration.rb +39 -3
  53. data/app/models/solidus_paypal_braintree/customer.rb +7 -3
  54. data/app/models/solidus_paypal_braintree/gateway.rb +150 -43
  55. data/app/models/solidus_paypal_braintree/response.rb +49 -21
  56. data/app/models/solidus_paypal_braintree/source.rb +70 -10
  57. data/app/models/solidus_paypal_braintree/transaction.rb +3 -2
  58. data/app/models/solidus_paypal_braintree/transaction_address.rb +36 -15
  59. data/app/models/solidus_paypal_braintree/transaction_import.rb +18 -12
  60. data/app/overrides/spree/payments/payment/add_paypal_funding_source_to_payment.rb +9 -0
  61. data/app/views/spree/api/payments/source_views/_paypal_braintree.json.jbuilder +3 -0
  62. data/app/views/spree/checkout/existing_payment/_paypal_braintree.html.erb +10 -0
  63. data/app/views/spree/shared/_apple_pay_button.html.erb +27 -0
  64. data/app/views/spree/shared/_braintree_errors.html.erb +16 -0
  65. data/app/views/spree/shared/_braintree_hosted_fields.html.erb +25 -8
  66. data/app/views/spree/shared/_paypal_braintree_head_scripts.html.erb +26 -0
  67. data/app/views/spree/shared/_paypal_cart_button.html.erb +38 -0
  68. data/app/views/spree/shared/_paypal_messaging.html.erb +13 -0
  69. data/app/views/spree/shared/_venmo_button.html.erb +33 -0
  70. data/bin/console +17 -0
  71. data/bin/rails +15 -0
  72. data/bin/setup +8 -0
  73. data/config/locales/en.yml +66 -0
  74. data/config/locales/it.yml +56 -0
  75. data/config/routes.rb +2 -0
  76. data/db/migrate/20160906201711_create_solidus_paypal_braintree_customers.rb +3 -1
  77. data/db/migrate/20161125172005_add_braintree_configuration_to_stores.rb +5 -7
  78. data/db/migrate/20170505193712_add_null_constraint_to_sources.rb +3 -1
  79. data/db/migrate/20190705115327_add_paypal_button_preferences_to_braintree_configurations.rb +5 -0
  80. data/db/migrate/20190911141712_add_3d_secure_to_braintree_configuration.rb +5 -0
  81. data/db/migrate/20211222170950_add_paypal_funding_source_to_solidus_paypal_braintree_sources.rb +5 -0
  82. data/db/migrate/20220104150301_add_venmo_to_braintree_configuration.rb +5 -0
  83. data/lib/controllers/backend/solidus_paypal_braintree/configurations_controller.rb +23 -5
  84. data/lib/controllers/frontend/solidus_paypal_braintree/checkouts_controller.rb +25 -21
  85. data/lib/controllers/frontend/solidus_paypal_braintree/transactions_controller.rb +56 -50
  86. data/lib/generators/solidus_paypal_braintree/install/install_generator.rb +7 -6
  87. data/lib/solidus_paypal_braintree/country_mapper.rb +4 -2
  88. data/lib/solidus_paypal_braintree/engine.rb +34 -22
  89. data/lib/solidus_paypal_braintree/factories.rb +41 -6
  90. data/lib/solidus_paypal_braintree/request_protection.rb +21 -0
  91. data/lib/solidus_paypal_braintree/version.rb +3 -1
  92. data/lib/solidus_paypal_braintree.rb +5 -1
  93. data/lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb +38 -5
  94. data/lib/views/backend/spree/admin/payments/source_forms/_paypal_braintree.html.erb +2 -2
  95. data/lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb +7 -2
  96. data/lib/views/backend/spree/admin/shared/preference_fields/_preference_select.html.erb +13 -0
  97. data/lib/views/backend_v1.2/spree/admin/payments/source_forms/_paypal_braintree.html.erb +2 -2
  98. data/lib/views/backend_v2.4/spree/admin/shared/preference_fields/_hash.html.erb +12 -0
  99. data/lib/views/frontend/solidus_paypal_braintree/payments/_payment.html.erb +12 -0
  100. data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +10 -77
  101. data/lib/views/frontend/spree/shared/_paypal_checkout_button.html.erb +32 -0
  102. data/solidus_paypal_braintree.gemspec +43 -0
  103. data/spec/controllers/solidus_paypal_braintree/checkouts_controller_spec.rb +99 -0
  104. data/spec/controllers/solidus_paypal_braintree/client_tokens_controller_spec.rb +55 -0
  105. data/spec/controllers/solidus_paypal_braintree/configurations_controller_spec.rb +73 -0
  106. data/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb +183 -0
  107. data/spec/features/backend/configuration_spec.rb +23 -0
  108. data/spec/features/backend/new_payment_spec.rb +137 -0
  109. data/spec/features/frontend/braintree_credit_card_checkout_spec.rb +191 -0
  110. data/spec/features/frontend/paypal_checkout_spec.rb +166 -0
  111. data/spec/features/frontend/venmo_checkout_spec.rb +189 -0
  112. data/spec/fixtures/cassettes/admin/invalid_credit_card.yml +63 -0
  113. data/spec/fixtures/cassettes/admin/resubmit_credit_card.yml +352 -0
  114. data/spec/fixtures/cassettes/admin/valid_credit_card.yml +412 -0
  115. data/spec/fixtures/cassettes/braintree/create_profile.yml +71 -0
  116. data/spec/fixtures/cassettes/braintree/generate_token.yml +63 -0
  117. data/spec/fixtures/cassettes/braintree/token.yml +63 -0
  118. data/spec/fixtures/cassettes/checkout/invalid_credit_card.yml +63 -0
  119. data/spec/fixtures/cassettes/checkout/resubmit_credit_card.yml +216 -0
  120. data/spec/fixtures/cassettes/checkout/update.yml +71 -0
  121. data/spec/fixtures/cassettes/checkout/valid_credit_card.yml +156 -0
  122. data/spec/fixtures/cassettes/checkout/valid_venmo_transaction.yml +599 -0
  123. data/spec/fixtures/cassettes/gateway/authorize/credit_card/address.yml +86 -0
  124. data/spec/fixtures/cassettes/gateway/authorize/merchant_account/EUR.yml +154 -0
  125. data/spec/fixtures/cassettes/gateway/authorize/paypal/EUR.yml +90 -0
  126. data/spec/fixtures/cassettes/gateway/authorize/paypal/address.yml +90 -0
  127. data/spec/fixtures/cassettes/gateway/authorize.yml +86 -0
  128. data/spec/fixtures/cassettes/gateway/authorized_transaction.yml +73 -0
  129. data/spec/fixtures/cassettes/gateway/cancel/missing.yml +63 -0
  130. data/spec/fixtures/cassettes/gateway/cancel/refunds.yml +272 -0
  131. data/spec/fixtures/cassettes/gateway/cancel/void.yml +201 -0
  132. data/spec/fixtures/cassettes/gateway/capture.yml +141 -0
  133. data/spec/fixtures/cassettes/gateway/complete.yml +157 -0
  134. data/spec/fixtures/cassettes/gateway/credit.yml +208 -0
  135. data/spec/fixtures/cassettes/gateway/purchase.yml +87 -0
  136. data/spec/fixtures/cassettes/gateway/settled_transaction.yml +140 -0
  137. data/spec/fixtures/cassettes/gateway/void.yml +137 -0
  138. data/spec/fixtures/cassettes/source/card_type.yml +267 -0
  139. data/spec/fixtures/cassettes/source/last4.yml +267 -0
  140. data/spec/fixtures/cassettes/transaction/import/valid/capture.yml +224 -0
  141. data/spec/fixtures/cassettes/transaction/import/valid.yml +71 -0
  142. data/spec/fixtures/views/spree/orders/edit.html.erb +50 -0
  143. data/spec/helpers/solidus_paypal_braintree/braintree_admin_helper_spec.rb +17 -0
  144. data/spec/helpers/solidus_paypal_braintree/braintree_checkout_helper_spec.rb +70 -0
  145. data/spec/models/solidus_paypal_braintree/address_spec.rb +71 -0
  146. data/spec/models/solidus_paypal_braintree/avs_result_spec.rb +317 -0
  147. data/spec/models/solidus_paypal_braintree/gateway_spec.rb +692 -0
  148. data/spec/models/solidus_paypal_braintree/response_spec.rb +280 -0
  149. data/spec/models/solidus_paypal_braintree/source_spec.rb +499 -0
  150. data/spec/models/solidus_paypal_braintree/transaction_address_spec.rb +235 -0
  151. data/spec/models/solidus_paypal_braintree/transaction_import_spec.rb +300 -0
  152. data/spec/models/solidus_paypal_braintree/transaction_spec.rb +85 -0
  153. data/spec/models/spree/store_spec.rb +14 -0
  154. data/spec/requests/spree/api/orders_controller_spec.rb +36 -0
  155. data/spec/spec_helper.rb +29 -0
  156. data/spec/support/capybara.rb +7 -0
  157. data/spec/support/factories.rb +2 -0
  158. data/spec/support/gateway_helpers.rb +29 -0
  159. data/spec/support/order_ready_for_payment.rb +37 -0
  160. data/spec/support/vcr.rb +42 -0
  161. data/spec/support/views.rb +1 -0
  162. metadata +226 -166
  163. data/app/controllers/solidus_paypal_braintree/client_tokens_controller.rb +0 -21
  164. data/app/helpers/braintree_admin_helper.rb +0 -18
  165. data/app/models/spree/store_decorator.rb +0 -11
  166. data/app/overrides/admin_navigation_menu.rb +0 -6
  167. data/config/initializers/braintree.rb +0 -1
  168. data/lib/views/backend/solidus_paypal_braintree/configurations/_admin_tab.html.erb +0 -3
@@ -4,15 +4,32 @@
4
4
  * @constructor
5
5
  * @param {object} element - The DOM element of your PayPal button
6
6
  */
7
- SolidusPaypalBraintree.PaypalButton = function(element, paypalOptions) {
7
+ SolidusPaypalBraintree.PaypalButton = function(element, paypalOptions, options) {
8
8
  this._element = element;
9
9
  this._paypalOptions = paypalOptions || {};
10
+
11
+ this.locale = paypalOptions['locale'] || "en_US";
12
+ this.style = paypalOptions['style'] || {};
13
+ delete paypalOptions['locale'];
14
+ delete paypalOptions['style'];
15
+
16
+ this._options = options || {};
10
17
  this._client = null;
18
+ this._environment = this._paypalOptions.environment || 'sandbox';
19
+ delete this._paypalOptions.environment;
20
+
21
+ this._buyerCountry = this._paypalOptions.buyerCountry;
22
+ delete paypalOptions['buyerCountry'];
23
+
24
+ this._enabledFunding = [];
25
+
26
+ if (paypalOptions['venmoFunding']) this._enabledFunding.push('venmo');
27
+ delete paypalOptions['venmoFunding'];
11
28
 
12
29
  if(!this._element) {
13
30
  throw new Error("Element for the paypal button must be present on the page");
14
31
  }
15
- }
32
+ };
16
33
 
17
34
  /**
18
35
  * Creates the PayPal session using the provided options and enables the button
@@ -23,7 +40,10 @@ SolidusPaypalBraintree.PaypalButton = function(element, paypalOptions) {
23
40
  * See {@link https://braintree.github.io/braintree-web/3.9.0/PayPal.html#tokenize}
24
41
  */
25
42
  SolidusPaypalBraintree.PaypalButton.prototype.initialize = function() {
26
- this._client = new SolidusPaypalBraintree.createClient({useDataCollector: true, usePaypal: true});
43
+ this._client = new SolidusPaypalBraintree.createClient({
44
+ useDataCollector: this._paypalOptions.useDataCollector,
45
+ usePaypal: true
46
+ });
27
47
 
28
48
  return this._client.initialize().then(this.initializeCallback.bind(this));
29
49
  };
@@ -31,10 +51,38 @@ SolidusPaypalBraintree.PaypalButton.prototype.initialize = function() {
31
51
  SolidusPaypalBraintree.PaypalButton.prototype.initializeCallback = function() {
32
52
  this._paymentMethodId = this._client.paymentMethodId;
33
53
 
34
- this._element.removeAttribute('disabled');
35
- this._element.addEventListener('click', function(event) {
36
- this._client.getPaypalInstance().tokenize(this._paypalOptions, this._tokenizeCallback.bind(this));
37
- }.bind(this), false);
54
+ var args = {
55
+ "client-id": this._environment === "sandbox" ? "sb" : null,
56
+ currency: this._paypalOptions.currency,
57
+ commit: true,
58
+ vault: this._paypalOptions.flow == "vault",
59
+ components: this.style['messaging'] == "true" && this._paypalOptions.flow != "vault" ? "buttons,messages" : "buttons",
60
+ intent: this._paypalOptions.flow == "vault" ? "tokenize" : "authorize"
61
+ };
62
+
63
+ if (this._environment === "sandbox" && this._buyerCountry) {
64
+ args["buyer-country"] = this._buyerCountry
65
+ }
66
+ if (this._enabledFunding.length !== 0) {
67
+ args["enable-funding"] = this._enabledFunding.join(',');
68
+ }
69
+
70
+ this._client.getPaypalInstance().loadPayPalSDK(args).then(() => {
71
+ var create_method = this._paypalOptions.flow == "vault" ? "createBillingAgreement" : "createOrder"
72
+
73
+ var render_config = {
74
+ style: this.style,
75
+ onClick: (data) => { SolidusPaypalBraintree.fundingSource = data.fundingSource },
76
+ [create_method]: function () {
77
+ return this._client.getPaypalInstance().createPayment(this._paypalOptions);
78
+ }.bind(this),
79
+ onApprove: function (data, actions) {
80
+ return this._client.getPaypalInstance().tokenizePayment(data, this._tokenizeCallback.bind(this));
81
+ }.bind(this)
82
+ };
83
+
84
+ paypal.Buttons(render_config).render(this._element);
85
+ })
38
86
  };
39
87
 
40
88
  /**
@@ -60,7 +108,22 @@ SolidusPaypalBraintree.PaypalButton.prototype._tokenizeCallback = function(token
60
108
  window.location.href = response.redirectUrl;
61
109
  },
62
110
  error: function(xhr) {
63
- console.error("Error submitting transaction")
111
+ var errorText = BraintreeError.DEFAULT;
112
+
113
+ if (xhr.responseJSON && xhr.responseJSON.errors) {
114
+ var errors = [];
115
+ $.each(xhr.responseJSON.errors, function(key, values) {
116
+ $.each(values, function(index, value) {
117
+ errors.push(key + " " + value)
118
+ });
119
+ });
120
+
121
+ if (errors.length > 0)
122
+ errorText = errors.join(", ");
123
+ }
124
+
125
+ console.error("Error submitting transaction: " + errorText);
126
+ SolidusPaypalBraintree.showError(errorText);
64
127
  },
65
128
  });
66
129
  };
@@ -74,14 +137,16 @@ SolidusPaypalBraintree.PaypalButton.prototype._tokenizeCallback = function(token
74
137
  SolidusPaypalBraintree.PaypalButton.prototype._transactionParams = function(payload) {
75
138
  return {
76
139
  "payment_method_id" : this._paymentMethodId,
140
+ "options": this._options,
77
141
  "transaction" : {
78
142
  "email" : payload.details.email,
79
143
  "phone" : payload.details.phone,
80
144
  "nonce" : payload.nonce,
81
145
  "payment_type" : payload.type,
146
+ "paypal_funding_source": SolidusPaypalBraintree.fundingSource,
82
147
  "address_attributes" : this._addressParams(payload)
83
148
  }
84
- }
149
+ };
85
150
  };
86
151
 
87
152
  /**
@@ -91,24 +156,23 @@ SolidusPaypalBraintree.PaypalButton.prototype._transactionParams = function(payl
91
156
  * @param {object} payload - The payload returned by Braintree after tokenization
92
157
  */
93
158
  SolidusPaypalBraintree.PaypalButton.prototype._addressParams = function(payload) {
94
- if (payload.details.shippingAddress.recipientName) {
95
- var first_name = payload.details.shippingAddress.recipientName.split(" ")[0];
96
- var last_name = payload.details.shippingAddress.recipientName.split(" ")[1];
97
- }
98
- if (first_name == null || last_name == null) {
99
- var first_name = payload.details.firstName;
100
- var last_name = payload.details.lastName;
159
+ var name;
160
+ var payload_address = payload.details.shippingAddress || payload.details.billingAddress;
161
+ if (!payload_address) return {};
162
+
163
+ if (payload_address.recipientName) {
164
+ name = payload_address.recipientName
165
+ } else {
166
+ name = payload.details.firstName + " " + payload.details.lastName;
101
167
  }
102
168
 
103
169
  return {
104
- "first_name" : first_name,
105
- "last_name" : last_name,
106
- "address_line_1" : payload.details.shippingAddress.line1,
107
- "address_line_2" : payload.details.shippingAddress.line2,
108
- "city" : payload.details.shippingAddress.city,
109
- "state_code" : payload.details.shippingAddress.state,
110
- "zip" : payload.details.shippingAddress.postalCode,
111
- "country_code" : payload.details.shippingAddress.countryCode
112
- }
170
+ "name" : name,
171
+ "address_line_1" : payload_address.line1,
172
+ "address_line_2" : payload_address.line2,
173
+ "city" : payload_address.city,
174
+ "state_code" : payload_address.state,
175
+ "zip" : payload_address.postalCode,
176
+ "country_code" : payload_address.countryCode
177
+ };
113
178
  };
114
-
@@ -0,0 +1,22 @@
1
+ //= require solidus_paypal_braintree/constants
2
+
3
+ SolidusPaypalBraintree.PaypalMessaging = function(paypalOptions) {
4
+ this._paypalOptions = paypalOptions || {};
5
+
6
+ this._client = null;
7
+ };
8
+
9
+ SolidusPaypalBraintree.PaypalMessaging.prototype.initialize = function() {
10
+ this._client = new SolidusPaypalBraintree.createClient({usePaypal: true});
11
+
12
+ return this._client.initialize().then(this.initializeCallback.bind(this));
13
+ };
14
+
15
+ SolidusPaypalBraintree.PaypalMessaging.prototype.initializeCallback = function() {
16
+ this._paymentMethodId = this._client.paymentMethodId;
17
+
18
+ this._client.getPaypalInstance().loadPayPalSDK({
19
+ currency: this._paypalOptions.currency,
20
+ components: "messages"
21
+ })
22
+ };
@@ -0,0 +1,86 @@
1
+ //= require solidus_paypal_braintree/constants
2
+ /**
3
+ * Constructor for Venmo button object
4
+ * @constructor
5
+ * @param {object} element - The DOM element of your Venmo button
6
+ */
7
+ SolidusPaypalBraintree.VenmoButton = function(element, venmoOptions) {
8
+ this._element = element;
9
+ this._client = null;
10
+ this._venmoOptions = venmoOptions || {};
11
+
12
+ if(!this._element) {
13
+ throw new Error("Element for the Venmo button must be present on the page");
14
+ }
15
+ };
16
+
17
+ /**
18
+ * Creates the Venmo session using the provided options and enables the button
19
+ *
20
+ * @param {object} options - The options passed to tokenize when constructing
21
+ * the Venmo instance
22
+ *
23
+ * See {@link https://braintree.github.io/braintree-web/3.84.0/module-braintree-web_venmo.html#.create}
24
+ */
25
+ SolidusPaypalBraintree.VenmoButton.prototype.initialize = function() {
26
+ this._client = new SolidusPaypalBraintree.createClient({
27
+ useVenmo: true,
28
+ newBrowserTabSupported: this._venmoOptions.newBrowserTabSupported,
29
+ flow: this._venmoOptions.flow
30
+ });
31
+
32
+ return this._client.initialize().then(this.initializeCallback.bind(this));
33
+ };
34
+
35
+ SolidusPaypalBraintree.VenmoButton.prototype.initializeCallback = function() {
36
+ this._venmoInstance = this._client.getVenmoInstance();
37
+
38
+ this._element.classList.add('visible');
39
+
40
+ // Check if tokenization results already exist. This occurs when your
41
+ // checkout page is relaunched in a new tab.
42
+ if (!this._venmoOptions.newBrowserTabSupported && this._venmoInstance.hasTokenizationResult()) {
43
+ this.tokenize();
44
+ }
45
+
46
+ this._element.addEventListener('click', function(event) {
47
+ event.preventDefault();
48
+ this._element.disabled = true;
49
+ this.initializeVenmoSession();
50
+ }.bind(this), false);
51
+ };
52
+
53
+ SolidusPaypalBraintree.VenmoButton.prototype.initializeVenmoSession = function() {
54
+ this.tokenize();
55
+ };
56
+
57
+ SolidusPaypalBraintree.VenmoButton.prototype.tokenize = function() {
58
+ var venmoButton = this._element;
59
+ this._venmoInstance.tokenize().then(handleVenmoSuccess).catch(handleVenmoError).then(function () {
60
+ venmoButton.removeAttribute('disabled');
61
+ });
62
+ };
63
+
64
+ function handleVenmoSuccess(payload) {
65
+ var $paymentForm = $("#checkout_form_payment");
66
+ var $nonceField = $("#venmo_payment_method_nonce", $paymentForm);
67
+
68
+ // Disable hostedFields' and enable Venmo's inputs as they use the same fields.
69
+ // Otherwise, they will clash. (Disabled inputs are not used on form submission)
70
+ $('.hosted-fields input').each(function(_index, input) {
71
+ input.disabled = true;
72
+ });
73
+ $('.venmo-fields input').each(function(_index, input) {
74
+ input.removeAttribute('disabled');
75
+ });
76
+
77
+ // remove hostedFields submit listener, otherwise empty credit card errors occur
78
+ $paymentForm.off('submit');
79
+
80
+ $nonceField.val(payload.nonce);
81
+ $paymentForm.submit();
82
+ }
83
+
84
+ function handleVenmoError(error) {
85
+ SolidusPaypalBraintree.config.braintreeErrorHandle(error);
86
+ }
@@ -68,8 +68,8 @@ $(function() {
68
68
  if (!$paymentForm.length || !$hostedFields.length) { return; }
69
69
 
70
70
  $.when(
71
- $.getScript("https://js.braintreegateway.com/web/3.9.0/js/client.min.js"),
72
- $.getScript("https://js.braintreegateway.com/web/3.9.0/js/hosted-fields.min.js")
71
+ $.getScript("https://js.braintreegateway.com/web/3.84.0/js/client.min.js"),
72
+ $.getScript("https://js.braintreegateway.com/web/3.84.0/js/hosted-fields.min.js")
73
73
  ).done(function() {
74
74
  $hostedFields.each(function() {
75
75
  var $this = $(this),
@@ -1,32 +1,34 @@
1
1
  //= require solidus_paypal_braintree/paypal_button
2
2
 
3
+ // This is the PayPal button on the cart page
3
4
  $(document).ready(function() {
4
5
  if (document.getElementById("empty-cart")) {
5
6
  $.when(
6
- $.getScript("https://js.braintreegateway.com/web/3.14.0/js/client.min.js"),
7
- $.getScript("https://js.braintreegateway.com/web/3.14.0/js/paypal.min.js"),
8
- $.getScript("https://js.braintreegateway.com/web/3.14.0/js/data-collector.min.js")
7
+ $.getScript("https://js.braintreegateway.com/3.84.0/js/client.min.js"),
8
+ $.getScript("https://js.braintreegateway.com/3.84.0/js/paypal-checkout.min.js"),
9
+ $.getScript("https://js.braintreegateway.com/3.84.0/js/data-collector.min.js")
9
10
  ).done(function() {
11
+ $("#content").append('<div id="paypal-button"/>');
10
12
  $('<script/>').attr({
11
- 'data-merchant' : "braintree",
12
- 'data-id' : "paypal-button",
13
- 'data-button' : "checkout",
14
- 'data-color' : "blue",
15
- 'data-size' : "medium",
16
- 'data-shape' : "pill",
17
- 'data-button_type' : "button",
18
- 'data-button_disabled' : "true"
13
+ 'data-version-4' : "true"
19
14
  }).
20
15
  load(function() {
21
16
  var paypalOptions = {
22
17
  flow: 'vault',
23
18
  enableShippingAddress: true
24
19
  }
25
- var button = new SolidusPaypalBraintree.createPaypalButton(document.querySelector("#paypal-button"), paypalOptions);
20
+ var options = {
21
+ restart_checkout: true
22
+ }
23
+ var button = new SolidusPaypalBraintree.createPaypalButton(
24
+ document.querySelector("#paypal-button"),
25
+ paypalOptions,
26
+ options
27
+ );
26
28
  return button.initialize();
27
29
  }).
28
30
  insertAfter("#content").
29
- attr('src', 'https://www.paypalobjects.com/api/button.js?')
31
+ attr('src', 'https://www.paypalobjects.com/api/checkout.js');
30
32
  });
31
33
  }
32
34
  });
@@ -37,3 +37,15 @@ the installer will append this file to the app vendored assets here: 'vendor/ass
37
37
  .apple-pay-button.visible {
38
38
  visibility: visible;
39
39
  }
40
+
41
+ .venmo-button {
42
+ visibility: hidden;
43
+ border: none;
44
+ height: 48px;
45
+ background-color: transparent;
46
+ background-repeat: no-repeat;
47
+ }
48
+
49
+ .venmo-button.visible {
50
+ visibility: visible;
51
+ }
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module AdminPaymentsControllerDecorator
5
+ def self.prepended(base)
6
+ base.helper ::SolidusPaypalBraintree::BraintreeAdminHelper
7
+ end
8
+
9
+ ::Spree::Admin::PaymentsController.prepend(self)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module CheckoutControllerDecorator
5
+ def self.prepended(base)
6
+ base.helper ::SolidusPaypalBraintree::BraintreeCheckoutHelper
7
+ end
8
+
9
+ ::Spree::CheckoutController.prepend(self) if SolidusSupport.frontend_available?
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ class ClientTokensController < ::Spree::Api::BaseController
5
+ skip_before_action :authenticate_user
6
+
7
+ before_action :load_gateway
8
+
9
+ def create
10
+ token = @gateway.generate_token
11
+ if token
12
+ render json: { client_token: token, payment_method_id: @gateway.id }
13
+ else
14
+ render json: { error: Gateway::TOKEN_GENERATION_DISABLED_MESSAGE }, status: :unprocessable_entity
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def load_gateway
21
+ if params[:payment_method_id]
22
+ @gateway = ::SolidusPaypalBraintree::Gateway.find(params[:payment_method_id])
23
+ else
24
+ store_payment_methods_scope =
25
+ if current_store.payment_methods.empty?
26
+ ::SolidusPaypalBraintree::Gateway.all
27
+ else
28
+ ::SolidusPaypalBraintree::Gateway.where(id: current_store.payment_method_ids)
29
+ end
30
+ @gateway = ::SolidusPaypalBraintree::Gateway.where(active: true).merge(store_payment_methods_scope).first!
31
+ end
32
+ end
33
+
34
+ def generate_token
35
+ @gateway.generate_token
36
+ rescue ::SolidusPaypalBraintree::Gateway::TokenGenerationDisabledError => e
37
+ Rails.logger.error e
38
+ nil
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module OrdersControllerDecorator
5
+ def self.prepended(base)
6
+ base.helper ::SolidusPaypalBraintree::BraintreeCheckoutHelper
7
+ end
8
+
9
+ ::Spree::OrdersController.prepend(self) if SolidusSupport.frontend_available?
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module Spree
5
+ module StoreDecorator
6
+ def self.prepended(base)
7
+ base.has_one :braintree_configuration, class_name: "SolidusPaypalBraintree::Configuration", dependent: :destroy
8
+ base.before_create :build_default_configuration
9
+ end
10
+
11
+ private
12
+
13
+ def build_default_configuration
14
+ build_braintree_configuration unless braintree_configuration
15
+ end
16
+
17
+ ::Spree::Store.prepend self
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module Spree
5
+ module UserDecorator
6
+ def self.prepended(base)
7
+ base.has_one :braintree_customer, class_name: 'SolidusPaypalBraintree::Customer', inverse_of: :user
8
+ end
9
+
10
+ ::Spree.user_class.prepend self
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module BraintreeAdminHelper
5
+ # Returns a link to the Braintree web UI for the given Braintree payment
6
+ def braintree_transaction_link(payment)
7
+ environment = payment.payment_method.preferred_environment == 'sandbox' ? 'sandbox' : 'www'
8
+ merchant_id = payment.payment_method.preferred_merchant_id
9
+ response_code = payment.response_code
10
+
11
+ return if response_code.blank?
12
+ return response_code if merchant_id.blank?
13
+
14
+ link_to(
15
+ response_code,
16
+ "https://#{environment}.braintreegateway.com/merchants/#{merchant_id}/transactions/#{response_code}",
17
+ title: 'Show payment on Braintree',
18
+ target: '_blank',
19
+ rel: 'noopener'
20
+ )
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ module BraintreeCheckoutHelper
5
+ def braintree_3ds_options_for(order)
6
+ ship_address = SolidusPaypalBraintree::Address.new(order.ship_address)
7
+ bill_address = SolidusPaypalBraintree::Address.new(order.bill_address)
8
+ {
9
+ nonce: nil, # populated after tokenization
10
+ bin: nil, # populated after tokenization
11
+ onLookupComplete: nil, # populated after tokenization
12
+ amount: order.total,
13
+ email: order.email,
14
+ billingAddress: {
15
+ givenName: bill_address.firstname,
16
+ surname: bill_address.lastname,
17
+ phoneNumber: bill_address.phone,
18
+ streetAddress: bill_address.address1,
19
+ extendedAddress: bill_address.address2,
20
+ locality: bill_address.city,
21
+ region: bill_address.state&.abbr,
22
+ postalCode: bill_address.zipcode,
23
+ countryCodeAlpha2: bill_address.country&.iso,
24
+ },
25
+ additionalInformation: {
26
+ shippingGivenName: ship_address.firstname,
27
+ shippingSurname: ship_address.lastname,
28
+ shippingPhone: ship_address.phone,
29
+ shippingAddress: {
30
+ streedAddress: ship_address.address1,
31
+ extendedAddress: ship_address.address2,
32
+ locality: ship_address.city,
33
+ region: ship_address.state&.abbr,
34
+ postalCode: ship_address.zipcode,
35
+ countryCodeAlpha2: ship_address.country&.iso,
36
+ }
37
+ }
38
+ }
39
+ end
40
+
41
+ def paypal_button_preference(key, store:)
42
+ store.braintree_configuration.preferences[key]
43
+ end
44
+
45
+ def venmo_button_style(store)
46
+ configuration = store.braintree_configuration
47
+ color = configuration.preferred_venmo_button_color
48
+ width = configuration.preferred_venmo_button_width
49
+
50
+ { width: width, color: color }
51
+ end
52
+
53
+ def venmo_button_asset_url(style, active: false)
54
+ prefix = 'solidus_paypal_braintree/venmo/venmo_'
55
+ active_string = active ? 'active_' : ''
56
+ path = "#{prefix}#{active_string}#{style[:color]}_button_#{style[:width]}x48.svg"
57
+ asset_path(path)
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationRecord < ActiveRecord::Base
2
4
  self.abstract_class = true
3
5
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalBraintree
4
+ class Address
5
+ delegate :address1,
6
+ :address2,
7
+ :city,
8
+ :country,
9
+ :phone,
10
+ :state,
11
+ :zipcode,
12
+ to: :spree_address
13
+
14
+ def self.split_name(name)
15
+ if defined?(Spree::Address::Name)
16
+ address_name = Spree::Address::Name.new(name)
17
+ [address_name.first_name, address_name.last_name]
18
+ else
19
+ name.strip.split(' ', 2)
20
+ end
21
+ end
22
+
23
+ def initialize(spree_address)
24
+ @spree_address = spree_address
25
+ end
26
+
27
+ def to_json(*_args)
28
+ address_hash = {
29
+ line1: address1,
30
+ line2: address2,
31
+ city: city,
32
+ postalCode: zipcode,
33
+ countryCode: country.iso,
34
+ phone: phone,
35
+ recipientName: "#{firstname} #{lastname}"
36
+ }
37
+
38
+ if ::Spree::Config.address_requires_state && country.states_required
39
+ address_hash[:state] = state.name
40
+ end
41
+ address_hash.to_json
42
+ end
43
+
44
+ def firstname
45
+ if SolidusSupport.combined_first_and_last_name_in_address?
46
+ self.class.split_name(spree_address.name).first
47
+ else
48
+ spree_address.firstname
49
+ end
50
+ end
51
+
52
+ def lastname
53
+ if SolidusSupport.combined_first_and_last_name_in_address?
54
+ self.class.split_name(spree_address.name).last
55
+ else
56
+ spree_address.lastname
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ attr_reader :spree_address
63
+ end
64
+ end