solidus_paypal_braintree 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/Gemfile +1 -3
  4. data/README.md +96 -5
  5. data/app/assets/config/solidus_paypal_braintree_manifest.js +1 -0
  6. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_blue_button_280x48.svg +19 -0
  7. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_blue_button_320x48.svg +19 -0
  8. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_blue_button_375x48.svg +19 -0
  9. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_white_button_280x48.svg +19 -0
  10. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_white_button_320x48.svg +19 -0
  11. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_active_white_button_375x48.svg +19 -0
  12. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_acceptance_mark.svg +15 -0
  13. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_button_280x48.svg +19 -0
  14. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_button_320x48.svg +19 -0
  15. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_button_375x48.svg +19 -0
  16. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_blue_logo.svg +18 -0
  17. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_acceptance_mark.svg +20 -0
  18. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_button_280x48.svg +19 -0
  19. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_button_320x48.svg +19 -0
  20. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_button_375x48.svg +19 -0
  21. data/app/assets/images/solidus_paypal_braintree/venmo/venmo_white_logo.svg +18 -0
  22. data/app/assets/javascripts/solidus_paypal_braintree/client.js +34 -0
  23. data/app/assets/javascripts/solidus_paypal_braintree/constants.js +8 -0
  24. data/app/assets/javascripts/solidus_paypal_braintree/frontend.js +1 -0
  25. data/app/assets/javascripts/solidus_paypal_braintree/paypal_button.js +26 -3
  26. data/app/assets/javascripts/solidus_paypal_braintree/venmo_button.js +86 -0
  27. data/app/assets/javascripts/spree/backend/solidus_paypal_braintree.js +2 -2
  28. data/app/assets/javascripts/spree/frontend/paypal_button.js +3 -3
  29. data/app/assets/stylesheets/spree/frontend/solidus_paypal_braintree.css +12 -0
  30. data/app/decorators/controllers/solidus_paypal_braintree/checkout_controller_decorator.rb +1 -1
  31. data/app/decorators/controllers/solidus_paypal_braintree/orders_controller_decorator.rb +1 -1
  32. data/app/helpers/solidus_paypal_braintree/braintree_checkout_helper.rb +19 -5
  33. data/app/models/solidus_paypal_braintree/address.rb +43 -9
  34. data/app/models/solidus_paypal_braintree/avs_result.rb +3 -0
  35. data/app/models/solidus_paypal_braintree/configuration.rb +15 -2
  36. data/app/models/solidus_paypal_braintree/gateway.rb +27 -2
  37. data/app/models/solidus_paypal_braintree/source.rb +31 -2
  38. data/app/models/solidus_paypal_braintree/transaction.rb +1 -1
  39. data/app/models/solidus_paypal_braintree/transaction_address.rb +17 -13
  40. data/app/models/solidus_paypal_braintree/transaction_import.rb +2 -1
  41. data/app/overrides/spree/payments/payment/add_paypal_funding_source_to_payment.rb +9 -0
  42. data/app/views/spree/shared/_braintree_errors.html.erb +3 -0
  43. data/app/views/spree/shared/_paypal_braintree_head_scripts.html.erb +10 -6
  44. data/app/views/spree/shared/_paypal_cart_button.html.erb +2 -0
  45. data/app/views/spree/shared/_venmo_button.html.erb +33 -0
  46. data/config/locales/en.yml +34 -0
  47. data/config/locales/it.yml +2 -0
  48. data/db/migrate/20211222170950_add_paypal_funding_source_to_solidus_paypal_braintree_sources.rb +5 -0
  49. data/db/migrate/20220104150301_add_venmo_to_braintree_configuration.rb +5 -0
  50. data/lib/controllers/backend/solidus_paypal_braintree/configurations_controller.rb +4 -1
  51. data/lib/controllers/frontend/solidus_paypal_braintree/transactions_controller.rb +1 -0
  52. data/lib/solidus_paypal_braintree/engine.rb +4 -2
  53. data/lib/solidus_paypal_braintree/factories.rb +32 -1
  54. data/lib/solidus_paypal_braintree/version.rb +1 -1
  55. data/lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb +8 -0
  56. data/lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb +5 -0
  57. data/lib/views/backend/spree/admin/shared/preference_fields/_preference_select.html.erb +13 -0
  58. data/lib/views/frontend/solidus_paypal_braintree/payments/_payment.html.erb +12 -0
  59. data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +4 -0
  60. data/lib/views/frontend/spree/shared/_paypal_checkout_button.html.erb +2 -0
  61. data/solidus_paypal_braintree.gemspec +6 -5
  62. data/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb +1 -1
  63. data/spec/features/frontend/braintree_credit_card_checkout_spec.rb +5 -1
  64. data/spec/features/frontend/venmo_checkout_spec.rb +189 -0
  65. data/spec/fixtures/cassettes/checkout/valid_venmo_transaction.yml +599 -0
  66. data/spec/helpers/solidus_paypal_braintree/braintree_checkout_helper_spec.rb +70 -0
  67. data/spec/models/solidus_paypal_braintree/address_spec.rb +20 -0
  68. data/spec/models/solidus_paypal_braintree/gateway_spec.rb +1 -1
  69. data/spec/models/solidus_paypal_braintree/source_spec.rb +139 -0
  70. data/spec/models/solidus_paypal_braintree/transaction_address_spec.rb +8 -26
  71. data/spec/models/solidus_paypal_braintree/transaction_import_spec.rb +17 -0
  72. data/spec/spec_helper.rb +3 -0
  73. metadata +52 -15
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="375px" height="48px" viewBox="0 0 375 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <!-- Generator: Sketch 46.1 (44463) - http://www.bohemiancoding.com/sketch -->
4
+ <title>svg/white_venmo_button_375x48</title>
5
+ <desc>Created with Sketch.</desc>
6
+ <defs></defs>
7
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
8
+ <g id="white_venmo_button_375x48">
9
+ <rect id="Rectangle" fill="#FFFFFF" x="0" y="0" width="375" height="48" rx="4"></rect>
10
+ <g id="Group" transform="translate(146.000000, 16.000000)" fill="#3D95CE">
11
+ <path d="M14.1355722,0.0643062201 C14.6997229,0.996022242 14.9540614,1.95569119 14.9540614,3.16795034 C14.9540614,7.03443424 11.6533091,12.0572714 8.97435371,15.5842648 L2.85545503,15.5842648 L0.401435711,0.910859951 L5.75920168,0.402203543 L7.05667586,10.8432743 C8.26898429,8.86832019 9.76503373,5.76467606 9.76503373,3.64865382 C9.76503373,2.49041769 9.56660332,1.70150782 9.25650148,1.0519281 L14.1355722,0.0643062201 L14.1355722,0.0643062201 Z" id="Shape"></path>
12
+ <path d="M21.0794779,6.525633 C22.0654018,6.525633 24.5475201,6.07462046 24.5475201,4.66393896 C24.5475201,3.98655114 24.0685351,3.64865382 23.5040948,3.64865382 C22.5165776,3.64865382 21.2206966,4.83281521 21.0794779,6.525633 L21.0794779,6.525633 Z M20.9665029,9.31947756 C20.9665029,11.0419863 21.924328,11.7177809 23.1941378,11.7177809 C24.5769225,11.7177809 25.9009024,11.3798836 27.6217431,10.505377 L26.9735853,14.9065874 C25.7611321,15.4989577 23.8715531,15.8942092 22.0374478,15.8942092 C17.3850512,15.8942092 15.7199738,13.0728462 15.7199738,9.545708 C15.7199738,4.97417302 18.4284766,0.120067244 24.0124822,0.120067244 C27.08685,0.120067244 28.8059526,1.84243114 28.8059526,4.24073451 C28.8062423,8.10707358 23.8437439,9.29152463 20.9665029,9.31947756 L20.9665029,9.31947756 Z" id="Shape"></path>
13
+ <path d="M44.2677372,3.50758567 C44.2677372,4.07185827 44.1821369,4.89031424 44.0969712,5.42518557 L42.4892503,15.58412 L37.2722686,15.58412 L38.7387707,6.27159447 C38.7665799,6.01900427 38.8520354,5.51049269 38.8520354,5.22835639 C38.8520354,4.55096858 38.4288137,4.3819475 37.9199918,4.3819475 C37.2441697,4.3819475 36.5667543,4.69203673 36.1155786,4.918412 L34.4522393,15.5842648 L29.2058551,15.5842648 L31.6026627,0.374540282 L36.1433878,0.374540282 L36.2008892,1.58853744 C37.2721237,0.88319669 38.6827177,0.120356912 40.6841129,0.120356912 C43.3356936,0.120067244 44.2677372,1.47498771 44.2677372,3.50758567 L44.2677372,3.50758567 Z" id="Shape"></path>
14
+ <path d="M59.7554481,1.78507694 C61.2496147,0.713885943 62.6604983,0.120067244 64.6058406,0.120067244 C67.2846511,0.120067244 68.216405,1.47498771 68.216405,3.50758567 C68.216405,4.07185827 68.1310944,4.89031424 68.0459287,5.42518557 L66.4400908,15.58412 L61.2216606,15.58412 L62.7161168,6.07476529 C62.7436363,5.82058192 62.8014274,5.51049269 62.8014274,5.31380835 C62.8014274,4.55111341 62.3780609,4.3819475 61.8693838,4.3819475 C61.2213709,4.3819475 60.5736477,4.6640838 60.0927798,4.918412 L58.4297302,15.5842648 L53.2126036,15.5842648 L54.7070598,6.07491013 C54.7345794,5.82072676 54.7906323,5.51063753 54.7906323,5.31395319 C54.7906323,4.55125824 54.367121,4.38209233 53.860182,4.38209233 C53.1829115,4.38209233 52.5069445,4.69218156 52.0557688,4.91855683 L50.3911259,15.5844097 L45.1464798,15.5844097 L47.5429977,0.374685116 L52.0282492,0.374685116 L52.1691783,1.64444329 C53.2126036,0.883486357 54.6220389,0.12064658 56.511473,0.12064658 C58.1474376,0.120067244 59.2185273,0.825552826 59.7554481,1.78507694 L59.7554481,1.78507694 Z" id="Shape"></path>
15
+ <path d="M78.5990953,6.21583344 C78.5990953,4.97417302 78.288559,4.12761929 77.358688,4.12761929 C75.2997914,4.12761929 74.8770043,7.76743825 74.8770043,9.62942196 C74.8770043,11.0419863 75.2722719,11.9162033 76.2018532,11.9162033 C78.1479196,11.9162033 78.5990953,8.07767231 78.5990953,6.21583344 L78.5990953,6.21583344 Z M69.5751464,9.40463986 C69.5751464,4.60817794 72.1127383,0.120067244 77.9512273,0.120067244 C82.3505888,0.120067244 83.9587442,2.71679297 83.9587442,6.30099573 C83.9587442,11.0418415 81.4485271,15.9514186 75.4692539,15.9514186 C71.0415037,15.9514186 69.5751464,13.0446036 69.5751464,9.40463986 L69.5751464,9.40463986 Z" id="Shape"></path>
16
+ </g>
17
+ </g>
18
+ </g>
19
+ </svg>
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="126px" height="24px" viewBox="0 0 126 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <!-- Generator: Sketch 46.1 (44463) - http://www.bohemiancoding.com/sketch -->
4
+ <title>white_logosvg/</title>
5
+ <desc>Created with Sketch.</desc>
6
+ <defs></defs>
7
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
8
+ <g id="white_logo" fill="#FFFFFF">
9
+ <g id="Group">
10
+ <path d="M21.2033583,0.0964593301 C22.0495843,1.49403336 22.4310921,2.93353679 22.4310921,4.75192551 C22.4310921,10.5516514 17.4799637,18.0859072 13.4615306,23.3763973 L4.28318255,23.3763973 L0.602153567,1.36628993 L8.63880252,0.603305315 L10.5850138,16.2649114 C12.4034764,13.3024803 14.6475506,8.6470141 14.6475506,5.47298073 C14.6475506,3.73562654 14.349905,2.55226174 13.8847522,1.57789215 L21.2033583,0.0964593301 L21.2033583,0.0964593301 Z" id="Shape"></path>
11
+ <path d="M31.6192168,9.7884495 C33.0981028,9.7884495 36.8212801,9.11193069 36.8212801,6.99590844 C36.8212801,5.97982672 36.1028027,5.47298073 35.2561421,5.47298073 C33.7748663,5.47298073 31.8310449,7.24922281 31.6192168,9.7884495 L31.6192168,9.7884495 Z M31.4497544,13.9792163 C31.4497544,16.5629794 32.8864919,17.5766714 34.7912066,17.5766714 C36.8653838,17.5766714 38.8513536,17.0698254 41.4326146,15.7580654 L40.460378,22.359881 C38.6416981,23.2484366 35.8073297,23.8413138 33.0561717,23.8413138 C26.0775769,23.8413138 23.5799608,19.6092694 23.5799608,14.318562 C23.5799608,7.46125954 27.6427148,0.180100866 36.0187232,0.180100866 C40.630275,0.180100866 43.2089289,2.76364671 43.2089289,6.36110177 C43.2093634,12.1606104 35.7656158,13.937287 31.4497544,13.9792163 L31.4497544,13.9792163 Z" id="Shape"></path>
12
+ <path d="M66.4016058,5.26137851 C66.4016058,6.1077874 66.2732054,7.33547136 66.1454568,8.13777835 L63.7338755,23.37618 L55.9084028,23.37618 L58.108156,9.4073917 C58.1498699,9.0285064 58.278053,8.26573904 58.278053,7.84253459 C58.278053,6.82645286 57.6432206,6.57292125 56.8799877,6.57292125 C55.8662545,6.57292125 54.8501315,7.03805509 54.1733679,7.377618 L51.6783589,23.3763973 L43.8087826,23.3763973 L47.403994,0.561810423 L54.2150817,0.561810423 L54.3013338,2.38280616 C55.9081856,1.32479503 58.0240766,0.180535368 61.0261694,0.180535368 C65.0035404,0.180100866 66.4016058,2.21248157 66.4016058,5.26137851 L66.4016058,5.26137851 Z" id="Shape"></path>
13
+ <path d="M89.6331722,2.67761541 C91.874422,1.07082892 93.9907475,0.180100866 96.9087609,0.180100866 C100.926977,0.180100866 102.324608,2.21248157 102.324608,5.26137851 C102.324608,6.1077874 102.196642,7.33547136 102.068893,8.13777835 L99.6601361,23.37618 L91.8324909,23.37618 L94.0741752,9.11214794 C94.1154545,8.73087288 94.2021411,8.26573904 94.2021411,7.97071253 C94.2021411,6.82667012 93.5670913,6.57292125 92.8040757,6.57292125 C91.8320564,6.57292125 90.8604715,6.9961257 90.1391698,7.377618 L87.6445953,23.3763973 L79.8189054,23.3763973 L82.0605897,9.11236519 C82.101869,8.73109013 82.1859485,8.26595629 82.1859485,7.97092978 C82.1859485,6.82688737 81.5506815,6.5731385 80.790273,6.5731385 C79.7743672,6.5731385 78.7604168,7.03827234 78.0836532,7.37783525 L75.5866889,23.3766145 L67.7197197,23.3766145 L71.3144966,0.562027674 L78.0423739,0.562027674 L78.2537674,2.46666494 C79.8189054,1.32522954 81.9330583,0.180969869 84.7672095,0.180969869 C87.2211564,0.180100866 88.827791,1.23832924 89.6331722,2.67761541 L89.6331722,2.67761541 Z" id="Shape"></path>
14
+ <path d="M117.898643,9.32375016 C117.898643,7.46125954 117.432838,6.19142894 116.038032,6.19142894 C112.949687,6.19142894 112.315506,11.6511574 112.315506,14.4441329 C112.315506,16.5629794 112.908408,17.8743049 114.30278,17.8743049 C117.221879,17.8743049 117.898643,12.1165085 117.898643,9.32375016 L117.898643,9.32375016 Z M104.36272,14.1069598 C104.36272,6.91226691 108.169107,0.180100866 116.926841,0.180100866 C123.525883,0.180100866 125.938116,4.07518945 125.938116,9.4514936 C125.938116,16.5627622 122.172791,23.9271279 113.203881,23.9271279 C106.562256,23.9271279 104.36272,19.5669055 104.36272,14.1069598 L104.36272,14.1069598 Z" id="Shape"></path>
15
+ </g>
16
+ </g>
17
+ </g>
18
+ </svg>
@@ -57,11 +57,15 @@ SolidusPaypalBraintree.Client = function(config) {
57
57
  this.useDataCollector = config.useDataCollector;
58
58
  this.usePaypal = config.usePaypal;
59
59
  this.useApplepay = config.useApplepay;
60
+ this.useVenmo = config.useVenmo;
61
+ this.flow = config.flow;
62
+ this.venmoNewTabSupported = config.newBrowserTabSupported
60
63
  this.useThreeDSecure = config.useThreeDSecure;
61
64
 
62
65
  this._braintreeInstance = null;
63
66
  this._dataCollectorInstance = null;
64
67
  this._paypalInstance = null;
68
+ this._venmoInstance = null;
65
69
  this._threeDSecureInstance = null;
66
70
  };
67
71
 
@@ -85,6 +89,10 @@ SolidusPaypalBraintree.Client.prototype.initialize = function() {
85
89
  initializationPromise = initializationPromise.then(this._createApplepay.bind(this));
86
90
  }
87
91
 
92
+ if (this.useVenmo) {
93
+ initializationPromise = initializationPromise.then(this._createVenmo.bind(this));
94
+ }
95
+
88
96
  if (this.useThreeDSecure) {
89
97
  initializationPromise = initializationPromise.then(this._createThreeDSecure.bind(this));
90
98
  }
@@ -116,6 +124,14 @@ SolidusPaypalBraintree.Client.prototype.getApplepayInstance = function() {
116
124
  return this._applepayInstance;
117
125
  };
118
126
 
127
+ /**
128
+ * Returns the braintree Venmo instance
129
+ * @returns {external:"braintree.Venmo"} The Braintree Venmo that was initialized by this class
130
+ **/
131
+ SolidusPaypalBraintree.Client.prototype.getVenmoInstance = function() {
132
+ return this._venmoInstance;
133
+ };
134
+
119
135
  /**
120
136
  * Returns the braintree dataCollector instance
121
137
  * @returns {external:"braintree.DataCollector"} The braintree dataCollector that was initialized by this class
@@ -193,6 +209,24 @@ SolidusPaypalBraintree.Client.prototype._createApplepay = function() {
193
209
  }.bind(this));
194
210
  };
195
211
 
212
+ SolidusPaypalBraintree.Client.prototype._createVenmo = function() {
213
+ return SolidusPaypalBraintree.PromiseShim.convertBraintreePromise(braintree.venmo.create, [{
214
+ client: this._braintreeInstance,
215
+ allowDesktop: true,
216
+ paymentMethodUsage: this.flow === 'vault' ? 'multi_use' : 'single_use',
217
+ allowNewBrowserTab: this.venmoNewTabSupported
218
+ }]).then(function (venmoInstance) {
219
+ // Verify browser support before proceeding.
220
+ if (!venmoInstance.isBrowserSupported()) {
221
+ console.log('Browser does not support Venmo');
222
+ return;
223
+ }
224
+
225
+ this._venmoInstance = venmoInstance;
226
+ return venmoInstance;
227
+ }.bind(this));
228
+ };
229
+
196
230
  SolidusPaypalBraintree.Client.prototype._createThreeDSecure = function() {
197
231
  return SolidusPaypalBraintree.PromiseShim.convertBraintreePromise(braintree.threeDSecure.create, [{
198
232
  client: this._braintreeInstance,
@@ -32,6 +32,10 @@ SolidusPaypalBraintree = {
32
32
 
33
33
  applepayButton: function() {
34
34
  return SolidusPaypalBraintree.ApplepayButton;
35
+ },
36
+
37
+ venmoButton: function() {
38
+ return SolidusPaypalBraintree.VenmoButton;
35
39
  }
36
40
  }
37
41
  },
@@ -63,6 +67,10 @@ SolidusPaypalBraintree = {
63
67
  return SolidusPaypalBraintree._factory(SolidusPaypalBraintree.config.classes.applepayButton(), arguments);
64
68
  },
65
69
 
70
+ createVenmoButton: function() {
71
+ return SolidusPaypalBraintree._factory(SolidusPaypalBraintree.config.classes.venmoButton(), arguments);
72
+ },
73
+
66
74
  _factory: function(klass, args) {
67
75
  var normalizedArgs = Array.prototype.slice.call(args);
68
76
  return new (Function.prototype.bind.apply(klass, [null].concat(normalizedArgs)));
@@ -11,3 +11,4 @@
11
11
  //= require solidus_paypal_braintree/paypal_button
12
12
  //= require solidus_paypal_braintree/paypal_messaging
13
13
  //= require solidus_paypal_braintree/apple_pay_button
14
+ //= require solidus_paypal_braintree/venmo_button
@@ -18,6 +18,14 @@ SolidusPaypalBraintree.PaypalButton = function(element, paypalOptions, options)
18
18
  this._environment = this._paypalOptions.environment || 'sandbox';
19
19
  delete this._paypalOptions.environment;
20
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'];
28
+
21
29
  if(!this._element) {
22
30
  throw new Error("Element for the paypal button must be present on the page");
23
31
  }
@@ -32,7 +40,10 @@ SolidusPaypalBraintree.PaypalButton = function(element, paypalOptions, options)
32
40
  * See {@link https://braintree.github.io/braintree-web/3.9.0/PayPal.html#tokenize}
33
41
  */
34
42
  SolidusPaypalBraintree.PaypalButton.prototype.initialize = function() {
35
- this._client = new SolidusPaypalBraintree.createClient({useDataCollector: true, usePaypal: true});
43
+ this._client = new SolidusPaypalBraintree.createClient({
44
+ useDataCollector: this._paypalOptions.useDataCollector,
45
+ usePaypal: true
46
+ });
36
47
 
37
48
  return this._client.initialize().then(this.initializeCallback.bind(this));
38
49
  };
@@ -40,17 +51,28 @@ SolidusPaypalBraintree.PaypalButton.prototype.initialize = function() {
40
51
  SolidusPaypalBraintree.PaypalButton.prototype.initializeCallback = function() {
41
52
  this._paymentMethodId = this._client.paymentMethodId;
42
53
 
43
- this._client.getPaypalInstance().loadPayPalSDK({
54
+ var args = {
55
+ "client-id": this._environment === "sandbox" ? "sb" : null,
44
56
  currency: this._paypalOptions.currency,
45
57
  commit: true,
46
58
  vault: this._paypalOptions.flow == "vault",
47
59
  components: this.style['messaging'] == "true" && this._paypalOptions.flow != "vault" ? "buttons,messages" : "buttons",
48
60
  intent: this._paypalOptions.flow == "vault" ? "tokenize" : "authorize"
49
- }).then(() => {
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(() => {
50
71
  var create_method = this._paypalOptions.flow == "vault" ? "createBillingAgreement" : "createOrder"
51
72
 
52
73
  var render_config = {
53
74
  style: this.style,
75
+ onClick: (data) => { SolidusPaypalBraintree.fundingSource = data.fundingSource },
54
76
  [create_method]: function () {
55
77
  return this._client.getPaypalInstance().createPayment(this._paypalOptions);
56
78
  }.bind(this),
@@ -121,6 +143,7 @@ SolidusPaypalBraintree.PaypalButton.prototype._transactionParams = function(payl
121
143
  "phone" : payload.details.phone,
122
144
  "nonce" : payload.nonce,
123
145
  "payment_type" : payload.type,
146
+ "paypal_funding_source": SolidusPaypalBraintree.fundingSource,
124
147
  "address_attributes" : this._addressParams(payload)
125
148
  }
126
149
  };
@@ -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.34.0/js/client.min.js"),
72
- $.getScript("https://js.braintreegateway.com/web/3.34.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),
@@ -4,9 +4,9 @@
4
4
  $(document).ready(function() {
5
5
  if (document.getElementById("empty-cart")) {
6
6
  $.when(
7
- $.getScript("https://js.braintreegateway.com/web/3.31.0/js/client.min.js"),
8
- $.getScript("https://js.braintreegateway.com/web/3.31.0/js/paypal-checkout.min.js"),
9
- $.getScript("https://js.braintreegateway.com/web/3.31.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")
10
10
  ).done(function() {
11
11
  $("#content").append('<div id="paypal-button"/>');
12
12
  $('<script/>').attr({
@@ -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
+ }
@@ -6,6 +6,6 @@ module SolidusPaypalBraintree
6
6
  base.helper ::SolidusPaypalBraintree::BraintreeCheckoutHelper
7
7
  end
8
8
 
9
- ::Spree::CheckoutController.prepend(self)
9
+ ::Spree::CheckoutController.prepend(self) if SolidusSupport.frontend_available?
10
10
  end
11
11
  end
@@ -6,6 +6,6 @@ module SolidusPaypalBraintree
6
6
  base.helper ::SolidusPaypalBraintree::BraintreeCheckoutHelper
7
7
  end
8
8
 
9
- ::Spree::OrdersController.prepend(self)
9
+ ::Spree::OrdersController.prepend(self) if SolidusSupport.frontend_available?
10
10
  end
11
11
  end
@@ -3,9 +3,8 @@
3
3
  module SolidusPaypalBraintree
4
4
  module BraintreeCheckoutHelper
5
5
  def braintree_3ds_options_for(order)
6
- ship_address = order.ship_address
7
- bill_address = order.bill_address
8
-
6
+ ship_address = SolidusPaypalBraintree::Address.new(order.ship_address)
7
+ bill_address = SolidusPaypalBraintree::Address.new(order.bill_address)
9
8
  {
10
9
  nonce: nil, # populated after tokenization
11
10
  bin: nil, # populated after tokenization
@@ -19,7 +18,7 @@ module SolidusPaypalBraintree
19
18
  streetAddress: bill_address.address1,
20
19
  extendedAddress: bill_address.address2,
21
20
  locality: bill_address.city,
22
- region: bill_address.state&.name,
21
+ region: bill_address.state&.abbr,
23
22
  postalCode: bill_address.zipcode,
24
23
  countryCodeAlpha2: bill_address.country&.iso,
25
24
  },
@@ -31,7 +30,7 @@ module SolidusPaypalBraintree
31
30
  streedAddress: ship_address.address1,
32
31
  extendedAddress: ship_address.address2,
33
32
  locality: ship_address.city,
34
- region: ship_address.state&.name,
33
+ region: ship_address.state&.abbr,
35
34
  postalCode: ship_address.zipcode,
36
35
  countryCodeAlpha2: ship_address.country&.iso,
37
36
  }
@@ -42,5 +41,20 @@ module SolidusPaypalBraintree
42
41
  def paypal_button_preference(key, store:)
43
42
  store.braintree_configuration.preferences[key]
44
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
45
59
  end
46
60
  end
@@ -2,27 +2,61 @@
2
2
 
3
3
  module SolidusPaypalBraintree
4
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
+
5
23
  def initialize(spree_address)
6
24
  @spree_address = spree_address
7
25
  end
8
26
 
9
27
  def to_json(*_args)
10
28
  address_hash = {
11
- line1: spree_address.address1,
12
- line2: spree_address.address2,
13
- city: spree_address.city,
14
- postalCode: spree_address.zipcode,
15
- countryCode: spree_address.country.iso,
16
- phone: spree_address.phone,
17
- recipientName: spree_address.full_name
29
+ line1: address1,
30
+ line2: address2,
31
+ city: city,
32
+ postalCode: zipcode,
33
+ countryCode: country.iso,
34
+ phone: phone,
35
+ recipientName: "#{firstname} #{lastname}"
18
36
  }
19
37
 
20
- if ::Spree::Config.address_requires_state && spree_address.country.states_required
21
- address_hash[:state] = spree_address.state.name
38
+ if ::Spree::Config.address_requires_state && country.states_required
39
+ address_hash[:state] = state.name
22
40
  end
23
41
  address_hash.to_json
24
42
  end
25
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
+
26
60
  private
27
61
 
28
62
  attr_reader :spree_address
@@ -41,6 +41,9 @@ module SolidusPaypalBraintree
41
41
  'I' => 'I',
42
42
  'A' => 'I'
43
43
  },
44
+ 'B' => {
45
+ 'B' => 'B'
46
+ },
44
47
  nil => { nil => nil }
45
48
  }.freeze
46
49
 
@@ -11,9 +11,11 @@ module SolidusPaypalBraintree
11
11
  messaging: { availables: %w[true false], default: 'false' }
12
12
  }.freeze
13
13
 
14
- belongs_to :store, class_name: 'Spree::Store'
14
+ unless respond_to?(:preference)
15
+ include ::Spree::Preferences::Persistable
16
+ end
15
17
 
16
- validates :store, presence: true
18
+ belongs_to :store, class_name: 'Spree::Store', optional: false
17
19
 
18
20
  # Preferences for Paypal button
19
21
  PAYPAL_BUTTON_PREFERENCES.each do |name, desc|
@@ -24,5 +26,16 @@ module SolidusPaypalBraintree
24
26
 
25
27
  validates attribute_name, inclusion: desc[:availables]
26
28
  end
29
+
30
+ preference :venmo_button_color, :preference_select, default: 'blue'
31
+ preference :venmo_button_width, :preference_select, default: '320'
32
+
33
+ def preferred_venmo_button_color_options
34
+ [["Blue", "blue"], ["White", "white"]]
35
+ end
36
+
37
+ def preferred_venmo_button_width_options
38
+ [["280", "280"], ["320", "320"], ["375", "375"]]
39
+ end
27
40
  end
28
41
  end
@@ -12,8 +12,8 @@ module SolidusPaypalBraintree
12
12
  NON_VOIDABLE_STATUS_ERROR_REGEXP = /can only be voided if status is authorized/.freeze
13
13
 
14
14
  TOKEN_GENERATION_DISABLED_MESSAGE = 'Token generation is disabled.' \
15
- ' To re-enable set the `token_generation_enabled` preference on the' \
16
- ' gateway to `true`.'
15
+ ' To re-enable set the `token_generation_enabled` preference on the' \
16
+ ' gateway to `true`.'
17
17
 
18
18
  ALLOWED_BRAINTREE_OPTIONS = [
19
19
  :device_data,
@@ -55,6 +55,21 @@ module SolidusPaypalBraintree
55
55
  # Example: { number: "Enter card number", cvv: "Enter CVV", expirationDate: "mm/yy" }
56
56
  preference(:placeholder_text, :hash, default: {})
57
57
 
58
+ # Wether to use the JS device data collector
59
+ preference(:use_data_collector, :boolean, default: true)
60
+
61
+ # Useful for testing purposes, as PayPal will show funding sources based on the buyer's country;
62
+ # usually retrieved by their ip geolocation. I.e. Venmo will show for US buyers, but not European.
63
+ preference(:force_buyer_country, :string)
64
+
65
+ preference(:enable_venmo_funding, :boolean, default: false)
66
+
67
+ # When on mobile, paying with Venmo, the user may be returned to the same store tab
68
+ # depending on if their browser supports it, otherwise a new tab will be created
69
+ # However, returning to a new tab may break the payment checkout flow for some stores, for example,
70
+ # if they are single-page applications (SPA). Set this to false if this is the case
71
+ preference(:venmo_new_tab_support, :boolean, default: true)
72
+
58
73
  def partial_name
59
74
  "paypal_braintree"
60
75
  end
@@ -334,6 +349,10 @@ module SolidusPaypalBraintree
334
349
  params[:options][:paypal] = { payee_email: paypal_email }
335
350
  end
336
351
 
352
+ if source.venmo? && venmo_business_profile_id
353
+ params[:options][:venmo] = { profile_id: venmo_business_profile_id }
354
+ end
355
+
337
356
  if merchant_account_id = merchant_account_for(source, options)
338
357
  params[:merchant_account_id] = merchant_account_id
339
358
  end
@@ -401,5 +420,11 @@ module SolidusPaypalBraintree
401
420
 
402
421
  params
403
422
  end
423
+
424
+ # override with the Venmo business profile that you want to use for transactions,
425
+ # or leave it to be nil if want Braintree to use your default account
426
+ def venmo_business_profile_id
427
+ nil
428
+ end
404
429
  end
405
430
  end
@@ -6,21 +6,30 @@ module SolidusPaypalBraintree
6
6
 
7
7
  PAYPAL = "PayPalAccount"
8
8
  APPLE_PAY = "ApplePayCard"
9
+ VENMO = "VenmoAccount"
9
10
  CREDIT_CARD = "CreditCard"
10
11
 
12
+ enum paypal_funding_source: {
13
+ applepay: 0, bancontact: 1, blik: 2, boleto: 3, card: 4, credit: 5, eps: 6, giropay: 7, ideal: 8,
14
+ itau: 9, maxima: 10, mercadopago: 11, mybank: 12, oxxo: 13, p24: 14, paylater: 15, paypal: 16, payu: 17,
15
+ sepa: 18, sofort: 19, trustly: 20, venmo: 21, verkkopankki: 22, wechatpay: 23, zimpler: 24
16
+ }, _suffix: :funding
17
+
11
18
  belongs_to :user, class_name: ::Spree::UserClassHandle.new, optional: true
12
19
  belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
13
20
  has_many :payments, as: :source, class_name: "Spree::Payment", dependent: :destroy
14
21
 
15
22
  belongs_to :customer, class_name: "SolidusPaypalBraintree::Customer", optional: true
16
23
 
17
- validates :payment_type, inclusion: [PAYPAL, APPLE_PAY, CREDIT_CARD]
24
+ validates :payment_type, inclusion: [PAYPAL, APPLE_PAY, VENMO, CREDIT_CARD]
25
+
26
+ before_save :clear_paypal_funding_source, unless: :paypal?
18
27
 
19
28
  scope(:with_payment_profile, -> { joins(:customer) })
20
29
  scope(:credit_card, -> { where(payment_type: CREDIT_CARD) })
21
30
 
22
31
  delegate :last_4, :card_type, :expiration_month, :expiration_year, :email,
23
- to: :braintree_payment_method, allow_nil: true
32
+ :username, :source_description, to: :braintree_payment_method, allow_nil: true
24
33
 
25
34
  # Aliases to match Spree::CreditCard's interface
26
35
  alias_method :last_digits, :last_4
@@ -68,6 +77,10 @@ module SolidusPaypalBraintree
68
77
  payment_type == PAYPAL
69
78
  end
70
79
 
80
+ def venmo?
81
+ payment_type == VENMO
82
+ end
83
+
71
84
  def reusable?
72
85
  token.present?
73
86
  end
@@ -79,11 +92,23 @@ module SolidusPaypalBraintree
79
92
  def display_number
80
93
  if paypal?
81
94
  email
95
+ elsif venmo?
96
+ username
82
97
  else
83
98
  "XXXX-XXXX-XXXX-#{last_digits.to_s.rjust(4, 'X')}"
84
99
  end
85
100
  end
86
101
 
102
+ def display_paypal_funding_source
103
+ I18n.t(paypal_funding_source,
104
+ scope: 'solidus_paypal_braintree.paypal_funding_sources',
105
+ default: paypal_funding_source)
106
+ end
107
+
108
+ def display_payment_type
109
+ "#{I18n.t('solidus_paypal_braintree.payment_type.label')}: #{friendly_payment_type}"
110
+ end
111
+
87
112
  private
88
113
 
89
114
  def braintree_payment_method
@@ -100,5 +125,9 @@ module SolidusPaypalBraintree
100
125
  def braintree_client
101
126
  @braintree_client ||= payment_method.try(:braintree)
102
127
  end
128
+
129
+ def clear_paypal_funding_source
130
+ self.paypal_funding_source = nil
131
+ end
103
132
  end
104
133
  end
@@ -6,7 +6,7 @@ module SolidusPaypalBraintree
6
6
  class Transaction
7
7
  include ActiveModel::Model
8
8
 
9
- attr_accessor :nonce, :payment_method, :payment_type, :address, :email, :phone
9
+ attr_accessor :nonce, :payment_method, :payment_type, :paypal_funding_source, :address, :email, :phone
10
10
 
11
11
  validates :nonce, presence: true
12
12
  validates :payment_method, presence: true