From 3063b041d15670702f40da7372c5b8f4e7b66a37 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 16 Nov 2021 15:24:25 +0100 Subject: [PATCH 01/30] Change the paymentmethod controller to handle both desc and form Add tests as well Update _payment.html.haml Update _payment.html.haml --- app/views/split_checkout/_payment.html.haml | 11 +++-- .../controllers/paymentmethod_controller.js | 22 +++++++-- .../stimulus/paymentmethod_controller_test.js | 49 +++++++++++++++++++ 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 spec/javascripts/stimulus/paymentmethod_controller_test.js diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 02e9d7e28e..17f84635bb 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -11,13 +11,18 @@ name: "order[payments_attributes][][payment_method_id]", checked: (payment_method.id == selected_payment_method), "data-action": "paymentmethod#selectPaymentMethod", - "data-paymentmethod-description": "#{payment_method.description}" + "data-paymentmethod-id": "paymentmethod#{payment_method.id}" = f.label :payment_method_id, "#{payment_method.name} (#{payment_or_shipping_price(payment_method, @order)})", for: "payment_method_#{payment_method.id}" = f.error_message_on :payment_method, standalone: true - %div - .panel{"data-paymentmethod-target": "panel", style: "display: none"} + - available_payment_methods.each do |payment_method| + .paymentmethod-container{id: "paymentmethod#{payment_method.id}"} + - if payment_method.description && !payment_method.description.empty? + .paymentmethod-description.panel + #{payment_method.description} + .paymentmethod-form + = render partial: "spree/checkout/payment/#{payment_method.method_type}", :locals => { :payment_method => payment_method } %div.checkout-substep = t("split_checkout.step2.explaination") diff --git a/app/webpacker/controllers/paymentmethod_controller.js b/app/webpacker/controllers/paymentmethod_controller.js index ac1c9dff60..4f04f30370 100644 --- a/app/webpacker/controllers/paymentmethod_controller.js +++ b/app/webpacker/controllers/paymentmethod_controller.js @@ -1,9 +1,25 @@ import { Controller } from "stimulus"; export default class extends Controller { - static targets = ["panel"]; + static targets = ["paymentMethod"]; + + connect() { + this.hideAll(); + } selectPaymentMethod(event) { - this.panelTarget.innerHTML = `${event.target.dataset.paymentmethodDescription}`; - this.panelTarget.style.display = "block"; + this.hideAll(); + const paymentMethodContainerId = event.target.dataset.paymentmethodId; + const paymentMethodContainer = document.getElementById( + paymentMethodContainerId + ); + paymentMethodContainer.style.display = "block"; + } + + hideAll() { + Array.from( + document.getElementsByClassName("paymentmethod-container") + ).forEach((e) => { + e.style.display = "none"; + }); } } diff --git a/spec/javascripts/stimulus/paymentmethod_controller_test.js b/spec/javascripts/stimulus/paymentmethod_controller_test.js new file mode 100644 index 0000000000..e55b10e62f --- /dev/null +++ b/spec/javascripts/stimulus/paymentmethod_controller_test.js @@ -0,0 +1,49 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import paymentmethod_controller from "../../../app/webpacker/controllers/paymentmethod_controller"; + +describe("PaymentmethodController", () => { + describe("#selectPaymentMethod", () => { + beforeEach(() => { + document.body.innerHTML = `
+ + + + +
+
+
+
`; + + const application = Application.start(); + application.register("paymentmethod", paymentmethod_controller); + }); + + it("fill the right payment description", () => { + const paymentMethod1 = document.getElementById("paymentmethod_1"); + const paymentMethod2 = document.getElementById("paymentmethod_2"); + const paymentMethod3 = document.getElementById("paymentmethod_3"); + + const paymentMethod1Container = document.getElementById("paymentmethod1"); + const paymentMethod2Container = document.getElementById("paymentmethod2"); + const paymentMethod3Container = document.getElementById("paymentmethod3"); + + expect(paymentMethod1Container.style.display).toBe("none"); + expect(paymentMethod2Container.style.display).toBe("none"); + expect(paymentMethod3Container.style.display).toBe("none"); + + paymentMethod1.click(); + expect(paymentMethod1Container.style.display).toBe("block"); + expect(paymentMethod2Container.style.display).toBe("none"); + expect(paymentMethod3Container.style.display).toBe("none"); + + paymentMethod3.click(); + expect(paymentMethod1Container.style.display).toBe("none"); + expect(paymentMethod2Container.style.display).toBe("none"); + expect(paymentMethod3Container.style.display).toBe("block"); + }); + }); +}); From 7159cc3ff10aff3b3cee107a7569957342174a66 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 17 Nov 2021 11:46:59 +0100 Subject: [PATCH 02/30] Handle required attribute on input for PaymentMethod controller This is done for one reason : do not submit form with required attribute on input that are actually hidden ; this is not handle correctly by browsers. This idea here is to add/remove the required attribute on each input if the form is visible or not. --- .../controllers/paymentmethod_controller.js | 41 +++++++++++++------ .../stimulus/paymentmethod_controller_test.js | 41 +++++++++++++++++-- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/app/webpacker/controllers/paymentmethod_controller.js b/app/webpacker/controllers/paymentmethod_controller.js index 4f04f30370..f212524f23 100644 --- a/app/webpacker/controllers/paymentmethod_controller.js +++ b/app/webpacker/controllers/paymentmethod_controller.js @@ -3,23 +3,40 @@ export default class extends Controller { static targets = ["paymentMethod"]; connect() { - this.hideAll(); + this.selectPaymentMethod(); } - selectPaymentMethod(event) { - this.hideAll(); - const paymentMethodContainerId = event.target.dataset.paymentmethodId; - const paymentMethodContainer = document.getElementById( - paymentMethodContainerId - ); - paymentMethodContainer.style.display = "block"; - } - - hideAll() { + selectPaymentMethod(event = null) { + const paymentMethodContainerId = event + ? event.target.dataset.paymentmethodId + : null; Array.from( document.getElementsByClassName("paymentmethod-container") ).forEach((e) => { - e.style.display = "none"; + if (e.id === paymentMethodContainerId) { + e.style.display = "block"; + this.addRequiredAttributeOnInputIfNeeded(e); + } else { + e.style.display = "none"; + this.removeRequiredAttributeOnInput(e); + } + }); + } + + removeRequiredAttributeOnInput(container) { + Array.from(container.getElementsByTagName("input")).forEach((i) => { + if (i.required) { + i.dataset.required = i.required; + i.required = false; + } + }); + } + + addRequiredAttributeOnInputIfNeeded(container) { + Array.from(container.getElementsByTagName("input")).forEach((i) => { + if (i.dataset.required === "true") { + i.required = true; + } }); } } diff --git a/spec/javascripts/stimulus/paymentmethod_controller_test.js b/spec/javascripts/stimulus/paymentmethod_controller_test.js index e55b10e62f..a7ad61fe0a 100644 --- a/spec/javascripts/stimulus/paymentmethod_controller_test.js +++ b/spec/javascripts/stimulus/paymentmethod_controller_test.js @@ -13,16 +13,16 @@ describe("PaymentmethodController", () => { -
-
-
+
+
+
`; const application = Application.start(); application.register("paymentmethod", paymentmethod_controller); }); - it("fill the right payment description", () => { + it("fill the right payment container", () => { const paymentMethod1 = document.getElementById("paymentmethod_1"); const paymentMethod2 = document.getElementById("paymentmethod_2"); const paymentMethod3 = document.getElementById("paymentmethod_3"); @@ -45,5 +45,38 @@ describe("PaymentmethodController", () => { expect(paymentMethod2Container.style.display).toBe("none"); expect(paymentMethod3Container.style.display).toBe("block"); }); + + it("handle well the add/remove on 'required' attribute on each input", () => { + const paymentMethod1 = document.getElementById("paymentmethod_1"); + const paymentMethod2 = document.getElementById("paymentmethod_2"); + const paymentMethod3 = document.getElementById("paymentmethod_3"); + + const input1 = document.getElementById("input1"); + const input2 = document.getElementById("input2"); + const input3 = document.getElementById("input3"); + + paymentMethod1.click(); + expect(input1.required).toBe(true); + expect(input2.dataset.required).toBe("true"); + expect(input2.required).toBe(false); + expect(input3.required).toBe(false); + + paymentMethod2.click(); + expect(input2.required).toBe(true); + expect(input1.dataset.required).toBe("true"); + expect(input1.required).toBe(false); + expect(input3.required).toBe(false); + + paymentMethod3.click(); + expect(input1.required).toBe(false); + expect(input2.required).toBe(false); + expect(input3.required).toBe(false); + + paymentMethod1.click(); + expect(input1.required).toBe(true); + expect(input2.dataset.required).toBe("true"); + expect(input2.required).toBe(false); + expect(input3.required).toBe(false); + }); }); }); From 5f3ea6accb9da8319229069db3de20f9e673e03d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 18 Nov 2021 09:50:38 +0100 Subject: [PATCH 03/30] Do not init by hidden all the container that allow to init the content without clicking on anything --- app/webpacker/controllers/paymentmethod_controller.js | 10 ++-------- .../stimulus/paymentmethod_controller_test.js | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/webpacker/controllers/paymentmethod_controller.js b/app/webpacker/controllers/paymentmethod_controller.js index f212524f23..49553c6f63 100644 --- a/app/webpacker/controllers/paymentmethod_controller.js +++ b/app/webpacker/controllers/paymentmethod_controller.js @@ -2,14 +2,8 @@ import { Controller } from "stimulus"; export default class extends Controller { static targets = ["paymentMethod"]; - connect() { - this.selectPaymentMethod(); - } - - selectPaymentMethod(event = null) { - const paymentMethodContainerId = event - ? event.target.dataset.paymentmethodId - : null; + selectPaymentMethod(event) { + const paymentMethodContainerId = event.target.dataset.paymentmethodId; Array.from( document.getElementsByClassName("paymentmethod-container") ).forEach((e) => { diff --git a/spec/javascripts/stimulus/paymentmethod_controller_test.js b/spec/javascripts/stimulus/paymentmethod_controller_test.js index a7ad61fe0a..e7af59fec6 100644 --- a/spec/javascripts/stimulus/paymentmethod_controller_test.js +++ b/spec/javascripts/stimulus/paymentmethod_controller_test.js @@ -13,9 +13,9 @@ describe("PaymentmethodController", () => { -
-
-
+ +
+ `; const application = Application.start(); @@ -32,7 +32,7 @@ describe("PaymentmethodController", () => { const paymentMethod3Container = document.getElementById("paymentmethod3"); expect(paymentMethod1Container.style.display).toBe("none"); - expect(paymentMethod2Container.style.display).toBe("none"); + expect(paymentMethod2Container.style.display).toBe("block"); expect(paymentMethod3Container.style.display).toBe("none"); paymentMethod1.click(); From 2564de4ca426e92ce336243b7526c9b6acc99dcb Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 18 Nov 2021 09:51:01 +0100 Subject: [PATCH 04/30] Display the right container by default regarding the selected_payment_method --- app/views/split_checkout/_payment.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 17f84635bb..5ba7d7f0cc 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -17,7 +17,7 @@ = f.error_message_on :payment_method, standalone: true - available_payment_methods.each do |payment_method| - .paymentmethod-container{id: "paymentmethod#{payment_method.id}"} + .paymentmethod-container{id: "paymentmethod#{payment_method.id}", style: "display: #{payment_method.id == selected_payment_method ? "block" : "none"}"} - if payment_method.description && !payment_method.description.empty? .paymentmethod-description.panel #{payment_method.description} From 792763d275bb949ac9cccc6249c9e78a4a7283b9 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 18 Nov 2021 14:56:19 +0100 Subject: [PATCH 05/30] use specific gateway form template for split checkout --- app/views/split_checkout/_gateway.html.haml | 27 +++++++++++++++++++++ app/views/split_checkout/_payment.html.haml | 5 +++- config/locales/en.yml | 13 ++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 app/views/split_checkout/_gateway.html.haml diff --git a/app/views/split_checkout/_gateway.html.haml b/app/views/split_checkout/_gateway.html.haml new file mode 100644 index 0000000000..a6dfdc5119 --- /dev/null +++ b/app/views/split_checkout/_gateway.html.haml @@ -0,0 +1,27 @@ += f.fields :bill_address, model: @order.bill_address do |bill_address| + %div.checkout-input + = bill_address.label :firstname, t("split_checkout.step1.your_details.first_name.label") + = bill_address.text_field :firstname, { placeholder: t("split_checkout.step1.your_details.first_name.placeholder") } + = f.error_message_on "bill_address.firstname" + + %div.checkout-input + = bill_address.label :lastname, t("split_checkout.step1.your_details.last_name.label") + = bill_address.text_field :lastname, { placeholder: t("split_checkout.step1.your_details.last_name.placeholder") } + = f.error_message_on "bill_address.lastname" + +.flex{style: "justify-content: space-between; gap: 10px;" } + %div.checkout-input{style: "flex-grow: 2;" } + = f.label :card_number, t("split_checkout.step2.form.card_number.label") + = f.text_field :card_number, { placeholder: t("split_checkout.step2.form.card_number.placeholder") } + + %div.checkout-input{style: "flex: 0 1 100px;"} + = f.label :card_verification_value, t("split_checkout.step2.form.card_verification_value.label") + = f.number_field :card_verification_value, { placeholder: t("split_checkout.step2.form.card_verification_value.placeholder") } + + %div.checkout-input{style: "flex: 0 1 70px;"} + = f.label :card_month, t("split_checkout.step2.form.card_month.label") + = f.number_field :card_month, { placeholder: t("split_checkout.step2.form.card_month.placeholder"), min: Time.now.month, max: 12 } + + %div.checkout-input{style: "flex: 0 1 70px;"} + = f.label :card_year, t("split_checkout.step2.form.card_year.label") + = f.number_field :card_year, { placeholder: t("split_checkout.step2.form.card_year.placeholder"), min: Time.now.year, max: Time.now.year + 10 } diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 5ba7d7f0cc..3bc2767e06 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -22,7 +22,10 @@ .paymentmethod-description.panel #{payment_method.description} .paymentmethod-form - = render partial: "spree/checkout/payment/#{payment_method.method_type}", :locals => { :payment_method => payment_method } + - if payment_method.method_type == "gateway" + = render partial: "split_checkout/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } + - else + = render partial: "spree/checkout/payment/#{payment_method.method_type}", :locals => { payment_method: payment_method } %div.checkout-substep = t("split_checkout.step2.explaination") diff --git a/config/locales/en.yml b/config/locales/en.yml index eec506ff28..1b68988a48 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1724,6 +1724,19 @@ en: step2: payment_method: title: Payment method + form: + card_number: + label: Card number + placeholder: e.g. 4242 4242 4242 4242 + card_verification_value: + label: CVC + placeholder: 123 + card_month: + label: Month + placeholder: 01 + card_year: + label: Year + placeholder: 2020 explaination: You can review and confirm your order in the next step which includes the final costs. submit: Next - Order summary cancel: Back to Your details From 216e06cd6bda41c2942544b8a265d8db3b91aac2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 23 Nov 2021 10:48:48 +0100 Subject: [PATCH 06/30] Update stripe card errors styles --- .../stylesheets/darkswarm/split-checkout.scss | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/split-checkout.scss b/app/assets/stylesheets/darkswarm/split-checkout.scss index ac535ea8c8..b6c262f817 100644 --- a/app/assets/stylesheets/darkswarm/split-checkout.scss +++ b/app/assets/stylesheets/darkswarm/split-checkout.scss @@ -106,23 +106,6 @@ margin: 0; } - span.formError { - background-color: rgba(193, 18, 43, 0.1); - color: $red-700; - font-style: normal; - margin: 0; - font-size: 0.8rem; - display: block; - padding-left: 5px; - padding-right: 5px; - padding-top: 2px; - padding-bottom: 2px; - - &.standalone { - padding: 10px; - } - } - #distributor_address.panel { font-size: 0.875rem; padding: 1rem; @@ -138,6 +121,29 @@ } } + .checkout-input span.formError, div.error.card-errors { + background-color: rgba(193, 18, 43, 0.1); + color: $red-700; + font-style: normal; + margin: 0; + font-size: 0.8rem; + display: block; + padding-left: 5px; + padding-right: 5px; + padding-top: 2px; + padding-bottom: 2px; + + &.standalone { + padding: 10px; + } + } + + div.error.card-errors { + &:empty { + display: none; + } + } + .checkout-submit { margin-top: 5rem; margin-bottom: 5rem; From fc6fba4fe905eb8a573c5e25f9183c82383511ea Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 25 Nov 2021 12:00:27 +0100 Subject: [PATCH 07/30] Move into specific folder --- app/views/split_checkout/_payment.html.haml | 2 +- app/views/split_checkout/{ => payment}/_gateway.html.haml | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename app/views/split_checkout/{ => payment}/_gateway.html.haml (100%) diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 3bc2767e06..0c4cf666d8 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -23,7 +23,7 @@ #{payment_method.description} .paymentmethod-form - if payment_method.method_type == "gateway" - = render partial: "split_checkout/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } + = render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } - else = render partial: "spree/checkout/payment/#{payment_method.method_type}", :locals => { payment_method: payment_method } diff --git a/app/views/split_checkout/_gateway.html.haml b/app/views/split_checkout/payment/_gateway.html.haml similarity index 100% rename from app/views/split_checkout/_gateway.html.haml rename to app/views/split_checkout/payment/_gateway.html.haml From e89caf7a0d376155d5f0f0e51ec69ec69706246a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 25 Nov 2021 12:00:45 +0100 Subject: [PATCH 08/30] Add stripe as payment method --- .../concerns/checkout_callbacks.rb | 8 ++++++- app/views/split_checkout/_payment.html.haml | 2 +- .../split_checkout/payment/_stripe.html.haml | 23 +++++++++++++++++++ config/locales/en.yml | 4 ++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 app/views/split_checkout/payment/_stripe.html.haml diff --git a/app/controllers/concerns/checkout_callbacks.rb b/app/controllers/concerns/checkout_callbacks.rb index ff8d207add..ae9c2fb357 100644 --- a/app/controllers/concerns/checkout_callbacks.rb +++ b/app/controllers/concerns/checkout_callbacks.rb @@ -13,7 +13,7 @@ module CheckoutCallbacks prepend_before_action :require_order_cycle prepend_before_action :require_distributor_chosen - before_action :load_order, :associate_user, :load_saved_addresses + before_action :load_order, :associate_user, :load_saved_addresses, :load_saved_credit_cards before_action :load_shipping_methods, :load_countries, if: -> { params[:step] == "details" } before_action :ensure_order_not_completed @@ -41,6 +41,11 @@ module CheckoutCallbacks @order.ship_address ||= finder.ship_address end + def load_saved_credit_cards + @saved_credit_cards = spree_current_user ? spree_current_user.credit_cards.with_payment_profile.all : [] + @selected_card = nil + end + def load_shipping_methods @shipping_methods = Spree::ShippingMethod.for_distributor(@order.distributor).order(:name) end @@ -89,4 +94,5 @@ module CheckoutCallbacks def check_authorization authorize!(:edit, current_order, session[:access_token]) end + end diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 0c4cf666d8..45333c5c9f 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -22,7 +22,7 @@ .paymentmethod-description.panel #{payment_method.description} .paymentmethod-form - - if payment_method.method_type == "gateway" + - if payment_method.method_type == "gateway" || payment_method.method_type == "stripe" = render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } - else = render partial: "spree/checkout/payment/#{payment_method.method_type}", :locals => { payment_method: payment_method } diff --git a/app/views/split_checkout/payment/_stripe.html.haml b/app/views/split_checkout/payment/_stripe.html.haml new file mode 100644 index 0000000000..47f6b30f7e --- /dev/null +++ b/app/views/split_checkout/payment/_stripe.html.haml @@ -0,0 +1,23 @@ +- content_for :injection_data do + - if Stripe.publishable_key + :javascript + angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) + +- if @saved_credit_cards.length > 0 + .checkout-input + %label + = t('split_checkout.step2.form.stripe.use_saved_card') + = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ cc.id, "#{cc.brand} #{cc.number}" ] }, @selected_card) + +.checkout-input + - if @saved_credit_cards.length > 0 + %label + = t('split_checkout.step2.form.stripe.use_new_card') + %stripe-elements + + +- if spree_current_user + .checkout-input + = check_box_tag :save_card, {id: "save_card", name: "save_card", checked: false, value: "1"} + = label :save_card, t('split_checkout.step2.form.stripe.save_card'), { for: "save_card" } + \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 1b68988a48..de27c5c0c2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1737,6 +1737,10 @@ en: card_year: label: Year placeholder: 2020 + stripe: + use_saved_card: Use saved card + use_new_card: or use a new card + save_card: Save card for future use explaination: You can review and confirm your order in the next step which includes the final costs. submit: Next - Order summary cancel: Back to Your details From f9b0798c88b3aaa57415110870f2b7c41002e64e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 25 Nov 2021 12:23:38 +0100 Subject: [PATCH 09/30] Add stripe_sca, paypal, check payment method Stripe_sca use the same template than stripe --- app/views/split_checkout/_payment.html.haml | 11 ++++++----- app/views/split_checkout/payment/_check.html.erb | 0 app/views/split_checkout/payment/_paypal.html.haml | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 app/views/split_checkout/payment/_check.html.erb create mode 100644 app/views/split_checkout/payment/_paypal.html.haml diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 45333c5c9f..32c0478671 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -22,11 +22,12 @@ .paymentmethod-description.panel #{payment_method.description} .paymentmethod-form - - if payment_method.method_type == "gateway" || payment_method.method_type == "stripe" - = render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } - - else - = render partial: "spree/checkout/payment/#{payment_method.method_type}", :locals => { payment_method: payment_method } - + - if payment_method.method_type == "stripe_sca" + - method = "stripe" + -else + - method = payment_method.method_type + = render partial: "split_checkout/payment/#{method}", locals: { payment_method: payment_method, f: f } + %div.checkout-substep = t("split_checkout.step2.explaination") diff --git a/app/views/split_checkout/payment/_check.html.erb b/app/views/split_checkout/payment/_check.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/split_checkout/payment/_paypal.html.haml b/app/views/split_checkout/payment/_paypal.html.haml new file mode 100644 index 0000000000..1cc8aa25a7 --- /dev/null +++ b/app/views/split_checkout/payment/_paypal.html.haml @@ -0,0 +1 @@ +-# This file intentionally overrides the view in the spree_paypal_express gem From 68e4d55f809f8c30767352010b5841b2e71a3dee Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 25 Nov 2021 15:27:52 +0100 Subject: [PATCH 10/30] Avoid ternary operator --- app/controllers/concerns/checkout_callbacks.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/checkout_callbacks.rb b/app/controllers/concerns/checkout_callbacks.rb index ae9c2fb357..bb620715a0 100644 --- a/app/controllers/concerns/checkout_callbacks.rb +++ b/app/controllers/concerns/checkout_callbacks.rb @@ -42,7 +42,8 @@ module CheckoutCallbacks end def load_saved_credit_cards - @saved_credit_cards = spree_current_user ? spree_current_user.credit_cards.with_payment_profile.all : [] + @saved_credit_cards = [] if !spree_current_user + @saved_credit_cards = spree_current_user.credit_cards.with_payment_profile.all if spree_current_user @selected_card = nil end From 34ce08d901bc18766cf8d1b216eb185aa3c2d86d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 25 Nov 2021 15:32:41 +0100 Subject: [PATCH 11/30] Format card label in card selector --- app/views/split_checkout/payment/_stripe.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/split_checkout/payment/_stripe.html.haml b/app/views/split_checkout/payment/_stripe.html.haml index 47f6b30f7e..233460a722 100644 --- a/app/views/split_checkout/payment/_stripe.html.haml +++ b/app/views/split_checkout/payment/_stripe.html.haml @@ -7,7 +7,7 @@ .checkout-input %label = t('split_checkout.step2.form.stripe.use_saved_card') - = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ cc.id, "#{cc.brand} #{cc.number}" ] }, @selected_card) + = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] }, @selected_card) .checkout-input - if @saved_credit_cards.length > 0 From 7437c6a6d79567a0e22eb4e5fc75de579258e458 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 26 Nov 2021 10:21:49 +0100 Subject: [PATCH 12/30] Add a stripe controller that show/hide stripe form if saved cards or not --- .../controllers/stripe_controller.js | 21 +++++++ .../stimulus/stripe_controller_test.js | 55 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 app/webpacker/controllers/stripe_controller.js create mode 100644 spec/javascripts/stimulus/stripe_controller_test.js diff --git a/app/webpacker/controllers/stripe_controller.js b/app/webpacker/controllers/stripe_controller.js new file mode 100644 index 0000000000..0d388ee991 --- /dev/null +++ b/app/webpacker/controllers/stripe_controller.js @@ -0,0 +1,21 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["stripeelements", "select"]; + + connect() { + this.selectCard(this.selectTarget.value); + } + + onSelectCard(event) { + this.selectCard(event.target.value); + } + + selectCard(cardValue) { + if (cardValue == "") { + this.stripeelementsTarget.style.display = "block"; + } else { + this.stripeelementsTarget.style.display = "none"; + } + } +} diff --git a/spec/javascripts/stimulus/stripe_controller_test.js b/spec/javascripts/stimulus/stripe_controller_test.js new file mode 100644 index 0000000000..d3cd70833f --- /dev/null +++ b/spec/javascripts/stimulus/stripe_controller_test.js @@ -0,0 +1,55 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import stripe_controller from "../../../app/webpacker/controllers/stripe_controller"; + +describe("StripeController", () => { + beforeEach(() => { + document.body.innerHTML = `
+ +
+
`; + + const application = Application.start(); + application.register("stripe", stripe_controller); + }); + describe("#connect", () => { + it("initialize with the right display state", () => { + const select = document.getElementById("select"); + select.value = ""; + select.dispatchEvent(new Event("change")); + expect(document.getElementById("stripeelements").style.display).toBe( + "block" + ); + }); + }); + describe("#selectCard", () => { + it("fill the right payment container", () => { + const select = document.getElementById("select"); + select.value = "1"; + select.dispatchEvent(new Event("change")); + + expect(document.getElementById("stripeelements").style.display).toBe( + "none" + ); + + select.value = "2"; + select.dispatchEvent(new Event("change")); + expect(document.getElementById("stripeelements").style.display).toBe( + "none" + ); + + select.value = ""; + select.dispatchEvent(new Event("change")); + expect(document.getElementById("stripeelements").style.display).toBe( + "block" + ); + }); + }); +}); From fbe69b8027fd3fb47569794ebd29c39af75bef8d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 26 Nov 2021 10:22:11 +0100 Subject: [PATCH 13/30] Manage savedCards or enter new card behavior for stripe payment form --- .../split_checkout/payment/_stripe.html.haml | 29 ++++++++++--------- config/locales/en.yml | 3 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/views/split_checkout/payment/_stripe.html.haml b/app/views/split_checkout/payment/_stripe.html.haml index 233460a722..a52152ad1f 100644 --- a/app/views/split_checkout/payment/_stripe.html.haml +++ b/app/views/split_checkout/payment/_stripe.html.haml @@ -3,21 +3,22 @@ :javascript angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) -- if @saved_credit_cards.length > 0 - .checkout-input - %label - = t('split_checkout.step2.form.stripe.use_saved_card') - = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] }, @selected_card) - -.checkout-input +%div{"data-controller": "stripe"} - if @saved_credit_cards.length > 0 - %label - = t('split_checkout.step2.form.stripe.use_new_card') - %stripe-elements + .checkout-input + %label + = t('split_checkout.step2.form.stripe.use_saved_card') + = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] } + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), { "data-action": "change->stripe#onSelectCard", "data-stripe-target": "select" } + + .checkout-input{"data-stripe-target": "stripeelements"} + - if @saved_credit_cards.length == 0 + %label + = t('split_checkout.step2.form.stripe.use_new_card') + %stripe-elements -- if spree_current_user - .checkout-input - = check_box_tag :save_card, {id: "save_card", name: "save_card", checked: false, value: "1"} - = label :save_card, t('split_checkout.step2.form.stripe.save_card'), { for: "save_card" } + - if spree_current_user + .checkout-input + = check_box_tag :save_card, {id: "save_card", name: "save_card", checked: false, value: "1"} + = label :save_card, t('split_checkout.step2.form.stripe.save_card'), { for: "save_card" } \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index de27c5c0c2..bd815d6587 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1739,8 +1739,9 @@ en: placeholder: 2020 stripe: use_saved_card: Use saved card - use_new_card: or use a new card + use_new_card: Enter your card identifiers save_card: Save card for future use + create_new_card: or create new card below explaination: You can review and confirm your order in the next step which includes the final costs. submit: Next - Order summary cancel: Back to Your details From f7662947dc7b0188d0151c8e83f578cc41a5d9f7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 26 Nov 2021 11:35:45 +0100 Subject: [PATCH 14/30] Extract to a method that handle all the form elements --- app/webpacker/controllers/paymentmethod_controller.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/webpacker/controllers/paymentmethod_controller.js b/app/webpacker/controllers/paymentmethod_controller.js index 49553c6f63..133024227c 100644 --- a/app/webpacker/controllers/paymentmethod_controller.js +++ b/app/webpacker/controllers/paymentmethod_controller.js @@ -17,8 +17,11 @@ export default class extends Controller { }); } + getFormElementsArray(container) { + return Array.from(container.querySelectorAll("input, select, textarea")); + } removeRequiredAttributeOnInput(container) { - Array.from(container.getElementsByTagName("input")).forEach((i) => { + this.getFormElementsArray(container).forEach((i) => { if (i.required) { i.dataset.required = i.required; i.required = false; @@ -27,7 +30,7 @@ export default class extends Controller { } addRequiredAttributeOnInputIfNeeded(container) { - Array.from(container.getElementsByTagName("input")).forEach((i) => { + this.getFormElementsArray(container).forEach((i) => { if (i.dataset.required === "true") { i.required = true; } From fd5ad7566a843017a269fefd58b49a6cdf30ee86 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 26 Nov 2021 11:36:06 +0100 Subject: [PATCH 15/30] Disabled all form elements that should not be sent to the controller --- .../controllers/paymentmethod_controller.js | 15 +++++ .../stimulus/paymentmethod_controller_test.js | 61 ++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/app/webpacker/controllers/paymentmethod_controller.js b/app/webpacker/controllers/paymentmethod_controller.js index 133024227c..f352430b71 100644 --- a/app/webpacker/controllers/paymentmethod_controller.js +++ b/app/webpacker/controllers/paymentmethod_controller.js @@ -10,9 +10,11 @@ export default class extends Controller { if (e.id === paymentMethodContainerId) { e.style.display = "block"; this.addRequiredAttributeOnInputIfNeeded(e); + this.removeDisabledAttributeOnInput(e); } else { e.style.display = "none"; this.removeRequiredAttributeOnInput(e); + this.addDisabledAttributeOnInput(e); } }); } @@ -20,6 +22,19 @@ export default class extends Controller { getFormElementsArray(container) { return Array.from(container.querySelectorAll("input, select, textarea")); } + + addDisabledAttributeOnInput(container) { + this.getFormElementsArray(container).forEach((i) => { + i.disabled = true; + }); + } + + removeDisabledAttributeOnInput(container) { + this.getFormElementsArray(container).forEach((i) => { + i.disabled = false; + }); + } + removeRequiredAttributeOnInput(container) { this.getFormElementsArray(container).forEach((i) => { if (i.required) { diff --git a/spec/javascripts/stimulus/paymentmethod_controller_test.js b/spec/javascripts/stimulus/paymentmethod_controller_test.js index e7af59fec6..aab38a9615 100644 --- a/spec/javascripts/stimulus/paymentmethod_controller_test.js +++ b/spec/javascripts/stimulus/paymentmethod_controller_test.js @@ -13,9 +13,24 @@ describe("PaymentmethodController", () => { - -
- + +
+ + +
+
`; const application = Application.start(); @@ -78,5 +93,45 @@ describe("PaymentmethodController", () => { expect(input2.required).toBe(false); expect(input3.required).toBe(false); }); + + it("handle well the add/remove 'disabled='disabled'' attribute on each input/select", () => { + const paymentMethod1 = document.getElementById("paymentmethod_1"); + const paymentMethod2 = document.getElementById("paymentmethod_2"); + const paymentMethod3 = document.getElementById("paymentmethod_3"); + + const input1 = document.getElementById("input1"); + const input2 = document.getElementById("input2"); + const input3 = document.getElementById("input3"); + const select1 = document.getElementById("select1"); + const select2 = document.getElementById("select2"); + const select3 = document.getElementById("select3"); + + paymentMethod1.click(); + expect(input1.disabled).toBe(false); + expect(select1.disabled).toBe(false); + + expect(input2.disabled).toBe(true); + expect(select2.disabled).toBe(true); + expect(input3.disabled).toBe(true); + expect(select3.disabled).toBe(true); + + paymentMethod2.click(); + expect(input2.disabled).toBe(false); + expect(select2.disabled).toBe(false); + + expect(input1.disabled).toBe(true); + expect(select1.disabled).toBe(true); + expect(input3.disabled).toBe(true); + expect(select3.disabled).toBe(true); + + paymentMethod3.click(); + expect(input3.disabled).toBe(false); + expect(select3.disabled).toBe(false); + + expect(input1.disabled).toBe(true); + expect(select1.disabled).toBe(true); + expect(input2.disabled).toBe(true); + expect(select2.disabled).toBe(true); + }); }); }); From 33887e8b6eed6b129bb332ab89b79c4afc6a242e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 26 Nov 2021 11:36:34 +0100 Subject: [PATCH 16/30] Remove wrong `min` attribute on input --- app/views/split_checkout/payment/_gateway.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/split_checkout/payment/_gateway.html.haml b/app/views/split_checkout/payment/_gateway.html.haml index a6dfdc5119..c8e32ba578 100644 --- a/app/views/split_checkout/payment/_gateway.html.haml +++ b/app/views/split_checkout/payment/_gateway.html.haml @@ -20,7 +20,7 @@ %div.checkout-input{style: "flex: 0 1 70px;"} = f.label :card_month, t("split_checkout.step2.form.card_month.label") - = f.number_field :card_month, { placeholder: t("split_checkout.step2.form.card_month.placeholder"), min: Time.now.month, max: 12 } + = f.number_field :card_month, { placeholder: t("split_checkout.step2.form.card_month.placeholder"), max: 12 } %div.checkout-input{style: "flex: 0 1 70px;"} = f.label :card_year, t("split_checkout.step2.form.card_year.label") From 8105f0ebf3b8aeceb36d3e9def9a897cf2bc973a Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:25:42 +0000 Subject: [PATCH 17/30] Simplify partial selecting and rename stripe_sca partial --- app/views/split_checkout/_payment.html.haml | 7 ++----- .../payment/{_stripe.html.haml => _stripe_sca.html.haml} | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) rename app/views/split_checkout/payment/{_stripe.html.haml => _stripe_sca.html.haml} (95%) diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 32c0478671..18459a21a2 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -21,12 +21,9 @@ - if payment_method.description && !payment_method.description.empty? .paymentmethod-description.panel #{payment_method.description} + .paymentmethod-form - - if payment_method.method_type == "stripe_sca" - - method = "stripe" - -else - - method = payment_method.method_type - = render partial: "split_checkout/payment/#{method}", locals: { payment_method: payment_method, f: f } + = render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } %div.checkout-substep = t("split_checkout.step2.explaination") diff --git a/app/views/split_checkout/payment/_stripe.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml similarity index 95% rename from app/views/split_checkout/payment/_stripe.html.haml rename to app/views/split_checkout/payment/_stripe_sca.html.haml index a52152ad1f..0931b81e64 100644 --- a/app/views/split_checkout/payment/_stripe.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -8,7 +8,7 @@ .checkout-input %label = t('split_checkout.step2.form.stripe.use_saved_card') - = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] } + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), { "data-action": "change->stripe#onSelectCard", "data-stripe-target": "select" } + = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] } + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), { "data-action": "change->stripe#onSelectCard", "data-stripe-target": "select" } .checkout-input{"data-stripe-target": "stripeelements"} - if @saved_credit_cards.length == 0 From d85597fe4ce53596327538c4c2e7f59a71831f09 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:27:22 +0000 Subject: [PATCH 18/30] Rename stripe cards controller --- .../split_checkout/payment/_stripe_sca.html.haml | 8 +++++--- ...ripe_controller.js => stripe_cards_controller.js} | 2 ++ ...oller_test.js => stripe_cards_controller_test.js} | 12 ++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) rename app/webpacker/controllers/{stripe_controller.js => stripe_cards_controller.js} (83%) rename spec/javascripts/stimulus/{stripe_controller_test.js => stripe_cards_controller_test.js} (75%) diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index 0931b81e64..db56c50faa 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -3,14 +3,16 @@ :javascript angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) -%div{"data-controller": "stripe"} +%div{"data-controller": "stripe-cards"} - if @saved_credit_cards.length > 0 .checkout-input %label = t('split_checkout.step2.form.stripe.use_saved_card') - = select_tag :card, options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] } + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), { "data-action": "change->stripe#onSelectCard", "data-stripe-target": "select" } + = select_tag :card, + options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] } + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), + { "data-action": "change->stripe-cards#onSelectCard", "data-stripe-cards-target": "select" } - .checkout-input{"data-stripe-target": "stripeelements"} + .checkout-input{"data-stripe-cards-target": "stripeelements"} - if @saved_credit_cards.length == 0 %label = t('split_checkout.step2.form.stripe.use_new_card') diff --git a/app/webpacker/controllers/stripe_controller.js b/app/webpacker/controllers/stripe_cards_controller.js similarity index 83% rename from app/webpacker/controllers/stripe_controller.js rename to app/webpacker/controllers/stripe_cards_controller.js index 0d388ee991..03eb1ef4ec 100644 --- a/app/webpacker/controllers/stripe_controller.js +++ b/app/webpacker/controllers/stripe_cards_controller.js @@ -1,5 +1,7 @@ import { Controller } from "stimulus"; +// Handles form elements for selecting previously saved Stripe cards from a list of cards + export default class extends Controller { static targets = ["stripeelements", "select"]; diff --git a/spec/javascripts/stimulus/stripe_controller_test.js b/spec/javascripts/stimulus/stripe_cards_controller_test.js similarity index 75% rename from spec/javascripts/stimulus/stripe_controller_test.js rename to spec/javascripts/stimulus/stripe_cards_controller_test.js index d3cd70833f..98b590861a 100644 --- a/spec/javascripts/stimulus/stripe_controller_test.js +++ b/spec/javascripts/stimulus/stripe_cards_controller_test.js @@ -3,21 +3,21 @@ */ import { Application } from "stimulus"; -import stripe_controller from "../../../app/webpacker/controllers/stripe_controller"; +import stripe_cards_controller from "../../../app/webpacker/controllers/stripe_cards_controller"; -describe("StripeController", () => { +describe("StripeCardsController", () => { beforeEach(() => { - document.body.innerHTML = `
- -
+
`; const application = Application.start(); - application.register("stripe", stripe_controller); + application.register("stripe-cards", stripe_cards_controller); }); describe("#connect", () => { it("initialize with the right display state", () => { From ce7fb1b4dc84ba7f7e637b136283c348c6bd45e6 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:35:39 +0000 Subject: [PATCH 19/30] Move select-options-formatting logic to helper --- app/helpers/checkout_helper.rb | 9 +++++++++ app/views/split_checkout/payment/_stripe_sca.html.haml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb index c5b22b4784..780c27c595 100644 --- a/app/helpers/checkout_helper.rb +++ b/app/helpers/checkout_helper.rb @@ -143,4 +143,13 @@ module CheckoutHelper def checkout_step?(step) checkout_step == step.to_s end + + def stripe_card_options(cards) + cards.map do |cc| + [ + "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", + cc.id + ] + end + end end diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index db56c50faa..6b1715d215 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -9,7 +9,7 @@ %label = t('split_checkout.step2.form.stripe.use_saved_card') = select_tag :card, - options_for_select(@saved_credit_cards.map {|cc| [ "#{cc.brand} #{cc.last_digits} #{I18n.t(:card_expiry_abbreviation)}:#{cc.month.to_s.rjust(2, '0')}/#{cc.year}", cc.id ] } + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), + options_for_select(stripe_card_options(@saved_credit_cards) + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), { "data-action": "change->stripe-cards#onSelectCard", "data-stripe-cards-target": "select" } .checkout-input{"data-stripe-cards-target": "stripeelements"} From 7f4b2cc00a1476318d155e7e74818fc83f668079 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:37:26 +0000 Subject: [PATCH 20/30] Tidy up checks on Enumerable objects --- app/views/split_checkout/payment/_stripe_sca.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index 6b1715d215..b9eab088e5 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -4,7 +4,7 @@ angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) %div{"data-controller": "stripe-cards"} - - if @saved_credit_cards.length > 0 + - if @saved_credit_cards.any? .checkout-input %label = t('split_checkout.step2.form.stripe.use_saved_card') @@ -13,7 +13,7 @@ { "data-action": "change->stripe-cards#onSelectCard", "data-stripe-cards-target": "select" } .checkout-input{"data-stripe-cards-target": "stripeelements"} - - if @saved_credit_cards.length == 0 + - if @saved_credit_cards.none? %label = t('split_checkout.step2.form.stripe.use_new_card') %stripe-elements From 720d1474215ac9989f8de6e0f681f57d96006497 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Tue, 30 Nov 2021 14:30:18 +0000 Subject: [PATCH 21/30] Bring in stimulus controller for stripe elements --- .../controllers/stripe_controller.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 app/webpacker/controllers/stripe_controller.js diff --git a/app/webpacker/controllers/stripe_controller.js b/app/webpacker/controllers/stripe_controller.js new file mode 100644 index 0000000000..c7f50d47e1 --- /dev/null +++ b/app/webpacker/controllers/stripe_controller.js @@ -0,0 +1,53 @@ +import { Controller } from "stimulus" + +export default class extends Controller { + static targets = [ "cardElement", "cardErrors", "responseToken" ]; + static styles = { + base: { + fontFamily: "Roboto, Arial, sans-serif", + fontSize: "16px", + color: "#5c5c5c", + "::placeholder": { + color: "#6c6c6c" + } + } + }; + + connect() { + const stripe = Stripe(this.data.get("key")); + const elements = stripe.elements(); + const form = this.responseTokenTarget.form; + const error_container = this.cardErrorsTarget; + const token_field = this.responseTokenTarget; + + const stripe_element = elements.create("card", { + style: this.constructor.styles, + hidePostalCode: true + }); + + // Mount Stripe Elements JS to the field and add form validations + stripe_element.mount(this.cardElementTarget); + stripe_element.addEventListener("change", function (event) { + if (event.error) { + error_container.textContent = event.error.message; + } else { + error_container.textContent = ""; + } + }); + + // Before the form is submitted we send the card details directly to Stripe (via StripeJS), + // and receive a token which represents the card object, and add that token into the form. + form.addEventListener("submit", function (event) { + event.preventDefault(); + + stripe.createPaymentMethod({type: "card", card: stripe_element}).then(function (response) { + if (response.error) { + error_container.textContent = response.error.message; + } else { + token_field.setAttribute("value", response.paymentMethod.id); + form.submit(); + } + }); + }); + } +} From cbd7b37eb30f5364a59ea8ec23ef4b011e351895 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:15:26 +0000 Subject: [PATCH 22/30] Change card field name The order[card] field is already in use / needed for other data :+1: --- app/views/split_checkout/payment/_stripe_sca.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index b9eab088e5..639a4370b0 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -8,7 +8,7 @@ .checkout-input %label = t('split_checkout.step2.form.stripe.use_saved_card') - = select_tag :card, + = select_tag :existing_card, options_for_select(stripe_card_options(@saved_credit_cards) + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), { "data-action": "change->stripe-cards#onSelectCard", "data-stripe-cards-target": "select" } From 9979ecf8b457c6c2d364adec9c8442a0fcd6338a Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:17:37 +0000 Subject: [PATCH 23/30] Update view to use new Stimulus controller for Stripe card form --- .../split_checkout/payment/_stripe_sca.html.haml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index 639a4370b0..c65b41d12d 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -1,8 +1,3 @@ -- content_for :injection_data do - - if Stripe.publishable_key - :javascript - angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) - %div{"data-controller": "stripe-cards"} - if @saved_credit_cards.any? .checkout-input @@ -16,8 +11,12 @@ - if @saved_credit_cards.none? %label = t('split_checkout.step2.form.stripe.use_new_card') - %stripe-elements + %div{ "data-controller": "stripe", "data-stripe-key": "#{Stripe.publishable_key}" } + = f.hidden_field :token, { "data-target": "stripe.responseToken" } + + %div.card-element{ "data-target": "stripe.cardElement" } + %div.card-errors{ "data-target": "stripe.cardErrors" } - if spree_current_user .checkout-input From 2fe96c5f63eb665ca1d261edeab4eac0083115f5 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Fri, 3 Dec 2021 01:21:13 +0000 Subject: [PATCH 24/30] Update Stripe controller hidden fields --- .../payment/_stripe_sca.html.haml | 14 +++++++---- .../controllers/stripe_controller.js | 24 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index c65b41d12d..0cfb1c238a 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -13,13 +13,17 @@ = t('split_checkout.step2.form.stripe.use_new_card') %div{ "data-controller": "stripe", "data-stripe-key": "#{Stripe.publishable_key}" } - = f.hidden_field :token, { "data-target": "stripe.responseToken" } + = hidden_field_tag "order[payments_attributes][][source_attributes][month]", nil, { "data-stripe-target": "expMonth" } + = hidden_field_tag "order[payments_attributes][][source_attributes][year]", nil, { "data-stripe-target": "expYear" } + = hidden_field_tag "order[payments_attributes][][source_attributes][cc_type]", nil, { "data-stripe-target": "brand" } + = hidden_field_tag "order[payments_attributes][][source_attributes][last_digits]", nil, { "data-stripe-target": "last4" } + = hidden_field_tag "order[payments_attributes][][source_attributes][gateway_payment_profile_id]", nil, { "data-stripe-target": "pmId" } - %div.card-element{ "data-target": "stripe.cardElement" } - %div.card-errors{ "data-target": "stripe.cardErrors" } + %div.card-element{ "data-stripe-target": "cardElement" } + %div.card-errors{ "data-stripe-target": "cardErrors" } - if spree_current_user .checkout-input - = check_box_tag :save_card, {id: "save_card", name: "save_card", checked: false, value: "1"} - = label :save_card, t('split_checkout.step2.form.stripe.save_card'), { for: "save_card" } + = check_box_tag "order[payments_attributes][][source_attributes][save_requested_by_customer]", 1, false + = label :save_requested_by_customer, t('split_checkout.step2.form.stripe.save_card'), { for: "save_requested_by_customer" } \ No newline at end of file diff --git a/app/webpacker/controllers/stripe_controller.js b/app/webpacker/controllers/stripe_controller.js index c7f50d47e1..9ebb2deedf 100644 --- a/app/webpacker/controllers/stripe_controller.js +++ b/app/webpacker/controllers/stripe_controller.js @@ -1,7 +1,7 @@ import { Controller } from "stimulus" export default class extends Controller { - static targets = [ "cardElement", "cardErrors", "responseToken" ]; + static targets = [ "cardElement", "cardErrors", "expMonth", "expYear", "brand", "last4", "pmId" ]; static styles = { base: { fontFamily: "Roboto, Arial, sans-serif", @@ -16,9 +16,13 @@ export default class extends Controller { connect() { const stripe = Stripe(this.data.get("key")); const elements = stripe.elements(); - const form = this.responseTokenTarget.form; + const form = this.pmIdTarget.form; const error_container = this.cardErrorsTarget; - const token_field = this.responseTokenTarget; + const exp_month_field = this.expMonthTarget; + const exp_year_field = this.expYearTarget; + const brand_field = this.brandTarget; + const last4_field = this.last4Target; + const pm_id_field = this.pmIdTarget; const stripe_element = elements.create("card", { style: this.constructor.styles, @@ -27,7 +31,7 @@ export default class extends Controller { // Mount Stripe Elements JS to the field and add form validations stripe_element.mount(this.cardElementTarget); - stripe_element.addEventListener("change", function (event) { + stripe_element.addEventListener("change", event => { if (event.error) { error_container.textContent = event.error.message; } else { @@ -37,14 +41,20 @@ export default class extends Controller { // Before the form is submitted we send the card details directly to Stripe (via StripeJS), // and receive a token which represents the card object, and add that token into the form. - form.addEventListener("submit", function (event) { + form.addEventListener("submit", event => { event.preventDefault(); + event.stopPropagation(); - stripe.createPaymentMethod({type: "card", card: stripe_element}).then(function (response) { + stripe.createPaymentMethod({type: "card", card: stripe_element}).then(response => { if (response.error) { error_container.textContent = response.error.message; } else { - token_field.setAttribute("value", response.paymentMethod.id); + pm_id_field.setAttribute("value", response.paymentMethod.id); + exp_month_field.setAttribute("value", response.paymentMethod.card.exp_month); + exp_year_field.setAttribute("value", response.paymentMethod.card.exp_year); + brand_field.setAttribute("value", response.paymentMethod.card.brand); + last4_field.setAttribute("value", response.paymentMethod.card.last4); + form.submit(); } }); From 6b8c91845efb1f7131c9ad6a611024e3426f126a Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Fri, 3 Dec 2021 12:14:36 +0000 Subject: [PATCH 25/30] Update strong params --- app/services/checkout/params.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/services/checkout/params.rb b/app/services/checkout/params.rb index 209b3cc2e4..eaa0a0c815 100644 --- a/app/services/checkout/params.rb +++ b/app/services/checkout/params.rb @@ -23,10 +23,13 @@ module Checkout def apply_strong_parameters @order_params = params.require(:order).permit( - :email, :shipping_method_id, :special_instructions, + :email, :shipping_method_id, :special_instructions, :existing_card_id, bill_address_attributes: ::PermittedAttributes::Address.attributes, ship_address_attributes: ::PermittedAttributes::Address.attributes, - payments_attributes: [:payment_method_id] + payments_attributes: [ + :payment_method_id, + { source_attributes: PermittedAttributes::PaymentSource.attributes } + ] ) end From d22cb0e1e15780432b6d907963ee0e6a3bdeef23 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Fri, 3 Dec 2021 12:21:30 +0000 Subject: [PATCH 26/30] Add billing address name to card --- app/views/split_checkout/payment/_stripe_sca.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index 0cfb1c238a..34c4326385 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -13,6 +13,8 @@ = t('split_checkout.step2.form.stripe.use_new_card') %div{ "data-controller": "stripe", "data-stripe-key": "#{Stripe.publishable_key}" } + = hidden_field_tag "order[payments_attributes][][source_attributes][first_name]", @order.bill_address.first_name + = hidden_field_tag "order[payments_attributes][][source_attributes][last_name]", @order.bill_address.last_name = hidden_field_tag "order[payments_attributes][][source_attributes][month]", nil, { "data-stripe-target": "expMonth" } = hidden_field_tag "order[payments_attributes][][source_attributes][year]", nil, { "data-stripe-target": "expYear" } = hidden_field_tag "order[payments_attributes][][source_attributes][cc_type]", nil, { "data-stripe-target": "brand" } From 83fafe9969a5db3278928a6d2498f1dad1fd0d77 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Tue, 7 Dec 2021 16:46:42 +0000 Subject: [PATCH 27/30] Fix stripe elements styling The selectors are different now that we're not using Angular to build the stripe card element. --- app/assets/stylesheets/darkswarm/split-checkout.scss | 12 ++++++++++++ .../split_checkout/payment/_stripe_sca.html.haml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/darkswarm/split-checkout.scss b/app/assets/stylesheets/darkswarm/split-checkout.scss index b6c262f817..90e70a6a23 100644 --- a/app/assets/stylesheets/darkswarm/split-checkout.scss +++ b/app/assets/stylesheets/darkswarm/split-checkout.scss @@ -97,6 +97,18 @@ } } + .stripe-card { + background: white; + box-sizing: border-box; + font-weight: 400; + padding: 0.6rem 0.5rem; + border: 1px solid #cccccc; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + border-radius: 0; + height: 42px; + width: 100%; + } + label { margin-bottom: 0.3rem; } diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index 34c4326385..3b0d20c98e 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -12,7 +12,7 @@ %label = t('split_checkout.step2.form.stripe.use_new_card') - %div{ "data-controller": "stripe", "data-stripe-key": "#{Stripe.publishable_key}" } + %div.stripe-card{ "data-controller": "stripe", "data-stripe-key": "#{Stripe.publishable_key}" } = hidden_field_tag "order[payments_attributes][][source_attributes][first_name]", @order.bill_address.first_name = hidden_field_tag "order[payments_attributes][][source_attributes][last_name]", @order.bill_address.last_name = hidden_field_tag "order[payments_attributes][][source_attributes][month]", nil, { "data-stripe-target": "expMonth" } From 3c491299fd6065c670aaf026ced11b98f9a85ec4 Mon Sep 17 00:00:00 2001 From: jibees Date: Thu, 9 Dec 2021 11:05:41 +0100 Subject: [PATCH 28/30] Update config/locales/en.yml Co-authored-by: Maikel --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index bd815d6587..bff8d76dec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1741,7 +1741,7 @@ en: use_saved_card: Use saved card use_new_card: Enter your card identifiers save_card: Save card for future use - create_new_card: or create new card below + create_new_card: or enter new card details below explaination: You can review and confirm your order in the next step which includes the final costs. submit: Next - Order summary cancel: Back to Your details From 53e12fb5e52b39174cfd92d80e71c18c7197c551 Mon Sep 17 00:00:00 2001 From: jibees Date: Thu, 9 Dec 2021 11:12:23 +0100 Subject: [PATCH 29/30] Get rid of conditional branches with `&` operator Co-authored-by: Maikel --- app/controllers/concerns/checkout_callbacks.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/concerns/checkout_callbacks.rb b/app/controllers/concerns/checkout_callbacks.rb index bb620715a0..102fa5d185 100644 --- a/app/controllers/concerns/checkout_callbacks.rb +++ b/app/controllers/concerns/checkout_callbacks.rb @@ -42,8 +42,7 @@ module CheckoutCallbacks end def load_saved_credit_cards - @saved_credit_cards = [] if !spree_current_user - @saved_credit_cards = spree_current_user.credit_cards.with_payment_profile.all if spree_current_user + @saved_credit_cards = spree_current_user&.credit_cards&.with_payment_profile.to_a @selected_card = nil end From a80818d2b6a550c7a9ba619ea5d43eb7b39d2fb7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 9 Dec 2021 11:30:57 +0100 Subject: [PATCH 30/30] Fix rubocop warning Extra empty line detected at module body end. --- app/controllers/concerns/checkout_callbacks.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/concerns/checkout_callbacks.rb b/app/controllers/concerns/checkout_callbacks.rb index 102fa5d185..4c97ee2bd9 100644 --- a/app/controllers/concerns/checkout_callbacks.rb +++ b/app/controllers/concerns/checkout_callbacks.rb @@ -94,5 +94,4 @@ module CheckoutCallbacks def check_authorization authorize!(:edit, current_order, session[:access_token]) end - end