workarea-stripe 1.0.0

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