summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordaniel <noreply@maennchen1.de>2026-10-07 14:10:51 +0200
committerdaniel <noreply@maennchen1.de>2026-10-07 14:10:51 +0200
commit52df56d7476408d2c2ac5b729b6e02202524a0f3 (patch)
treecd7c7000375acc28afed35e76705de1c8bf715de
parentca7620bbc817dd9ed1ef7e380e5158025066a286 (diff)
- Modul SmartCheckout um Abschlussoptionen erweitert
-rw-r--r--views/css/smartcheckout.css11
-rw-r--r--views/js/smartcheckout.js115
-rw-r--r--views/mods/mod_smartcheckout/checkout.phtml441
-rw-r--r--views/mods/mod_smartcheckout/settings_edit.phtml13
4 files changed, 372 insertions, 208 deletions
diff --git a/views/css/smartcheckout.css b/views/css/smartcheckout.css
index aed9849..6b1f0ec 100644
--- a/views/css/smartcheckout.css
+++ b/views/css/smartcheckout.css
@@ -43,6 +43,11 @@ html body.content-style-boxed .entry-content .wpsg_smartcheckout_after.alignwide
.wpsg_smartcheckout .sc_layout { display:grid; grid-template-columns:minmax(0, 1fr) calc(21 * var(--sc-u, 1rem)); gap:var(--sc-gap); align-items:start; }
.wpsg_smartcheckout_single .sc_layout { grid-template-columns:minmax(0, 1fr); }
.wpsg_smartcheckout .sc_main { display:flex; flex-direction:column; min-width:0; }
+/* Hülle der Eingaben, im Bestätigungsschritt gesperrt; ohne die Rahmen und Abstände, die Browser einem fieldset geben */
+.wpsg_smartcheckout .sc_fieldset { display:flex; flex-direction:column; min-width:0; margin:0; padding:0; border:0; }
+.wpsg_smartcheckout .sc_fieldset:disabled .sc_box { opacity:0.75; }
+.wpsg_smartcheckout .sc_confirm_note { margin-bottom:var(--sc-gap); padding:calc(0.9 * var(--sc-u, 1rem)); border-left:3px solid var(--sc-accent); background:var(--sc-flat); scroll-margin-top:calc(1.5 * var(--sc-u, 1rem)); }
+.wpsg_smartcheckout .sc_confirm_note p { margin:calc(0.4 * var(--sc-u, 1rem)) 0 calc(0.75 * var(--sc-u, 1rem)) 0; }
.wpsg_smartcheckout .sc_side { position:sticky; top:calc(1.5 * var(--sc-u, 1rem)); }
.wpsg_smartcheckout_single .sc_side { position:static; }
.wpsg_smartcheckout .sc_layout.sc_busy { opacity:0.6; pointer-events:none; transition:opacity 0.15s ease-out; }
@@ -259,6 +264,11 @@ html body.content-style-boxed .entry-content .wpsg_smartcheckout_after.alignwide
/* Zusammenfassung */
.wpsg_smartcheckout .sc_summary { position:relative; padding:calc(1.25 * var(--sc-u, 1rem)); border:1px solid var(--sc-line-soft); border-radius:var(--sc-radius); }
.wpsg_smartcheckout_single .sc_side { margin-top:var(--sc-gap); }
+.wpsg_smartcheckout .sc_sum_products { display:flex; flex-direction:column; gap:calc(0.3 * var(--sc-u, 1rem)); margin-bottom:calc(0.75 * var(--sc-u, 1rem)); padding-bottom:calc(0.75 * var(--sc-u, 1rem)); border-bottom:1px solid var(--sc-line-soft); font-size:calc(0.85 * var(--sc-u, 1rem)); }
+.wpsg_smartcheckout .sc_sum_products:empty { display:none; }
+.wpsg_smartcheckout .sc_sum_product { display:flex; justify-content:space-between; gap:var(--sc-u, 1rem); align-items:baseline; }
+.wpsg_smartcheckout .sc_sum_product_name { min-width:0; overflow-wrap:anywhere; }
+.wpsg_smartcheckout .sc_sum_product_price { white-space:nowrap; }
.wpsg_smartcheckout .sc_sum_rows { display:flex; flex-direction:column; gap:calc(0.4 * var(--sc-u, 1rem)); }
.wpsg_smartcheckout .sc_sum_row { display:flex; justify-content:space-between; gap:var(--sc-u, 1rem); align-items:baseline; font-size:calc(0.95 * var(--sc-u, 1rem)); }
.wpsg_smartcheckout .sc_sum_value { white-space:nowrap; }
@@ -298,6 +308,7 @@ html body.content-style-boxed .entry-content .wpsg_smartcheckout_after.alignwide
.wpsg_smartcheckout .sc_button:disabled { opacity:0.5; cursor:default; }
.wpsg_smartcheckout .sc_button_secondary { padding:calc(0.5 * var(--sc-u, 1rem)) calc(0.9 * var(--sc-u, 1rem)); border-color:var(--sc-line); font-size:calc(0.9 * var(--sc-u, 1rem)); }
.wpsg_smartcheckout .sc_button_order { width:100%; margin-top:calc(1.1 * var(--sc-u, 1rem)); font-weight:600; }
+.wpsg_smartcheckout .sc_button_edit { width:100%; margin-top:calc(0.5 * var(--sc-u, 1rem)); }
/* Ladeanzeige */
.wpsg_smartcheckout .sc_spinner { position:absolute; top:calc(1.25 * var(--sc-u, 1rem)); right:calc(1.25 * var(--sc-u, 1rem)); }
diff --git a/views/js/smartcheckout.js b/views/js/smartcheckout.js
index 20674f8..9c4f4f1 100644
--- a/views/js/smartcheckout.js
+++ b/views/js/smartcheckout.js
@@ -46,11 +46,25 @@
queued: false,
queuedUpload: null,
submitting: false,
+ confirm: false,
timer: null
};
},
+ computed: {
+
+ /**
+ * Sind Bestellbedingungen und Bestellbutton zu sehen? Beim Bestellabschluss „Zusammenfassung vor Abschluss“ erst im Bestätigungsschritt
+ */
+ showFinal: function() {
+
+ return (this.state.finish !== 'confirm' || this.confirm);
+
+ } // showFinal()
+
+ },
+
created: function() {
this.syncForm(true);
@@ -65,8 +79,8 @@
// Nach einem abgelehnten Bestellversuch steht der Kunde vor dem ersten Fehler
if (this.state.errors.length !== 0) this.$nextTick(this.focusError);
- // Das GARAN-Gewährleistungslabel erscheint wie in der klassischen Zusammenfassung beim Betreten der Seite
- if (this.state.warranty && this.state.warranty.popup === true && this.state.empty !== true) this.garanPopup = true;
+ // Das GARAN-Gewährleistungslabel erscheint wie in der klassischen Zusammenfassung beim Betreten der Seite, mit Bestätigungsschritt erst dort
+ if (this.state.warranty && this.state.warranty.popup === true && this.state.empty !== true && this.state.finish !== 'confirm') this.garanPopup = true;
// Die Lightbox des Gewährleistungslabels lässt sich mit Esc schließen
var self = this;
@@ -77,10 +91,13 @@
});
- // Zurück aus dem Seiten-Cache des Browsers (z.B. Zurück-Button nach dem Zahlungsanbieter): Layer wieder entfernen
+ // Zurück aus dem Seiten-Cache des Browsers (z.B. Zurück-Button nach dem Zahlungsanbieter): Layer entfernen, Eingaben wieder bearbeitbar
window.addEventListener('pageshow', function(event) {
- if (event.persisted) self.submitting = false;
+ if (!event.persisted) return;
+
+ self.submitting = false;
+ self.confirm = false;
});
@@ -157,7 +174,7 @@
selectChoice: function(type, id, event) {
if (event && event.target && typeof event.target.closest === 'function' && event.target.closest('.sc_choice_head') !== null) return;
- if (this.form[type] === id) return;
+ if (this.form[type] === id || this.confirm) return;
this.form[type] = id;
this.refresh();
@@ -242,7 +259,8 @@
var el_form = document.getElementById('wpsg_smartcheckout_form');
- if (el_form === null) return;
+ // Im Bestätigungsschritt sind die Eingaben gesperrt und würden ohne sie übertragen
+ if (el_form === null || this.confirm) return;
// Dateien der direkt hochzuladenden Felder sofort sichern: der Block wird mit der nächsten Antwort neu aufgebaut
var arUpload = [];
@@ -358,6 +376,8 @@
}
+ if (action === 'confirm') self.afterConfirm(result.confirm === true);
+
if (self.queuedUpload !== null) {
var upload = self.queuedUpload;
@@ -477,8 +497,75 @@
}, // focusError()
/**
- * Blockt das Absenden, solange der Server noch rechnet
+ * Prüft die Angaben und wechselt in den Bestätigungsschritt (Bestellabschluss „Zusammenfassung vor Abschluss“)
+ *
+ * Die Pflichtfelder der Eingaben prüft zuerst der Browser, Bestellbedingungen und AGB erst der Bestätigungsschritt.
*/
+ startConfirm: function() {
+
+ if (this.busy) return;
+
+ var el_fieldset = el_root.querySelector('.sc_fieldset');
+
+ if (el_fieldset !== null && typeof el_fieldset.reportValidity === 'function' && !el_fieldset.reportValidity()) return;
+
+ // Eine noch ausstehende Änderung geht in derselben Anfrage mit
+ if (this.timer !== null) {
+
+ window.clearTimeout(this.timer);
+ this.timer = null;
+
+ }
+
+ this.send('confirm');
+
+ }, // startConfirm()
+
+ /**
+ * Antwort auf startConfirm(): bei bestandener Prüfung Eingaben sperren, sonst zum ersten Fehler
+ */
+ afterConfirm: function(bConfirm) {
+
+ var self = this;
+
+ if (!bConfirm) {
+
+ this.$nextTick(this.focusError);
+
+ return;
+
+ }
+
+ this.confirm = true;
+
+ // Das GARAN-Gewährleistungslabel erscheint wie in der klassischen Zusammenfassung
+ if (this.state.warranty && this.state.warranty.popup === true && this.garanConfirmed !== true) this.garanPopup = true;
+
+ this.$nextTick(function() {
+
+ var el_note = el_root.querySelector('.sc_confirm_note');
+
+ if (el_note !== null) el_note.scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+ });
+
+ }, // afterConfirm(bConfirm)
+
+ /**
+ * Verlässt den Bestätigungsschritt, die Eingaben sind wieder bearbeitbar
+ */
+ editConfirm: function() {
+
+ this.confirm = false;
+
+ this.$nextTick(function() {
+
+ el_root.scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+ });
+
+ }, // editConfirm()
+
/**
* Berechnet neu, wenn sich eine Eingabe in einem Block der Module ändert (z.B. Lieferadresse, Produktvariablen)
*
@@ -698,6 +785,20 @@
}
+ /*
+ * Gesperrte Felder würden nicht übertragen. Der Browser stellt die Formulardaten erst nach diesem Ereignis
+ * zusammen, deshalb hier direkt im DOM freigeben und nicht über Vue (das aktualisiert erst später).
+ */
+ if (this.confirm) {
+
+ el_root.querySelectorAll('fieldset.sc_fieldset').forEach(function(el) {
+
+ el.disabled = false;
+
+ });
+
+ }
+
// Ab hier wird tatsächlich bestellt: Layer über die Seite, bis der Browser die nächste Seite lädt
this.submitting = true;
diff --git a/views/mods/mod_smartcheckout/checkout.phtml b/views/mods/mod_smartcheckout/checkout.phtml
index 947f3d8..043b151 100644
--- a/views/mods/mod_smartcheckout/checkout.phtml
+++ b/views/mods/mod_smartcheckout/checkout.phtml
@@ -46,269 +46,281 @@
<div class="sc_main">
- <?php /* Schritt 1: Warenkorb */ ?>
- <section class="sc_box" aria-labelledby="sc_title_basket">
- <h2 class="sc_title" id="sc_title_basket"><span class="sc_step">1</span><?php echo esc_html__('Ihr Warenkorb', 'wpsg'); ?></h2>
-
- <div class="sc_modul_basket sc_modul_dynamic" v-if="state.html.basketTop" v-html="state.html.basketTop"></div>
+ <?php /* Bestätigungsschritt beim Bestellabschluss „Zusammenfassung vor Abschluss“ */ ?>
+ <div class="sc_confirm_note" v-if="confirm" role="status">
+ <strong><?php echo esc_html__('Bitte prüfen Sie Ihre Angaben', 'wpsg'); ?></strong>
+ <p><?php echo esc_html__('Alle Eingaben sind jetzt gesperrt. Stimmt alles, schließen Sie die Bestellung in der Zusammenfassung ab. Um etwas zu korrigieren, klicken Sie auf „Angaben ändern“.', 'wpsg'); ?></p>
+ <button type="button" class="sc_button sc_button_secondary" @click="editConfirm" :disabled="busy"><?php echo esc_html__('Angaben ändern', 'wpsg'); ?></button>
+ </div>
- <div class="sc_products">
+ <?php /* Im Bestätigungsschritt sind alle Eingaben gesperrt, vor dem Absenden gibt smartcheckout.js sie wieder frei */ ?>
+ <fieldset class="sc_fieldset" :disabled="confirm">
- <div class="sc_product" v-for="product in state.products" :key="product.index">
+ <?php /* Schritt 1: Warenkorb */ ?>
+ <section class="sc_box" aria-labelledby="sc_title_basket">
+ <h2 class="sc_title" id="sc_title_basket"><span class="sc_step">1</span><?php echo esc_html__('Ihr Warenkorb', 'wpsg'); ?></h2>
- <div class="sc_product_image" v-if="product.image">
- <img :src="product.image" :alt="product.name" loading="lazy" />
- </div>
+ <div class="sc_modul_basket sc_modul_dynamic" v-if="state.html.basketTop" v-html="state.html.basketTop"></div>
- <div class="sc_product_body">
+ <div class="sc_products">
- <?php /* Angaben der Module vor dem Produkt, z.B. Maße beim Druckshop */ ?>
- <div class="sc_product_before sc_modul_dynamic" v-if="product.before" v-html="product.before"></div>
+ <div class="sc_product" v-for="product in state.products" :key="product.index">
- <div class="sc_product_name">
- <a v-if="product.link" :href="product.link">{{ product.name }}</a>
- <span v-else>{{ product.name }}</span>
+ <div class="sc_product_image" v-if="product.image">
+ <img :src="product.image" :alt="product.name" loading="lazy" />
</div>
- <div class="sc_product_desc" v-if="product.shortdesc" v-html="product.shortdesc"></div>
- <div class="sc_product_extra sc_modul_dynamic" v-if="product.extra" v-html="product.extra"></div>
- <div class="sc_product_garan" v-if="product.garan" v-html="product.garan"></div>
- <div class="sc_product_revocation" v-if="product.revocation" v-html="product.revocation"></div>
+ <div class="sc_product_body">
- <div class="sc_product_tax" v-if="state.showTax">
- <?php echo esc_html__('MwSt.', 'wpsg'); ?> {{ product.tax_value }}
- </div>
+ <?php /* Angaben der Module vor dem Produkt, z.B. Maße beim Druckshop */ ?>
+ <div class="sc_product_before sc_modul_dynamic" v-if="product.before" v-html="product.before"></div>
- </div>
+ <div class="sc_product_name">
+ <a v-if="product.link" :href="product.link">{{ product.name }}</a>
+ <span v-else>{{ product.name }}</span>
+ </div>
- <div class="sc_product_amount">
+ <div class="sc_product_desc" v-if="product.shortdesc" v-html="product.shortdesc"></div>
+ <div class="sc_product_extra sc_modul_dynamic" v-if="product.extra" v-html="product.extra"></div>
+ <div class="sc_product_garan" v-if="product.garan" v-html="product.garan"></div>
+ <div class="sc_product_revocation" v-if="product.revocation" v-html="product.revocation"></div>
+
+ <div class="sc_product_tax" v-if="state.showTax">
+ <?php echo esc_html__('MwSt.', 'wpsg'); ?> {{ product.tax_value }}
+ </div>
- <div class="sc_stepper" v-if="!product.fixed">
- <button type="button" class="sc_stepper_btn" @click="changeAmount(product, -1)" :disabled="busy" :aria-label="'<?php echo esc_js(__('Menge verringern', 'wpsg')); ?>'">&minus;</button>
- <input
- type="text"
- inputmode="numeric"
- class="sc_stepper_input"
- :name="'wpsg[menge][' + product.index + ']'"
- v-model="amounts[product.index]"
- @change="refresh"
- :aria-label="'<?php echo esc_js(__('Menge', 'wpsg')); ?>'"
- />
- <button type="button" class="sc_stepper_btn" @click="changeAmount(product, 1)" :disabled="busy" :aria-label="'<?php echo esc_js(__('Menge erhöhen', 'wpsg')); ?>'">+</button>
</div>
- <div class="sc_stepper sc_stepper_fixed" v-else>
- <input type="hidden" :name="'wpsg[menge][' + product.index + ']'" :value="product.amount" />
- <span class="sc_stepper_static">{{ product.amount }}</span>
+ <div class="sc_product_amount">
+
+ <div class="sc_stepper" v-if="!product.fixed">
+ <button type="button" class="sc_stepper_btn" @click="changeAmount(product, -1)" :disabled="busy" :aria-label="'<?php echo esc_js(__('Menge verringern', 'wpsg')); ?>'">&minus;</button>
+ <input
+ type="text"
+ inputmode="numeric"
+ class="sc_stepper_input"
+ :name="'wpsg[menge][' + product.index + ']'"
+ v-model="amounts[product.index]"
+ @change="refresh"
+ :aria-label="'<?php echo esc_js(__('Menge', 'wpsg')); ?>'"
+ />
+ <button type="button" class="sc_stepper_btn" @click="changeAmount(product, 1)" :disabled="busy" :aria-label="'<?php echo esc_js(__('Menge erhöhen', 'wpsg')); ?>'">+</button>
+ </div>
+
+ <div class="sc_stepper sc_stepper_fixed" v-else>
+ <input type="hidden" :name="'wpsg[menge][' + product.index + ']'" :value="product.amount" />
+ <span class="sc_stepper_static">{{ product.amount }}</span>
+ </div>
+
+ <button type="button" class="sc_remove" @click="removeProduct(product)" :disabled="busy">
+ <?php echo esc_html__('Entfernen', 'wpsg'); ?>
+ </button>
+
</div>
- <button type="button" class="sc_remove" @click="removeProduct(product)" :disabled="busy">
- <?php echo esc_html__('Entfernen', 'wpsg'); ?>
- </button>
+ <div class="sc_product_price">
+ <div class="sc_product_price_total">{{ product.price_total }}</div>
+ <div class="sc_product_price_single" v-if="product.amount !== 1">{{ product.price }} <?php echo esc_html__('je Stück', 'wpsg'); ?></div>
+ </div>
- </div>
+ <?php /* Eingabefelder der Module (z.B. Produktvariablen) über die volle Breite der Position */ ?>
+ <div class="sc_product_fields sc_modul_dynamic" v-if="product.fields" v-html="product.fields"></div>
- <div class="sc_product_price">
- <div class="sc_product_price_total">{{ product.price_total }}</div>
- <div class="sc_product_price_single" v-if="product.amount !== 1">{{ product.price }} <?php echo esc_html__('je Stück', 'wpsg'); ?></div>
</div>
- <?php /* Eingabefelder der Module (z.B. Produktvariablen) über die volle Breite der Position */ ?>
- <div class="sc_product_fields sc_modul_dynamic" v-if="product.fields" v-html="product.fields"></div>
-
</div>
- </div>
+ <div class="sc_modul_basket sc_modul_dynamic" v-if="state.html.basketAfterProducts" v-html="state.html.basketAfterProducts"></div>
- <div class="sc_modul_basket sc_modul_dynamic" v-if="state.html.basketAfterProducts" v-html="state.html.basketAfterProducts"></div>
-
- <?php if ($this->hasMod('wpsg_mod_gutschein')) { ?>
- <div class="sc_voucher">
-
- <label class="sc_voucher_label" for="wpsg_smartcheckout_voucher"><?php echo esc_html__('Gutscheincode', 'wpsg'); ?></label>
-
- <div class="sc_voucher_row">
- <input
- type="text"
- id="wpsg_smartcheckout_voucher"
- class="sc_voucher_input"
- :class="{ 'sc_invalid': voucherError }"
- name="wpsg[gutschein]"
- v-model="voucher"
- @keydown.enter.prevent="addVoucher"
- />
- <button type="button" class="sc_button sc_button_secondary" @click="addVoucher" :disabled="busy || voucher === ''">
- <?php echo esc_html__('Einlösen', 'wpsg'); ?>
- </button>
- </div>
+ <?php if ($this->hasMod('wpsg_mod_gutschein')) { ?>
+ <div class="sc_voucher">
+
+ <label class="sc_voucher_label" for="wpsg_smartcheckout_voucher"><?php echo esc_html__('Gutscheincode', 'wpsg'); ?></label>
- <div class="sc_voucher_list" v-if="state.hasVouchers">
- <div class="sc_voucher_item" v-for="item in state.vouchers" :key="item.id">
- <span class="sc_voucher_code"><template v-if="item.coupon"><?php echo esc_html__('Wertgutschein', 'wpsg'); ?> </template>{{ item.code }}</span>
- <span class="sc_voucher_partial" v-if="item.partial"><?php echo esc_html__('teilweise angerechnet', 'wpsg'); ?></span>
- <span class="sc_voucher_value">{{ item.value }}</span>
- <button type="button" class="sc_remove" @click="removeVoucher(item)" :disabled="busy"><?php echo esc_html__('Entfernen', 'wpsg'); ?></button>
+ <div class="sc_voucher_row">
+ <input
+ type="text"
+ id="wpsg_smartcheckout_voucher"
+ class="sc_voucher_input"
+ :class="{ 'sc_invalid': voucherError }"
+ name="wpsg[gutschein]"
+ v-model="voucher"
+ @keydown.enter.prevent="addVoucher"
+ />
+ <button type="button" class="sc_button sc_button_secondary" @click="addVoucher" :disabled="busy || voucher === ''">
+ <?php echo esc_html__('Einlösen', 'wpsg'); ?>
+ </button>
</div>
+
+ <div class="sc_voucher_list" v-if="state.hasVouchers">
+ <div class="sc_voucher_item" v-for="item in state.vouchers" :key="item.id">
+ <span class="sc_voucher_code"><template v-if="item.coupon"><?php echo esc_html__('Wertgutschein', 'wpsg'); ?> </template>{{ item.code }}</span>
+ <span class="sc_voucher_partial" v-if="item.partial"><?php echo esc_html__('teilweise angerechnet', 'wpsg'); ?></span>
+ <span class="sc_voucher_value">{{ item.value }}</span>
+ <button type="button" class="sc_remove" @click="removeVoucher(item)" :disabled="busy"><?php echo esc_html__('Entfernen', 'wpsg'); ?></button>
+ </div>
+ </div>
+
</div>
+ <?php } ?>
- </div>
- <?php } ?>
+ </section>
- </section>
+ <?php /* Schritt 2: Kundendaten */ ?>
+ <section class="sc_box" aria-labelledby="sc_title_customer">
+ <h2 class="sc_title" id="sc_title_customer"><span class="sc_step">2</span><?php echo esc_html__('Ihre Daten', 'wpsg'); ?></h2>
- <?php /* Schritt 2: Kundendaten */ ?>
- <section class="sc_box" aria-labelledby="sc_title_customer">
- <h2 class="sc_title" id="sc_title_customer"><span class="sc_step">2</span><?php echo esc_html__('Ihre Daten', 'wpsg'); ?></h2>
+ <?php /* Login und Registrierung der Kundenverwaltung, serverseitig gerendert */ ?>
+ <div class="sc_modul_login" v-pre>
+ <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_kundenverwaltung', 'checkout_login'); ?>
+ </div>
- <?php /* Login und Registrierung der Kundenverwaltung, serverseitig gerendert */ ?>
- <div class="sc_modul_login" v-pre>
- <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_kundenverwaltung', 'checkout_login'); ?>
- </div>
+ <div class="sc_fields">
- <div class="sc_fields">
-
- <div
- class="sc_field"
- v-for="field in state.fields"
- :key="field.name"
- :class="['sc_field_' + field.width, { 'sc_invalid': hasError(field.name) }]"
- >
-
- <label class="sc_label" :for="'wpsg_sc_' + field.name">
- {{ field.label }}<span class="sc_required" v-if="field.required" aria-hidden="true">*</span>
- </label>
-
- <select
- v-if="field.type === 'select'"
- class="sc_input"
- :id="'wpsg_sc_' + field.name"
- :name="fieldName(field)"
- :required="field.required"
- v-model="form[field.name]"
- @change="refresh"
+ <div
+ class="sc_field"
+ v-for="field in state.fields"
+ :key="field.name"
+ :class="['sc_field_' + field.width, { 'sc_invalid': hasError(field.name) }]"
>
- <option value="-1"><?php echo esc_html__('Bitte auswählen', 'wpsg'); ?></option>
- <option v-for="option in field.options" :key="option.value" :value="option.value">{{ option.label }}</option>
- </select>
- <template v-else-if="field.type === 'checkbox'">
- <input type="hidden" :name="fieldName(field)" value="0" />
+ <label class="sc_label" :for="'wpsg_sc_' + field.name">
+ {{ field.label }}<span class="sc_required" v-if="field.required" aria-hidden="true">*</span>
+ </label>
+
+ <select
+ v-if="field.type === 'select'"
+ class="sc_input"
+ :id="'wpsg_sc_' + field.name"
+ :name="fieldName(field)"
+ :required="field.required"
+ v-model="form[field.name]"
+ @change="refresh"
+ >
+ <option value="-1"><?php echo esc_html__('Bitte auswählen', 'wpsg'); ?></option>
+ <option v-for="option in field.options" :key="option.value" :value="option.value">{{ option.label }}</option>
+ </select>
+
+ <template v-else-if="field.type === 'checkbox'">
+ <input type="hidden" :name="fieldName(field)" value="0" />
+ <input
+ type="checkbox"
+ class="sc_checkbox"
+ :id="'wpsg_sc_' + field.name"
+ :name="fieldName(field)"
+ value="1"
+ :required="field.required"
+ v-model="form[field.name]"
+ true-value="1"
+ false-value="0"
+ @change="refresh"
+ />
+ </template>
+
<input
- type="checkbox"
- class="sc_checkbox"
+ v-else
+ class="sc_input"
+ :type="field.type"
:id="'wpsg_sc_' + field.name"
:name="fieldName(field)"
- value="1"
:required="field.required"
+ :autocomplete="field.autocomplete || 'off'"
v-model="form[field.name]"
- true-value="1"
- false-value="0"
@change="refresh"
+ @paste="field.nopaste ? $event.preventDefault() : null"
/>
- </template>
-
- <input
- v-else
- class="sc_input"
- :type="field.type"
- :id="'wpsg_sc_' + field.name"
- :name="fieldName(field)"
- :required="field.required"
- :autocomplete="field.autocomplete || 'off'"
- v-model="form[field.name]"
- @change="refresh"
- @paste="field.nopaste ? $event.preventDefault() : null"
- />
+
+ </div>
</div>
- </div>
+ <?php /* Erweiterungen der Module innerhalb der Kundendaten */ ?>
+ <div class="sc_modul_customer" v-pre>
+ <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_kundenverwaltung', 'checkout_customer_inner'); ?>
+ <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_nlsatolo', 'checkout_customer_inner'); ?>
+ <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_trustedshops', 'checkout_customer_inner'); ?>
+ <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_newsletter', 'checkout_customer_inner'); ?>
+ </div>
- <?php /* Erweiterungen der Module innerhalb der Kundendaten */ ?>
- <div class="sc_modul_customer" v-pre>
- <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_kundenverwaltung', 'checkout_customer_inner'); ?>
- <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_nlsatolo', 'checkout_customer_inner'); ?>
- <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_trustedshops', 'checkout_customer_inner'); ?>
- <?php echo \wpsgSmartcheckout\State::renderMod('wpsg_mod_newsletter', 'checkout_customer_inner'); ?>
- </div>
+ <div class="sc_hint">
+ <?php echo wpsg_translate(__('Mit #1# gekennzeichnete Felder sind Pflichtfelder.', 'wpsg'), '<span class="sc_required">*</span>'); ?>
+ </div>
- <div class="sc_hint">
- <?php echo wpsg_translate(__('Mit #1# gekennzeichnete Felder sind Pflichtfelder.', 'wpsg'), '<span class="sc_required">*</span>'); ?>
- </div>
+ </section>
- </section>
+ <?php /* Abweichende Lieferanschrift und weitere Module vor dem Abschluss */ ?>
+ <?php $strPreButton = \wpsgSmartcheckout\State::renderHook('checkout_inner_prebutton', [&$this->view]); ?>
+ <?php if (trim($strPreButton) !== '') { ?>
+ <section class="sc_box sc_modul_extra" v-pre>
+ <?php echo $strPreButton; ?>
+ </section>
+ <?php } ?>
- <?php /* Abweichende Lieferanschrift und weitere Module vor dem Abschluss */ ?>
- <?php $strPreButton = \wpsgSmartcheckout\State::renderHook('checkout_inner_prebutton', [&$this->view]); ?>
- <?php if (trim($strPreButton) !== '') { ?>
- <section class="sc_box sc_modul_extra" v-pre>
- <?php echo $strPreButton; ?>
- </section>
- <?php } ?>
+ <?php /* Blöcke der Module, die sich mit jeder Antwort ändern (z.B. Bestellupload) */ ?>
+ <section class="sc_box sc_modul_upload sc_modul_dynamic" v-if="state.html.checkoutDynamic" v-html="state.html.checkoutDynamic"></section>
- <?php /* Blöcke der Module, die sich mit jeder Antwort ändern (z.B. Bestellupload) */ ?>
- <section class="sc_box sc_modul_upload sc_modul_dynamic" v-if="state.html.checkoutDynamic" v-html="state.html.checkoutDynamic"></section>
+ <?php /* Schritt 3: Versandart */ ?>
+ <section class="sc_box" v-if="state.shipping.show" aria-labelledby="sc_title_shipping">
+ <h2 class="sc_title" id="sc_title_shipping"><span class="sc_step">3</span><?php echo esc_html__('Versandart', 'wpsg'); ?></h2>
- <?php /* Schritt 3: Versandart */ ?>
- <section class="sc_box" v-if="state.shipping.show" aria-labelledby="sc_title_shipping">
- <h2 class="sc_title" id="sc_title_shipping"><span class="sc_step">3</span><?php echo esc_html__('Versandart', 'wpsg'); ?></h2>
+ <div class="sc_choices" :class="{ 'sc_invalid': hasError('shipping') }">
- <div class="sc_choices" :class="{ 'sc_invalid': hasError('shipping') }">
+ <?php /* Das label umfasst nur die Kopfzeile, Hinweise der Module können eigene Eingabefelder enthalten */ ?>
+ <div class="sc_choice" v-for="item in state.shipping.list" :key="item.id" :class="{ 'sc_choice_active': form.shipping === item.id }" @click="selectChoice('shipping', item.id, $event)">
- <?php /* Das label umfasst nur die Kopfzeile, Hinweise der Module können eigene Eingabefelder enthalten */ ?>
- <div class="sc_choice" v-for="item in state.shipping.list" :key="item.id" :class="{ 'sc_choice_active': form.shipping === item.id }" @click="selectChoice('shipping', item.id, $event)">
+ <?php /* Radio, Logo und Bezeichnung auf einer Zeile, die Beschreibung darunter */ ?>
+ <label class="sc_choice_head">
+ <input type="radio" name="wpsg[checkout][shipping]" :value="item.id" v-model="form.shipping" @change="refresh" />
+ <img class="sc_choice_logo" v-if="item.logo" :src="item.logo" :alt="item.name" />
+ <span class="sc_choice_name">{{ item.name }}</span>
+ <span class="sc_choice_price" v-if="item.price">{{ item.price }}</span>
+ </label>
+ <div class="sc_choice_hint" v-if="item.hint" v-html="item.hint"></div>
- <?php /* Radio, Logo und Bezeichnung auf einer Zeile, die Beschreibung darunter */ ?>
- <label class="sc_choice_head">
- <input type="radio" name="wpsg[checkout][shipping]" :value="item.id" v-model="form.shipping" @change="refresh" />
- <img class="sc_choice_logo" v-if="item.logo" :src="item.logo" :alt="item.name" />
- <span class="sc_choice_name">{{ item.name }}</span>
- <span class="sc_choice_price" v-if="item.price">{{ item.price }}</span>
- </label>
- <div class="sc_choice_hint" v-if="item.hint" v-html="item.hint"></div>
+ </div>
</div>
- </div>
+ </section>
- </section>
+ <?php /* Schritt 4: Zahlungsart */ ?>
+ <section class="sc_box" v-if="state.payment.show" aria-labelledby="sc_title_payment">
+ <h2 class="sc_title" id="sc_title_payment"><span class="sc_step">4</span><?php echo esc_html__('Zahlungsart', 'wpsg'); ?></h2>
- <?php /* Schritt 4: Zahlungsart */ ?>
- <section class="sc_box" v-if="state.payment.show" aria-labelledby="sc_title_payment">
- <h2 class="sc_title" id="sc_title_payment"><span class="sc_step">4</span><?php echo esc_html__('Zahlungsart', 'wpsg'); ?></h2>
+ <div class="sc_choices" :class="{ 'sc_invalid': hasError('payment') }">
- <div class="sc_choices" :class="{ 'sc_invalid': hasError('payment') }">
+ <?php /* Das label umfasst nur die Kopfzeile, Hinweise der Module können eigene Eingabefelder enthalten (z.B. Kreditkarte) */ ?>
+ <div class="sc_choice" v-for="item in state.payment.list" :key="item.id" :class="{ 'sc_choice_active': form.payment === item.id }" @click="selectChoice('payment', item.id, $event)">
- <?php /* Das label umfasst nur die Kopfzeile, Hinweise der Module können eigene Eingabefelder enthalten (z.B. Kreditkarte) */ ?>
- <div class="sc_choice" v-for="item in state.payment.list" :key="item.id" :class="{ 'sc_choice_active': form.payment === item.id }" @click="selectChoice('payment', item.id, $event)">
+ <?php /* Radio, Logo und Bezeichnung auf einer Zeile, Beschreibung und Zusatzangaben darunter */ ?>
+ <label class="sc_choice_head">
+ <input type="radio" name="wpsg[checkout][payment]" :value="item.id" v-model="form.payment" @change="refresh" />
+ <img class="sc_choice_logo" v-if="item.logo" :src="item.logo" :alt="item.name" />
+ <span class="sc_choice_name">{{ item.name }}</span>
+ <span class="sc_choice_price" v-if="item.price">{{ item.price }}</span>
+ </label>
+ <div class="sc_choice_hint" v-if="item.hint" v-html="item.hint"></div>
+ <div class="sc_choice_extra" v-if="item.extra" v-html="item.extra"></div>
- <?php /* Radio, Logo und Bezeichnung auf einer Zeile, Beschreibung und Zusatzangaben darunter */ ?>
- <label class="sc_choice_head">
- <input type="radio" name="wpsg[checkout][payment]" :value="item.id" v-model="form.payment" @change="refresh" />
- <img class="sc_choice_logo" v-if="item.logo" :src="item.logo" :alt="item.name" />
- <span class="sc_choice_name">{{ item.name }}</span>
- <span class="sc_choice_price" v-if="item.price">{{ item.price }}</span>
- </label>
- <div class="sc_choice_hint" v-if="item.hint" v-html="item.hint"></div>
- <div class="sc_choice_extra" v-if="item.extra" v-html="item.extra"></div>
+ </div>
</div>
- </div>
+ <?php /* Zahlungsmodule, die eigene Eingabefelder mitbringen */ ?>
+ <div class="sc_modul_payment sc_modul_dynamic" v-if="state.html.paymentInner" v-html="state.html.paymentInner"></div>
- <?php /* Zahlungsmodule, die eigene Eingabefelder mitbringen */ ?>
- <div class="sc_modul_payment sc_modul_dynamic" v-if="state.html.paymentInner" v-html="state.html.paymentInner"></div>
+ </section>
- </section>
+ <?php /* Bestellkommentar */ ?>
+ <section class="sc_box" aria-labelledby="sc_title_comment">
+ <h2 class="sc_title" id="sc_title_comment"><?php echo esc_html__('Bestellkommentar', 'wpsg'); ?></h2>
- <?php /* Bestellkommentar */ ?>
- <section class="sc_box" aria-labelledby="sc_title_comment">
- <h2 class="sc_title" id="sc_title_comment"><?php echo esc_html__('Bestellkommentar', 'wpsg'); ?></h2>
+ <label class="sc_label sc_label_hidden" for="wpsg_sc_comment"><?php echo esc_html__('Bestellkommentar', 'wpsg'); ?></label>
+ <textarea class="sc_textarea" id="wpsg_sc_comment" name="wpsg[checkout][comment]" rows="4" v-model="form.comment"></textarea>
- <label class="sc_label sc_label_hidden" for="wpsg_sc_comment"><?php echo esc_html__('Bestellkommentar', 'wpsg'); ?></label>
- <textarea class="sc_textarea" id="wpsg_sc_comment" name="wpsg[checkout][comment]" rows="4" v-model="form.comment"></textarea>
+ </section>
- </section>
+ </fieldset>
</div>
@@ -319,6 +331,14 @@
<h2 class="sc_title" id="sc_title_sum"><?php echo esc_html__('Zusammenfassung', 'wpsg'); ?></h2>
+ <?php /* Positionen in Kurzform, damit Ware, Menge und Preis unmittelbar über dem Bestellbutton stehen (§ 312j Abs. 2 BGB) */ ?>
+ <div class="sc_sum_products">
+ <div class="sc_sum_product" v-for="product in state.products" :key="product.index">
+ <span class="sc_sum_product_name">{{ product.amount }} &times; {{ product.name }}</span>
+ <span class="sc_sum_product_price">{{ product.price_total }}</span>
+ </div>
+ </div>
+
<div class="sc_sum_rows">
<div class="sc_sum_row">
@@ -398,11 +418,11 @@
<div
class="sc_modul_conditions sc_modul_dynamic"
:class="{ 'sc_invalid': state.errorConditions }"
- v-if="state.html.conditions"
+ v-if="state.html.conditions && showFinal"
v-html="state.html.conditions"
></div>
- <div class="sc_agb" v-if="state.needAGB" :class="{ 'sc_invalid': hasError('agb') }">
+ <div class="sc_agb" v-if="state.needAGB && showFinal" :class="{ 'sc_invalid': hasError('agb') }">
<label>
<input type="checkbox" name="wpsg[agb]" value="1" v-model="agb" required />
<span><?php
@@ -416,14 +436,35 @@
</div>
<?php /* GARAN-Gewährleistungslabel direkt vor dem Bestellbutton */ ?>
- <div class="sc_garan" v-if="state.warranty && state.warranty.need && !state.warranty.popup">
+ <div class="sc_garan" v-if="state.warranty && state.warranty.need && !state.warranty.popup && showFinal">
<button type="button" class="sc_garan_zoom" @click="garanLightbox = true" title="<?php echo esc_attr__('Gewährleistungslabel vergrößern', 'wpsg'); ?>">
<img :src="state.warranty.image" alt="<?php echo esc_attr__('GARAN Gewährleistungslabel', 'wpsg'); ?>" width="1200" height="1697" />
</button>
</div>
+ <?php /* Bestellabschluss „Zusammenfassung anzeigen“: Prüfen und weiter zur klassischen Zusammenfassung, dort wird bestellt */ ?>
+ <button
+ v-if="state.finish === 'overview'"
+ type="submit"
+ class="sc_button sc_button_order"
+ name="wpsg_smartcheckout_order"
+ value="1"
+ :disabled="busy"
+ ><?php echo esc_html__('Weiter zur Zusammenfassung', 'wpsg'); ?></button>
+
+ <?php /* Bestellabschluss „Zusammenfassung vor Abschluss“: Prüfen und Eingaben für den Bestätigungsschritt sperren */ ?>
+ <button
+ v-if="state.canOrder && state.finish === 'confirm' && !confirm"
+ type="button"
+ class="sc_button sc_button_order"
+ @click="startConfirm"
+ :disabled="busy"
+ ><?php echo esc_html__('Weiter', 'wpsg'); ?></button>
+
+ <input v-if="confirm" type="hidden" name="wpsg_smartcheckout_confirmed" value="1" />
+
<button
- v-if="state.canOrder"
+ v-if="state.canOrder && showFinal && state.finish !== 'overview'"
type="submit"
class="sc_button sc_button_order"
name="wpsg_smartcheckout_order"
@@ -435,6 +476,8 @@
<template v-else><?php echo esc_html__('zahlungspflichtig bestellen', 'wpsg'); ?></template>
</button>
+ <button v-if="confirm" type="button" class="sc_button sc_button_secondary sc_button_edit" @click="editConfirm" :disabled="busy"><?php echo esc_html__('Angaben ändern', 'wpsg'); ?></button>
+
<div class="sc_sum_hint">
<?php echo wpsg_translate(__('Es gelten die Preise inklusive der gesetzlichen MwSt. zuzüglich #1#.', 'wpsg'), '<a target="_blank" rel="noopener" href="'.esc_url($state['url']['shipping']).'">'.esc_html__('Versandkosten', 'wpsg').'</a>'); ?>
</div>
diff --git a/views/mods/mod_smartcheckout/settings_edit.phtml b/views/mods/mod_smartcheckout/settings_edit.phtml
index f713efe..46e43d8 100644
--- a/views/mods/mod_smartcheckout/settings_edit.phtml
+++ b/views/mods/mod_smartcheckout/settings_edit.phtml
@@ -10,17 +10,26 @@
'1' => __('Einspaltig', 'wpsg')
];
+ // Reihenfolge wie im Bestellablauf: mit eigener Zusammenfassung zuerst
+ $arFinish = [
+ wpsg_mod_smartcheckout::FINISH_OVERVIEW => __('Zusammenfassung anzeigen', 'wpsg'),
+ wpsg_mod_smartcheckout::FINISH_CONFIRM => __('Zusammenfassung vor Abschluss', 'wpsg'),
+ wpsg_mod_smartcheckout::FINISH_DIRECT => __('Direkt abschließen', 'wpsg')
+ ];
+
?>
<?php echo wpsg_drawForm_Select('wpsg_mod_smartcheckout_page', __('Seite für den SmartCheckout', 'wpsg'), $this->view['arPages'], $this->get_option('wpsg_mod_smartcheckout_page'), ['hint' => __('Wählen Sie „Neu anlegen und zuordnen", damit beim Speichern automatisch eine eigene Seite erstellt wird — oder ordnen Sie eine bestehende Seite zu. Der Checkout wird automatisch in diese Seite eingebunden.', 'wpsg')]); ?>
<?php echo wpsg_drawForm_Select('wpsg_mod_smartcheckout_layout', __('Aufteilung', 'wpsg'), $arLayout, $this->get_option('wpsg_mod_smartcheckout_layout'), ['hint' => __('Zweispaltig: Eingaben links, Zusammenfassung rechts mitlaufend. Einspaltig: Zusammenfassung unterhalb der Eingaben.', 'wpsg')]); ?>
+<?php echo wpsg_drawForm_Select('wpsg_mod_smartcheckout_finish', __('Bestellabschluss', 'wpsg'), $arFinish, $this->get_option('wpsg_mod_smartcheckout_finish'), ['hint' => __('Zusammenfassung anzeigen: „Weiter zur Zusammenfassung“ führt auf die gesonderte Bestellübersicht, dort wird zahlungspflichtig bestellt. Zusammenfassung vor Abschluss: „Weiter“ prüft die Angaben und sperrt alle Felder, der Button wechselt auf „zahlungspflichtig bestellen“, mit „Angaben ändern“ lassen sich die Felder wieder bearbeiten. Direkt abschließen: Der Kunde bestellt direkt im SmartCheckout.', 'wpsg')]); ?>
+
<?php echo wpsg_drawForm_Checkbox('wpsg_mod_smartcheckout_image', __('Produktbilder im Warenkorb anzeigen', 'wpsg'), $this->get_option('wpsg_mod_smartcheckout_image')); ?>
<p class="wpsg_note">
- <?php echo __('Solange das Modul aktiv ist, wird die Warenkorbseite auf den SmartCheckout umgeleitet — der klassische Ablauf (Warenkorb, Kasse, Versand/Zahlung, Zusammenfassung) wird nicht mehr angesteuert.', 'wpsg'); ?>
+ <?php echo esc_html__('Solange das Modul aktiv ist, wird die Warenkorbseite auf den SmartCheckout umgeleitet — der klassische Ablauf (Warenkorb, Kasse, Versand/Zahlung) wird nicht mehr angesteuert. Die klassische Zusammenfassung erscheint nur beim Bestellabschluss „Zusammenfassung anzeigen“.', 'wpsg'); ?>
</p>
<p class="wpsg_note">
- <?php echo __('Der SmartCheckout rechnet ausschließlich serverseitig — jede Änderung an Menge, Anschrift, Versand- oder Zahlungsart wird an den Shop geschickt und mit der regulären Kalkulation neu berechnet.', 'wpsg'); ?>
+ <?php echo esc_html__('Der SmartCheckout rechnet ausschließlich serverseitig — jede Änderung an Menge, Anschrift, Versand- oder Zahlungsart wird an den Shop geschickt und mit der regulären Kalkulation neu berechnet.', 'wpsg'); ?>
</p>