spree_stripe 1.0.2

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +14 -0
  3. data/README.md +132 -0
  4. data/Rakefile +21 -0
  5. data/app/assets/config/spree_stripe_manifest.js +3 -0
  6. data/app/controllers/spree/api/v2/storefront/stripe/base_controller.rb +21 -0
  7. data/app/controllers/spree/api/v2/storefront/stripe/payment_intents_controller.rb +61 -0
  8. data/app/controllers/spree/api/v2/storefront/stripe/setup_intents_controller.rb +19 -0
  9. data/app/controllers/spree_stripe/apple_pay_domain_verification_controller.rb +11 -0
  10. data/app/controllers/spree_stripe/payment_intents_controller.rb +61 -0
  11. data/app/controllers/spree_stripe/store_controller_decorator.rb +9 -0
  12. data/app/controllers/stripe_event/webhook_controller_decorator.rb +17 -0
  13. data/app/helpers/spree_stripe/base_helper.rb +13 -0
  14. data/app/javascript/spree_stripe/application.js +18 -0
  15. data/app/javascript/spree_stripe/controllers/stripe_button_controller.js +452 -0
  16. data/app/javascript/spree_stripe/controllers/stripe_controller.js +311 -0
  17. data/app/jobs/spree_stripe/base_job.rb +5 -0
  18. data/app/jobs/spree_stripe/complete_order_job.rb +8 -0
  19. data/app/jobs/spree_stripe/create_webhook_endpoint_job.rb +7 -0
  20. data/app/jobs/spree_stripe/register_domain_job.rb +20 -0
  21. data/app/models/spree_stripe/base.rb +6 -0
  22. data/app/models/spree_stripe/credit_card_decorator.rb +13 -0
  23. data/app/models/spree_stripe/custom_domain_decorator.rb +18 -0
  24. data/app/models/spree_stripe/gateway.rb +366 -0
  25. data/app/models/spree_stripe/order_decorator.rb +19 -0
  26. data/app/models/spree_stripe/payment_decorator.rb +46 -0
  27. data/app/models/spree_stripe/payment_intent.rb +66 -0
  28. data/app/models/spree_stripe/payment_method_decorator.rb +18 -0
  29. data/app/models/spree_stripe/payment_methods_webhook_key.rb +8 -0
  30. data/app/models/spree_stripe/payment_source_decorator.rb +9 -0
  31. data/app/models/spree_stripe/payment_sources/affirm.rb +9 -0
  32. data/app/models/spree_stripe/payment_sources/after_pay.rb +13 -0
  33. data/app/models/spree_stripe/payment_sources/alipay.rb +9 -0
  34. data/app/models/spree_stripe/payment_sources/ideal.rb +15 -0
  35. data/app/models/spree_stripe/payment_sources/klarna.rb +9 -0
  36. data/app/models/spree_stripe/payment_sources/link.rb +13 -0
  37. data/app/models/spree_stripe/payment_sources/przelewy24.rb +11 -0
  38. data/app/models/spree_stripe/payment_sources/sepa_debit.rb +13 -0
  39. data/app/models/spree_stripe/store_decorator.rb +24 -0
  40. data/app/models/spree_stripe/webhook_key.rb +14 -0
  41. data/app/presenters/spree_stripe/customer_presenter.rb +35 -0
  42. data/app/presenters/spree_stripe/payment_intent_presenter.rb +103 -0
  43. data/app/serializers/spree/api/v2/platform/affirm_serializer.rb +9 -0
  44. data/app/serializers/spree/api/v2/platform/after_pay_serializer.rb +9 -0
  45. data/app/serializers/spree/api/v2/platform/alipay_serializer.rb +9 -0
  46. data/app/serializers/spree/api/v2/platform/ideal_serializer.rb +9 -0
  47. data/app/serializers/spree/api/v2/platform/klarna_serializer.rb +9 -0
  48. data/app/serializers/spree/api/v2/platform/link_serializer.rb +9 -0
  49. data/app/serializers/spree/api/v2/platform/przelewy24_serializer.rb +9 -0
  50. data/app/serializers/spree/api/v2/platform/sepa_debit_serializer.rb +9 -0
  51. data/app/serializers/spree/v2/storefront/affirm_serializer.rb +7 -0
  52. data/app/serializers/spree/v2/storefront/after_pay_serializer.rb +7 -0
  53. data/app/serializers/spree/v2/storefront/alipay_serializer.rb +7 -0
  54. data/app/serializers/spree/v2/storefront/ideal_serializer.rb +7 -0
  55. data/app/serializers/spree/v2/storefront/klarna_serializer.rb +7 -0
  56. data/app/serializers/spree/v2/storefront/link_serializer.rb +7 -0
  57. data/app/serializers/spree/v2/storefront/payment_intent_serializer.rb +11 -0
  58. data/app/serializers/spree/v2/storefront/przelewy24_serializer.rb +7 -0
  59. data/app/serializers/spree/v2/storefront/sepa_debit_serializer.rb +7 -0
  60. data/app/services/spree_stripe/complete_order.rb +99 -0
  61. data/app/services/spree_stripe/create_gateway_webhooks.rb +55 -0
  62. data/app/services/spree_stripe/create_payment.rb +43 -0
  63. data/app/services/spree_stripe/create_payment_intent.rb +40 -0
  64. data/app/services/spree_stripe/create_setup_intent.rb +20 -0
  65. data/app/services/spree_stripe/create_source.rb +87 -0
  66. data/app/services/spree_stripe/register_domain.rb +22 -0
  67. data/app/services/spree_stripe/webhook_handlers/payment_intent_payment_failed.rb +18 -0
  68. data/app/services/spree_stripe/webhook_handlers/payment_intent_succeeded.rb +13 -0
  69. data/app/services/spree_stripe/webhook_handlers/setup_intent_succeeded.rb +34 -0
  70. data/app/views/spree/admin/payment_methods/configuration_guides/_spree_stripe.html.erb +0 -0
  71. data/app/views/spree/admin/payment_methods/custom_form_fields/_spree_stripe.html.erb +47 -0
  72. data/app/views/spree/admin/payment_methods/descriptions/_spree_stripe.html.erb +15 -0
  73. data/app/views/spree/checkout/payment/_spree_stripe.html.erb +63 -0
  74. data/app/views/spree_stripe/_head.html.erb +2 -0
  75. data/app/views/spree_stripe/_quick_checkout.html.erb +34 -0
  76. data/config/importmap.rb +8 -0
  77. data/config/initializers/spree.rb +8 -0
  78. data/config/initializers/stripe.rb +14 -0
  79. data/config/locales/en.yml +5 -0
  80. data/config/routes.rb +24 -0
  81. data/db/migrate/20250310152812_setup_spree_stripe_models.rb +41 -0
  82. data/lib/generators/spree_stripe/install/install_generator.rb +20 -0
  83. data/lib/spree_stripe/configuration.rb +5 -0
  84. data/lib/spree_stripe/engine.rb +36 -0
  85. data/lib/spree_stripe/factories.rb +3 -0
  86. data/lib/spree_stripe/testing_support/factories/after_pay_payment_source_factory.rb +6 -0
  87. data/lib/spree_stripe/testing_support/factories/alipay_payment_source_factory.rb +6 -0
  88. data/lib/spree_stripe/testing_support/factories/gateway_factory.rb +23 -0
  89. data/lib/spree_stripe/testing_support/factories/ideal_payment_source_factory.rb +6 -0
  90. data/lib/spree_stripe/testing_support/factories/klarna_payment_source_factory.rb +6 -0
  91. data/lib/spree_stripe/testing_support/factories/link_payment_source_factory.rb +6 -0
  92. data/lib/spree_stripe/testing_support/factories/payment_intent_factory.rb +24 -0
  93. data/lib/spree_stripe/testing_support/factories/payment_source_factory.rb +8 -0
  94. data/lib/spree_stripe/testing_support/factories/przelewy24_payment_source_factory.rb +6 -0
  95. data/lib/spree_stripe/testing_support/factories/sepa_debit_payment_source_factory.rb +6 -0
  96. data/lib/spree_stripe/testing_support/factories/webhook_key_factory.rb +23 -0
  97. data/lib/spree_stripe/version.rb +7 -0
  98. data/lib/spree_stripe.rb +14 -0
  99. data/vendor/javascript/@stripe--stripe-js--dist--pure.esm.js.js +4 -0
  100. metadata +295 -0
@@ -0,0 +1,18 @@
1
+ module SpreeStripe
2
+ module WebhookHandlers
3
+ class PaymentIntentPaymentFailed
4
+ def call(event)
5
+ return unless ['affirm', 'afterpay_clearpay'].include?(event.data.object&.last_payment_error&.payment_method&.type)
6
+
7
+ payment_intent = SpreeStripe::PaymentIntent.find_by(stripe_id: event.data.object.id)
8
+ return if payment_intent.nil?
9
+
10
+ order = payment_intent.order
11
+
12
+ return if order.canceled?
13
+
14
+ order.cancel!
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module SpreeStripe
2
+ module WebhookHandlers
3
+ class PaymentIntentSucceeded
4
+ def call(event)
5
+ payment_intent_data = event.data.object
6
+ payment_intent = SpreeStripe::PaymentIntent.find_by(stripe_id: payment_intent_data[:id])
7
+ return if payment_intent.nil?
8
+
9
+ SpreeStripe::CompleteOrderJob.set(wait: 10.seconds).perform_later(payment_intent.id)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module SpreeStripe
2
+ module WebhookHandlers
3
+ class SetupIntentSucceeded
4
+ def call(event)
5
+ setup_intent_data = event.data.object
6
+ gateway_customer = Spree::GatewayCustomer.find_by(profile_id: setup_intent_data.customer)
7
+ return if gateway_customer.nil?
8
+
9
+ spree_payment_method = gateway_customer.payment_method
10
+
11
+ user = gateway_customer.user
12
+ return if user.nil?
13
+
14
+ begin
15
+ stripe_payment_method = Stripe::PaymentMethod.retrieve(
16
+ setup_intent_data.payment_method,
17
+ { api_key: spree_payment_method.preferred_secret_key }
18
+ )
19
+ rescue Stripe::StripeError => e
20
+ Rails.error.report(e, handled: false, context: { event: event, user_id: user.id }, source: 'spree_stripe')
21
+ return
22
+ end
23
+
24
+ SpreeStripe::CreateSource.new(
25
+ stripe_payment_method_details: stripe_payment_method,
26
+ stripe_payment_method_id: setup_intent_data.payment_method,
27
+ stripe_billing_details: stripe_payment_method.billing_details,
28
+ gateway: spree_payment_method,
29
+ user: user
30
+ ).call
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ <% if f.object.persisted? %>
2
+ <div class="form-group mb-0">
3
+ <%= f.label :apple_developer_merchantid_domain_association, 'Apple Pay', class: 'font-weight-bold' %>
4
+
5
+ <% if f.object.apple_developer_merchantid_domain_association.attached? %>
6
+ <div class="form-control d-flex align-items-center justify-content-between pr-1 mb-3 bg-light">
7
+ Verification file uploaded!
8
+ <%= link_to f.object.apple_developer_merchantid_domain_association.url, class: 'btn btn-light btn-sm' do %>
9
+ <%= icon 'download' %>
10
+ Download
11
+ <% end %>
12
+ </div>
13
+ <% else %>
14
+ <p class="form-text">
15
+ To enable Apple Pay, you need to:
16
+ </p>
17
+
18
+ <ol class="mb-4">
19
+ <li>
20
+ go to
21
+ <%= link_to 'payment method domains', 'https://dashboard.stripe.com/settings/payment_method_domains', target: '_blank', class: 'alert-link' %>
22
+ in your Stripe account
23
+ </li>
24
+ <li>
25
+ Click <strong>Add new domain</strong> and input:
26
+ <div class="flex bg-light my-2 p-2 w-50 rounded" data-controller="clipboard" data-clipboard-success-content-value="Copied!">
27
+ <%= text_field_tag :domain, current_store.url_or_custom_domain, readonly: true, class: 'form-control-plaintext py-0', data: { 'clipboard-target': 'source' } %>
28
+ <button data-action="clipboard#copy" class="btn btn-sm btn-light py-1 with-tip" title="Copy to clipboboard" type="button" data-clipboard-target="button">
29
+ <%= icon 'clipboard', width: 16, height: 16, class: 'mr-0 text-muted' %>
30
+ </button>
31
+ </div>
32
+ </li>
33
+ <li>
34
+ Click <strong>Download file</strong> and attach it below.
35
+ </li>
36
+ <li>
37
+ Click <strong>Update</strong> on this page.
38
+ </li>
39
+ <li>
40
+ Click <strong>Verify</strong> to verify if file was uploaded correctly.
41
+ </li>
42
+ </ol>
43
+ <% end %>
44
+
45
+ <%= f.file_field :apple_developer_merchantid_domain_association, class: "form-control-file" %>
46
+ </div>
47
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <p class="mb-1">
2
+ Get paid instantly when someone makes a purchase on your store.
3
+ Supports all major credit cards, wallets and more.
4
+ </p>
5
+
6
+ <div class="d-flex align-items-center">
7
+ <%= payment_method_icon_tag 'visa', class: 'm-1' %>
8
+ <%= payment_method_icon_tag 'master', class: 'm-1' %>
9
+ <%= payment_method_icon_tag 'american_express', class: 'm-1' %>
10
+ <%= payment_method_icon_tag 'apple_pay', class: 'm-1' %>
11
+ <%= payment_method_icon_tag 'google_pay', class: 'm-1' %>
12
+ <%= payment_method_icon_tag 'klarna', class: 'm-1' %>
13
+ <%= payment_method_icon_tag 'afterpay', class: 'm-1' %>
14
+ <%= payment_method_icon_tag 'affirm', class: 'm-1' %>
15
+ </div>
@@ -0,0 +1,63 @@
1
+ <div
2
+ data-controller="checkout-stripe"
3
+ data-checkout-stripe-payment-intent-value="<%= current_stripe_payment_intent.to_json %>"
4
+ data-checkout-stripe-return-url-value="<%= spree.stripe_payment_intent_url(current_stripe_payment_intent) %>"
5
+ data-checkout-stripe-api-key-value="<%= current_stripe_gateway.try(:preferred_publishable_key) %>"
6
+ data-checkout-stripe-order-token-value="<%= @order.token %>"
7
+ data-checkout-stripe-order-email-value="<%= @order.email %>"
8
+ data-checkout-stripe-color-primary-value="<%= hex_color_to_rgb(theme_setting(:primary_color)) %>"
9
+ data-checkout-stripe-color-text-value="<%= hex_color_to_rgb(theme_setting(:text_color)) %>"
10
+ >
11
+ <% payment_sources = checkout_payment_sources(payment_method) %>
12
+
13
+ <% if payment_sources.any? %>
14
+ <div id="existing_cards">
15
+ <div class="form-group">
16
+ <ul class="list-group mb-5 border rounded-md" data-controller="reveal">
17
+ <% payment_sources.each_with_index do |card, index| %>
18
+ <li class="p-0 m-0 border-b" id="<%= spree_dom_id(card) %>">
19
+ <div class="custom-control custom-radio px-5 py-4 flex items-center">
20
+ <label class="flex flex-row items-center cursor-pointer">
21
+ <%= radio_button_tag "order[existing_card]", card.gateway_payment_profile_id, index == 0, { data: {action: "checkout-stripe#initializeStripe reveal#hide", checkout_stripe_target: index == 0 ? "defaultCard" : nil}, class: "mr-5" } %>
22
+ <div class="flex space-x-4 items-center">
23
+ <%= payment_method_icon_tag(card.cc_type) %>
24
+ <span>****<%= card.last_digits %></span>
25
+ <span><%= card.month.to_s.rjust(2, '0') %>/<%= card.year %></span>
26
+ </div>
27
+ </label>
28
+ </div>
29
+ </li>
30
+ <% end %>
31
+ <li>
32
+ <div class="custom-control custom-radio px-5 py-4 flex items-center w-full">
33
+ <label class="flex flex-row items-center w-full pr-8">
34
+ <%= radio_button_tag "order[existing_card]", nil, false, { data: {action: "checkout-stripe#initializeStripe reveal#show"}, class: "mr-5" } %>
35
+ <div class="w-full flex flex-col items-start space-y-4 justify-center">
36
+ <span class="cursor-pointer">
37
+ <%= Spree.t(:add_new_credit_card) %>
38
+ </span>
39
+ <!-- Display a payment form -->
40
+ <div data-checkout-stripe-target="paymentElement" data-reveal-target="item" class="w-full hidden">
41
+ <!--Stripe.js injects the Payment Element-->
42
+ </div>
43
+ </div>
44
+ </label>
45
+ </div>
46
+ </li>
47
+ </ul>
48
+ </div>
49
+ </div>
50
+ <% else %>
51
+ <div data-checkout-stripe-target="paymentElement" class="w-full"></div>
52
+ <% end %>
53
+ <div data-checkout-stripe-target="messageContainer" class="hidden text-danger"></div>
54
+ <div data-checkout-stripe-target="loading" class="p-6">
55
+ <div class="mx-auto flex justify-center lg:w-1/4">
56
+ <svg class="animate-spin h-5 w-5 mr-4 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
57
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
58
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
59
+ </svg>
60
+ <%= Spree.t(:loading) %>...
61
+ </div>
62
+ </div>
63
+ </div>
@@ -0,0 +1,2 @@
1
+ <%= javascript_include_tag 'https://js.stripe.com/v3/', defer: true %>
2
+ <%= javascript_import_module_tag 'application-spree-stripe' %>
@@ -0,0 +1,34 @@
1
+ <% if quick_checkout_enabled?(@order) && current_stripe_gateway.present? && current_stripe_payment_intent.present? %>
2
+ <div id="quick-checkout" class="<%= defined?(container_class) ? container_class : nil %>">
3
+ <%= tag.div class: 'w-full', data: {
4
+ controller: 'checkout-stripe-button',
5
+ checkout_stripe_button_target: 'container',
6
+ checkout_stripe_button_api_key_value: current_stripe_gateway.try(:preferred_publishable_key),
7
+ checkout_stripe_button_order_token_value: @order.token,
8
+ checkout_stripe_button_client_secret_value: current_stripe_payment_intent.client_secret,
9
+ checkout_stripe_button_currency_value: @order.currency.downcase,
10
+ checkout_stripe_button_amount_value: @order.display_total_minus_store_credits.cents,
11
+ checkout_stripe_button_available_countries_value: available_countries_iso.as_json,
12
+ checkout_stripe_button_gift_card_code_value: @order.respond_to?(:gift_card) ? @order.gift_card&.code : nil,
13
+ checkout_stripe_button_gift_card_amount_value: @order.respond_to?(:gift_card_total) ? @order.gift_card_total : nil,
14
+ checkout_stripe_button_border_radius_value: theme_setting(:button_border_radius),
15
+ checkout_stripe_button_height_value: defined?(height) ? height : nil,
16
+ checkout_stripe_button_theme_value: defined?(theme) ? theme : nil,
17
+ checkout_stripe_button_max_rows_value: defined?(max_rows) ? max_rows : nil,
18
+ checkout_stripe_button_max_columns_value: defined?(max_columns) ? max_columns : nil,
19
+ checkout_stripe_button_button_width_value: defined?(button_width) ? button_width : nil,
20
+ checkout_stripe_button_store_url_value: current_store.formatted_url_or_custom_domain,
21
+ checkout_stripe_button_return_url_value: spree.stripe_payment_intent_url(current_stripe_payment_intent)
22
+ } do %>
23
+ <div id="payment-request-button"></div>
24
+
25
+ <div class="relative group-[.no-separator]:hidden flex py-5 items-center ">
26
+ <div class="flex-grow border-t border-default"></div>
27
+ <span class="flex-shrink mx-4 text-gray-500 text-sm">
28
+ <%= Spree.t(:or_continue_below) %>
29
+ </span>
30
+ <div class="flex-grow border-t border-default"></div>
31
+ </div>
32
+ <% end %>
33
+ </div>
34
+ <% end%>
@@ -0,0 +1,8 @@
1
+ pin 'application-spree-stripe', to: 'spree_stripe/application.js', preload: false
2
+
3
+ pin '@stripe/stripe-js/pure', to: '@stripe--stripe-js--dist--pure.esm.js.js' # @1.46.0
4
+
5
+ pin_all_from SpreeStripe::Engine.root.join('app/javascript/spree_stripe/controllers'),
6
+ under: 'spree_stripe/controllers',
7
+ to: 'spree_stripe/controllers',
8
+ preload: 'application-spree-stripe'
@@ -0,0 +1,8 @@
1
+ Rails.application.config.after_initialize do
2
+ Rails.application.config.spree.payment_methods << SpreeStripe::Gateway
3
+
4
+ if Rails.application.config.respond_to?(:spree_storefront)
5
+ Rails.application.config.spree_storefront.head_partials << 'spree_stripe/head'
6
+ Rails.application.config.spree_storefront.quick_checkout_partials << 'spree_stripe/quick_checkout'
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ # for local setup you need to use Stripe-CLI and change the key manually
2
+ StripeEvent.signing_secret = ENV['STRIPE_SIGNING_SECRET'] if ENV['STRIPE_SIGNING_SECRET'].present?
3
+
4
+ Stripe.log_level = ENV.fetch('STRIPE_LOG_LEVEL', 'debug')
5
+ Stripe.api_version = '2023-10-16'
6
+ Stripe.set_app_info('Spree Stripe', version: Spree.version, url: 'https://spreecommerce.org')
7
+
8
+ Rails.application.config.after_initialize do
9
+ StripeEvent.configure do |events|
10
+ events.subscribe 'payment_intent.succeeded', SpreeStripe::WebhookHandlers::PaymentIntentSucceeded.new
11
+ events.subscribe 'payment_intent.payment_failed', SpreeStripe::WebhookHandlers::PaymentIntentPaymentFailed.new
12
+ events.subscribe 'setup_intent.succeeded', SpreeStripe::WebhookHandlers::SetupIntentSucceeded.new
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ hello: "Hello world"
data/config/routes.rb ADDED
@@ -0,0 +1,24 @@
1
+ Spree::Core::Engine.add_routes do
2
+ # Stripe payment intents
3
+ get '/stripe/payment_intents/:id', to: '/spree_stripe/payment_intents#show',
4
+ as: :stripe_payment_intent,
5
+ controller: '/spree_stripe/payment_intents'
6
+
7
+ # Apple Pay domain verification certificate for Apple Pay
8
+ get '/.well-known/apple-developer-merchantid-domain-association' => '/spree_stripe/apple_pay_domain_verification#show'
9
+
10
+ # Stripe webhooks
11
+ mount StripeEvent::Engine, at: '/stripe'
12
+
13
+ # Storefront API
14
+ namespace :api, defaults: { format: 'json' } do
15
+ namespace :v2 do
16
+ namespace :storefront do
17
+ namespace :stripe do
18
+ resources :setup_intents, only: %i[create]
19
+ resources :payment_intents, only: %i[show create update]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ class SetupSpreeStripeModels < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :spree_stripe_payment_intents do |t|
4
+ t.decimal :amount, precision: 10, scale: 2, default: '0.0', null: false
5
+ t.bigint :order_id, null: false
6
+ t.bigint :payment_method_id, null: false
7
+ t.string :stripe_id, null: false
8
+ t.string :client_secret, null: false
9
+ t.string :customer_id
10
+ t.string :ephemeral_key_secret
11
+ t.string :stripe_payment_method_id
12
+
13
+ t.timestamps
14
+
15
+ t.index ['order_id', 'stripe_id'], name: 'index_spree_stripe_payment_intents_on_order_id_and_stripe_id', unique: true
16
+ t.index ['order_id'], name: 'index_spree_stripe_payment_intents_on_order_id'
17
+ t.index ['payment_method_id'], name: 'index_spree_stripe_payment_intents_on_payment_method_id'
18
+ end
19
+
20
+ create_table :spree_stripe_webhook_keys do |t|
21
+ t.string :stripe_id, null: false
22
+ t.string :signing_secret, null: false
23
+
24
+ t.timestamps
25
+
26
+ t.index ['signing_secret'], name: 'index_spree_stripe_webhook_keys_on_signing_secret', unique: true
27
+ t.index ['stripe_id'], name: 'index_spree_stripe_webhook_keys_on_stripe_id', unique: true
28
+ end
29
+
30
+ create_table :spree_stripe_payment_methods_webhook_keys do |t|
31
+ t.bigint :payment_method_id, null: false
32
+ t.bigint :webhook_key_id, null: false
33
+
34
+ t.timestamps
35
+
36
+ t.index ['payment_method_id', 'webhook_key_id'], name: 'index_payment_method_id_webhook_key_id_uniqueness', unique: true
37
+ t.index ['payment_method_id'], name: 'index_payment_methods_webhook_keys_on_payment_method_id'
38
+ t.index ['webhook_key_id'], name: 'index_payment_methods_webhook_keys_on_webhook_key_id'
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module SpreeStripe
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ class_option :migrate, type: :boolean, default: true
5
+
6
+ def add_migrations
7
+ run 'bundle exec rake railties:install:migrations FROM=spree_stripe'
8
+ end
9
+
10
+ def run_migrations
11
+ run_migrations = options[:migrate] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
12
+ if run_migrations
13
+ run 'bin/rails db:migrate'
14
+ else
15
+ puts 'Skipping rails db:migrate, don\'t forget to run it!'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module SpreeStripe
2
+ class Configuration < Spree::Preferences::Configuration
3
+ preference :supported_webhook_events, :array, default: %w[payment_intent.payment_failed payment_intent.succeeded setup_intent.succeeded]
4
+ end
5
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeStripe
2
+ class Engine < Rails::Engine
3
+ require 'spree/core'
4
+ isolate_namespace Spree
5
+ engine_name 'spree_stripe'
6
+
7
+ # use rspec for tests
8
+ config.generators do |g|
9
+ g.test_framework :rspec
10
+ end
11
+
12
+ initializer 'spree_stripe.environment', before: :load_config_initializers do |_app|
13
+ SpreeStripe::Config = SpreeStripe::Configuration.new
14
+ end
15
+
16
+ initializer 'spree_stripe.assets' do |app|
17
+ app.config.assets.paths << root.join('app/javascript')
18
+ app.config.assets.paths << root.join('vendor/javascript')
19
+ app.config.assets.precompile += %w[spree_stripe_manifest]
20
+ end
21
+
22
+ initializer 'spree_stripe.importmap', before: 'importmap' do |app|
23
+ app.config.importmap.paths << root.join('config/importmap.rb')
24
+ # https://github.com/rails/importmap-rails?tab=readme-ov-file#sweeping-the-cache-in-development-and-test
25
+ app.config.importmap.cache_sweepers << root.join('app/javascript')
26
+ end
27
+
28
+ def self.activate
29
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
30
+ Rails.configuration.cache_classes ? require(c) : load(c)
31
+ end
32
+ end
33
+
34
+ config.to_prepare(&method(:activate).to_proc)
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/testing_support/factories/**"].each do |f|
2
+ load File.expand_path(f)
3
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :after_pay_payment_source, class: SpreeStripe::PaymentSources::AfterPay do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::AfterPay' }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :alipay_payment_source, class: SpreeStripe::PaymentSources::Alipay do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::Alipay' }
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ FactoryBot.define do
2
+ factory :stripe_gateway, parent: :payment_method, class: SpreeStripe::Gateway do
3
+ name { 'Stripe' }
4
+ type { 'SpreeStripe::Gateway' }
5
+
6
+ preferences do
7
+ {
8
+ publishable_key: ENV.fetch('STRIPE_PUBLISHABLE_KEY', 'pk_test_1234567890'),
9
+ secret_key: ENV.fetch('STRIPE_SECRET_KEY', 'sk_test_1234567890')
10
+ }
11
+ end
12
+
13
+ trait :with_apple_domain_association_file do
14
+ transient do
15
+ apple_domain_association_file_path do
16
+ File.join(SpreeStripe::Engine.root, 'spec', 'fixtures', 'files', 'apple-domain-association-file.txt')
17
+ end
18
+ end
19
+
20
+ apple_developer_merchantid_domain_association { Rack::Test::UploadedFile.new(apple_domain_association_file_path) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :ideal_payment_source, class: SpreeStripe::PaymentSources::Ideal do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::Ideal' }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :klarna_payment_source, class: SpreeStripe::PaymentSources::Klarna do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::Klarna' }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :link_payment_source, class: SpreeStripe::PaymentSources::Link do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::Link' }
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: spree_stripe_payment_intents
4
+ #
5
+ # id :bigint not null, primary key
6
+ # amount :decimal(10, 2) default(0.0), not null
7
+ # client_secret :string not null
8
+ # ephemeral_key_secret :string
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ # customer_id :string
12
+ # order_id :bigint not null
13
+ # payment_method_id :bigint not null
14
+ # stripe_id :string not null
15
+ # stripe_payment_method_id :string
16
+ #
17
+ FactoryBot.define do
18
+ factory :payment_intent, class: SpreeStripe::PaymentIntent do
19
+ stripe_id { 'pi_123' }
20
+ client_secret { 'cs_123' }
21
+ order { create(:order_with_line_items) }
22
+ payment_method { create(:stripe_gateway, stores: [order.store]) }
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :payment_source, class: Spree::PaymentSource do
3
+ association :payment_method, factory: :payment_method
4
+ gateway_payment_profile_id { 'pm_1adfg34fgfdf5245' }
5
+ type { 'SpreeStripe::PaymentSources::Alipay' }
6
+ user
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :przelewy24_payment_source, class: SpreeStripe::PaymentSources::Przelewy24 do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::Przelewy24' }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :sepa_debit_payment_source, class: SpreeStripe::PaymentSources::SepaDebit do
3
+ payment_method { create(:stripe_gateway) }
4
+ type { 'SpreeStripe::PaymentSources::SepaDebit' }
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: spree_stripe_webhook_keys
4
+ #
5
+ # id :bigint not null, primary key
6
+ # kind :integer default("direct")
7
+ # signing_secret :string not null
8
+ # created_at :datetime not null
9
+ # updated_at :datetime not null
10
+ # stripe_id :string not null
11
+ #
12
+ FactoryBot.define do
13
+ factory :stripe_webhook_key, class: SpreeStripe::WebhookKey do
14
+ stripe_id { generate(:random_string) }
15
+ signing_secret { generate(:random_string) }
16
+
17
+ before(:create) do |wk|
18
+ if wk.payment_methods.empty?
19
+ wk.payment_methods << create(:stripe_gateway)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ module SpreeStripe
2
+ VERSION = '1.0.2'.freeze
3
+
4
+ def gem_version
5
+ Gem::Version.new(VERSION)
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'spree_core'
2
+ require 'spree_extension'
3
+ require 'spree_stripe/engine'
4
+ require 'spree_stripe/version'
5
+ require 'spree_stripe/configuration'
6
+
7
+ require 'stripe'
8
+ require 'stripe_event'
9
+
10
+ module SpreeStripe
11
+ def self.queue
12
+ 'spree_stripe'
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ // @stripe/stripe-js/dist/pure.esm.js@1.46.0 downloaded from https://ga.jspm.io/npm:@stripe/stripe-js@1.46.0/dist/pure.esm.js
2
+
3
+ function _typeof(r){_typeof="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"===typeof Symbol&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r};return _typeof(r)}var r="https://js.stripe.com/v3";var e=/^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;var t="loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used";var n=function findScript(){var t=document.querySelectorAll('script[src^="'.concat(r,'"]'));for(var n=0;n<t.length;n++){var o=t[n];if(e.test(o.src))return o}return null};var o=function injectScript(e){var t=e&&!e.advancedFraudSignals?"?advancedFraudSignals=false":"";var n=document.createElement("script");n.src="".concat(r).concat(t);var o=document.head||document.body;if(!o)throw new Error("Expected document.body not to be null. Stripe.js requires a <body> element.");o.appendChild(n);return n};var a=function registerWrapper(r,e){r&&r._registerWrapper&&r._registerWrapper({name:"stripe-js",version:"1.46.0",startTime:e})};var i=null;var c=function loadScript(r){if(null!==i)return i;i=new Promise((function(e,a){if("undefined"!==typeof window){window.Stripe&&r&&console.warn(t);if(window.Stripe)e(window.Stripe);else try{var i=n();i&&r?console.warn(t):i||(i=o(r));i.addEventListener("load",(function(){window.Stripe?e(window.Stripe):a(new Error("Stripe.js not available"))}));i.addEventListener("error",(function(){a(new Error("Failed to load Stripe.js"))}))}catch(r){a(r);return}}else e(null)}));return i};var l=function initStripe(r,e,t){if(null===r)return null;var n=r.apply(void 0,e);a(n,t);return n};var s=function validateLoadParams(r){var e="invalid load parameters; expected object of shape\n\n {advancedFraudSignals: boolean}\n\nbut received\n\n ".concat(JSON.stringify(r),"\n");if(null===r||"object"!==_typeof(r))throw new Error(e);if(1===Object.keys(r).length&&"boolean"===typeof r.advancedFraudSignals)return r;throw new Error(e)};var d;var u=false;var p=function loadStripe(){for(var r=arguments.length,e=new Array(r),t=0;t<r;t++)e[t]=arguments[t];u=true;var n=Date.now();return c(d).then((function(r){return l(r,e,n)}))};p.setLoadParameters=function(r){if(u)throw new Error("You cannot change load parameters after calling loadStripe");d=s(r)};export{p as loadStripe};
4
+