workarea-stripe 1.0.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.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.eslintrc +36 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  5. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  7. data/.gitignore +19 -0
  8. data/CHANGELOG.md +67 -0
  9. data/CODE_OF_CONDUCT.md +3 -0
  10. data/CONTRIBUTING.md +3 -0
  11. data/Gemfile +8 -0
  12. data/LICENSE +52 -0
  13. data/README.md +90 -0
  14. data/Rakefile +60 -0
  15. data/app/assets/javascripts/workarea/storefront/stripe/config.js.erb +29 -0
  16. data/app/assets/javascripts/workarea/storefront/stripe/modules/bogus_stripe_elements.js +55 -0
  17. data/app/assets/javascripts/workarea/storefront/stripe/modules/stripe_elements.js +113 -0
  18. data/app/assets/stylesheets/workarea/stripe/components/_checkout_payment_stripe.scss +19 -0
  19. data/app/controllers/workarea/storefront/users/credit_cards_controller.decorator +10 -0
  20. data/app/models/workarea/checkout/credit_card_params.decorator +30 -0
  21. data/app/models/workarea/checkout/steps/payment.decorator +16 -0
  22. data/app/models/workarea/payment.decorator +18 -0
  23. data/app/models/workarea/payment/authorize/credit_card.decorator +24 -0
  24. data/app/models/workarea/payment/credit_card.decorator +27 -0
  25. data/app/models/workarea/payment/purchase/credit_card.decorator +24 -0
  26. data/app/models/workarea/payment/store_credit_card.decorator +54 -0
  27. data/app/views/workarea/storefront/checkouts/_stripe_dialog.html.haml +2 -0
  28. data/app/views/workarea/storefront/checkouts/_stripe_form.html.haml +19 -0
  29. data/app/views/workarea/storefront/stripe/_stripe_js.html.haml +1 -0
  30. data/app/views/workarea/storefront/users/credit_cards/_stripe_form.html.haml +12 -0
  31. data/app/views/workarea/storefront/users/credit_cards/new.html.haml +42 -0
  32. data/bin/rails +20 -0
  33. data/config/initializers/appends.rb +33 -0
  34. data/config/initializers/workarea.rb +9 -0
  35. data/config/routes.rb +2 -0
  36. data/lib/active_merchant/billing/bogus_stripe_gateway.rb +29 -0
  37. data/lib/workarea/stripe.rb +46 -0
  38. data/lib/workarea/stripe/engine.rb +8 -0
  39. data/lib/workarea/stripe/version.rb +5 -0
  40. data/test/dummy/Rakefile +6 -0
  41. data/test/dummy/app/assets/config/manifest.js +4 -0
  42. data/test/dummy/app/assets/images/.keep +0 -0
  43. data/test/dummy/app/assets/javascripts/application.js +13 -0
  44. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  45. data/test/dummy/app/controllers/application_controller.rb +3 -0
  46. data/test/dummy/app/controllers/concerns/.keep +0 -0
  47. data/test/dummy/app/helpers/application_helper.rb +2 -0
  48. data/test/dummy/app/jobs/application_job.rb +2 -0
  49. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  50. data/test/dummy/app/models/concerns/.keep +0 -0
  51. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  53. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  54. data/test/dummy/bin/bundle +3 -0
  55. data/test/dummy/bin/rails +4 -0
  56. data/test/dummy/bin/rake +4 -0
  57. data/test/dummy/bin/setup +30 -0
  58. data/test/dummy/bin/update +26 -0
  59. data/test/dummy/bin/yarn +11 -0
  60. data/test/dummy/config.ru +5 -0
  61. data/test/dummy/config/application.rb +30 -0
  62. data/test/dummy/config/boot.rb +5 -0
  63. data/test/dummy/config/cable.yml +10 -0
  64. data/test/dummy/config/environment.rb +5 -0
  65. data/test/dummy/config/environments/development.rb +51 -0
  66. data/test/dummy/config/environments/production.rb +88 -0
  67. data/test/dummy/config/environments/test.rb +44 -0
  68. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  69. data/test/dummy/config/initializers/assets.rb +14 -0
  70. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  71. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  72. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  73. data/test/dummy/config/initializers/inflections.rb +16 -0
  74. data/test/dummy/config/initializers/mime_types.rb +4 -0
  75. data/test/dummy/config/initializers/workarea.rb +5 -0
  76. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  77. data/test/dummy/config/locales/en.yml +33 -0
  78. data/test/dummy/config/puma.rb +56 -0
  79. data/test/dummy/config/routes.rb +5 -0
  80. data/test/dummy/config/secrets.yml +32 -0
  81. data/test/dummy/config/spring.rb +6 -0
  82. data/test/dummy/db/seeds.rb +2 -0
  83. data/test/dummy/lib/assets/.keep +0 -0
  84. data/test/dummy/log/.keep +0 -0
  85. data/test/dummy/package.json +5 -0
  86. data/test/integration/workarea/storefront/place_order_integration_test.decorator +19 -0
  87. data/test/integration/workarea/storefront/users/credit_cards_integration_test.decorator +26 -0
  88. data/test/models/workarea/checkout/credit_card_params_test.rb +10 -0
  89. data/test/models/workarea/checkout/steps/payment_test.decorator +21 -0
  90. data/test/models/workarea/payment/authorize/credit_card_test.decorator +24 -0
  91. data/test/models/workarea/payment/capture_test.decorator +10 -0
  92. data/test/models/workarea/payment/credit_card_integration_test.decorator +26 -0
  93. data/test/models/workarea/payment/purchase/credit_card_test.decorator +25 -0
  94. data/test/models/workarea/payment/refund/credit_card_test.decorator +20 -0
  95. data/test/models/workarea/payment/refund_test.decorator +10 -0
  96. data/test/models/workarea/payment/store_credit_card_test.decorator +9 -0
  97. data/test/models/workarea/payment_test.decorator +20 -0
  98. data/test/support/stripe_public_key.rb +1 -0
  99. data/test/system/workarea/storefront/analytics_system_test.decorator +5 -0
  100. data/test/system/workarea/storefront/credit_cards_system_test.decorator +5 -0
  101. data/test/system/workarea/storefront/digital_products_system_test.decorator +67 -0
  102. data/test/system/workarea/storefront/guest_checkout_system_test.decorator +107 -0
  103. data/test/system/workarea/storefront/logged_in_checkout_system_test.decorator +5 -0
  104. data/test/system/workarea/storefront/no_js_system_test.decorator +5 -0
  105. data/test/system/workarea/storefront/stripe_payment_system_test.rb +97 -0
  106. data/test/teaspoon_env.rb +6 -0
  107. data/test/test_helper.rb +10 -0
  108. data/test/workers/workarea/send_refund_email_test.decorator +29 -0
  109. data/workarea-stripe.gemspec +21 -0
  110. metadata +171 -0
@@ -0,0 +1,29 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ /**
5
+ * Config Variables
6
+ */
7
+
8
+ WORKAREA.config.stripe = {
9
+ publicKey: '<%= Workarea.config.stripe[:public_key] %>',
10
+ cardStyles : {
11
+ base: {
12
+ color: "#32325d",
13
+ fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
14
+ fontSmoothing: 'antialiased',
15
+ fontSize: '16px',
16
+ '::placeholder': {
17
+ color: '#aab7c4'
18
+ }
19
+ },
20
+ invalid: {
21
+ color: '#fa755a',
22
+ iconColor: '#fa755a'
23
+ }
24
+ },
25
+ bogusToken: {
26
+ 'id': 'tok_1DCtE5DDRzjJ0gxwvRPfQ57m'
27
+ }
28
+ };
29
+ })();
@@ -0,0 +1,55 @@
1
+ WORKAREA.registerModule('bogusStripeElements', (function () {
2
+
3
+ 'use strict';
4
+
5
+ var createStripeTokenInput = function() {
6
+ return $('<input>').attr({
7
+ 'type': 'hidden',
8
+ 'name': 'stripeToken',
9
+ 'value': WORKAREA.config.stripe.bogusToken.id
10
+ });
11
+ },
12
+
13
+ paymentMethod = function($form) {
14
+ return $('input[name="payment"]:checked', $form).val();
15
+ },
16
+
17
+ formNeedsTokenization = function($form) {
18
+ if ($form.attr('action') === '/checkout/place_order') {
19
+ return paymentMethod($form) === 'stripe';
20
+ } else {
21
+ return true;
22
+ }
23
+ },
24
+
25
+ setupPaymentForm = function (index, paymentForm) {
26
+ var $form = $(paymentForm),
27
+ hiddenInput = createStripeTokenInput();
28
+
29
+ if (formNeedsTokenization($form)) {
30
+ $form.append(hiddenInput);
31
+ }
32
+ },
33
+
34
+ disable = function() {
35
+ $('.checkout-payment__primary-method').css('display', 'none');
36
+ $('.checkout-payment__primary-method--stripe').css('display', 'block');
37
+ },
38
+
39
+ /**
40
+ * @method
41
+ * @name init
42
+ * @memberof WORKAREA.bogusStripeElements
43
+ */
44
+ init = function ($scope) {
45
+ var $stripePayment = $('[data-stripe-card-input]', $scope),
46
+ $paymentForm = $stripePayment.closest('form');
47
+
48
+ $paymentForm.each(setupPaymentForm);
49
+ };
50
+
51
+ return {
52
+ init: init,
53
+ disable: disable
54
+ };
55
+ }()));
@@ -0,0 +1,113 @@
1
+ WORKAREA.registerModule('stripeElements', (function () {
2
+
3
+ 'use strict';
4
+
5
+ if (!WORKAREA.config.stripe.publicKey) { return; }
6
+
7
+ var stripe = Stripe(WORKAREA.config.stripe.publicKey),
8
+ elements = stripe.elements(),
9
+ card,
10
+
11
+ createStripeTokenInput = function(token) {
12
+ return $('<input>').attr({
13
+ 'type': 'hidden',
14
+ 'name': 'stripeToken',
15
+ 'value': token.id
16
+ });
17
+ },
18
+
19
+ stripeTokenHandler = function($form, token) {
20
+ var hiddenInput = createStripeTokenInput(token);
21
+
22
+ $form
23
+ .append(hiddenInput)
24
+ .unbind('submit')
25
+ .submit();
26
+ },
27
+
28
+ paymentMethod = function($form) {
29
+ return $('input[name="payment"]:checked', $form).val();
30
+ },
31
+
32
+ formNeedsTokenization = function($form) {
33
+ if ($form.attr('action') === '/checkout/place_order') {
34
+ return paymentMethod($form) === 'stripe';
35
+ } else {
36
+ return true;
37
+ }
38
+ },
39
+
40
+ tokenizeFormOnSubmit = function ($form, event) {
41
+ if (formNeedsTokenization($form)) {
42
+ event.preventDefault();
43
+
44
+ stripe.createToken(card).then(function (result) {
45
+ if (result.error) {
46
+ var errorElement = $('#card_errors');
47
+ errorElement.textContent = result.error.message;
48
+ } else {
49
+ stripeTokenHandler($form, result.token);
50
+ }
51
+ });
52
+ }
53
+ },
54
+
55
+ setupPaymentForm = function ($paymentForm) {
56
+ $paymentForm.on('submit', _.partial(tokenizeFormOnSubmit, $paymentForm));
57
+ },
58
+
59
+ displayErrorsOnChange = function(event) {
60
+ var displayError = $('#card_errors');
61
+
62
+ if (event.error) {
63
+ displayError.textContent = event.error.message;
64
+ } else {
65
+ displayError.textContent = '';
66
+ }
67
+ },
68
+
69
+ setIframeId = function($container) {
70
+ $('iframe', $container).attr('id', 'stripe-payment-frame');
71
+ },
72
+
73
+ // Inline styles are necessary here to override an inline style sometimes
74
+ // added by stripe elements. It seems stripe attempts to hide the card
75
+ // form by setting width: 1px !important; on the iframe, causing the form
76
+ // not to render in some checkouts. The iframe should always fill it's container
77
+ ensureIframeWidth = function ($container) {
78
+ $('iframe', $container).css('width', '100%');
79
+ },
80
+
81
+ setupCardInput = function($cardInput) {
82
+ var cardInputID = $cardInput.attr('id');
83
+
84
+ card = elements.create('card', { style: WORKAREA.config.stripe.cardStyles });
85
+ card.mount('#'+cardInputID);
86
+ card.addEventListener('change', displayErrorsOnChange);
87
+ setIframeId($cardInput);
88
+ ensureIframeWidth($cardInput);
89
+ },
90
+
91
+ setupStripe = function (index, cardInput) {
92
+ var $cardInput = $(cardInput),
93
+ $paymentForm = $cardInput.closest('form');
94
+
95
+ setupCardInput($cardInput);
96
+ setupPaymentForm($paymentForm);
97
+ },
98
+
99
+ /**
100
+ * @method
101
+ * @name init
102
+ * @memberof WORKAREA.stripeElements
103
+ */
104
+ init = function ($scope) {
105
+ if (!WORKAREA.config.stripe.publicKey) { return; }
106
+
107
+ $('[data-stripe-card-input]', $scope).each(setupStripe);
108
+ };
109
+
110
+ return {
111
+ init: init
112
+ };
113
+ }()));
@@ -0,0 +1,19 @@
1
+ /*------------------------------------*\
2
+ #CHECKOUT-PAYMENT
3
+ \*------------------------------------*/
4
+
5
+ /**
6
+ * Extend the `.checkout-payment` class from Workarea to hide new card form and
7
+ * display the stripe payment form.
8
+ *
9
+ * This is not appended in test to allow base tests to run normally.
10
+ */
11
+ .checkout-payment {}
12
+
13
+ .checkout-payment__primary-method {
14
+ display: none;
15
+ }
16
+
17
+ .checkout-payment__primary-method--stripe {
18
+ display: block;
19
+ }
@@ -0,0 +1,10 @@
1
+ module Workarea
2
+ decorate Storefront::Users::CreditCardsController, with: :stripe do
3
+ def credit_card_params
4
+ cc_params = super
5
+ cc_params.merge!(stripe_token: params["stripeToken"]) if params["stripeToken"].present?
6
+
7
+ cc_params
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ module Workarea
2
+ decorate Checkout::CreditCardParams, with: :stripe do
3
+
4
+ # HACK ALERT:
5
+ # Stripe does not give enough data to persist a credit card
6
+ # To get around this set fake data which is then updated
7
+ # when the card is stored on the gateway
8
+ def stripe_params
9
+ {
10
+ stripe_token: params["stripeToken"],
11
+ number: '4111111111111111',
12
+ display_number: '1111',
13
+ year: Time.current.year + 1,
14
+ month: Time.current.month,
15
+ first_name: 'stub',
16
+ last_name: 'stub',
17
+ cvv: '000'
18
+
19
+ }
20
+ end
21
+
22
+ def stripe_token
23
+ params["stripeToken"]
24
+ end
25
+
26
+ def stripe?
27
+ params["stripeToken"].present?
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ module Workarea
2
+ decorate Checkout::Steps::Payment, with: :stripe do
3
+ def set_credit_card(params)
4
+ payment.clear_credit_card
5
+ card_params = Checkout::CreditCardParams.new(params)
6
+
7
+ if card_params.stripe?
8
+ payment.set_credit_card(card_params.stripe_params)
9
+ elsif card_params.new? && card_params.number.present?
10
+ payment.set_credit_card(card_params.new_card)
11
+ elsif card_params.saved?
12
+ payment.set_credit_card(saved_card_id: card_params.saved_card_id)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Workarea
2
+ decorate Payment, with: :stripe do
3
+ def set_credit_card(attrs)
4
+ build_credit_card unless credit_card
5
+ credit_card.saved_card_id = nil
6
+ credit_card.attributes = attrs.slice(
7
+ :month,
8
+ :year,
9
+ :saved_card_id,
10
+ :number,
11
+ :cvv,
12
+ :amount,
13
+ :stripe_token
14
+ )
15
+ save
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module Workarea
2
+ decorate Payment::Authorize::CreditCard, with: :stripe do
3
+ def transaction_options
4
+ {
5
+ customer: tender.payment.profile.gateway_id,
6
+ description: order_content
7
+ }
8
+ end
9
+
10
+ private
11
+
12
+ def order
13
+ @order ||= Workarea::Order.find(tender.payment.id)
14
+ end
15
+
16
+ def order_content
17
+ @order_content ||= begin
18
+ contents = order.items.map(&:sku).join(',')
19
+ "Env: #{Rails.env}, Items: #{contents}"
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Workarea
2
+ decorate Payment::Tender::CreditCard, Payment::SavedCreditCard, with: :stripe do
3
+ decorated do
4
+ field :stripe_token, type: String
5
+ end
6
+
7
+ def stripe?
8
+ stripe_token.present?
9
+ end
10
+
11
+ def set_display_number
12
+ if !stripe? && number.present?
13
+ self.display_number = ActiveMerchant::Billing::CreditCard.mask(number)
14
+ end
15
+ end
16
+
17
+ def set_issuer
18
+ if !stripe? && number.present?
19
+ brand = ActiveMerchant::Billing::CreditCard.brand?(number)
20
+
21
+ if brand.present?
22
+ self.issuer = Workarea.config.credit_card_issuers[brand].to_s
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module Workarea
2
+ decorate Payment::Purchase::CreditCard, with: :stripe do
3
+ def transaction_options
4
+ {
5
+ customer: tender.payment.profile.gateway_id,
6
+ description: order_content
7
+ }
8
+ end
9
+
10
+ private
11
+
12
+ def order
13
+ @order ||= Workarea::Order.find(tender.payment.id)
14
+ end
15
+
16
+ def order_content
17
+ @order_content ||= begin
18
+ contents = order.items.map(&:sku).join(',')
19
+ "Env: #{Rails.env}, Items: #{contents}"
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ module Workarea
2
+ decorate Payment::StoreCreditCard, with: :stripe do
3
+ def perform!
4
+ return true if @credit_card.token.present?
5
+
6
+ response = handle_active_merchant_errors do
7
+ gateway.store(@credit_card.stripe_token, store_options)
8
+ end
9
+
10
+ params = response.params
11
+
12
+ stripe_source = card_details(params)
13
+
14
+ # workarea does not have access to the payment form
15
+ # to get the cc information because it is hosted by stripe.
16
+ # Add the information to the credit card that is
17
+ # returned from the tokenization request.
18
+ update_credit_card(stripe_source)
19
+
20
+ # store the customer ID on the profile
21
+ if @credit_card.profile.gateway_id.blank?
22
+ @credit_card.profile.gateway_id = response.params["id"]
23
+ @credit_card.profile.save!
24
+ end
25
+
26
+ response.success?
27
+ end
28
+
29
+ private
30
+
31
+ def store_options
32
+ {
33
+ email: @credit_card.profile.email,
34
+ customer: @credit_card.profile.gateway_id
35
+ }
36
+ end
37
+
38
+ def card_details(params)
39
+ if params["sources"].present?
40
+ params["sources"]["data"].first
41
+ else
42
+ params
43
+ end
44
+ end
45
+
46
+ def update_credit_card(stripe_source)
47
+ @credit_card.display_number = stripe_source["last4"]
48
+ @credit_card.month = stripe_source["exp_month"]
49
+ @credit_card.year = stripe_source["exp_year"]
50
+ @credit_card.issuer = stripe_source["brand"]
51
+ @credit_card.token = stripe_source["id"]
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,2 @@
1
+ %script.stripe-button{"data-amount" => "999", "data-description" => "Widget", "data-image" => "https://stripe.com/img/documentation/checkout/marketplace.png", "data-key" => "pk_test_MN4pzRoNbCa2lMUVVAQwpoiJ", "data-locale" => "auto", "data-name" => "Demo Site", "data-email" => step.order.email, :src => "https://checkout.stripe.com/checkout.js"}
2
+ :cdata
@@ -0,0 +1,19 @@
1
+ .checkout-payment__primary-method.checkout-payment__primary-method--stripe{ class: @step.using_new_card? ? 'checkout-payment__primary-method--selected': nil}
2
+ .button-property
3
+ .value
4
+ = radio_button_tag 'payment', 'stripe', @step.using_new_card?, data: { analytics: checkout_payment_selected_analytics_data('stripe').to_json }
5
+ = label_tag 'payment[stripe]', nil, class: 'button-property__name' do
6
+ %span.button-property__text= t('workarea.storefront.checkouts.new_credit_card')
7
+ %p.checkout-payment__primary-method-description
8
+ = all_payment_icons
9
+ .checkout-payment__primary-method-edit
10
+ .grid
11
+ .grid__cell.grid__cell--66-at-medium
12
+ .property
13
+ = label_tag 'credit_card[element]', nil, class: 'property__name' do
14
+ %span.property__requirement.property__requirement--required= t('workarea.storefront.forms.required')
15
+ %span.property__text= t('workarea.storefront.credit_cards.card_number')
16
+ .value
17
+ #card_element{ data: { stripe_card_input: '' } }
18
+ -# Stripe element will be inserted here
19
+ #card_errors.value__error{ role: 'alert' }