mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge pull request #5994 from openfoodfoundation/tcs_checkbox
Terms and Conditions checkbox on checkout
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
angular.module("admin.enterprises").directive 'termsAndConditionsWarning', ($compile, $templateCache, DialogDefaults, $timeout) ->
|
||||
restrict: 'A'
|
||||
scope: true
|
||||
|
||||
link: (scope, element, attr) ->
|
||||
# This file input click handler will hold the browser file input dialog and show a warning modal
|
||||
scope.hold_file_input_and_show_warning_modal = (event) ->
|
||||
event.preventDefault()
|
||||
scope.template = $compile($templateCache.get('admin/modals/terms_and_conditions_warning.html'))(scope)
|
||||
if scope.template.dialog
|
||||
scope.template.dialog(DialogDefaults)
|
||||
scope.template.dialog('open')
|
||||
scope.$apply()
|
||||
|
||||
element.bind 'click', scope.hold_file_input_and_show_warning_modal
|
||||
|
||||
# When the user presses continue in the warning modal, we open the browser file input dialog
|
||||
scope.continue = ->
|
||||
scope.template.dialog('close')
|
||||
|
||||
# unbind warning modal handler and click file input again to open the browser file input dialog
|
||||
element.unbind('click').trigger('click')
|
||||
# afterwards, bind warning modal handler again so that the warning is shown the next time
|
||||
$timeout ->
|
||||
element.bind 'click', scope.hold_file_input_and_show_warning_modal
|
||||
return
|
||||
|
||||
scope.close = ->
|
||||
scope.template.dialog('close')
|
||||
return
|
||||
@@ -95,6 +95,10 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE
|
||||
last_name: @order.bill_address.lastname
|
||||
save_requested_by_customer: @secrets.save_requested_by_customer
|
||||
}
|
||||
|
||||
if @terms_and_conditions_accepted()
|
||||
munged_order["terms_and_conditions_accepted"] = true
|
||||
|
||||
munged_order
|
||||
|
||||
shippingMethod: ->
|
||||
@@ -114,3 +118,7 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE
|
||||
|
||||
cartTotal: ->
|
||||
@order.display_total + @shippingPrice() + @paymentPrice()
|
||||
|
||||
terms_and_conditions_accepted: ->
|
||||
terms_and_conditions_checkbox = angular.element("#accept_terms")[0]
|
||||
terms_and_conditions_checkbox? && terms_and_conditions_checkbox.checked
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
%div
|
||||
.margin-bottom-30.text-center
|
||||
.text-big
|
||||
{{ 'js.admin.modals.terms_and_conditions_info.title' | t }}
|
||||
.margin-bottom-30
|
||||
%p
|
||||
{{ 'js.admin.modals.terms_and_conditions_info.message_1' | t }}
|
||||
.margin-bottom-30
|
||||
%p
|
||||
{{ 'js.admin.modals.terms_and_conditions_info.message_2' | t }}
|
||||
|
||||
.text-center
|
||||
%input.button.red.icon-plus{ type: 'button', value: t('js.admin.modals.got_it'), ng: { click: 'close()' } }
|
||||
@@ -0,0 +1,14 @@
|
||||
%div
|
||||
.margin-bottom-30.text-center
|
||||
.text-big
|
||||
{{ 'js.admin.modals.terms_and_conditions_warning.title' | t }}
|
||||
.margin-bottom-30
|
||||
%p
|
||||
{{ 'js.admin.modals.terms_and_conditions_warning.message_1' | t }}
|
||||
.margin-bottom-30
|
||||
%p
|
||||
{{ 'js.admin.modals.terms_and_conditions_warning.message_2' | t }}
|
||||
|
||||
.text-center
|
||||
%input.button.red{ type: 'button', value: t('js.admin.modals.close'), ng: { click: 'close()' } }
|
||||
%input.button.red{ type: 'button', value: t('js.admin.modals.continue'), ng: { click: 'continue()' } }
|
||||
16
app/helpers/terms_and_conditions_helper.rb
Normal file
16
app/helpers/terms_and_conditions_helper.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TermsAndConditionsHelper
|
||||
def terms_and_conditions_activated?
|
||||
current_order.distributor.terms_and_conditions.file?
|
||||
end
|
||||
|
||||
def terms_and_conditions_already_accepted?
|
||||
customer_terms_and_conditions_accepted_at = spree_current_user&.
|
||||
customer_of(current_order.distributor)&.terms_and_conditions_accepted_at
|
||||
|
||||
customer_terms_and_conditions_accepted_at.present? &&
|
||||
(customer_terms_and_conditions_accepted_at >
|
||||
current_order.distributor.terms_and_conditions_updated_at)
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,8 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
:preferred_product_selection_from_inventory_only,
|
||||
:preferred_show_customer_names_to_suppliers, :owner, :contact, :users, :tag_groups,
|
||||
:default_tag_group, :require_login, :allow_guest_orders, :allow_order_changes,
|
||||
:logo, :promo_image, :terms_and_conditions, :terms_and_conditions_file_name
|
||||
:logo, :promo_image, :terms_and_conditions,
|
||||
:terms_and_conditions_file_name, :terms_and_conditions_updated_at
|
||||
|
||||
has_one :owner, serializer: Api::Admin::UserSerializer
|
||||
has_many :users, serializer: Api::Admin::UserSerializer
|
||||
@@ -21,9 +22,13 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def terms_and_conditions
|
||||
return unless @object.terms_and_conditions.file?
|
||||
return unless object.terms_and_conditions.file?
|
||||
|
||||
@object.terms_and_conditions.url
|
||||
object.terms_and_conditions.url
|
||||
end
|
||||
|
||||
def terms_and_conditions_updated_at
|
||||
object.terms_and_conditions_updated_at&.to_s
|
||||
end
|
||||
|
||||
def tag_groups
|
||||
|
||||
@@ -8,6 +8,7 @@ module Checkout
|
||||
end
|
||||
|
||||
def success(controller, params, current_user)
|
||||
set_customer_terms_and_conditions_accepted_at(params)
|
||||
save_order_addresses_as_user_default(params, current_user)
|
||||
OrderCompletionReset.new(controller, @order).call
|
||||
end
|
||||
@@ -26,5 +27,11 @@ module Checkout
|
||||
user_default_address_setter.set_default_bill_address if params[:order][:default_bill_address]
|
||||
user_default_address_setter.set_default_ship_address if params[:order][:default_ship_address]
|
||||
end
|
||||
|
||||
def set_customer_terms_and_conditions_accepted_at(params)
|
||||
return unless params[:order]
|
||||
|
||||
@order.customer.update(terms_and_conditions_accepted_at: Time.zone.now) if params[:order][:terms_and_conditions_accepted]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,12 +35,15 @@
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :terms_and_conditions, t('.terms_and_conditions')
|
||||
%i.text-big.icon-question-sign.help-modal{ template: 'admin/modals/terms_and_conditions_info.html' }
|
||||
|
||||
.omega.eight.columns
|
||||
%a{ href: '{{ Enterprise.terms_and_conditions }}', ng: { if: 'Enterprise.terms_and_conditions' } }
|
||||
%a{ href: '{{ Enterprise.terms_and_conditions }}', target: '_blank', ng: { if: 'Enterprise.terms_and_conditions' } }
|
||||
= '{{ Enterprise.terms_and_conditions_file_name }}'
|
||||
= t('.uploaded_on')
|
||||
= '{{ Enterprise.terms_and_conditions_updated_at }}'
|
||||
.pad-top
|
||||
= f.file_field :terms_and_conditions
|
||||
= f.file_field :terms_and_conditions, accept: 'application/pdf', 'terms-and-conditions-warning' => 'true'
|
||||
.pad-top
|
||||
%a.button.red{ href: '', ng: {click: 'removeTermsAndConditions()', if: 'Enterprise.terms_and_conditions'} }
|
||||
= t('.remove_terms_and_conditions')
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
= render "checkout/already_ordered", f: f if show_bought_items?
|
||||
= render "checkout/terms_and_conditions", f: f
|
||||
%p
|
||||
%button.button.primary{type: :submit}
|
||||
%button.button.primary{ type: :submit, ng: { disabled: "terms_and_conditions_activated && !terms_and_conditions_accepted" } }
|
||||
= t :checkout_send
|
||||
/ {{ checkout.$valid }}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
%p.small
|
||||
= t('.message_html', terms_and_conditions_link: link_to( t( '.link_text' ), current_order.distributor.terms_and_conditions.url, target: '_blank')) if current_order.distributor.terms_and_conditions.file?
|
||||
- if terms_and_conditions_activated?
|
||||
%p
|
||||
%input{ type: 'checkbox', id: 'accept_terms', ng: { model: "terms_and_conditions_accepted", init: "terms_and_conditions_activated=#{terms_and_conditions_activated?}; terms_and_conditions_accepted=#{terms_and_conditions_already_accepted?}" } }
|
||||
%label.small{for: "accept_terms"}= t('.message_html', terms_and_conditions_link: link_to( t( '.link_text' ), current_order.distributor.terms_and_conditions.url, target: '_blank'))
|
||||
|
||||
@@ -705,6 +705,7 @@ en:
|
||||
invoice_text: Add customized text at the end of invoices
|
||||
terms_and_conditions: "Terms and Conditions"
|
||||
remove_terms_and_conditions: "Remove File"
|
||||
uploaded_on: "uploaded on"
|
||||
contact:
|
||||
name: Name
|
||||
name_placeholder: eg. Gustav Plum
|
||||
@@ -1235,8 +1236,8 @@ en:
|
||||
cart: "cart"
|
||||
message_html: "You have an order for this order cycle already. Check the %{cart} to see the items you ordered before. You can also cancel items as long as the order cycle is open."
|
||||
terms_and_conditions:
|
||||
message_html: "By placing this order you agree to the %{terms_and_conditions_link}."
|
||||
link_text: "Terms of Service"
|
||||
message_html: "I agree to the seller's %{terms_and_conditions_link}."
|
||||
link_text: "Terms and Conditions"
|
||||
failed: "The checkout failed. Please let us know so that we can process your order."
|
||||
shops:
|
||||
hubs:
|
||||
@@ -2517,8 +2518,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
admin:
|
||||
enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it."
|
||||
modals:
|
||||
got_it: Got it
|
||||
got_it: "Got it"
|
||||
close: "Close"
|
||||
continue: "Continue"
|
||||
invite: "Invite"
|
||||
invite_title: "Invite an unregistered user"
|
||||
tag_rule_help:
|
||||
@@ -2537,6 +2539,14 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
customer_tagged_rules_text: >
|
||||
By creating rules related to a specific customer tag, you can override the default
|
||||
behaviour (whether it be to show or to hide items) for customers with the specified tag.
|
||||
terms_and_conditions_info:
|
||||
title: "Uploading Terms and Conditions"
|
||||
message_1: "Terms and Conditions are the contract between you, the seller, and the shopper. If you upload a file here shoppers must accept your Terms and Conditions in order to complete checkout. For the shopper this will appear as a checkbox at checkout that must be checked in order to proceed with checkout. We highly recommend you upload Terms and Conditions in alignment with national legislation."
|
||||
message_2: "Shoppers will only be required to accept Terms and Conditions once. However if you change you Terms and Conditions shoppers will again be required to accept them before they can checkout."
|
||||
terms_and_conditions_warning:
|
||||
title: "Uploading Terms and Conditions"
|
||||
message_1: "All your buyers will have to agree to them once at checkout. If you update the file, all your buyers will have to agree to them again at checkout."
|
||||
message_2: "For buyers with subscriptions, you need to email them the Terms and Conditions (or the changes to them) for now, nothing will notify them about these new Terms and Conditions."
|
||||
panels:
|
||||
save: SAVE
|
||||
saved: SAVED
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddCustomerTermsAndConditionsAccepted < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :customers, :terms_and_conditions_accepted_at, :datetime
|
||||
end
|
||||
end
|
||||
11
db/schema.rb
11
db/schema.rb
@@ -47,16 +47,17 @@ ActiveRecord::Schema.define(version: 20200912190210) do
|
||||
add_index "coordinator_fees", ["order_cycle_id"], name: "index_coordinator_fees_on_order_cycle_id", using: :btree
|
||||
|
||||
create_table "customers", force: true do |t|
|
||||
t.string "email", null: false
|
||||
t.integer "enterprise_id", null: false
|
||||
t.string "email", null: false
|
||||
t.integer "enterprise_id", null: false
|
||||
t.string "code"
|
||||
t.integer "user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "bill_address_id"
|
||||
t.integer "ship_address_id"
|
||||
t.string "name"
|
||||
t.boolean "allow_charges", default: false, null: false
|
||||
t.boolean "allow_charges", default: false, null: false
|
||||
t.datetime "terms_and_conditions_accepted_at"
|
||||
end
|
||||
|
||||
add_index "customers", ["bill_address_id"], name: "index_customers_on_bill_address_id", using: :btree
|
||||
|
||||
@@ -57,7 +57,7 @@ class OrderFactory
|
||||
bill_address: order_address,
|
||||
ship_address: order_address
|
||||
)
|
||||
order.line_items.create( variant_id: first_variant.id, quantity: 5 )
|
||||
order.line_items.create(variant_id: first_variant.id, quantity: 5)
|
||||
order.payments.create(payment_method_id: first_payment_method_id)
|
||||
order
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ feature "Uploading Terms and Conditions PDF" do
|
||||
visit edit_admin_enterprise_path(distributor)
|
||||
end
|
||||
|
||||
describe "images for an enterprise" do
|
||||
describe "with terms and conditions to upload" do
|
||||
def go_to_business_details
|
||||
within(".side_menu") do
|
||||
click_link "Business Details"
|
||||
@@ -43,21 +43,27 @@ feature "Uploading Terms and Conditions PDF" do
|
||||
|
||||
# Add PDF
|
||||
attach_file "enterprise[terms_and_conditions]", white_pdf_file_name
|
||||
click_button "Update"
|
||||
|
||||
Timecop.freeze(run_time = Time.zone.local(2002, 4, 13, 0, 0, 0)) do
|
||||
click_button "Update"
|
||||
expect(distributor.reload.terms_and_conditions_updated_at).to eq run_time
|
||||
end
|
||||
expect(page).
|
||||
to have_content("Enterprise \"#{distributor.name}\" has been successfully updated!")
|
||||
to have_content "Enterprise \"#{distributor.name}\" has been successfully updated!"
|
||||
|
||||
go_to_business_details
|
||||
expect(page).to have_selector("a[href*='logo-white.pdf']")
|
||||
expect(page).to have_selector "a[href*='logo-white.pdf'][target=\"_blank\"]"
|
||||
expect(page).to have_content "2002-04-13 00:00:00 +1000"
|
||||
|
||||
# Replace PDF
|
||||
attach_file "enterprise[terms_and_conditions]", black_pdf_file_name
|
||||
click_button "Update"
|
||||
expect(page).
|
||||
to have_content("Enterprise \"#{distributor.name}\" has been successfully updated!")
|
||||
to have_content "Enterprise \"#{distributor.name}\" has been successfully updated!"
|
||||
expect(distributor.reload.terms_and_conditions_updated_at).to_not eq run_time
|
||||
|
||||
go_to_business_details
|
||||
expect(page).to have_selector("a[href*='logo-black.pdf']")
|
||||
expect(page).to have_selector "a[href*='logo-black.pdf']"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,11 +122,11 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
end
|
||||
|
||||
it "doesn't show link to terms and conditions" do
|
||||
expect(page).to have_no_link("Terms of Service")
|
||||
expect(page).to have_no_link("Terms and Conditions")
|
||||
end
|
||||
end
|
||||
|
||||
context "when distributor has terms and conditions" do
|
||||
context "when distributor has T&Cs" do
|
||||
let(:fake_terms_and_conditions_path) { Rails.root.join("app/assets/images/logo-white.png") }
|
||||
let(:terms_and_conditions_file) { Rack::Test::UploadedFile.new(fake_terms_and_conditions_path, "application/pdf") }
|
||||
|
||||
@@ -135,9 +135,37 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
order.distributor.save
|
||||
end
|
||||
|
||||
it "shows a link to the terms and conditions" do
|
||||
visit checkout_path
|
||||
expect(page).to have_link("Terms of Service", href: order.distributor.terms_and_conditions.url)
|
||||
describe "when customer has not accepted T&Cs before" do
|
||||
it "shows a link to the T&Cs and disables checkout button until terms are accepted" do
|
||||
visit checkout_path
|
||||
expect(page).to have_link("Terms and Conditions", href: order.distributor.terms_and_conditions.url)
|
||||
|
||||
expect(page).to have_button("Place order now", disabled: true)
|
||||
|
||||
check "accept_terms"
|
||||
expect(page).to have_button("Place order now", disabled: false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when customer has already accepted T&Cs before" do
|
||||
before do
|
||||
customer = create(:customer, enterprise: order.distributor, user: user)
|
||||
customer.update terms_and_conditions_accepted_at: Time.zone.now
|
||||
end
|
||||
|
||||
it "enables checkout button (because T&Cs are accepted by default)" do
|
||||
visit checkout_path
|
||||
expect(page).to have_button("Place order now", disabled: false)
|
||||
end
|
||||
|
||||
describe "but afterwards the enterprise has uploaded a new T&Cs file" do
|
||||
before { order.distributor.update terms_and_conditions_updated_at: Time.zone.now }
|
||||
|
||||
it "disables checkout button until terms are accepted" do
|
||||
visit checkout_path
|
||||
expect(page).to have_button("Place order now", disabled: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
describe "termsAndConditionsWarning", ->
|
||||
element = null
|
||||
templatecache = null
|
||||
|
||||
beforeEach ->
|
||||
module('admin.enterprises')
|
||||
|
||||
inject ($rootScope, $compile, $templateCache) ->
|
||||
templatecache = $templateCache
|
||||
el = angular.element("<input terms-and-conditions-warning=\"true\"></input>")
|
||||
element = $compile(el)($rootScope)
|
||||
$rootScope.$digest()
|
||||
|
||||
describe "terms and conditions warning", ->
|
||||
it "should load template", ->
|
||||
spyOn(templatecache, 'get')
|
||||
element.triggerHandler('click');
|
||||
expect(templatecache.get).toHaveBeenCalledWith('admin/modals/terms_and_conditions_warning.html')
|
||||
@@ -65,6 +65,7 @@ describe 'Checkout service', ->
|
||||
inject ($injector, _$httpBackend_, $rootScope)->
|
||||
$httpBackend = _$httpBackend_
|
||||
Checkout = $injector.get("Checkout")
|
||||
spyOn(Checkout, "terms_and_conditions_accepted")
|
||||
scope = $rootScope.$new()
|
||||
scope.Checkout = Checkout
|
||||
Navigation = $injector.get("Navigation")
|
||||
|
||||
@@ -23,6 +23,21 @@ describe Checkout::PostCheckoutActions do
|
||||
postCheckoutActions.success(controller, params, current_user)
|
||||
end
|
||||
|
||||
describe "setting customer terms_and_conditions_accepted_at" do
|
||||
before { order.customer = build(:customer) }
|
||||
|
||||
it "does not set customer's terms_and_conditions to the current time if terms have not been accepted" do
|
||||
postCheckoutActions.success(controller, params, current_user)
|
||||
expect(order.customer.terms_and_conditions_accepted_at).to be_nil
|
||||
end
|
||||
|
||||
it "sets customer's terms_and_conditions to the current time if terms have been accepted" do
|
||||
params = { order: { terms_and_conditions_accepted: true } }
|
||||
postCheckoutActions.success(controller, params, current_user)
|
||||
expect(order.customer.terms_and_conditions_accepted_at).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "setting the user default address" do
|
||||
let(:user_default_address_setter) { instance_double(UserDefaultAddressSetter) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user