From 8272aebe29072820ed27f40fc30e0b64eaca730c Mon Sep 17 00:00:00 2001 From: stveep Date: Sun, 17 Jun 2018 18:15:41 +0100 Subject: [PATCH 1/9] Add Stripe form and duplicate Stripe Elements in backend Angular app --- .../payments/controllers/payment.js.coffee | 9 ++++ .../directives/stripe_elements.js.coffee | 35 ++++++++++++++ .../admin/payments/payments.js.coffee | 1 + .../admin/payments/services/payment.js.coffee | 47 +++++++++++++++++++ .../services/stripe_elements.js.coffee | 47 +++++++++++++++++++ .../resources/payment_resource.js.coffee | 5 ++ .../new/add_angular_to_form.html.erb.deface | 4 ++ ...verride_submit_for_button.html.haml.deface | 2 + app/views/spree/admin/payments/_form.html.erb | 4 +- .../payments/source_forms/_stripe.html.haml | 16 ++++++- 10 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/admin/payments/controllers/payment.js.coffee create mode 100644 app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee create mode 100644 app/assets/javascripts/admin/payments/payments.js.coffee create mode 100644 app/assets/javascripts/admin/payments/services/payment.js.coffee create mode 100644 app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee create mode 100644 app/assets/javascripts/admin/resources/resources/payment_resource.js.coffee create mode 100644 app/overrides/spree/admin/payments/new/add_angular_to_form.html.erb.deface create mode 100644 app/overrides/spree/admin/payments/new/override_submit_for_button.html.haml.deface diff --git a/app/assets/javascripts/admin/payments/controllers/payment.js.coffee b/app/assets/javascripts/admin/payments/controllers/payment.js.coffee new file mode 100644 index 0000000000..e69ef1190d --- /dev/null +++ b/app/assets/javascripts/admin/payments/controllers/payment.js.coffee @@ -0,0 +1,9 @@ +angular.module("admin.payments").controller "PaymentCtrl", ($scope, Payment, Loading) -> + $scope.form_data = Payment.form_data + $scope.submitted = false + + $scope.submitPayment = () -> + return false if $scope.submitted == true + $scope.submitted = true + Loading.message = t("submitting_payment") + Payment.purchase() diff --git a/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee b/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee new file mode 100644 index 0000000000..418402b3c4 --- /dev/null +++ b/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee @@ -0,0 +1,35 @@ +angular.module("admin.payments").directive "stripeElements", ($injector, StripeElements) -> + restrict: 'E' + template: "" + + link: (scope, elem, attr)-> + if $injector.has('stripeObject') + stripe = $injector.get('stripeObject') + + card = stripe.elements().create 'card', + hidePostalCode: false + style: + base: + fontFamily: "Roboto, Arial, sans-serif" + fontSize: '16px' + color: '#5c5c5c' + '::placeholder': + color: '#6c6c6c' + card.mount('#card-element') + + # Elements validates user input as it is typed. To help your customers + # catch mistakes, you should listen to change events on the card Element + # and display any errors: + card.addEventListener 'change', (event) -> + displayError = document.getElementById('card-errors') + if event.error + displayError.textContent = event.error.message + else + displayError.textContent = '' + return + + StripeElements.stripe = stripe + StripeElements.card = card diff --git a/app/assets/javascripts/admin/payments/payments.js.coffee b/app/assets/javascripts/admin/payments/payments.js.coffee new file mode 100644 index 0000000000..16b943f05f --- /dev/null +++ b/app/assets/javascripts/admin/payments/payments.js.coffee @@ -0,0 +1 @@ +angular.module("admin.payments", ['OfnStripe','Loading','RailsFlashLoader','ngResource','admin.resources', 'ofn.admin','Navigation']) diff --git a/app/assets/javascripts/admin/payments/services/payment.js.coffee b/app/assets/javascripts/admin/payments/services/payment.js.coffee new file mode 100644 index 0000000000..167fade9bf --- /dev/null +++ b/app/assets/javascripts/admin/payments/services/payment.js.coffee @@ -0,0 +1,47 @@ +angular.module('admin.payments').factory 'Payment', (StripeElements, currentOrderNumber, paymentMethods, PaymentMethods, PaymentResource, Navigation, RailsFlashLoader)-> + new class Payment + order: currentOrderNumber + form_data: {} + + paymentMethodType: -> + PaymentMethods.byID[@form_data.payment_method].method_type + + preprocess: -> + munged_payment = {} + munged_payment["payment"] = {payment_method_id: @form_data.payment_method, amount: @form_data.amount} + munged_payment["order_id"] = @order + # Not tested with Gateway other than Stripe. Could fall back to Rails for this? + # Works ok without extra source_attrs for Cash, Bank Transfer etc. + switch @paymentMethodType() + when 'gateway' + angular.extend munged_payment.payment, { + source_attributes: + number: @form_data.card_number + month: @form_data.card_month + year: @form_data.card_year + verification_value: @form_data.card_verification_value + } + when 'stripe' + angular.extend munged_payment.payment, { + source_attributes: + gateway_payment_profile_id: @form_data.token + cc_type: @form_data.cc_type + last_digits: @form_data.card.last4 + month: @form_data.card.exp_month + year: @form_data.card.exp_year + } + munged_payment + + purchase: -> + if @paymentMethodType() == 'stripe' + StripeElements.requestToken(@form_data, @submit) + else + @submit() + + submit: => + munged = @preprocess() + PaymentResource.create({order_id: munged.order_id}, munged, (response, headers, status)=> + Navigation.go "/admin/orders/" + munged.order_id + "/payments" + , (response) -> + RailsFlashLoader.loadFlash({error: t("error saving payment")}) + ) diff --git a/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee b/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee new file mode 100644 index 0000000000..23c9a48e56 --- /dev/null +++ b/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee @@ -0,0 +1,47 @@ +angular.module("admin.payments").factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) -> + new class StripeElements + # TODO: add locale here for translations of error messages etc. from Stripe + + # These are both set from the StripeElements directive + stripe: null + card: null + + # New Stripe Elements method + requestToken: (secrets, submit, loading_message = t("processing_payment")) -> + return unless @stripe? && @card? + + Loading.message = loading_message + cardData = @makeCardData(secrets) + + @stripe.createToken(@card, cardData).then (response) => + if(response.error) + Loading.clear() + RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"}) + else + secrets.token = response.token.id + secrets.cc_type = @mapCC(response.token.card.brand) + secrets.card = response.token.card + submit() + + # Maps the brand returned by Stripe to that required by activemerchant + mapCC: (ccType) -> + if ccType == 'MasterCard' + return 'master' + else if ccType == 'Visa' + return 'visa' + else if ccType == 'American Express' + return 'american_express' + else if ccType == 'Discover' + return 'discover' + else if ccType == 'JCB' + return 'jcb' + else if ccType == 'Diners Club' + return 'diners_club' + return + + # It doesn't matter if any of these are nil, all are optional. + makeCardData: (secrets) -> + {'name': secrets.name, + 'address1': secrets.address1, + 'city': secrets.city, + 'zipcode': secrets.zipcode} diff --git a/app/assets/javascripts/admin/resources/resources/payment_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/payment_resource.js.coffee new file mode 100644 index 0000000000..c3dba12b64 --- /dev/null +++ b/app/assets/javascripts/admin/resources/resources/payment_resource.js.coffee @@ -0,0 +1,5 @@ +angular.module("admin.resources").factory 'PaymentResource', ($resource) -> + $resource('/admin/orders/:order_id/payments.json', {order_id: "@order_id"}, { + 'create': + method: 'POST' + }) diff --git a/app/overrides/spree/admin/payments/new/add_angular_to_form.html.erb.deface b/app/overrides/spree/admin/payments/new/add_angular_to_form.html.erb.deface new file mode 100644 index 0000000000..6ace1ee579 --- /dev/null +++ b/app/overrides/spree/admin/payments/new/add_angular_to_form.html.erb.deface @@ -0,0 +1,4 @@ + +<%= form_for @payment, :url => admin_order_payments_path(@order), + :html => {"ng-app" => "admin.payments", + "ng-controller" => "PaymentCtrl"} do |f| %> diff --git a/app/overrides/spree/admin/payments/new/override_submit_for_button.html.haml.deface b/app/overrides/spree/admin/payments/new/override_submit_for_button.html.haml.deface new file mode 100644 index 0000000000..4fb4d91ad5 --- /dev/null +++ b/app/overrides/spree/admin/payments/new/override_submit_for_button.html.haml.deface @@ -0,0 +1,2 @@ +/replace 'code[erb-loud]:contains("button @order.cart?")' += button_tag t(:update), type: 'button', "ng-click" => "submitPayment()" diff --git a/app/views/spree/admin/payments/_form.html.erb b/app/views/spree/admin/payments/_form.html.erb index fe019007b1..4b7d576db5 100644 --- a/app/views/spree/admin/payments/_form.html.erb +++ b/app/views/spree/admin/payments/_form.html.erb @@ -2,7 +2,7 @@
<%= f.label :amount, t(:amount) %> - <%= f.text_field :amount, :value => @order.outstanding_balance, :class => 'fullwidth' %> + <%= f.text_field :amount, :value => @order.outstanding_balance, :class => 'fullwidth', "watch-value-as" => 'form_data.amount', "ng-init" => "form_data.amount=#{@order.outstanding_balance}" %>
@@ -12,7 +12,7 @@ <% @payment_methods.each do |method| %>
  • diff --git a/app/views/spree/admin/payments/source_forms/_stripe.html.haml b/app/views/spree/admin/payments/source_forms/_stripe.html.haml index 24bde74a31..99e16aafb2 100644 --- a/app/views/spree/admin/payments/source_forms/_stripe.html.haml +++ b/app/views/spree/admin/payments/source_forms/_stripe.html.haml @@ -1,4 +1,16 @@ -# = render "spree/admin/payments/source_forms/gateway", payment_method: payment_method +.stripe + %script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"} + - if Stripe.publishable_key + :javascript + angular.module('admin.payments').value("stripeObject", Stripe("#{Stripe.publishable_key}")) -%strong - = t('.no_payment_via_admin_backend') + = admin_inject_json "admin.payments", "railsFlash", "flash" + = admin_inject_json "admin.payments", "currentOrderNumber", @order.number + = admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer + .row + .three.columns + = label_tag :cardholder_name, t(:cardholder_name) + .six.columns + = text_field_tag :cardholder_name, nil, {size: 40, "ng-model" => 'form_data.name'} + %stripe-elements From a848b9172ecd4f35d9ca80e0bb2017b464a48842 Mon Sep 17 00:00:00 2001 From: stveep Date: Sun, 17 Jun 2018 20:38:48 +0100 Subject: [PATCH 2/9] Change name to AdminStripeElements to avoid confusion; add status bar for StatusMessages --- .../admin/payments/controllers/payment.js.coffee | 5 +++-- .../payments/directives/stripe_elements.js.coffee | 6 +++--- .../javascripts/admin/payments/payments.js.coffee | 2 +- .../admin/payments/services/payment.js.coffee | 8 ++++---- .../payments/services/stripe_elements.js.coffee | 13 +++++-------- .../payments/new/add_save_bar.html.haml.deface | 3 +++ 6 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 app/overrides/spree/admin/payments/new/add_save_bar.html.haml.deface diff --git a/app/assets/javascripts/admin/payments/controllers/payment.js.coffee b/app/assets/javascripts/admin/payments/controllers/payment.js.coffee index e69ef1190d..6c763b778c 100644 --- a/app/assets/javascripts/admin/payments/controllers/payment.js.coffee +++ b/app/assets/javascripts/admin/payments/controllers/payment.js.coffee @@ -1,9 +1,10 @@ -angular.module("admin.payments").controller "PaymentCtrl", ($scope, Payment, Loading) -> +angular.module("admin.payments").controller "PaymentCtrl", ($scope, Payment, StatusMessage) -> $scope.form_data = Payment.form_data $scope.submitted = false + $scope.StatusMessage = StatusMessage $scope.submitPayment = () -> return false if $scope.submitted == true $scope.submitted = true - Loading.message = t("submitting_payment") + StatusMessage.display 'progress', t("submitting_payment") Payment.purchase() diff --git a/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee b/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee index 418402b3c4..b478a3f970 100644 --- a/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee +++ b/app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.payments").directive "stripeElements", ($injector, StripeElements) -> +angular.module('admin.payments').directive "stripeElements", ($injector, AdminStripeElements) -> restrict: 'E' template: "