@vio-live/web-sdk 0.11.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/{chunk-7TGCXO5J.js → chunk-MRJR6DG5.js} +2088 -298
- package/dist/chunk-MRJR6DG5.js.map +1 -0
- package/dist/chunk-PZUOMOLF.js +3 -0
- package/dist/{chunk-QYQQ5J6E.js.map → chunk-PZUOMOLF.js.map} +1 -1
- package/dist/{chunk-TJ43TE7H.js → chunk-WTVGVDZX.js} +1403 -71
- package/dist/chunk-WTVGVDZX.js.map +1 -0
- package/dist/core/index.d.ts +737 -121
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-D6PBisUm.d.ts → tokens-Dkbfcd1L.d.ts} +396 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +2 -1
- package/dist/chunk-7TGCXO5J.js.map +0 -1
- package/dist/chunk-QYQQ5J6E.js +0 -3
- package/dist/chunk-TJ43TE7H.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vio, __decorateClass, primaryImageUrl, displayPrice, getGlobalCurrency, formatPrice } from './chunk-
|
|
1
|
+
import { Vio, __decorateClass, primaryImageUrl, displayPrice, getGlobalCurrency, ensureVippsButton, formatPrice, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, AdyenOriginError } from './chunk-WTVGVDZX.js';
|
|
2
2
|
import { LitElement, css, html } from 'lit';
|
|
3
3
|
import { property, state, query } from 'lit/decorators.js';
|
|
4
4
|
|
|
@@ -863,6 +863,215 @@ function sponsorNameFromBootstrap(sponsorId) {
|
|
|
863
863
|
if (boot.primarySponsor?.id === sponsorId) return boot.primarySponsor.name;
|
|
864
864
|
return boot.secondarySponsors?.find((s) => s.id === sponsorId)?.name;
|
|
865
865
|
}
|
|
866
|
+
|
|
867
|
+
// src/core/checkout/method-taxonomy.ts
|
|
868
|
+
function normalizeMethodName(name) {
|
|
869
|
+
return String(name ?? "").toLowerCase().replace(/[^a-z]/g, "");
|
|
870
|
+
}
|
|
871
|
+
var EMBEDDED_METHODS = ["kustom", "qliro", "walley", "nexi"];
|
|
872
|
+
var FORM_FIRST_WIDGET_METHODS = ["adyen"];
|
|
873
|
+
var NO_EXPRESS_BUTTON_METHODS = [
|
|
874
|
+
...EMBEDDED_METHODS,
|
|
875
|
+
...FORM_FIRST_WIDGET_METHODS
|
|
876
|
+
];
|
|
877
|
+
var COLLECTS_OWN_ADDRESS = [...EMBEDDED_METHODS, "vipps"];
|
|
878
|
+
var isEmbeddedMethod = (name) => EMBEDDED_METHODS.includes(normalizeMethodName(name));
|
|
879
|
+
var isFormFirstWidgetMethod = (name) => FORM_FIRST_WIDGET_METHODS.includes(normalizeMethodName(name));
|
|
880
|
+
var collectsOwnAddress = (name) => COLLECTS_OWN_ADDRESS.includes(normalizeMethodName(name));
|
|
881
|
+
function isMethodEnabled(available, ...names) {
|
|
882
|
+
if (available === null) return true;
|
|
883
|
+
return available.some(
|
|
884
|
+
(m) => names.some((n) => normalizeMethodName(m) === normalizeMethodName(n))
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/ui/dialog-focus.ts
|
|
889
|
+
var DialogFocus = class {
|
|
890
|
+
constructor(opts) {
|
|
891
|
+
this.opts = opts;
|
|
892
|
+
this.opener = null;
|
|
893
|
+
this.active = false;
|
|
894
|
+
/** The element inside the dialog that had focus last — where a pull-back continues from. */
|
|
895
|
+
this.lastInside = null;
|
|
896
|
+
this.onKeydown = (e) => {
|
|
897
|
+
if (e.defaultPrevented) return;
|
|
898
|
+
if (e.key === "Escape" || e.key === "Esc") {
|
|
899
|
+
if (this.opts.canClose && !this.opts.canClose()) return;
|
|
900
|
+
e.preventDefault();
|
|
901
|
+
e.stopPropagation();
|
|
902
|
+
this.opts.close();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (e.key !== "Tab" || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
906
|
+
const container = this.opts.container();
|
|
907
|
+
if (!container) return;
|
|
908
|
+
const items = tabbableIn(container);
|
|
909
|
+
if (items.length === 0) {
|
|
910
|
+
e.preventDefault();
|
|
911
|
+
focusElement(container);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const active = deepActiveElement();
|
|
915
|
+
const index = active ? items.indexOf(active) : -1;
|
|
916
|
+
e.preventDefault();
|
|
917
|
+
const count = items.length;
|
|
918
|
+
const next = index === -1 ? e.shiftKey ? count - 1 : 0 : (index + (e.shiftKey ? -1 : 1) + count) % count;
|
|
919
|
+
focusElement(items[next]);
|
|
920
|
+
};
|
|
921
|
+
/**
|
|
922
|
+
* Focus moved somewhere. Inside the dialog: remember where. Into another
|
|
923
|
+
* open Vio dialog: theirs. Onto the page: the browser walked out of the
|
|
924
|
+
* dialog on its own (Safari leaving a widget iframe) — continue from the
|
|
925
|
+
* element that had focus, backwards when the page element sits before
|
|
926
|
+
* the host in document order.
|
|
927
|
+
*/
|
|
928
|
+
this.onFocusIn = (e) => {
|
|
929
|
+
if (!this.opts.host.isConnected) return;
|
|
930
|
+
const path = e.composedPath();
|
|
931
|
+
if (path.includes(this.opts.host)) {
|
|
932
|
+
const active = deepActiveElement();
|
|
933
|
+
this.lastInside = active instanceof HTMLElement ? active : null;
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (path.some((n) => isOpenVioDialog(n, this.opts.host))) return;
|
|
937
|
+
const container = this.opts.container();
|
|
938
|
+
if (!container) return;
|
|
939
|
+
const items = tabbableIn(container);
|
|
940
|
+
if (items.length === 0) {
|
|
941
|
+
focusElement(container);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const target = e.target instanceof Node ? e.target : null;
|
|
945
|
+
const backwards = !!target && precedes(target, this.opts.host);
|
|
946
|
+
const index = this.lastInside ? items.indexOf(this.lastInside) : -1;
|
|
947
|
+
const count = items.length;
|
|
948
|
+
const next = backwards ? index <= 0 ? count - 1 : index - 1 : index === -1 || index === count - 1 ? 0 : index + 1;
|
|
949
|
+
focusElement(items[next]);
|
|
950
|
+
};
|
|
951
|
+
/** Tab pressed while focus is on the page (or the body): enter the dialog. */
|
|
952
|
+
this.onDocumentKeydown = (e) => {
|
|
953
|
+
if (!this.opts.host.isConnected) return;
|
|
954
|
+
if (e.defaultPrevented || e.key !== "Tab" || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
955
|
+
const active = deepActiveElement();
|
|
956
|
+
if (active && isWithin(active, this.opts.host)) return;
|
|
957
|
+
if (active && hasOpenVioDialogAbove(active, this.opts.host)) return;
|
|
958
|
+
const container = this.opts.container();
|
|
959
|
+
if (!container) return;
|
|
960
|
+
e.preventDefault();
|
|
961
|
+
e.stopPropagation();
|
|
962
|
+
const items = tabbableIn(container);
|
|
963
|
+
focusElement(items.length === 0 ? container : e.shiftKey ? items[items.length - 1] : items[0]);
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Call from `updated()` when `open` changed. Lit reports `open` as changed
|
|
968
|
+
* on the first render too (from undefined to its initial value): with the
|
|
969
|
+
* same value twice this is a no-op, so that first call does nothing.
|
|
970
|
+
*/
|
|
971
|
+
update(open) {
|
|
972
|
+
if (open === this.active) return;
|
|
973
|
+
this.active = open;
|
|
974
|
+
if (open) this.opened();
|
|
975
|
+
else this.closed();
|
|
976
|
+
}
|
|
977
|
+
opened() {
|
|
978
|
+
this.opener = deepActiveElement();
|
|
979
|
+
this.lastInside = null;
|
|
980
|
+
this.opts.host.addEventListener("keydown", this.onKeydown);
|
|
981
|
+
document.addEventListener("focusin", this.onFocusIn, true);
|
|
982
|
+
document.addEventListener("keydown", this.onDocumentKeydown, true);
|
|
983
|
+
const container = this.opts.container();
|
|
984
|
+
if (container) focusInto(container);
|
|
985
|
+
}
|
|
986
|
+
closed() {
|
|
987
|
+
this.opts.host.removeEventListener("keydown", this.onKeydown);
|
|
988
|
+
document.removeEventListener("focusin", this.onFocusIn, true);
|
|
989
|
+
document.removeEventListener("keydown", this.onDocumentKeydown, true);
|
|
990
|
+
const opener = this.opener;
|
|
991
|
+
this.opener = null;
|
|
992
|
+
const active = deepActiveElement();
|
|
993
|
+
if (!active || !isWithin(active, this.opts.host)) return;
|
|
994
|
+
if (opener && opener.isConnected && isRestorable(opener)) {
|
|
995
|
+
focusElement(opener);
|
|
996
|
+
} else if (active instanceof HTMLElement) {
|
|
997
|
+
active.blur();
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
function isOpenVioDialog(node, host) {
|
|
1002
|
+
return node instanceof Element && node !== host && node.tagName.startsWith("VIO-") && node.hasAttribute("open");
|
|
1003
|
+
}
|
|
1004
|
+
function hasOpenVioDialogAbove(node, host) {
|
|
1005
|
+
let n = node;
|
|
1006
|
+
while (n) {
|
|
1007
|
+
if (isOpenVioDialog(n, host)) return true;
|
|
1008
|
+
n = n.parentNode ?? (n instanceof ShadowRoot ? n.host : null);
|
|
1009
|
+
}
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
function precedes(a, b) {
|
|
1013
|
+
return (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;
|
|
1014
|
+
}
|
|
1015
|
+
function deepActiveElement() {
|
|
1016
|
+
if (typeof document === "undefined") return null;
|
|
1017
|
+
let el = document.activeElement;
|
|
1018
|
+
while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement;
|
|
1019
|
+
return el;
|
|
1020
|
+
}
|
|
1021
|
+
var TABBABLE_CANDIDATES = "a[href], area[href], button, input, select, textarea, iframe, summary, [tabindex], [contenteditable]";
|
|
1022
|
+
function tabbableIn(container) {
|
|
1023
|
+
const out = [];
|
|
1024
|
+
walkChildren(container, out);
|
|
1025
|
+
return out;
|
|
1026
|
+
}
|
|
1027
|
+
function walkChildren(parent, out) {
|
|
1028
|
+
for (const child of Array.from(parent.children)) {
|
|
1029
|
+
if (child instanceof HTMLSlotElement) {
|
|
1030
|
+
const assigned = child.assignedElements({ flatten: true });
|
|
1031
|
+
if (assigned.length > 0) {
|
|
1032
|
+
for (const el of assigned) visit(el, out);
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
visit(child, out);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function visit(el, out) {
|
|
1040
|
+
if (isTabbable(el)) out.push(el);
|
|
1041
|
+
if (el.shadowRoot) walkChildren(el.shadowRoot, out);
|
|
1042
|
+
else walkChildren(el, out);
|
|
1043
|
+
}
|
|
1044
|
+
function isTabbable(el) {
|
|
1045
|
+
if (!(el instanceof HTMLElement) || !el.matches(TABBABLE_CANDIDATES)) return false;
|
|
1046
|
+
const tabindex = el.getAttribute("tabindex");
|
|
1047
|
+
if (tabindex !== null && Number(tabindex) < 0) return false;
|
|
1048
|
+
if (el.hasAttribute("disabled")) return false;
|
|
1049
|
+
if (el instanceof HTMLInputElement && el.type === "hidden") return false;
|
|
1050
|
+
if (el.getAttribute("contenteditable") === "false" && !el.matches("a[href], button, input, select, textarea, iframe, [tabindex]")) return false;
|
|
1051
|
+
if (el.hidden || el.getAttribute("aria-hidden") === "true") return false;
|
|
1052
|
+
if (typeof el.checkVisibility === "function" && !el.checkVisibility()) return false;
|
|
1053
|
+
return true;
|
|
1054
|
+
}
|
|
1055
|
+
function focusInto(container) {
|
|
1056
|
+
const first = tabbableIn(container)[0];
|
|
1057
|
+
focusElement(first ?? container);
|
|
1058
|
+
}
|
|
1059
|
+
function focusElement(el) {
|
|
1060
|
+
el.focus({ preventScroll: true });
|
|
1061
|
+
}
|
|
1062
|
+
function isRestorable(el) {
|
|
1063
|
+
return el instanceof HTMLElement && el !== document.body && el !== document.documentElement;
|
|
1064
|
+
}
|
|
1065
|
+
function isWithin(node, ancestor) {
|
|
1066
|
+
let n = node;
|
|
1067
|
+
while (n) {
|
|
1068
|
+
if (n === ancestor) return true;
|
|
1069
|
+
n = n.parentNode ?? (n instanceof ShadowRoot ? n.host : null);
|
|
1070
|
+
}
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/ui/components/vio-product-detail.ts
|
|
866
1075
|
var STRIPE_LOGO_SRC = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
867
1076
|
'<svg width="360" height="150" viewBox="0 0 360 150" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M360 77.4001C360 51.8001 347.6 31.6001 323.9 31.6001C300.1 31.6001 285.7 51.8001 285.7 77.2001C285.7 107.3 302.7 122.5 327.1 122.5C339 122.5 348 119.8 354.8 116V96.0001C348 99.4001 340.2 101.5 330.3 101.5C320.6 101.5 312 98.1001 310.9 86.3001H359.8C359.8 85.0001 360 79.8001 360 77.4001ZM310.6 67.9001C310.6 56.6001 317.5 51.9001 323.8 51.9001C329.9 51.9001 336.4 56.6001 336.4 67.9001H310.6Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 31.6001C237.3 31.6001 231 36.2001 227.5 39.4001L226.2 33.2001H204.2V149.8L229.2 144.5L229.3 116.2C232.9 118.8 238.2 122.5 247 122.5C264.9 122.5 281.2 108.1 281.2 76.4001C281.1 47.4001 264.6 31.6001 247.1 31.6001ZM241.1 100.5C235.2 100.5 231.7 98.4001 229.3 95.8001L229.2 58.7001C231.8 55.8001 235.4 53.8001 241.1 53.8001C250.2 53.8001 256.5 64.0001 256.5 77.1001C256.5 90.5001 250.3 100.5 241.1 100.5Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 25.7L194.9 20.3V0L169.8 5.3V25.7Z" fill="#533AFD"/><path d="M194.9 33.3H169.8V120.8H194.9V33.3Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 40.7L141.3 33.3H119.7V120.8H144.7V61.5C150.6 53.8 160.6 55.2 163.7 56.3V33.3C160.5 32.1 148.8 29.9 142.9 40.7Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 11.6001L68.4999 16.8001L68.3999 96.9001C68.3999 111.7 79.4999 122.6 94.2999 122.6C102.5 122.6 108.5 121.1 111.8 119.3V99.0001C108.6 100.3 92.7999 104.9 92.7999 90.1001V54.6001H111.8V33.3001H92.7999L92.8999 11.6001Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 58.7001C25.3 54.8001 28.5 53.3001 33.8 53.3001C41.4 53.3001 51 55.6001 58.6 59.7001V36.2001C50.3 32.9001 42.1 31.6001 33.8 31.6001C13.5 31.6001 0 42.2001 0 59.9001C0 87.5001 38 83.1001 38 95.0001C38 99.6001 34 101.1 28.4 101.1C20.1 101.1 9.5 97.7001 1.1 93.1001V116.9C10.4 120.9 19.8 122.6 28.4 122.6C49.2 122.6 63.5 112.3 63.5 94.4001C63.4 64.6001 25.3 69.9001 25.3 58.7001Z" fill="#533AFD"/></svg>'
|
|
868
1077
|
);
|
|
@@ -875,6 +1084,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
875
1084
|
this.autoOpen = true;
|
|
876
1085
|
this.currency = "NOK";
|
|
877
1086
|
this.product = null;
|
|
1087
|
+
this.sellerName = null;
|
|
1088
|
+
this.sellerSettled = false;
|
|
878
1089
|
this.isLoading = false;
|
|
879
1090
|
this.fetchError = "";
|
|
880
1091
|
this.selectedOptions = {};
|
|
@@ -883,6 +1094,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
883
1094
|
this.adding = false;
|
|
884
1095
|
this.applePayOk = false;
|
|
885
1096
|
this.availableMethods = null;
|
|
1097
|
+
this.vippsOfficial = false;
|
|
886
1098
|
/** Race guard: only the latest fetch may write state (two quick taps on
|
|
887
1099
|
* different cards would otherwise let the slow response win). */
|
|
888
1100
|
this.fetchSeq = 0;
|
|
@@ -890,6 +1102,12 @@ var VioProductDetail = class extends LitElement {
|
|
|
890
1102
|
this.optMapCache = /* @__PURE__ */ new WeakMap();
|
|
891
1103
|
/** Pre-prepared Apple Pay handle — show() fires synchronously on tap. */
|
|
892
1104
|
this.applePayHandle = null;
|
|
1105
|
+
/** Keyboard and focus while open: Escape closes, Tab stays inside, focus returns to the opener. */
|
|
1106
|
+
this.dialogFocus = new DialogFocus({
|
|
1107
|
+
host: this,
|
|
1108
|
+
container: () => this.renderRoot.querySelector(".modal"),
|
|
1109
|
+
close: () => this.close()
|
|
1110
|
+
});
|
|
893
1111
|
this.boundProductClick = (e) => {
|
|
894
1112
|
if (!this.autoOpen) return;
|
|
895
1113
|
const ev = e;
|
|
@@ -933,6 +1151,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
933
1151
|
overflow-y: auto;
|
|
934
1152
|
}
|
|
935
1153
|
:host([open]) .modal { transform: translateY(0); }
|
|
1154
|
+
/* Focus lands on the panel itself only as a fallback — no ring for it. */
|
|
1155
|
+
.modal:focus { outline: none; }
|
|
936
1156
|
|
|
937
1157
|
.handle { display: none; }
|
|
938
1158
|
|
|
@@ -941,7 +1161,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
941
1161
|
.modal {
|
|
942
1162
|
top: auto;
|
|
943
1163
|
height: 92vh;
|
|
944
|
-
border-radius: 16px 16px 0 0;
|
|
1164
|
+
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
945
1165
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
946
1166
|
}
|
|
947
1167
|
.handle {
|
|
@@ -955,7 +1175,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
955
1175
|
width: 40px;
|
|
956
1176
|
height: 4px;
|
|
957
1177
|
background: var(--vio-color-border, #e5e5e5);
|
|
958
|
-
border-radius: 2px;
|
|
1178
|
+
border-radius: var(--vio-radius-sm, 2px);
|
|
959
1179
|
}
|
|
960
1180
|
}
|
|
961
1181
|
|
|
@@ -1193,6 +1413,25 @@ var VioProductDetail = class extends LitElement {
|
|
|
1193
1413
|
cursor: not-allowed;
|
|
1194
1414
|
}
|
|
1195
1415
|
/* Klarna express button — sits full-width below the add-to-cart row. */
|
|
1416
|
+
/* Generic buy-now for the embedded checkouts, which have no branded
|
|
1417
|
+
express button of their own. */
|
|
1418
|
+
.buy-now {
|
|
1419
|
+
width: 100%;
|
|
1420
|
+
margin-top: 10px;
|
|
1421
|
+
padding: 14px 24px;
|
|
1422
|
+
background: var(--vio-color-text, #0a0a0a);
|
|
1423
|
+
color: var(--vio-color-text-on-primary, #fff);
|
|
1424
|
+
border: none;
|
|
1425
|
+
border-radius: var(--vio-radius-md, 4px);
|
|
1426
|
+
font-family: var(--vio-font-sans, inherit);
|
|
1427
|
+
font-size: 12px;
|
|
1428
|
+
text-transform: uppercase;
|
|
1429
|
+
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
1430
|
+
font-weight: 700;
|
|
1431
|
+
cursor: pointer;
|
|
1432
|
+
}
|
|
1433
|
+
.buy-now:hover:not(:disabled) { opacity: 0.9; }
|
|
1434
|
+
.buy-now:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1196
1435
|
.buy-klarna {
|
|
1197
1436
|
width: 100%;
|
|
1198
1437
|
margin-top: 10px;
|
|
@@ -1244,6 +1483,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1244
1483
|
}
|
|
1245
1484
|
.buy-vipps:hover:not(:disabled) { background: #ec4f1c; }
|
|
1246
1485
|
.buy-vipps:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1486
|
+
/* Vipps' own button (web component) — same slot, their drawing. */
|
|
1487
|
+
.buy-vipps-official { display: block; width: 100%; margin-top: 10px; cursor: pointer; }
|
|
1247
1488
|
/* Apple Pay express button — black, per Apple's button guidelines. Only
|
|
1248
1489
|
shown in Safari (canApplePay). The glyph is the Apple logo (Safari). */
|
|
1249
1490
|
.buy-applepay {
|
|
@@ -1299,9 +1540,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
1299
1540
|
);
|
|
1300
1541
|
background-size: 200% 100%;
|
|
1301
1542
|
animation: vio-skeleton-shimmer 1.4s ease-in-out infinite;
|
|
1302
|
-
border-radius: 8px;
|
|
1543
|
+
border-radius: var(--vio-radius-lg, 8px);
|
|
1303
1544
|
}
|
|
1304
|
-
.sk-image { width: 100%; aspect-ratio: 1; border-radius: 12px; }
|
|
1545
|
+
.sk-image { width: 100%; aspect-ratio: 1; border-radius: var(--vio-radius-lg, 12px); }
|
|
1305
1546
|
.sk-line { height: 14px; }
|
|
1306
1547
|
.sk-brand { width: 38%; height: 10px; margin-top: 6px; }
|
|
1307
1548
|
.sk-title { width: 85%; height: 26px; }
|
|
@@ -1328,12 +1569,16 @@ var VioProductDetail = class extends LitElement {
|
|
|
1328
1569
|
}
|
|
1329
1570
|
connectedCallback() {
|
|
1330
1571
|
super.connectedCallback();
|
|
1572
|
+
void this.loadVippsButton();
|
|
1331
1573
|
document.addEventListener("vio:product-click", this.boundProductClick);
|
|
1332
1574
|
}
|
|
1333
1575
|
disconnectedCallback() {
|
|
1334
1576
|
document.removeEventListener("vio:product-click", this.boundProductClick);
|
|
1335
1577
|
super.disconnectedCallback();
|
|
1336
1578
|
}
|
|
1579
|
+
updated(changed) {
|
|
1580
|
+
if (changed.has("open")) this.dialogFocus.update(this.open);
|
|
1581
|
+
}
|
|
1337
1582
|
show() {
|
|
1338
1583
|
this.open = true;
|
|
1339
1584
|
}
|
|
@@ -1342,6 +1587,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1342
1587
|
setTimeout(() => {
|
|
1343
1588
|
if (!this.open) {
|
|
1344
1589
|
this.product = null;
|
|
1590
|
+
this.sellerName = null;
|
|
1591
|
+
this.sellerSettled = false;
|
|
1345
1592
|
this.selectedOptions = {};
|
|
1346
1593
|
this.quantity = 1;
|
|
1347
1594
|
this.activeImageIndex = 0;
|
|
@@ -1358,6 +1605,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1358
1605
|
const seq = ++this.fetchSeq;
|
|
1359
1606
|
this.isLoading = true;
|
|
1360
1607
|
this.fetchError = "";
|
|
1608
|
+
this.sellerName = null;
|
|
1609
|
+
this.sellerSettled = false;
|
|
1361
1610
|
let products = null;
|
|
1362
1611
|
let lastError = null;
|
|
1363
1612
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
@@ -1386,6 +1635,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1386
1635
|
}
|
|
1387
1636
|
this.optMapCache = /* @__PURE__ */ new WeakMap();
|
|
1388
1637
|
this.product = products?.[0] ?? null;
|
|
1638
|
+
if (this.product) void this.loadSeller(productIdNum, seq);
|
|
1389
1639
|
if (this.product) {
|
|
1390
1640
|
try {
|
|
1391
1641
|
const price = Number(
|
|
@@ -1452,9 +1702,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1452
1702
|
}
|
|
1453
1703
|
/** Backend names arrive as e.g. "Apple Pay" — compare letters only. */
|
|
1454
1704
|
methodEnabled(...names) {
|
|
1455
|
-
|
|
1456
|
-
const norm = (s) => s.toLowerCase().replace(/[^a-z]/g, "");
|
|
1457
|
-
return this.availableMethods.some((m) => names.some((n) => norm(m) === norm(n)));
|
|
1705
|
+
return isMethodEnabled(this.availableMethods, ...names);
|
|
1458
1706
|
}
|
|
1459
1707
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1460
1708
|
/**
|
|
@@ -1773,10 +2021,60 @@ var VioProductDetail = class extends LitElement {
|
|
|
1773
2021
|
}
|
|
1774
2022
|
}
|
|
1775
2023
|
/** Buy with Vipps — opens the checkout with Vipps preselected. */
|
|
2024
|
+
/**
|
|
2025
|
+
* Buy now with an embedded checkout (Kustom, Qliro, Walley).
|
|
2026
|
+
*
|
|
2027
|
+
* Those three have no express path of their own, so the product page showed
|
|
2028
|
+
* a "Kjøp nå" for Apple Pay, Stripe, Klarna and Vipps and nothing at all for
|
|
2029
|
+
* them — the same structural gap the cart footer had. Opens the checkout
|
|
2030
|
+
* without preselecting: with a single method the checkout selects it itself,
|
|
2031
|
+
* with several the shopper picks.
|
|
2032
|
+
*
|
|
2033
|
+
* Like every other "Kjøp nå" here, it adds to the cart first — so a cart
|
|
2034
|
+
* that already had items buys those too. Isolating a single-product purchase
|
|
2035
|
+
* needs a checkout that is not bound to the sponsor's cart, which is a
|
|
2036
|
+
* separate piece of work and deliberately not done here.
|
|
2037
|
+
*/
|
|
2038
|
+
buyNow() {
|
|
2039
|
+
if (!this.product || this.availableQuantity <= 0 || this.adding) return;
|
|
2040
|
+
this.addCurrentToCart();
|
|
2041
|
+
this.close();
|
|
2042
|
+
this.dispatchEvent(
|
|
2043
|
+
new CustomEvent("vio:checkout-open", {
|
|
2044
|
+
bubbles: true,
|
|
2045
|
+
composed: true,
|
|
2046
|
+
detail: { sponsorId: this.sponsorId, express: false }
|
|
2047
|
+
})
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Buy with Vipps — Express: the app opens with address and delivery
|
|
2052
|
+
* already there, nothing to fill in here (Vipps' own recommendation,
|
|
2053
|
+
* 2026-09-29). When this cart cannot have Express, or Vipps cannot be
|
|
2054
|
+
* started, the checkout opens with Vipps preselected, as before.
|
|
2055
|
+
*/
|
|
2056
|
+
/** Vipps' own button when its script answers; our orange one otherwise. */
|
|
2057
|
+
async loadVippsButton() {
|
|
2058
|
+
this.vippsOfficial = await ensureVippsButton();
|
|
2059
|
+
}
|
|
1776
2060
|
buyWithVipps() {
|
|
1777
2061
|
if (!this.product || this.availableQuantity <= 0 || this.adding) return;
|
|
1778
2062
|
this.addCurrentToCart();
|
|
1779
2063
|
this.close();
|
|
2064
|
+
void this.startVippsExpressOrCheckout();
|
|
2065
|
+
}
|
|
2066
|
+
async startVippsExpressOrCheckout() {
|
|
2067
|
+
let fallback = true;
|
|
2068
|
+
try {
|
|
2069
|
+
if (!await Vio.checkout.getVippsExpressEnabled(this.sponsorId)) throw new Error("express off");
|
|
2070
|
+
const res = await Vio.checkout.startVippsExpress(this.sponsorId);
|
|
2071
|
+
fallback = !res?.payment_url;
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
if (typeof console !== "undefined") {
|
|
2074
|
+
console.warn("[VioProductDetail] Vipps Express not started:", err);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
if (!fallback) return;
|
|
1780
2078
|
this.dispatchEvent(
|
|
1781
2079
|
new CustomEvent("vio:checkout-open", {
|
|
1782
2080
|
bubbles: true,
|
|
@@ -1818,13 +2116,29 @@ var VioProductDetail = class extends LitElement {
|
|
|
1818
2116
|
void this.applePayHandle.show();
|
|
1819
2117
|
this.close();
|
|
1820
2118
|
}
|
|
2119
|
+
/** The seller's Company name; the brand when there is none; nothing while asking. */
|
|
2120
|
+
headerName() {
|
|
2121
|
+
if (this.sellerName) return this.sellerName;
|
|
2122
|
+
return this.sellerSettled ? this.product?.brand ?? "" : "";
|
|
2123
|
+
}
|
|
2124
|
+
async loadSeller(productId, seq) {
|
|
2125
|
+
let name = null;
|
|
2126
|
+
try {
|
|
2127
|
+
name = await Vio.commerceFor(this.sponsorId).channel.product.getSellerCompany(productId);
|
|
2128
|
+
} catch {
|
|
2129
|
+
name = null;
|
|
2130
|
+
}
|
|
2131
|
+
if (seq !== this.fetchSeq) return;
|
|
2132
|
+
this.sellerName = name;
|
|
2133
|
+
this.sellerSettled = true;
|
|
2134
|
+
}
|
|
1821
2135
|
render() {
|
|
1822
2136
|
return html`
|
|
1823
2137
|
<div class="backdrop" @click=${this.close}></div>
|
|
1824
|
-
<div class="modal" role="dialog" aria-label="Produkt">
|
|
2138
|
+
<div class="modal" role="dialog" aria-modal="true" aria-label="Produkt" tabindex="-1">
|
|
1825
2139
|
<div class="handle" @click=${this.close} aria-hidden="true"></div>
|
|
1826
2140
|
<div class="topbar">
|
|
1827
|
-
<span class="topbar-brand">${this.
|
|
2141
|
+
<span class="topbar-brand">${this.headerName()}</span>
|
|
1828
2142
|
<button class="close" @click=${this.close} aria-label="Lukk">×</button>
|
|
1829
2143
|
</div>
|
|
1830
2144
|
|
|
@@ -1922,6 +2236,17 @@ var VioProductDetail = class extends LitElement {
|
|
|
1922
2236
|
</button>
|
|
1923
2237
|
</div>
|
|
1924
2238
|
|
|
2239
|
+
${this.availableQuantity > 0 && this.methodEnabled(...NO_EXPRESS_BUTTON_METHODS) ? html`
|
|
2240
|
+
<button
|
|
2241
|
+
class="buy-now"
|
|
2242
|
+
@click=${this.buyNow}
|
|
2243
|
+
?disabled=${this.adding}
|
|
2244
|
+
aria-label="Kjøp nå"
|
|
2245
|
+
>
|
|
2246
|
+
Kjøp nå
|
|
2247
|
+
</button>
|
|
2248
|
+
` : ""}
|
|
2249
|
+
|
|
1925
2250
|
${this.availableQuantity > 0 && this.applePayOk && this.methodEnabled("apple-pay", "applepay") ? html`
|
|
1926
2251
|
<button
|
|
1927
2252
|
class="buy-applepay"
|
|
@@ -1965,17 +2290,30 @@ var VioProductDetail = class extends LitElement {
|
|
|
1965
2290
|
</button>
|
|
1966
2291
|
` : ""}
|
|
1967
2292
|
|
|
1968
|
-
${this.availableQuantity > 0 && this.methodEnabled("vipps") ? html`
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2293
|
+
${this.availableQuantity > 0 && this.methodEnabled("vipps") ? this.vippsOfficial ? html`
|
|
2294
|
+
<vipps-mobilepay-button
|
|
2295
|
+
class="buy-vipps-official"
|
|
2296
|
+
brand="vipps"
|
|
2297
|
+
language="no"
|
|
2298
|
+
verb="buy"
|
|
2299
|
+
variant="primary"
|
|
2300
|
+
rounded="true"
|
|
2301
|
+
stretched="true"
|
|
2302
|
+
role="button"
|
|
2303
|
+
aria-label="Kjøp nå med Vipps"
|
|
2304
|
+
@click=${this.buyWithVipps}
|
|
2305
|
+
></vipps-mobilepay-button>
|
|
2306
|
+
` : html`
|
|
2307
|
+
<button
|
|
2308
|
+
class="buy-vipps"
|
|
2309
|
+
@click=${this.buyWithVipps}
|
|
2310
|
+
?disabled=${this.adding}
|
|
2311
|
+
aria-label="Kjøp nå med Vipps"
|
|
2312
|
+
>
|
|
2313
|
+
<span>Kjøp nå med</span>
|
|
2314
|
+
<span class="vipps-badge">vipps</span>
|
|
2315
|
+
</button>
|
|
2316
|
+
` : ""}
|
|
1979
2317
|
|
|
1980
2318
|
${p.description ? html`
|
|
1981
2319
|
<div>
|
|
@@ -2040,6 +2378,12 @@ __decorateClass([
|
|
|
2040
2378
|
__decorateClass([
|
|
2041
2379
|
state()
|
|
2042
2380
|
], VioProductDetail.prototype, "product", 2);
|
|
2381
|
+
__decorateClass([
|
|
2382
|
+
state()
|
|
2383
|
+
], VioProductDetail.prototype, "sellerName", 2);
|
|
2384
|
+
__decorateClass([
|
|
2385
|
+
state()
|
|
2386
|
+
], VioProductDetail.prototype, "sellerSettled", 2);
|
|
2043
2387
|
__decorateClass([
|
|
2044
2388
|
state()
|
|
2045
2389
|
], VioProductDetail.prototype, "isLoading", 2);
|
|
@@ -2064,6 +2408,9 @@ __decorateClass([
|
|
|
2064
2408
|
__decorateClass([
|
|
2065
2409
|
state()
|
|
2066
2410
|
], VioProductDetail.prototype, "availableMethods", 2);
|
|
2411
|
+
__decorateClass([
|
|
2412
|
+
state()
|
|
2413
|
+
], VioProductDetail.prototype, "vippsOfficial", 2);
|
|
2067
2414
|
function normalizeValues(raw) {
|
|
2068
2415
|
if (Array.isArray(raw)) {
|
|
2069
2416
|
return raw.map((v) => String(v).trim()).filter((v) => v.length > 0);
|
|
@@ -2087,6 +2434,9 @@ var VioCart = class extends LitElement {
|
|
|
2087
2434
|
this.itemCount = 0;
|
|
2088
2435
|
this.applePayAvailable = false;
|
|
2089
2436
|
this.availableMethods = null;
|
|
2437
|
+
this.vippsOfficial = false;
|
|
2438
|
+
/** Whether the method lookup has been attempted — see boundOnCartChange. */
|
|
2439
|
+
this.paymentMethodsAttempted = false;
|
|
2090
2440
|
this.confirmedOrder = null;
|
|
2091
2441
|
/** Pre-resolved Apple Pay handle; `show()` fires the sheet synchronously. */
|
|
2092
2442
|
this.applePayHandle = null;
|
|
@@ -2094,11 +2444,18 @@ var VioCart = class extends LitElement {
|
|
|
2094
2444
|
this.preparedTotal = -1;
|
|
2095
2445
|
/** In-flight guard for loadAvailablePaymentMethods. */
|
|
2096
2446
|
this.loadingPaymentMethods = false;
|
|
2447
|
+
/** Keyboard and focus while open: Escape closes, Tab stays inside, focus returns to the opener. */
|
|
2448
|
+
this.dialogFocus = new DialogFocus({
|
|
2449
|
+
host: this,
|
|
2450
|
+
container: () => this.renderRoot.querySelector(".drawer"),
|
|
2451
|
+
close: () => this.close()
|
|
2452
|
+
});
|
|
2097
2453
|
this.boundOnCartChange = (e) => {
|
|
2098
2454
|
const detail = e.detail;
|
|
2099
2455
|
this.carts = detail.cartsBySponsor;
|
|
2100
2456
|
this.itemCount = detail.itemCount;
|
|
2101
|
-
if (this.
|
|
2457
|
+
if (!this.paymentMethodsAttempted) {
|
|
2458
|
+
this.paymentMethodsAttempted = true;
|
|
2102
2459
|
void this.loadAvailablePaymentMethods();
|
|
2103
2460
|
}
|
|
2104
2461
|
};
|
|
@@ -2135,6 +2492,8 @@ var VioCart = class extends LitElement {
|
|
|
2135
2492
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.06);
|
|
2136
2493
|
}
|
|
2137
2494
|
:host([open]) .drawer { transform: translateX(0); }
|
|
2495
|
+
/* Focus lands on the drawer itself only as a fallback — no ring for it. */
|
|
2496
|
+
.drawer:focus { outline: none; }
|
|
2138
2497
|
|
|
2139
2498
|
.handle { display: none; }
|
|
2140
2499
|
|
|
@@ -2147,7 +2506,7 @@ var VioCart = class extends LitElement {
|
|
|
2147
2506
|
bottom: 0;
|
|
2148
2507
|
width: 100%;
|
|
2149
2508
|
height: 88vh;
|
|
2150
|
-
border-radius: 16px 16px 0 0;
|
|
2509
|
+
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
2151
2510
|
transform: translateY(100%);
|
|
2152
2511
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
2153
2512
|
}
|
|
@@ -2162,7 +2521,7 @@ var VioCart = class extends LitElement {
|
|
|
2162
2521
|
width: 40px;
|
|
2163
2522
|
height: 4px;
|
|
2164
2523
|
background: var(--vio-color-border, #e5e5e5);
|
|
2165
|
-
border-radius: 2px;
|
|
2524
|
+
border-radius: var(--vio-radius-sm, 2px);
|
|
2166
2525
|
}
|
|
2167
2526
|
}
|
|
2168
2527
|
|
|
@@ -2255,7 +2614,7 @@ var VioCart = class extends LitElement {
|
|
|
2255
2614
|
height: 24px;
|
|
2256
2615
|
border: 1px solid var(--vio-color-border, #e5e5e5);
|
|
2257
2616
|
background: #fff;
|
|
2258
|
-
border-radius: 2px;
|
|
2617
|
+
border-radius: var(--vio-radius-sm, 2px);
|
|
2259
2618
|
cursor: pointer;
|
|
2260
2619
|
font-size: 12px;
|
|
2261
2620
|
color: var(--vio-color-text, #0a0a0a);
|
|
@@ -2273,6 +2632,23 @@ var VioCart = class extends LitElement {
|
|
|
2273
2632
|
font-size: 14px;
|
|
2274
2633
|
}
|
|
2275
2634
|
|
|
2635
|
+
/* Always present: the express buttons below are shortcuts, not the only
|
|
2636
|
+
way out of the cart. Without this a channel offering only an embedded
|
|
2637
|
+
checkout (Kustom, Qliro, Walley) had no button at all. */
|
|
2638
|
+
.checkout-btn {
|
|
2639
|
+
width: 100%;
|
|
2640
|
+
padding: 14px;
|
|
2641
|
+
margin-bottom: var(--vio-space-sm, 8px);
|
|
2642
|
+
background: var(--vio-color-text, #0a0a0a);
|
|
2643
|
+
color: var(--vio-color-text-on-primary, #fff);
|
|
2644
|
+
border: none;
|
|
2645
|
+
border-radius: var(--vio-radius-md, 4px);
|
|
2646
|
+
font-family: var(--vio-font-sans, inherit);
|
|
2647
|
+
font-size: var(--vio-size-sm, 14px);
|
|
2648
|
+
font-weight: 600;
|
|
2649
|
+
cursor: pointer;
|
|
2650
|
+
}
|
|
2651
|
+
.checkout-btn:hover { opacity: 0.9; }
|
|
2276
2652
|
.footer {
|
|
2277
2653
|
padding: var(--vio-space-lg, 24px);
|
|
2278
2654
|
border-top: 1px solid var(--vio-color-border, #e5e5e5);
|
|
@@ -2295,7 +2671,7 @@ var VioCart = class extends LitElement {
|
|
|
2295
2671
|
background: #000;
|
|
2296
2672
|
color: #fff;
|
|
2297
2673
|
border: none;
|
|
2298
|
-
border-radius: 8px;
|
|
2674
|
+
border-radius: var(--vio-radius-lg, 8px);
|
|
2299
2675
|
font-size: 19px;
|
|
2300
2676
|
font-weight: 500;
|
|
2301
2677
|
line-height: 1;
|
|
@@ -2317,7 +2693,7 @@ var VioCart = class extends LitElement {
|
|
|
2317
2693
|
background: #ffb3c7;
|
|
2318
2694
|
color: #0a0a0a;
|
|
2319
2695
|
border: none;
|
|
2320
|
-
border-radius: 8px;
|
|
2696
|
+
border-radius: var(--vio-radius-lg, 8px);
|
|
2321
2697
|
font-size: 16px;
|
|
2322
2698
|
font-weight: 600;
|
|
2323
2699
|
cursor: pointer;
|
|
@@ -2338,7 +2714,7 @@ var VioCart = class extends LitElement {
|
|
|
2338
2714
|
background: #ff5b24;
|
|
2339
2715
|
color: #fff;
|
|
2340
2716
|
border: none;
|
|
2341
|
-
border-radius: 8px;
|
|
2717
|
+
border-radius: var(--vio-radius-lg, 8px);
|
|
2342
2718
|
cursor: pointer;
|
|
2343
2719
|
display: flex;
|
|
2344
2720
|
align-items: center;
|
|
@@ -2347,6 +2723,7 @@ var VioCart = class extends LitElement {
|
|
|
2347
2723
|
transition: background 0.15s;
|
|
2348
2724
|
}
|
|
2349
2725
|
.vipps-btn:hover { background: #ec4f1c; }
|
|
2726
|
+
.vipps-btn-official { display: block; width: 100%; margin-bottom: 10px; cursor: pointer; }
|
|
2350
2727
|
.vipps-btn .vipps-logo {
|
|
2351
2728
|
font-size: 20px;
|
|
2352
2729
|
font-weight: 800;
|
|
@@ -2361,7 +2738,7 @@ var VioCart = class extends LitElement {
|
|
|
2361
2738
|
background: #635bff;
|
|
2362
2739
|
color: #fff;
|
|
2363
2740
|
border: none;
|
|
2364
|
-
border-radius: 8px;
|
|
2741
|
+
border-radius: var(--vio-radius-lg, 8px);
|
|
2365
2742
|
font-size: 16px;
|
|
2366
2743
|
font-weight: 600;
|
|
2367
2744
|
cursor: pointer;
|
|
@@ -2445,6 +2822,7 @@ var VioCart = class extends LitElement {
|
|
|
2445
2822
|
}
|
|
2446
2823
|
connectedCallback() {
|
|
2447
2824
|
super.connectedCallback();
|
|
2825
|
+
void this.loadVippsButton();
|
|
2448
2826
|
this.carts = Vio.cart.getAllCarts();
|
|
2449
2827
|
this.itemCount = Vio.cart.itemCount;
|
|
2450
2828
|
Vio.cart.addEventListener("change", this.boundOnCartChange);
|
|
@@ -2494,12 +2872,11 @@ var VioCart = class extends LitElement {
|
|
|
2494
2872
|
}
|
|
2495
2873
|
}
|
|
2496
2874
|
/** Backend names arrive as e.g. "Apple Pay" — compare letters only. */
|
|
2497
|
-
methodEnabled(
|
|
2498
|
-
|
|
2499
|
-
const norm = (s) => s.toLowerCase().replace(/[^a-z]/g, "");
|
|
2500
|
-
return this.availableMethods.some((m) => norm(m) === norm(name));
|
|
2875
|
+
methodEnabled(...names) {
|
|
2876
|
+
return isMethodEnabled(this.availableMethods, ...names);
|
|
2501
2877
|
}
|
|
2502
2878
|
updated(changed) {
|
|
2879
|
+
if (changed.has("open")) this.dialogFocus.update(this.open);
|
|
2503
2880
|
if (changed.has("open") && this.open) {
|
|
2504
2881
|
this.confirmedOrder = null;
|
|
2505
2882
|
this.preparedTotal = -1;
|
|
@@ -2585,15 +2962,59 @@ var VioCart = class extends LitElement {
|
|
|
2585
2962
|
);
|
|
2586
2963
|
}
|
|
2587
2964
|
/** Vipps — opens the checkout with Vipps preselected. */
|
|
2965
|
+
/**
|
|
2966
|
+
* Open the checkout without preselecting a method.
|
|
2967
|
+
*
|
|
2968
|
+
* The footer used to be express-only — Apple Pay, Stripe, Klarna, Vipps —
|
|
2969
|
+
* so a channel offering just an embedded checkout (Kustom, Qliro, Walley)
|
|
2970
|
+
* had NO button at all and the cart was a dead end. Even with an express
|
|
2971
|
+
* method present the others were unreachable from here.
|
|
2972
|
+
*/
|
|
2973
|
+
onCheckout() {
|
|
2974
|
+
if (this.itemCount === 0) return;
|
|
2975
|
+
const firstSponsorId = [...this.carts.keys()][0];
|
|
2976
|
+
if (firstSponsorId === void 0) return;
|
|
2977
|
+
this.dispatchEvent(
|
|
2978
|
+
new CustomEvent("vio:checkout-open", {
|
|
2979
|
+
bubbles: true,
|
|
2980
|
+
composed: true,
|
|
2981
|
+
detail: { sponsorId: firstSponsorId, express: false }
|
|
2982
|
+
})
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Vipps — Express: the app collects address and delivery, so the cart
|
|
2987
|
+
* goes straight to Vipps. When this cart cannot have Express (several
|
|
2988
|
+
* suppliers, digital goods, no rate) or Vipps cannot be started, the
|
|
2989
|
+
* checkout opens with Vipps preselected, as before.
|
|
2990
|
+
*/
|
|
2588
2991
|
onVipps() {
|
|
2589
2992
|
if (this.itemCount === 0) return;
|
|
2590
2993
|
const firstSponsorId = [...this.carts.keys()][0];
|
|
2591
2994
|
if (firstSponsorId === void 0) return;
|
|
2995
|
+
void this.startVippsExpressOrCheckout(firstSponsorId);
|
|
2996
|
+
}
|
|
2997
|
+
/** Vipps' own button when its script answers; our orange one otherwise. */
|
|
2998
|
+
async loadVippsButton() {
|
|
2999
|
+
this.vippsOfficial = await ensureVippsButton();
|
|
3000
|
+
}
|
|
3001
|
+
async startVippsExpressOrCheckout(sponsorId) {
|
|
3002
|
+
let fallback = true;
|
|
3003
|
+
try {
|
|
3004
|
+
if (!await Vio.checkout.getVippsExpressEnabled(sponsorId)) throw new Error("express off");
|
|
3005
|
+
const res = await Vio.checkout.startVippsExpress(sponsorId);
|
|
3006
|
+
fallback = !res?.payment_url;
|
|
3007
|
+
} catch (err) {
|
|
3008
|
+
if (typeof console !== "undefined") {
|
|
3009
|
+
console.warn("[VioCart] Vipps Express not started:", err);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
if (!fallback) return;
|
|
2592
3013
|
this.dispatchEvent(
|
|
2593
3014
|
new CustomEvent("vio:checkout-open", {
|
|
2594
3015
|
bubbles: true,
|
|
2595
3016
|
composed: true,
|
|
2596
|
-
detail: { sponsorId
|
|
3017
|
+
detail: { sponsorId, paymentMethod: "vipps", express: false }
|
|
2597
3018
|
})
|
|
2598
3019
|
);
|
|
2599
3020
|
}
|
|
@@ -2635,7 +3056,7 @@ var VioCart = class extends LitElement {
|
|
|
2635
3056
|
const sponsorCarts = [...this.carts.values()].filter((c) => c.items.length > 0);
|
|
2636
3057
|
return html`
|
|
2637
3058
|
<div class="backdrop" @click=${this.close}></div>
|
|
2638
|
-
<aside class="drawer" role="dialog" aria-label=${this.heading}>
|
|
3059
|
+
<aside class="drawer" role="dialog" aria-modal="true" aria-label=${this.heading} tabindex="-1">
|
|
2639
3060
|
<div class="handle" @click=${this.close} aria-hidden="true"></div>
|
|
2640
3061
|
<div class="header">
|
|
2641
3062
|
<h2 class="heading">${this.confirmedOrder ? "Takk!" : this.heading}</h2>
|
|
@@ -2705,6 +3126,13 @@ var VioCart = class extends LitElement {
|
|
|
2705
3126
|
${formatPrice(this.totalAcrossSponsors, this.primaryCurrency)}
|
|
2706
3127
|
</span>
|
|
2707
3128
|
</div>
|
|
3129
|
+
<button
|
|
3130
|
+
class="checkout-btn"
|
|
3131
|
+
@click=${this.onCheckout}
|
|
3132
|
+
aria-label="Til kassen"
|
|
3133
|
+
>
|
|
3134
|
+
Til kassen
|
|
3135
|
+
</button>
|
|
2708
3136
|
${this.methodEnabled("apple-pay") && this.applePayAvailable ? html`
|
|
2709
3137
|
<button
|
|
2710
3138
|
class="applepay-btn"
|
|
@@ -2738,15 +3166,28 @@ var VioCart = class extends LitElement {
|
|
|
2738
3166
|
/>
|
|
2739
3167
|
</button>
|
|
2740
3168
|
` : ""}
|
|
2741
|
-
${this.methodEnabled("vipps") ? html`
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
3169
|
+
${this.methodEnabled("vipps") ? this.vippsOfficial ? html`
|
|
3170
|
+
<vipps-mobilepay-button
|
|
3171
|
+
class="vipps-btn-official"
|
|
3172
|
+
brand="vipps"
|
|
3173
|
+
language="no"
|
|
3174
|
+
verb="pay"
|
|
3175
|
+
variant="primary"
|
|
3176
|
+
rounded="true"
|
|
3177
|
+
stretched="true"
|
|
3178
|
+
role="button"
|
|
3179
|
+
aria-label="Betal med Vipps"
|
|
3180
|
+
@click=${this.onVipps}
|
|
3181
|
+
></vipps-mobilepay-button>
|
|
3182
|
+
` : html`
|
|
3183
|
+
<button
|
|
3184
|
+
class="vipps-btn"
|
|
3185
|
+
@click=${this.onVipps}
|
|
3186
|
+
aria-label="Betal med Vipps"
|
|
3187
|
+
>
|
|
3188
|
+
<span class="vipps-logo">vipps</span>
|
|
3189
|
+
</button>
|
|
3190
|
+
` : ""}
|
|
2750
3191
|
</div>
|
|
2751
3192
|
`}
|
|
2752
3193
|
</aside>
|
|
@@ -2803,9 +3244,44 @@ __decorateClass([
|
|
|
2803
3244
|
__decorateClass([
|
|
2804
3245
|
state()
|
|
2805
3246
|
], VioCart.prototype, "availableMethods", 2);
|
|
3247
|
+
__decorateClass([
|
|
3248
|
+
state()
|
|
3249
|
+
], VioCart.prototype, "vippsOfficial", 2);
|
|
2806
3250
|
__decorateClass([
|
|
2807
3251
|
state()
|
|
2808
3252
|
], VioCart.prototype, "confirmedOrder", 2);
|
|
3253
|
+
|
|
3254
|
+
// src/ui/checkout-errors.ts
|
|
3255
|
+
var EMPTY_CART_MESSAGE = "Handlekurven er tom. Legg til et produkt og pr\xF8v igjen.";
|
|
3256
|
+
var CART_CHANGED_MESSAGE = "Handlekurven ble endret i en annen fane. Last siden p\xE5 nytt og pr\xF8v igjen.";
|
|
3257
|
+
var CART_PAID_MESSAGE = "Denne handlekurven er allerede betalt. Start en ny handlekurv.";
|
|
3258
|
+
var PAYMENT_START_FAILED_MESSAGE = "Betalingen kunne ikke startes. Pr\xF8v igjen, eller velg en annen betalingsm\xE5te.";
|
|
3259
|
+
var SHIPS_APART = "kan ikke sendes sammen";
|
|
3260
|
+
function friendlyPaymentError(raw, method) {
|
|
3261
|
+
const text = String(raw ?? "");
|
|
3262
|
+
const lower = text.toLowerCase();
|
|
3263
|
+
if (lower.includes("got 0") || lower.includes("amount must be a positive")) {
|
|
3264
|
+
return EMPTY_CART_MESSAGE;
|
|
3265
|
+
}
|
|
3266
|
+
if (lower.includes("replaced by a newer") || text.includes("CHECKOUT_SUPERSEDED")) {
|
|
3267
|
+
return CART_CHANGED_MESSAGE;
|
|
3268
|
+
}
|
|
3269
|
+
if (lower.includes("already been paid") || text.includes("CART_ALREADY_PAID")) {
|
|
3270
|
+
return CART_PAID_MESSAGE;
|
|
3271
|
+
}
|
|
3272
|
+
if (text.includes(SHIPS_APART)) return norwegianSentence(text);
|
|
3273
|
+
return PAYMENT_START_FAILED_MESSAGE;
|
|
3274
|
+
}
|
|
3275
|
+
function norwegianSentence(text) {
|
|
3276
|
+
const at = text.indexOf(SHIPS_APART);
|
|
3277
|
+
const prefixEnd = text.lastIndexOf(": ", at);
|
|
3278
|
+
return (prefixEnd === -1 ? text : text.slice(prefixEnd + 2)).trim();
|
|
3279
|
+
}
|
|
3280
|
+
function errorText(err) {
|
|
3281
|
+
return err instanceof Error ? err.message : String(err);
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
// src/ui/components/vio-checkout.ts
|
|
2809
3285
|
var STRIPE_LOGO_SRC3 = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
2810
3286
|
'<svg width="360" height="150" viewBox="0 0 360 150" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M360 77.4001C360 51.8001 347.6 31.6001 323.9 31.6001C300.1 31.6001 285.7 51.8001 285.7 77.2001C285.7 107.3 302.7 122.5 327.1 122.5C339 122.5 348 119.8 354.8 116V96.0001C348 99.4001 340.2 101.5 330.3 101.5C320.6 101.5 312 98.1001 310.9 86.3001H359.8C359.8 85.0001 360 79.8001 360 77.4001ZM310.6 67.9001C310.6 56.6001 317.5 51.9001 323.8 51.9001C329.9 51.9001 336.4 56.6001 336.4 67.9001H310.6Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 31.6001C237.3 31.6001 231 36.2001 227.5 39.4001L226.2 33.2001H204.2V149.8L229.2 144.5L229.3 116.2C232.9 118.8 238.2 122.5 247 122.5C264.9 122.5 281.2 108.1 281.2 76.4001C281.1 47.4001 264.6 31.6001 247.1 31.6001ZM241.1 100.5C235.2 100.5 231.7 98.4001 229.3 95.8001L229.2 58.7001C231.8 55.8001 235.4 53.8001 241.1 53.8001C250.2 53.8001 256.5 64.0001 256.5 77.1001C256.5 90.5001 250.3 100.5 241.1 100.5Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 25.7L194.9 20.3V0L169.8 5.3V25.7Z" fill="#533AFD"/><path d="M194.9 33.3H169.8V120.8H194.9V33.3Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 40.7L141.3 33.3H119.7V120.8H144.7V61.5C150.6 53.8 160.6 55.2 163.7 56.3V33.3C160.5 32.1 148.8 29.9 142.9 40.7Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 11.6001L68.4999 16.8001L68.3999 96.9001C68.3999 111.7 79.4999 122.6 94.2999 122.6C102.5 122.6 108.5 121.1 111.8 119.3V99.0001C108.6 100.3 92.7999 104.9 92.7999 90.1001V54.6001H111.8V33.3001H92.7999L92.8999 11.6001Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 58.7001C25.3 54.8001 28.5 53.3001 33.8 53.3001C41.4 53.3001 51 55.6001 58.6 59.7001V36.2001C50.3 32.9001 42.1 31.6001 33.8 31.6001C13.5 31.6001 0 42.2001 0 59.9001C0 87.5001 38 83.1001 38 95.0001C38 99.6001 34 101.1 28.4 101.1C20.1 101.1 9.5 97.7001 1.1 93.1001V116.9C10.4 120.9 19.8 122.6 28.4 122.6C49.2 122.6 63.5 112.3 63.5 94.4001C63.4 64.6001 25.3 69.9001 25.3 58.7001Z" fill="#533AFD"/></svg>'
|
|
2811
3287
|
);
|
|
@@ -2822,9 +3298,13 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2822
3298
|
this.applePayInProgress = false;
|
|
2823
3299
|
this.klarnaAvailable = false;
|
|
2824
3300
|
this.orderConfirmed = false;
|
|
3301
|
+
this.providerReceipt = null;
|
|
2825
3302
|
this.confirmedMethod = null;
|
|
2826
3303
|
this.paymentError = null;
|
|
2827
3304
|
this.paymentNotice = null;
|
|
3305
|
+
this.verifyingReturn = null;
|
|
3306
|
+
/** The checkout whose confirmation is on screen — see fillOrderNumber. */
|
|
3307
|
+
this.confirmedCheckoutId = null;
|
|
2828
3308
|
this.confirmedOrder = null;
|
|
2829
3309
|
/** Active Klarna Payments widget handle + the subtotal it was mounted for. */
|
|
2830
3310
|
this.klarnaHandle = null;
|
|
@@ -2837,12 +3317,70 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2837
3317
|
this.selectedShipping = "";
|
|
2838
3318
|
this.availableShippingsList = [];
|
|
2839
3319
|
this.availableMethods = null;
|
|
3320
|
+
this.vippsOfficial = false;
|
|
2840
3321
|
this.kustomMounting = false;
|
|
3322
|
+
this.kustomSyncing = false;
|
|
3323
|
+
this.kustomShipping = null;
|
|
3324
|
+
this.kustomTotal = null;
|
|
3325
|
+
this.kustomNotice = null;
|
|
3326
|
+
/** The cart the mounted Kustom order describes — see kustomCartKey. */
|
|
3327
|
+
this.kustomMountedKey = null;
|
|
2841
3328
|
this.kustomMountedOrderId = null;
|
|
3329
|
+
/** The checkout session (see embedSession) each embedded widget's order belongs to. */
|
|
3330
|
+
this.kustomMountedFor = null;
|
|
3331
|
+
this.qliroMountedFor = null;
|
|
3332
|
+
this.walleyMountedFor = null;
|
|
3333
|
+
this.nexiMountedFor = null;
|
|
2842
3334
|
this.qliroMounting = false;
|
|
2843
3335
|
this.qliroMountedOrderId = null;
|
|
2844
3336
|
this.walleyMounting = false;
|
|
2845
3337
|
this.walleyMountedOrderId = null;
|
|
3338
|
+
this.nexiMounting = false;
|
|
3339
|
+
this.nexiMountedOrderId = null;
|
|
3340
|
+
this.nexiShippingNotice = null;
|
|
3341
|
+
this.nexiShipping = null;
|
|
3342
|
+
this.nexiPicking = false;
|
|
3343
|
+
/** The Nexi payment to resume on the next mount — set only by the ?paymentId= return. */
|
|
3344
|
+
this.nexiResumePaymentId = null;
|
|
3345
|
+
this.adyenMounting = false;
|
|
3346
|
+
this.adyenRequested = false;
|
|
3347
|
+
this.adyenMounted = false;
|
|
3348
|
+
this.adyenStale = false;
|
|
3349
|
+
this.adyenSettled = false;
|
|
3350
|
+
/** The checkout session (see embedSession) and the purchase the mounted photo is of. */
|
|
3351
|
+
this.adyenMountedFor = null;
|
|
3352
|
+
this.adyenMountedKey = null;
|
|
3353
|
+
this.stripeMode = null;
|
|
3354
|
+
this.stripeRequested = false;
|
|
3355
|
+
this.stripeMounting = false;
|
|
3356
|
+
this.stripeMounted = false;
|
|
3357
|
+
this.stripeStale = false;
|
|
3358
|
+
this.stripeSettled = false;
|
|
3359
|
+
this.stripePaying = false;
|
|
3360
|
+
this.stripeReady = false;
|
|
3361
|
+
this.stripeMountedFor = null;
|
|
3362
|
+
this.stripeMountedKey = null;
|
|
3363
|
+
/** The checkout the mounted PaymentIntent pays for. */
|
|
3364
|
+
this.stripeCheckoutId = null;
|
|
3365
|
+
this.stripeElement = null;
|
|
3366
|
+
this.qliroShipping = null;
|
|
3367
|
+
/**
|
|
3368
|
+
* Set while this page load is showing the outcome of an embedded checkout's
|
|
3369
|
+
* confirmation redirect (Kustom, Qliro, Walley). While set, nothing may start
|
|
3370
|
+
* a new payment.
|
|
3371
|
+
*
|
|
3372
|
+
* The return handler selects the method so the panel has something to show,
|
|
3373
|
+
* and only THEN asks the provider whether the order was paid. Selecting is a
|
|
3374
|
+
* state change; the render it schedules runs `updated()` → `mount*IfNeeded()`
|
|
3375
|
+
* long before the provider answers, and in a fresh page load there is no
|
|
3376
|
+
* mounted order to stop it — so every return created a new backend checkout
|
|
3377
|
+
* and a new provider order for a cart that had just been paid. Not a race
|
|
3378
|
+
* that needs a slow network: a microtask always beats a round trip.
|
|
3379
|
+
* Reproduced in `vio-checkout.return.dom.test.ts` before this existed.
|
|
3380
|
+
*
|
|
3381
|
+
* Cleared when the checkout closes: reopening it is a fresh checkout.
|
|
3382
|
+
*/
|
|
3383
|
+
this.returningFrom = null;
|
|
2846
3384
|
this.stripeLoading = false;
|
|
2847
3385
|
this.vippsLoading = false;
|
|
2848
3386
|
this.form = {
|
|
@@ -2855,10 +3393,41 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2855
3393
|
};
|
|
2856
3394
|
/** In-flight guard for loadAvailablePaymentMethods. */
|
|
2857
3395
|
this.loadingPaymentMethods = false;
|
|
3396
|
+
/**
|
|
3397
|
+
* Keyboard and focus while open: Escape closes — not while a payment is
|
|
3398
|
+
* in flight — Tab stays inside, focus returns to the opener.
|
|
3399
|
+
*/
|
|
3400
|
+
this.dialogFocus = new DialogFocus({
|
|
3401
|
+
host: this,
|
|
3402
|
+
container: () => this.renderRoot.querySelector(".modal"),
|
|
3403
|
+
close: () => this.close(),
|
|
3404
|
+
canClose: () => !this.paymentInFlight
|
|
3405
|
+
});
|
|
3406
|
+
this.paymentMethodsResolved = false;
|
|
3407
|
+
/** Guards `autoSelectSoleMethod` to ONE attempt per opening — see there. */
|
|
3408
|
+
this.autoSelectAttempted = false;
|
|
2858
3409
|
/** In-flight + attempted guards for loadAvailableShippings (change events
|
|
2859
3410
|
* fire per keystroke — without these, typing an address hammers the API). */
|
|
2860
3411
|
this.loadingShippings = false;
|
|
2861
3412
|
this.shippingsAttempted = false;
|
|
3413
|
+
this.shippingBlocked = false;
|
|
3414
|
+
this.shippingsReadyFor = null;
|
|
3415
|
+
this.boundOnQliroEvent = (e) => {
|
|
3416
|
+
const event = e.detail;
|
|
3417
|
+
if (!event) return;
|
|
3418
|
+
if (event.type === "shipping-price-changed" && typeof event.price === "number") {
|
|
3419
|
+
this.qliroShipping = { ...this.qliroShipping ?? {}, price: event.price };
|
|
3420
|
+
} else if (event.type === "shipping-changed") {
|
|
3421
|
+
const shipping = event.shipping ?? {};
|
|
3422
|
+
const ref = shipping.method ?? shipping.merchantReference ?? shipping.MerchantReference;
|
|
3423
|
+
const price = typeof shipping.price === "number" ? shipping.price : this.availableShippingsList.find((o) => o.id === String(ref))?.priceMajor;
|
|
3424
|
+
this.qliroShipping = {
|
|
3425
|
+
...this.qliroShipping ?? {},
|
|
3426
|
+
...ref != null ? { ref: String(ref) } : {},
|
|
3427
|
+
...typeof price === "number" ? { price } : {}
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
2862
3431
|
this.boundOnCheckoutChange = (e) => {
|
|
2863
3432
|
const detail = e.detail;
|
|
2864
3433
|
const prevSponsorId = this.checkoutState?.sponsorId;
|
|
@@ -2866,7 +3435,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2866
3435
|
this.checkoutState = detail.state;
|
|
2867
3436
|
this.items = Vio.checkout.items;
|
|
2868
3437
|
if (this.checkoutState) {
|
|
2869
|
-
if (sponsorChanged || this.
|
|
3438
|
+
if (sponsorChanged || !this.paymentMethodsResolved) {
|
|
3439
|
+
if (sponsorChanged) this.paymentMethodsResolved = false;
|
|
2870
3440
|
void this.refreshApplePay();
|
|
2871
3441
|
void this.refreshKlarna();
|
|
2872
3442
|
void this.loadAvailablePaymentMethods();
|
|
@@ -2878,13 +3448,28 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2878
3448
|
this.applePayAvailable = false;
|
|
2879
3449
|
this.klarnaAvailable = false;
|
|
2880
3450
|
this.orderConfirmed = false;
|
|
3451
|
+
this.providerReceipt = null;
|
|
2881
3452
|
this.confirmedMethod = null;
|
|
2882
3453
|
this.confirmedOrder = null;
|
|
2883
3454
|
this.paymentError = null;
|
|
2884
3455
|
this.paymentNotice = null;
|
|
3456
|
+
this.verifyingReturn = null;
|
|
3457
|
+
this.confirmedCheckoutId = null;
|
|
2885
3458
|
this.express = false;
|
|
2886
3459
|
this.availableMethods = null;
|
|
3460
|
+
this.paymentMethodsResolved = false;
|
|
3461
|
+
this.autoSelectAttempted = false;
|
|
3462
|
+
this.returningFrom = null;
|
|
3463
|
+
this.shippingBlocked = false;
|
|
2887
3464
|
this.unmountKlarna();
|
|
3465
|
+
this.unmountKustom();
|
|
3466
|
+
this.unmountQliro();
|
|
3467
|
+
this.unmountWalley();
|
|
3468
|
+
this.unmountNexi();
|
|
3469
|
+
this.unmountAdyen();
|
|
3470
|
+
this.adyenSettled = false;
|
|
3471
|
+
this.unmountStripe();
|
|
3472
|
+
this.stripeSettled = false;
|
|
2888
3473
|
}
|
|
2889
3474
|
};
|
|
2890
3475
|
this.boundOnPaymentComplete = (e) => {
|
|
@@ -2896,10 +3481,17 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2896
3481
|
};
|
|
2897
3482
|
this.boundOnPaymentError = (e) => {
|
|
2898
3483
|
const detail = e.detail;
|
|
2899
|
-
|
|
3484
|
+
if (typeof console !== "undefined") {
|
|
3485
|
+
console.warn("[VioCheckout] payment-error:", detail.method, detail.error);
|
|
3486
|
+
}
|
|
3487
|
+
this.paymentError = detail.method === "klarna" ? `Klarna-betalingen kunne ikke fullf\xF8res: ${detail.error}` : friendlyPaymentError(detail.error, detail.method);
|
|
2900
3488
|
this.open = true;
|
|
2901
3489
|
};
|
|
2902
3490
|
}
|
|
3491
|
+
/** A payment is being started or confirmed right now: the overlay must not close under it. */
|
|
3492
|
+
get paymentInFlight() {
|
|
3493
|
+
return this.stripePaying || this.stripeLoading || this.vippsLoading || this.klarnaAuthorizing || this.applePayInProgress;
|
|
3494
|
+
}
|
|
2903
3495
|
static {
|
|
2904
3496
|
this.styles = css`
|
|
2905
3497
|
:host { display: contents; }
|
|
@@ -2928,6 +3520,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2928
3520
|
overflow-y: auto;
|
|
2929
3521
|
}
|
|
2930
3522
|
:host([open]) .modal { transform: translateY(0); }
|
|
3523
|
+
/* Focus lands on the panel itself only as a fallback — no ring for it. */
|
|
3524
|
+
.modal:focus { outline: none; }
|
|
2931
3525
|
|
|
2932
3526
|
/* Confirmation drawer — compact bottom sheet, not the full-screen overlay. */
|
|
2933
3527
|
.modal.as-drawer {
|
|
@@ -2966,10 +3560,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2966
3560
|
:host([open]) .modal.as-side { transform: translateX(0); }
|
|
2967
3561
|
|
|
2968
3562
|
/* Desktop: the checkout ALWAYS lives as a right-side panel (like the cart,
|
|
2969
|
-
product detail and express drawer) — never a full-width overlay.
|
|
2970
|
-
|
|
3563
|
+
product detail and express drawer) — never a full-width overlay. The
|
|
3564
|
+
confirmation stays in that panel too: a centered card jumped out of the
|
|
3565
|
+
panel the shopper was in and looked like the phone layout (Angelo,
|
|
3566
|
+
2026-09-16). */
|
|
2971
3567
|
@media (min-width: 601px) {
|
|
2972
|
-
.modal
|
|
3568
|
+
.modal {
|
|
2973
3569
|
inset: 0 0 0 auto;
|
|
2974
3570
|
width: min(440px, 100%);
|
|
2975
3571
|
max-width: 440px;
|
|
@@ -2977,7 +3573,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2977
3573
|
border-radius: 0;
|
|
2978
3574
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
2979
3575
|
}
|
|
2980
|
-
:host([open]) .modal
|
|
3576
|
+
:host([open]) .modal { transform: translateX(0); }
|
|
2981
3577
|
}
|
|
2982
3578
|
@media (max-width: 600px) {
|
|
2983
3579
|
/* On phones, keep it a bottom sheet for reachability. */
|
|
@@ -2992,37 +3588,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
2992
3588
|
:host([open]) .modal.as-side { transform: translateY(0); }
|
|
2993
3589
|
}
|
|
2994
3590
|
|
|
2995
|
-
/*
|
|
2996
|
-
mobile on desktop). Falls back to a bottom sheet on phones. */
|
|
2997
|
-
.modal.as-confirm {
|
|
2998
|
-
inset: auto;
|
|
2999
|
-
top: 50%;
|
|
3000
|
-
left: 50%;
|
|
3001
|
-
width: min(92vw, 440px);
|
|
3002
|
-
max-width: 440px;
|
|
3003
|
-
height: auto;
|
|
3004
|
-
max-height: 88vh;
|
|
3005
|
-
border-radius: var(--vio-radius-xl, 16px);
|
|
3006
|
-
box-shadow: 0 30px 80px -24px rgba(0, 0, 0, 0.4);
|
|
3007
|
-
transform: translate(-50%, -50%) scale(0.96);
|
|
3008
|
-
opacity: 0;
|
|
3009
|
-
}
|
|
3010
|
-
:host([open]) .modal.as-confirm {
|
|
3011
|
-
transform: translate(-50%, -50%) scale(1);
|
|
3012
|
-
opacity: 1;
|
|
3013
|
-
}
|
|
3591
|
+
/* Phones: the confirmation is a compact bottom sheet, as tall as it needs. */
|
|
3014
3592
|
@media (max-width: 600px) {
|
|
3015
3593
|
.modal.as-confirm {
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
left: 0;
|
|
3019
|
-
width: 100%;
|
|
3020
|
-
max-width: none;
|
|
3021
|
-
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
3022
|
-
transform: translateY(100%) scale(1);
|
|
3023
|
-
opacity: 1;
|
|
3594
|
+
height: auto;
|
|
3595
|
+
max-height: 88vh;
|
|
3024
3596
|
}
|
|
3025
|
-
:host([open]) .modal.as-confirm { transform: translateY(0); }
|
|
3026
3597
|
}
|
|
3027
3598
|
|
|
3028
3599
|
.handle { display: none; }
|
|
@@ -3139,6 +3710,61 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3139
3710
|
grid-template-columns: 1fr 1fr;
|
|
3140
3711
|
gap: var(--vio-space-md, 16px);
|
|
3141
3712
|
}
|
|
3713
|
+
/* Shown until we know which methods the channel offers. Rendering every
|
|
3714
|
+
button meanwhile would let a shopper click one that is not enabled. */
|
|
3715
|
+
.payment-loading {
|
|
3716
|
+
display: flex;
|
|
3717
|
+
align-items: center;
|
|
3718
|
+
gap: 10px;
|
|
3719
|
+
padding: 20px 0;
|
|
3720
|
+
font-size: 13px;
|
|
3721
|
+
color: var(--vio-color-text-secondary, #666);
|
|
3722
|
+
}
|
|
3723
|
+
.payment-spinner {
|
|
3724
|
+
width: 14px;
|
|
3725
|
+
height: 14px;
|
|
3726
|
+
border: 2px solid var(--vio-color-border, #e5e5e5);
|
|
3727
|
+
border-top-color: var(--vio-color-text-secondary, #666);
|
|
3728
|
+
border-radius: 50%;
|
|
3729
|
+
animation: vio-payment-spin 0.7s linear infinite;
|
|
3730
|
+
}
|
|
3731
|
+
@keyframes vio-payment-spin {
|
|
3732
|
+
to { transform: rotate(360deg); }
|
|
3733
|
+
}
|
|
3734
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3735
|
+
.payment-spinner { animation: none; }
|
|
3736
|
+
}
|
|
3737
|
+
/* Back from a payment page: only this until we know. */
|
|
3738
|
+
.verifying {
|
|
3739
|
+
display: flex;
|
|
3740
|
+
flex-direction: column;
|
|
3741
|
+
align-items: center;
|
|
3742
|
+
text-align: center;
|
|
3743
|
+
gap: 10px;
|
|
3744
|
+
padding: 56px 16px;
|
|
3745
|
+
}
|
|
3746
|
+
.verifying-spinner {
|
|
3747
|
+
width: 28px;
|
|
3748
|
+
height: 28px;
|
|
3749
|
+
border: 3px solid var(--vio-color-border, #e5e5e5);
|
|
3750
|
+
border-top-color: var(--vio-color-accent, #c14a3b);
|
|
3751
|
+
border-radius: 50%;
|
|
3752
|
+
animation: vio-payment-spin 0.8s linear infinite;
|
|
3753
|
+
}
|
|
3754
|
+
.verifying-text {
|
|
3755
|
+
margin: 6px 0 0;
|
|
3756
|
+
font-size: 15px;
|
|
3757
|
+
font-weight: 600;
|
|
3758
|
+
color: var(--vio-color-text, #1a1a1a);
|
|
3759
|
+
}
|
|
3760
|
+
.verifying-hint {
|
|
3761
|
+
margin: 0;
|
|
3762
|
+
font-size: 13px;
|
|
3763
|
+
color: var(--vio-color-text-secondary, #666);
|
|
3764
|
+
}
|
|
3765
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3766
|
+
.verifying-spinner { animation: none; }
|
|
3767
|
+
}
|
|
3142
3768
|
.payment-btn {
|
|
3143
3769
|
padding: 20px;
|
|
3144
3770
|
background: #fff;
|
|
@@ -3175,13 +3801,6 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3175
3801
|
.payment-btn[aria-pressed='true'] {
|
|
3176
3802
|
border-color: var(--vio-color-accent, #c14a3b);
|
|
3177
3803
|
}
|
|
3178
|
-
.apple-pay-note {
|
|
3179
|
-
grid-column: 1 / -1;
|
|
3180
|
-
font-size: 12px;
|
|
3181
|
-
color: var(--vio-color-text-tertiary, #999);
|
|
3182
|
-
text-align: center;
|
|
3183
|
-
margin-top: 4px;
|
|
3184
|
-
}
|
|
3185
3804
|
/* Klarna Payments — inline widget panel, shown once Klarna is selected. */
|
|
3186
3805
|
.klarna-panel {
|
|
3187
3806
|
grid-column: 1 / -1;
|
|
@@ -3223,6 +3842,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3223
3842
|
}
|
|
3224
3843
|
/* Shipping selector (express) — Standard / Express options. */
|
|
3225
3844
|
.ship-select { display: flex; flex-direction: column; gap: 8px; }
|
|
3845
|
+
.nexi-ship { margin-bottom: 12px; }
|
|
3226
3846
|
.ship-opt {
|
|
3227
3847
|
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
3228
3848
|
padding: 12px 14px; border: 1px solid var(--vio-color-border, #ddd);
|
|
@@ -3272,58 +3892,103 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3272
3892
|
|
|
3273
3893
|
/* Order confirmation — compact, shown inside the drawer. */
|
|
3274
3894
|
.confirmation {
|
|
3275
|
-
|
|
3276
|
-
padding: 24px 24px 28px;
|
|
3895
|
+
padding: 8px 0 28px;
|
|
3277
3896
|
display: flex;
|
|
3278
3897
|
flex-direction: column;
|
|
3279
|
-
|
|
3280
|
-
gap: 14px;
|
|
3898
|
+
gap: 20px;
|
|
3281
3899
|
}
|
|
3282
|
-
.confirm-
|
|
3283
|
-
width: 64px;
|
|
3284
|
-
height: 64px;
|
|
3285
|
-
border-radius: 50%;
|
|
3286
|
-
background: #2e7d32;
|
|
3287
|
-
color: #fff;
|
|
3900
|
+
.confirm-head {
|
|
3288
3901
|
display: flex;
|
|
3289
3902
|
align-items: center;
|
|
3290
|
-
|
|
3291
|
-
font-size: 32px;
|
|
3292
|
-
line-height: 1;
|
|
3293
|
-
}
|
|
3294
|
-
.confirm-title {
|
|
3295
|
-
font-family: var(--vio-font-serif, Georgia, serif);
|
|
3296
|
-
font-size: 28px;
|
|
3297
|
-
font-weight: 400;
|
|
3298
|
-
margin: 0;
|
|
3903
|
+
gap: 16px;
|
|
3299
3904
|
}
|
|
3300
|
-
.confirm-
|
|
3301
|
-
font-size: 14px;
|
|
3302
|
-
color: var(--vio-color-text-secondary, #666);
|
|
3303
|
-
max-width: 360px;
|
|
3304
|
-
line-height: 1.6;
|
|
3905
|
+
.confirm-details {
|
|
3305
3906
|
margin: 0;
|
|
3907
|
+
display: flex;
|
|
3908
|
+
flex-direction: column;
|
|
3909
|
+
border: 1px solid var(--vio-color-border, #eee);
|
|
3910
|
+
border-radius: var(--vio-radius-lg, 8px);
|
|
3306
3911
|
}
|
|
3307
|
-
.confirm-
|
|
3912
|
+
.confirm-details > div {
|
|
3913
|
+
display: flex;
|
|
3914
|
+
justify-content: space-between;
|
|
3915
|
+
gap: 16px;
|
|
3916
|
+
padding: 10px 14px;
|
|
3308
3917
|
font-size: 13px;
|
|
3309
|
-
color: var(--vio-color-text-secondary, #666);
|
|
3310
3918
|
}
|
|
3311
|
-
.confirm-
|
|
3312
|
-
|
|
3313
|
-
|
|
3919
|
+
.confirm-details > div + div { border-top: 1px solid var(--vio-color-border, #eee); }
|
|
3920
|
+
.confirm-details dt { color: var(--vio-color-text-secondary, #666); white-space: nowrap; }
|
|
3921
|
+
.confirm-details dd.ref { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 12px; }
|
|
3922
|
+
.confirm-details dd {
|
|
3923
|
+
margin: 0;
|
|
3924
|
+
font-weight: 600;
|
|
3925
|
+
text-align: right;
|
|
3926
|
+
font-variant-numeric: tabular-nums;
|
|
3927
|
+
word-break: break-all;
|
|
3928
|
+
}
|
|
3929
|
+
.confirm-label {
|
|
3930
|
+
font-size: 11px;
|
|
3931
|
+
text-transform: uppercase;
|
|
3932
|
+
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
3933
|
+
color: var(--vio-color-text-secondary, #666);
|
|
3934
|
+
}
|
|
3935
|
+
.confirm-line {
|
|
3936
|
+
display: grid;
|
|
3937
|
+
grid-template-columns: 56px 1fr auto;
|
|
3938
|
+
gap: 12px;
|
|
3939
|
+
align-items: center;
|
|
3940
|
+
}
|
|
3941
|
+
.confirm-thumb {
|
|
3942
|
+
width: 56px;
|
|
3943
|
+
height: 56px;
|
|
3944
|
+
object-fit: cover;
|
|
3945
|
+
background: var(--vio-color-surface-muted, #f2f2f2);
|
|
3946
|
+
display: block;
|
|
3947
|
+
}
|
|
3948
|
+
.confirm-line-name { display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
|
|
3949
|
+
.confirm-line-name small { font-size: 11px; color: var(--vio-color-text-secondary, #666); }
|
|
3950
|
+
.confirm-line-price { font-size: 14px; font-weight: 600; }
|
|
3951
|
+
.confirm-check {
|
|
3952
|
+
flex-shrink: 0;
|
|
3953
|
+
width: 48px;
|
|
3954
|
+
height: 48px;
|
|
3955
|
+
border-radius: 50%;
|
|
3956
|
+
background: #2e7d32;
|
|
3957
|
+
color: #fff;
|
|
3958
|
+
display: flex;
|
|
3959
|
+
align-items: center;
|
|
3960
|
+
justify-content: center;
|
|
3961
|
+
font-size: 24px;
|
|
3962
|
+
line-height: 1;
|
|
3963
|
+
}
|
|
3964
|
+
.confirm-title {
|
|
3965
|
+
font-family: var(--vio-font-serif, Georgia, serif);
|
|
3966
|
+
font-size: 24px;
|
|
3967
|
+
font-weight: 400;
|
|
3968
|
+
margin: 0;
|
|
3969
|
+
}
|
|
3970
|
+
.confirm-text {
|
|
3971
|
+
font-size: 14px;
|
|
3972
|
+
color: var(--vio-color-text-secondary, #666);
|
|
3973
|
+
line-height: 1.5;
|
|
3974
|
+
margin: 4px 0 0;
|
|
3975
|
+
}
|
|
3976
|
+
.confirm-order-id {
|
|
3977
|
+
font-size: 13px;
|
|
3978
|
+
color: var(--vio-color-text-secondary, #666);
|
|
3979
|
+
}
|
|
3980
|
+
.confirm-order-id b {
|
|
3981
|
+
color: var(--vio-color-text, #0a0a0a);
|
|
3982
|
+
font-variant-numeric: tabular-nums;
|
|
3314
3983
|
}
|
|
3315
3984
|
.confirm-summary {
|
|
3316
|
-
width: 100%;
|
|
3317
|
-
max-width: 360px;
|
|
3318
|
-
border-top: 1px solid var(--vio-color-border, #eee);
|
|
3319
|
-
padding-top: 14px;
|
|
3320
3985
|
display: flex;
|
|
3321
3986
|
flex-direction: column;
|
|
3322
|
-
gap:
|
|
3323
|
-
text-align: left;
|
|
3987
|
+
gap: 12px;
|
|
3324
3988
|
font-size: 13px;
|
|
3325
3989
|
}
|
|
3326
3990
|
.confirm-close {
|
|
3991
|
+
width: 100%;
|
|
3327
3992
|
border: none;
|
|
3328
3993
|
background: var(--vio-color-text, #0a0a0a);
|
|
3329
3994
|
color: var(--vio-color-text-on-primary, #fff);
|
|
@@ -3388,11 +4053,13 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3388
4053
|
}
|
|
3389
4054
|
connectedCallback() {
|
|
3390
4055
|
super.connectedCallback();
|
|
4056
|
+
void this.loadVippsButton();
|
|
3391
4057
|
this.checkoutState = Vio.checkout.currentState;
|
|
3392
4058
|
this.items = Vio.checkout.items;
|
|
3393
4059
|
Vio.checkout.addEventListener("change", this.boundOnCheckoutChange);
|
|
3394
4060
|
Vio.checkout.addEventListener("payment-complete", this.boundOnPaymentComplete);
|
|
3395
4061
|
Vio.checkout.addEventListener("payment-error", this.boundOnPaymentError);
|
|
4062
|
+
Vio.checkout.addEventListener("qliro-event", this.boundOnQliroEvent);
|
|
3396
4063
|
if (this.checkoutState) {
|
|
3397
4064
|
void this.refreshApplePay();
|
|
3398
4065
|
void this.refreshKlarna();
|
|
@@ -3400,6 +4067,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3400
4067
|
}
|
|
3401
4068
|
void Vio.checkout.resumeKlarnaReturn();
|
|
3402
4069
|
void this.checkReturnPaymentStatus();
|
|
4070
|
+
this.requestUpdate();
|
|
3403
4071
|
}
|
|
3404
4072
|
static {
|
|
3405
4073
|
/**
|
|
@@ -3435,6 +4103,18 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3435
4103
|
"unpaid"
|
|
3436
4104
|
]);
|
|
3437
4105
|
}
|
|
4106
|
+
static {
|
|
4107
|
+
/**
|
|
4108
|
+
* Shown instead of the payment step when the cart's products cannot ship
|
|
4109
|
+
* together. Angelo, 2026-09-16: block the purchase rather than sell it
|
|
4110
|
+
* without shipping, or with a rate that does not fit every product.
|
|
4111
|
+
*/
|
|
4112
|
+
this.SHIPPING_BLOCKED_MESSAGE = "Produktene i handlekurven kan ikke sendes sammen. Fjern ett av dem for \xE5 fullf\xF8re kj\xF8pet.";
|
|
4113
|
+
}
|
|
4114
|
+
static {
|
|
4115
|
+
/** The payment may or may not have gone through, and we could not find out. */
|
|
4116
|
+
this.RETURN_UNVERIFIED_MESSAGE = "Vi kunne ikke bekrefte betalingen. Handlekurven er uendret \u2014 pr\xF8v igjen, eller kontakt support hvis du ble belastet.";
|
|
4117
|
+
}
|
|
3438
4118
|
static {
|
|
3439
4119
|
/** Waits between GetCheckout attempts — PSP webhooks (Vipps especially) can
|
|
3440
4120
|
* land seconds after the shopper is redirected back, so a single immediate
|
|
@@ -3457,6 +4137,10 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3457
4137
|
* anything else (including an unrecognized state) is treated as failed —
|
|
3458
4138
|
* fail-closed by default rather than guessing.
|
|
3459
4139
|
*/
|
|
4140
|
+
/** Vipps' own button when its script answers; our orange one otherwise. */
|
|
4141
|
+
async loadVippsButton() {
|
|
4142
|
+
this.vippsOfficial = await ensureVippsButton();
|
|
4143
|
+
}
|
|
3460
4144
|
async pollVippsStatus(checkoutId, sponsorId) {
|
|
3461
4145
|
let sawStatus = false;
|
|
3462
4146
|
for (let attempt = 0; attempt < _VioCheckout.VIPPS_STATUS_MAX_ATTEMPTS; attempt++) {
|
|
@@ -3467,6 +4151,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3467
4151
|
const vippsStatus = await Vio.checkout.getVippsStatus(checkoutId, sponsorId || void 0);
|
|
3468
4152
|
const state6 = String(vippsStatus?.state ?? "").toUpperCase();
|
|
3469
4153
|
sawStatus = true;
|
|
4154
|
+
if (vippsStatus?.duplicate_of) return { outcome: "duplicate", sawStatus };
|
|
3470
4155
|
if (state6 === "AUTHORIZED") return { outcome: "paid", sawStatus };
|
|
3471
4156
|
if (state6 === "CREATED") continue;
|
|
3472
4157
|
return { outcome: "failed", sawStatus };
|
|
@@ -3486,12 +4171,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3486
4171
|
this.confirmedOrder = {
|
|
3487
4172
|
items: [],
|
|
3488
4173
|
currency: getGlobalCurrency(),
|
|
3489
|
-
total: 0
|
|
3490
|
-
orderId: checkoutId || void 0
|
|
4174
|
+
total: 0
|
|
3491
4175
|
};
|
|
3492
4176
|
this.confirmedMethod = vioMethod;
|
|
3493
4177
|
this.orderConfirmed = true;
|
|
3494
4178
|
this.open = true;
|
|
4179
|
+
void this.fillOrderNumber(checkoutId, sponsorId);
|
|
3495
4180
|
this.dispatchEvent(
|
|
3496
4181
|
new CustomEvent("vio:payment-success", {
|
|
3497
4182
|
bubbles: true,
|
|
@@ -3499,6 +4184,9 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3499
4184
|
detail: { method: vioMethod, sponsorId, orderId: checkoutId }
|
|
3500
4185
|
})
|
|
3501
4186
|
);
|
|
4187
|
+
} else if (outcome === "duplicate") {
|
|
4188
|
+
this.paymentError = CART_PAID_MESSAGE;
|
|
4189
|
+
this.open = true;
|
|
3502
4190
|
} else if (outcome === "failed") {
|
|
3503
4191
|
this.paymentError = "Betalingen ble avbrutt eller feilet. Vennligst pr\xF8v igjen.";
|
|
3504
4192
|
this.open = true;
|
|
@@ -3506,7 +4194,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3506
4194
|
this.paymentNotice = "Betalingen behandles fortsatt. Du f\xE5r ordrebekreftelse p\xE5 e-post n\xE5r den er gjennomf\xF8rt. Handlekurven er uendret.";
|
|
3507
4195
|
this.open = true;
|
|
3508
4196
|
} else {
|
|
3509
|
-
this.paymentError =
|
|
4197
|
+
this.paymentError = _VioCheckout.RETURN_UNVERIFIED_MESSAGE;
|
|
3510
4198
|
this.open = true;
|
|
3511
4199
|
}
|
|
3512
4200
|
}
|
|
@@ -3518,9 +4206,33 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3518
4206
|
const vioMethod = urlParams.get("vio_method") || "stripe";
|
|
3519
4207
|
const sponsorId = Number(urlParams.get("vio_sponsor") || 0);
|
|
3520
4208
|
const checkoutId = urlParams.get("checkout_id") || "";
|
|
3521
|
-
|
|
3522
|
-
const
|
|
3523
|
-
if (
|
|
4209
|
+
const nexiPaymentId = nexiReturnPaymentId();
|
|
4210
|
+
const nexiPending = readNexiPending();
|
|
4211
|
+
if (nexiPaymentId && nexiPending && nexiPending.paymentId === nexiPaymentId) {
|
|
4212
|
+
this.nexiResumePaymentId = nexiPaymentId;
|
|
4213
|
+
this.cleanReturnQueryParams(["paymentId"]);
|
|
4214
|
+
if (!this.checkoutState && nexiPending.sponsorId) {
|
|
4215
|
+
try {
|
|
4216
|
+
Vio.checkout.open(nexiPending.sponsorId);
|
|
4217
|
+
} catch {
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
try {
|
|
4221
|
+
Vio.checkout.selectPaymentMethod("nexi");
|
|
4222
|
+
} catch {
|
|
4223
|
+
}
|
|
4224
|
+
this.open = true;
|
|
4225
|
+
return;
|
|
4226
|
+
}
|
|
4227
|
+
const kustomReturn = kustomReturnOutcome(urlParams);
|
|
4228
|
+
if (kustomReturn?.kind === "rejected") {
|
|
4229
|
+
this.cleanReturnQueryParams([...KUSTOM_RETURN_QUERY_KEYS]);
|
|
4230
|
+
this.kustomNotice = "Bestillingen ble endret. Betalingen ble ikke gjennomf\xF8rt \u2014 pr\xF8v igjen.";
|
|
4231
|
+
return;
|
|
4232
|
+
}
|
|
4233
|
+
if (kustomReturn?.kind === "confirmation") {
|
|
4234
|
+
const kustomOrderId = kustomReturn.orderId;
|
|
4235
|
+
this.returningFrom = "kustom";
|
|
3524
4236
|
if (!this.checkoutState) {
|
|
3525
4237
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
3526
4238
|
if (spIdToUse) {
|
|
@@ -3535,49 +4247,35 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3535
4247
|
} catch {
|
|
3536
4248
|
}
|
|
3537
4249
|
this.open = true;
|
|
3538
|
-
this.
|
|
4250
|
+
this.verifyingReturn = "Bekrefter betalingen\u2026";
|
|
4251
|
+
this.cleanReturnQueryParams([...KUSTOM_RETURN_QUERY_KEYS]);
|
|
3539
4252
|
try {
|
|
3540
4253
|
const order = await Vio.checkout.getKustomOrder(
|
|
3541
4254
|
kustomOrderId,
|
|
3542
4255
|
this.checkoutState?.sponsorId
|
|
3543
4256
|
);
|
|
3544
|
-
this.
|
|
3545
|
-
if (order?.html_snippet) {
|
|
3546
|
-
this.
|
|
4257
|
+
this.verifyingReturn = null;
|
|
4258
|
+
if (order?.status === "checkout_complete" && order.html_snippet) {
|
|
4259
|
+
const spId = this.checkoutState?.sponsorId ?? 0;
|
|
4260
|
+
this.showProviderReceipt("kustom", spId, order, kustomReturn.checkoutId);
|
|
3547
4261
|
await this.updateComplete;
|
|
3548
|
-
const container = this.
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
Vio.checkout.renderKustomSnippet(container, order.html_snippet);
|
|
3553
|
-
}
|
|
3554
|
-
if (order.status === "checkout_complete") {
|
|
3555
|
-
this.dispatchEvent(
|
|
3556
|
-
new CustomEvent("vio:payment-success", {
|
|
3557
|
-
detail: { method: "kustom", orderId: order.order_id },
|
|
3558
|
-
bubbles: true,
|
|
3559
|
-
composed: true
|
|
3560
|
-
})
|
|
3561
|
-
);
|
|
3562
|
-
const spId = this.checkoutState?.sponsorId;
|
|
3563
|
-
if (spId) {
|
|
3564
|
-
try {
|
|
3565
|
-
Vio.cart.clearSponsorCart(spId);
|
|
3566
|
-
} catch {
|
|
3567
|
-
}
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
4262
|
+
const container = this.lightContainer("vio-kustom-checkout-container", "vio-kustom");
|
|
4263
|
+
Vio.checkout.renderKustomSnippet(container, order.html_snippet);
|
|
4264
|
+
} else {
|
|
4265
|
+
this.paymentError = _VioCheckout.RETURN_UNVERIFIED_MESSAGE;
|
|
3570
4266
|
}
|
|
3571
4267
|
} catch (err) {
|
|
3572
|
-
this.
|
|
4268
|
+
this.verifyingReturn = null;
|
|
4269
|
+
this.paymentError = _VioCheckout.RETURN_UNVERIFIED_MESSAGE;
|
|
3573
4270
|
if (typeof console !== "undefined") {
|
|
3574
4271
|
console.warn("[VioCheckout] Kustom confirmation read failed:", err);
|
|
3575
4272
|
}
|
|
3576
4273
|
}
|
|
3577
|
-
this.cleanReturnQueryParams(["order_id", "payment_processor"]);
|
|
3578
4274
|
return;
|
|
3579
4275
|
}
|
|
4276
|
+
if (!vioPayment && !checkoutId) return;
|
|
3580
4277
|
if (urlParams.get("payment_processor") === "QLIRO" && checkoutId) {
|
|
4278
|
+
this.returningFrom = "qliro";
|
|
3581
4279
|
if (!this.checkoutState) {
|
|
3582
4280
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
3583
4281
|
if (spIdToUse) {
|
|
@@ -3591,40 +4289,38 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3591
4289
|
Vio.checkout.selectPaymentMethod("qliro");
|
|
3592
4290
|
} catch {
|
|
3593
4291
|
}
|
|
4292
|
+
this.autoSelectAttempted = true;
|
|
3594
4293
|
this.open = true;
|
|
3595
|
-
this.
|
|
4294
|
+
this.verifyingReturn = "Bekrefter betalingen\u2026";
|
|
3596
4295
|
try {
|
|
3597
4296
|
const order = await Vio.checkout.getQliroOrder(
|
|
3598
4297
|
checkoutId,
|
|
3599
4298
|
this.checkoutState?.sponsorId
|
|
3600
4299
|
);
|
|
3601
|
-
this.
|
|
3602
|
-
if (order?.
|
|
4300
|
+
this.verifyingReturn = null;
|
|
4301
|
+
if (order?.status === "Completed") {
|
|
4302
|
+
const spId = this.checkoutState?.sponsorId ?? sponsorId ?? 0;
|
|
4303
|
+
const result = { order: { orderId: order.order_id }, chargedTotal: order.total_price };
|
|
4304
|
+
this.qliroMountedOrderId = order.order_id;
|
|
4305
|
+
if (order.html_snippet) {
|
|
4306
|
+
this.showProviderReceipt("qliro", spId, result, checkoutId);
|
|
4307
|
+
await this.updateComplete;
|
|
4308
|
+
const container = this.lightContainer("vio-qliro-checkout-container", "vio-qliro");
|
|
4309
|
+
Vio.checkout.renderKustomSnippet(container, order.html_snippet);
|
|
4310
|
+
} else {
|
|
4311
|
+
this.confirmOrder("qliro", spId, result, checkoutId);
|
|
4312
|
+
}
|
|
4313
|
+
} else if (order?.html_snippet) {
|
|
3603
4314
|
this.qliroMountedOrderId = order.order_id;
|
|
3604
4315
|
await this.updateComplete;
|
|
3605
4316
|
const container = this.lightContainer("vio-qliro-checkout-container", "vio-qliro");
|
|
3606
4317
|
if (container) {
|
|
3607
4318
|
Vio.checkout.renderKustomSnippet(container, order.html_snippet);
|
|
3608
4319
|
}
|
|
3609
|
-
if (order.status === "Completed") {
|
|
3610
|
-
this.dispatchEvent(
|
|
3611
|
-
new CustomEvent("vio:payment-success", {
|
|
3612
|
-
detail: { method: "qliro", orderId: order.order_id },
|
|
3613
|
-
bubbles: true,
|
|
3614
|
-
composed: true
|
|
3615
|
-
})
|
|
3616
|
-
);
|
|
3617
|
-
const spId = this.checkoutState?.sponsorId;
|
|
3618
|
-
if (spId) {
|
|
3619
|
-
try {
|
|
3620
|
-
Vio.cart.clearSponsorCart(spId);
|
|
3621
|
-
} catch {
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
4320
|
}
|
|
3626
4321
|
} catch (err) {
|
|
3627
|
-
this.
|
|
4322
|
+
this.verifyingReturn = null;
|
|
4323
|
+
this.paymentError = _VioCheckout.RETURN_UNVERIFIED_MESSAGE;
|
|
3628
4324
|
if (typeof console !== "undefined") {
|
|
3629
4325
|
console.warn("[VioCheckout] Qliro confirmation read failed:", err);
|
|
3630
4326
|
}
|
|
@@ -3633,6 +4329,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3633
4329
|
return;
|
|
3634
4330
|
}
|
|
3635
4331
|
if (urlParams.get("payment_processor") === "WALLEY" && checkoutId) {
|
|
4332
|
+
this.returningFrom = "walley";
|
|
3636
4333
|
if (!this.checkoutState) {
|
|
3637
4334
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
3638
4335
|
if (spIdToUse) {
|
|
@@ -3647,13 +4344,13 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3647
4344
|
} catch {
|
|
3648
4345
|
}
|
|
3649
4346
|
this.open = true;
|
|
3650
|
-
this.
|
|
4347
|
+
this.verifyingReturn = "Bekrefter betalingen\u2026";
|
|
3651
4348
|
try {
|
|
3652
4349
|
const order = await Vio.checkout.getWalleyOrder(
|
|
3653
4350
|
checkoutId,
|
|
3654
4351
|
this.checkoutState?.sponsorId
|
|
3655
4352
|
);
|
|
3656
|
-
this.
|
|
4353
|
+
this.verifyingReturn = null;
|
|
3657
4354
|
if (order?.html_snippet) {
|
|
3658
4355
|
this.walleyMountedOrderId = order.order_id;
|
|
3659
4356
|
await this.updateComplete;
|
|
@@ -3662,24 +4359,17 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3662
4359
|
Vio.checkout.renderKustomSnippet(container, order.html_snippet);
|
|
3663
4360
|
}
|
|
3664
4361
|
if (order.status === "PurchaseCompleted") {
|
|
3665
|
-
this.
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
})
|
|
4362
|
+
this.showProviderReceipt(
|
|
4363
|
+
"walley",
|
|
4364
|
+
this.checkoutState?.sponsorId ?? 0,
|
|
4365
|
+
{ orderId: order.order_id },
|
|
4366
|
+
checkoutId
|
|
3671
4367
|
);
|
|
3672
|
-
const spId = this.checkoutState?.sponsorId;
|
|
3673
|
-
if (spId) {
|
|
3674
|
-
try {
|
|
3675
|
-
Vio.cart.clearSponsorCart(spId);
|
|
3676
|
-
} catch {
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
4368
|
}
|
|
3680
4369
|
}
|
|
3681
4370
|
} catch (err) {
|
|
3682
|
-
this.
|
|
4371
|
+
this.verifyingReturn = null;
|
|
4372
|
+
this.paymentError = _VioCheckout.RETURN_UNVERIFIED_MESSAGE;
|
|
3683
4373
|
if (typeof console !== "undefined") {
|
|
3684
4374
|
console.warn("[VioCheckout] Walley confirmation read failed:", err);
|
|
3685
4375
|
}
|
|
@@ -3691,6 +4381,56 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3691
4381
|
this.cleanReturnQueryParams(["vio_payment", "vio_method", "vio_sponsor", "checkout_id"]);
|
|
3692
4382
|
return;
|
|
3693
4383
|
}
|
|
4384
|
+
if (vioMethod === ADYEN_RETURN_METHOD && checkoutId) {
|
|
4385
|
+
const adyenReturn = adyenReturnParams(window.location.search);
|
|
4386
|
+
this.cleanReturnQueryParams(ADYEN_RETURN_QUERY_KEYS);
|
|
4387
|
+
if (!adyenReturn) return;
|
|
4388
|
+
this.returningFrom = "adyen";
|
|
4389
|
+
this.adyenSettled = true;
|
|
4390
|
+
this.autoSelectAttempted = true;
|
|
4391
|
+
if (!this.checkoutState) {
|
|
4392
|
+
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
4393
|
+
if (spIdToUse) {
|
|
4394
|
+
try {
|
|
4395
|
+
Vio.checkout.open(spIdToUse);
|
|
4396
|
+
Vio.checkout.selectPaymentMethod("adyen");
|
|
4397
|
+
} catch {
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
this.open = true;
|
|
4402
|
+
this.verifyingReturn = "Bekrefter betalingen\u2026";
|
|
4403
|
+
let payment = null;
|
|
4404
|
+
let sawStatus = false;
|
|
4405
|
+
try {
|
|
4406
|
+
payment = await Vio.checkout.getAdyenPayment(
|
|
4407
|
+
checkoutId,
|
|
4408
|
+
{ redirectResult: adyenReturn.redirectResult },
|
|
4409
|
+
sponsorId || void 0
|
|
4410
|
+
);
|
|
4411
|
+
sawStatus = !!payment;
|
|
4412
|
+
} catch (err) {
|
|
4413
|
+
if (typeof console !== "undefined") {
|
|
4414
|
+
console.warn("[VioCheckout] Adyen return could not be verified:", err);
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
this.verifyingReturn = null;
|
|
4418
|
+
const outcome = sawStatus ? adyenReturnOutcome(payment) : "pending";
|
|
4419
|
+
this.applyReturnOutcome(outcome, sawStatus, "adyen", sponsorId, checkoutId);
|
|
4420
|
+
if (outcome === "paid" && this.confirmedOrder) {
|
|
4421
|
+
this.confirmedOrder = {
|
|
4422
|
+
...this.confirmedOrder,
|
|
4423
|
+
currency: payment?.purchase_currency ?? this.confirmedOrder.currency,
|
|
4424
|
+
total: payment?.total_price ?? this.confirmedOrder.total
|
|
4425
|
+
};
|
|
4426
|
+
this.addAdyenPaymentDetails(payment);
|
|
4427
|
+
}
|
|
4428
|
+
if (outcome === "failed") {
|
|
4429
|
+
this.returningFrom = null;
|
|
4430
|
+
this.adyenSettled = false;
|
|
4431
|
+
}
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
3694
4434
|
if (vioMethod === "vipps" && checkoutId) {
|
|
3695
4435
|
if (!this.checkoutState) {
|
|
3696
4436
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
@@ -3703,9 +4443,9 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3703
4443
|
}
|
|
3704
4444
|
}
|
|
3705
4445
|
this.open = true;
|
|
3706
|
-
this.
|
|
4446
|
+
this.verifyingReturn = "Bekrefter betalingen med Vipps\u2026";
|
|
3707
4447
|
const { outcome, sawStatus } = await this.pollVippsStatus(checkoutId, sponsorId);
|
|
3708
|
-
this.
|
|
4448
|
+
this.verifyingReturn = null;
|
|
3709
4449
|
this.applyReturnOutcome(outcome, sawStatus, vioMethod, sponsorId, checkoutId);
|
|
3710
4450
|
this.cleanReturnQueryParams(["vio_payment", "vio_method", "vio_sponsor", "checkout_id"]);
|
|
3711
4451
|
} else if (vioPayment === "success") {
|
|
@@ -3713,18 +4453,26 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3713
4453
|
let sawStatus = false;
|
|
3714
4454
|
if (checkoutId) {
|
|
3715
4455
|
this.open = true;
|
|
3716
|
-
this.
|
|
4456
|
+
this.verifyingReturn = "Bekrefter betalingen\u2026";
|
|
3717
4457
|
for (const delayMs of _VioCheckout.RETURN_VERIFY_DELAYS_MS) {
|
|
3718
4458
|
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
3719
4459
|
try {
|
|
3720
4460
|
const checkout = await Vio.checkout.getCheckout(checkoutId, sponsorId || void 0);
|
|
3721
4461
|
const status = String(checkout?.status ?? "").toLowerCase();
|
|
3722
4462
|
sawStatus = true;
|
|
4463
|
+
if (checkout?.duplicate_of) {
|
|
4464
|
+
outcome = "duplicate";
|
|
4465
|
+
break;
|
|
4466
|
+
}
|
|
3723
4467
|
if (_VioCheckout.FAILED_STATUSES.has(status)) {
|
|
3724
4468
|
outcome = "failed";
|
|
3725
4469
|
break;
|
|
3726
4470
|
}
|
|
3727
|
-
if (_VioCheckout.PAID_STATUSES.has(status) ||
|
|
4471
|
+
if (_VioCheckout.PAID_STATUSES.has(status) || // A Stripe checkout carries `origin_payment_id` from the
|
|
4472
|
+
// moment the link or the intent is CREATED (shopcart), so for
|
|
4473
|
+
// Stripe that pointer says nothing about who paid. Only the
|
|
4474
|
+
// status does, and it is the webhook that sets it.
|
|
4475
|
+
vioMethod !== "stripe" && Boolean(checkout?.origin_payment_id)) {
|
|
3728
4476
|
outcome = "paid";
|
|
3729
4477
|
break;
|
|
3730
4478
|
}
|
|
@@ -3734,7 +4482,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3734
4482
|
}
|
|
3735
4483
|
}
|
|
3736
4484
|
}
|
|
3737
|
-
this.
|
|
4485
|
+
this.verifyingReturn = null;
|
|
3738
4486
|
}
|
|
3739
4487
|
this.applyReturnOutcome(outcome, sawStatus, vioMethod, sponsorId, checkoutId);
|
|
3740
4488
|
this.cleanReturnQueryParams(["vio_payment", "vio_method", "vio_sponsor", "checkout_id"]);
|
|
@@ -3773,17 +4521,45 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3773
4521
|
Vio.checkout.removeEventListener("change", this.boundOnCheckoutChange);
|
|
3774
4522
|
Vio.checkout.removeEventListener("payment-complete", this.boundOnPaymentComplete);
|
|
3775
4523
|
Vio.checkout.removeEventListener("payment-error", this.boundOnPaymentError);
|
|
4524
|
+
Vio.checkout.removeEventListener("qliro-event", this.boundOnQliroEvent);
|
|
3776
4525
|
this.unmountKlarna();
|
|
4526
|
+
this.unmountNexi();
|
|
4527
|
+
this.unmountAdyen();
|
|
3777
4528
|
super.disconnectedCallback();
|
|
3778
4529
|
}
|
|
3779
4530
|
updated(changed) {
|
|
4531
|
+
if (changed.has("open")) this.dialogFocus.update(this.open);
|
|
3780
4532
|
if (changed.has("open") && this.open && this.items.length > 0 && this.availableShippingsList.length === 0) {
|
|
3781
4533
|
void this.loadAvailableShippings();
|
|
3782
4534
|
}
|
|
4535
|
+
if (changed.has("open") && this.open) {
|
|
4536
|
+
this.autoSelectSoleMethod();
|
|
4537
|
+
}
|
|
4538
|
+
if ((changed.has("klarnaAvailable") && this.klarnaAvailable || changed.has("applePayAvailable") && this.applePayAvailable) && this.paymentMethodsResolved && !this.checkoutState?.paymentMethod) {
|
|
4539
|
+
this.autoSelectAttempted = false;
|
|
4540
|
+
this.autoSelectSoleMethod();
|
|
4541
|
+
}
|
|
4542
|
+
if (changed.has("shippingBlocked") && this.shippingBlocked) {
|
|
4543
|
+
this.unmountKlarna();
|
|
4544
|
+
this.unmountKustom();
|
|
4545
|
+
this.unmountQliro();
|
|
4546
|
+
this.unmountWalley();
|
|
4547
|
+
this.unmountNexi();
|
|
4548
|
+
this.unmountAdyen();
|
|
4549
|
+
this.unmountStripe();
|
|
4550
|
+
this.paymentError = null;
|
|
4551
|
+
}
|
|
4552
|
+
if (!this.isConnected) return;
|
|
3783
4553
|
void this.mountKlarnaIfNeeded();
|
|
3784
4554
|
void this.mountKustomIfNeeded();
|
|
4555
|
+
this.syncKustomIfStale();
|
|
3785
4556
|
void this.mountQliroIfNeeded();
|
|
3786
4557
|
void this.mountWalleyIfNeeded();
|
|
4558
|
+
void this.mountNexiIfNeeded();
|
|
4559
|
+
this.dropStaleAdyen();
|
|
4560
|
+
void this.mountAdyenIfNeeded();
|
|
4561
|
+
this.dropStaleStripe();
|
|
4562
|
+
void this.mountStripeIfNeeded();
|
|
3787
4563
|
}
|
|
3788
4564
|
close() {
|
|
3789
4565
|
this.open = false;
|
|
@@ -3796,17 +4572,30 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3796
4572
|
void this.loadAvailableShippings();
|
|
3797
4573
|
}
|
|
3798
4574
|
}
|
|
3799
|
-
/**
|
|
4575
|
+
/**
|
|
4576
|
+
* Fetch the real per-supplier shippings and preselect + persist the first.
|
|
4577
|
+
*
|
|
4578
|
+
* Also decides whether the cart can ship at all (`shippingBlocked`), and
|
|
4579
|
+
* records which opening the answer belongs to (`shippingsReadyFor`).
|
|
4580
|
+
*/
|
|
3800
4581
|
async loadAvailableShippings() {
|
|
3801
4582
|
const spId = this.checkoutState?.sponsorId;
|
|
3802
4583
|
if (!spId) return;
|
|
3803
4584
|
if (this.items.length === 0) return;
|
|
3804
4585
|
if (this.loadingShippings) return;
|
|
4586
|
+
const session = this.embedSession();
|
|
3805
4587
|
this.loadingShippings = true;
|
|
3806
4588
|
this.shippingsAttempted = true;
|
|
3807
4589
|
try {
|
|
3808
4590
|
const shippings = await Vio.checkout.fetchAvailableShippings(spId);
|
|
3809
|
-
if (
|
|
4591
|
+
if (session !== this.embedSession()) return;
|
|
4592
|
+
if (!Array.isArray(shippings) || shippings.length === 0) {
|
|
4593
|
+
if (Vio.checkout.lastShippingsResolved) {
|
|
4594
|
+
this.availableShippingsList = [];
|
|
4595
|
+
this.shippingBlocked = this.someLineShipsOnItsOwn();
|
|
4596
|
+
}
|
|
4597
|
+
} else {
|
|
4598
|
+
this.shippingBlocked = false;
|
|
3810
4599
|
this.availableShippingsList = shippings;
|
|
3811
4600
|
if (!this.selectedShipping || !shippings.some((s) => s.id === this.selectedShipping)) {
|
|
3812
4601
|
const firstOpt = shippings[0];
|
|
@@ -3825,8 +4614,24 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3825
4614
|
}
|
|
3826
4615
|
} finally {
|
|
3827
4616
|
this.loadingShippings = false;
|
|
4617
|
+
if (session === this.embedSession()) {
|
|
4618
|
+
this.shippingsReadyFor = session;
|
|
4619
|
+
} else if (this.checkoutState && this.items.length > 0) {
|
|
4620
|
+
void this.loadAvailableShippings();
|
|
4621
|
+
}
|
|
3828
4622
|
}
|
|
3829
4623
|
}
|
|
4624
|
+
/**
|
|
4625
|
+
* Whether some product in the cart has shipping rates of its own. With no
|
|
4626
|
+
* rate shared by all of them, that means they cannot ship TOGETHER. A cart
|
|
4627
|
+
* where no line has rates is left alone: digital products have none, and
|
|
4628
|
+
* shopcart refuses the physical case on its side.
|
|
4629
|
+
*/
|
|
4630
|
+
someLineShipsOnItsOwn() {
|
|
4631
|
+
return this.items.some(
|
|
4632
|
+
(i) => Array.isArray(i.availableShippings) && i.availableShippings.length > 0
|
|
4633
|
+
);
|
|
4634
|
+
}
|
|
3830
4635
|
/** Whether all mandatory Leveringsadresse fields are filled. */
|
|
3831
4636
|
get isAddressValid() {
|
|
3832
4637
|
const f = this.form;
|
|
@@ -3848,11 +4653,13 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3848
4653
|
if (this.loadingPaymentMethods) return;
|
|
3849
4654
|
this.loadingPaymentMethods = true;
|
|
3850
4655
|
try {
|
|
4656
|
+
const spId = this.checkoutState.sponsorId;
|
|
3851
4657
|
const methods = await Vio.checkout.getAvailablePaymentMethods(
|
|
3852
|
-
|
|
4658
|
+
spId
|
|
3853
4659
|
);
|
|
3854
4660
|
if (Array.isArray(methods)) {
|
|
3855
4661
|
this.availableMethods = methods.map((m) => m.name);
|
|
4662
|
+
this.stripeMode = await Vio.checkout.getStripeMode(spId);
|
|
3856
4663
|
} else {
|
|
3857
4664
|
this.availableMethods = null;
|
|
3858
4665
|
}
|
|
@@ -3863,13 +4670,70 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3863
4670
|
this.availableMethods = null;
|
|
3864
4671
|
} finally {
|
|
3865
4672
|
this.loadingPaymentMethods = false;
|
|
4673
|
+
this.paymentMethodsResolved = true;
|
|
4674
|
+
this.autoSelectSoleMethod();
|
|
3866
4675
|
}
|
|
3867
4676
|
}
|
|
4677
|
+
/**
|
|
4678
|
+
* With exactly one payment method there is no choice to make, so making the
|
|
4679
|
+
* shopper click it is a step that can only have one outcome. Select it —
|
|
4680
|
+
* and since the checkout asks for the method first (2026-09-22), this is
|
|
4681
|
+
* what skips that step: the form, or the provider's widget, comes straight
|
|
4682
|
+
* up.
|
|
4683
|
+
*
|
|
4684
|
+
* Deliberately narrow:
|
|
4685
|
+
* - never overrides a method the shopper already picked;
|
|
4686
|
+
* - only for methods usable RIGHT NOW — Apple Pay and Klarna depend on the
|
|
4687
|
+
* browser and on their script loading, and auto-selecting an unusable one
|
|
4688
|
+
* would strand the shopper on a panel that cannot pay (see `updated`: a
|
|
4689
|
+
* late "available" gets one more try);
|
|
4690
|
+
* - selecting never pays: Stripe and Vipps still wait for their own button.
|
|
4691
|
+
*/
|
|
4692
|
+
autoSelectSoleMethod() {
|
|
4693
|
+
if (!this.open) return;
|
|
4694
|
+
if (!this.paymentMethodsResolved) return;
|
|
4695
|
+
if (this.autoSelectAttempted) return;
|
|
4696
|
+
this.autoSelectAttempted = true;
|
|
4697
|
+
if (this.checkoutState?.paymentMethod) return;
|
|
4698
|
+
const methods = this.offeredMethods();
|
|
4699
|
+
if (!Array.isArray(methods) || methods.length !== 1) return;
|
|
4700
|
+
const id = String(methods[0] ?? "").toLowerCase().replace(/[^a-z]/g, "");
|
|
4701
|
+
const usable = {
|
|
4702
|
+
kustom: "kustom",
|
|
4703
|
+
qliro: "qliro",
|
|
4704
|
+
walley: "walley",
|
|
4705
|
+
nexi: "nexi",
|
|
4706
|
+
// Selected, not mounted: Adyen waits for the form (see adyenRequested).
|
|
4707
|
+
adyen: "adyen",
|
|
4708
|
+
vipps: "vipps",
|
|
4709
|
+
stripe: "stripe",
|
|
4710
|
+
klarna: this.klarnaAvailable ? "klarna" : void 0,
|
|
4711
|
+
applepay: this.applePayAvailable ? "apple-pay" : void 0
|
|
4712
|
+
};
|
|
4713
|
+
const only = usable[id];
|
|
4714
|
+
if (!only) return;
|
|
4715
|
+
Vio.checkout.selectPaymentMethod(only);
|
|
4716
|
+
}
|
|
3868
4717
|
/** Backend names arrive as e.g. "Apple Pay" — compare letters only. */
|
|
3869
|
-
methodEnabled(
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
4718
|
+
methodEnabled(...names) {
|
|
4719
|
+
return isMethodEnabled(this.availableMethods, ...names);
|
|
4720
|
+
}
|
|
4721
|
+
/**
|
|
4722
|
+
* Stripe's Payment Element shows the wallets of the account that charges —
|
|
4723
|
+
* Apple Pay and Google Pay among them. Offering our own Apple Pay button
|
|
4724
|
+
* next to it is the same wallet twice, on the same screen, paying through
|
|
4725
|
+
* the same account: when Stripe pays on our page, Stripe owns the wallets
|
|
4726
|
+
* (Angelo, 2026-09-24). The cart's express button is another surface and
|
|
4727
|
+
* keeps its own Apple Pay — the Element does not exist there.
|
|
4728
|
+
*/
|
|
4729
|
+
stripeOwnsWallets() {
|
|
4730
|
+
return this.stripeMode === "native" && this.methodEnabled("stripe");
|
|
4731
|
+
}
|
|
4732
|
+
/** The methods the shopper actually sees (see stripeOwnsWallets). */
|
|
4733
|
+
offeredMethods() {
|
|
4734
|
+
const methods = this.availableMethods;
|
|
4735
|
+
if (!Array.isArray(methods) || !this.stripeOwnsWallets()) return methods;
|
|
4736
|
+
return methods.filter((m) => !isMethodEnabled([m], "apple-pay"));
|
|
3873
4737
|
}
|
|
3874
4738
|
async refreshApplePay() {
|
|
3875
4739
|
Vio.checkout.clearApplePayCache();
|
|
@@ -3886,10 +4750,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3886
4750
|
async mountKlarnaIfNeeded() {
|
|
3887
4751
|
if (!this.klarnaAvailable || !this.open || !this.checkoutState) return;
|
|
3888
4752
|
if (this.checkoutState.paymentMethod !== "klarna" && !this.express) return;
|
|
4753
|
+
if (!this.express && (!this.isAddressValid || !this.hasSelectedShipping)) return;
|
|
3889
4754
|
if (this.klarnaMounting) return;
|
|
3890
4755
|
if (this.items.length > 0 && this.availableShippingsList.length === 0 && !this.shippingsAttempted) {
|
|
3891
4756
|
await this.loadAvailableShippings();
|
|
3892
4757
|
}
|
|
4758
|
+
if (this.shippingBlocked) return;
|
|
3893
4759
|
const amount = this.checkoutState.subtotal;
|
|
3894
4760
|
if (this.klarnaHandle && this.klarnaMountedAmount === amount && this.klarnaMountedShipping === this.selectedShipping) {
|
|
3895
4761
|
return;
|
|
@@ -3921,7 +4787,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3921
4787
|
if (typeof console !== "undefined") {
|
|
3922
4788
|
console.warn("[VioCheckout] Klarna Payments mount failed:", err);
|
|
3923
4789
|
}
|
|
3924
|
-
this.paymentError =
|
|
4790
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
3925
4791
|
} finally {
|
|
3926
4792
|
this.klarnaMounting = false;
|
|
3927
4793
|
}
|
|
@@ -3933,7 +4799,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3933
4799
|
}
|
|
3934
4800
|
this.klarnaMountedAmount = null;
|
|
3935
4801
|
this.klarnaMountedShipping = null;
|
|
3936
|
-
this.klarnaCategories = [];
|
|
4802
|
+
if (this.klarnaCategories.length > 0) this.klarnaCategories = [];
|
|
3937
4803
|
this.klarnaSelectedCat = "";
|
|
3938
4804
|
this.klarnaAuthorizing = false;
|
|
3939
4805
|
}
|
|
@@ -3982,7 +4848,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3982
4848
|
}
|
|
3983
4849
|
/** Authorize the selected Klarna method → backend creates the order. */
|
|
3984
4850
|
async onKlarnaAuthorize() {
|
|
3985
|
-
if (!this.klarnaHandle || this.klarnaAuthorizing) return;
|
|
4851
|
+
if (!this.klarnaHandle || this.klarnaAuthorizing || this.shippingBlocked) return;
|
|
3986
4852
|
this.paymentError = null;
|
|
3987
4853
|
this.paymentNotice = null;
|
|
3988
4854
|
this.klarnaAuthorizing = true;
|
|
@@ -4000,6 +4866,33 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4000
4866
|
this.form = { ...this.form, [field]: value };
|
|
4001
4867
|
Vio.checkout.setAddress(this.form);
|
|
4002
4868
|
}
|
|
4869
|
+
/** After choosing a method that needs our form, bring the form into view. */
|
|
4870
|
+
revealAddressForm() {
|
|
4871
|
+
void this.updateComplete.then(() => {
|
|
4872
|
+
const form = this.renderRoot?.querySelector(".address-section");
|
|
4873
|
+
if (!form) return;
|
|
4874
|
+
const bar = this.renderRoot?.querySelector(".topbar");
|
|
4875
|
+
form.style.scrollMarginTop = `${(bar?.offsetHeight ?? 0) + 8}px`;
|
|
4876
|
+
form.scrollIntoView?.({ behavior: "smooth", block: "start" });
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4879
|
+
/**
|
|
4880
|
+
* Vipps from the checkout: Express when the channel allows it, so the
|
|
4881
|
+
* shopper picks the delivery inside the app — our rate on the cart is the
|
|
4882
|
+
* one preselected there — and never silently keeps the default our form
|
|
4883
|
+
* chose for them. When the cart cannot have Express (several suppliers)
|
|
4884
|
+
* the backend says so without creating a payment, and the plain payment
|
|
4885
|
+
* follows: profile shared, the rate already on the cart.
|
|
4886
|
+
*/
|
|
4887
|
+
async startVippsFromCheckout() {
|
|
4888
|
+
const sponsorId = this.checkoutState?.sponsorId;
|
|
4889
|
+
const express = await Vio.checkout.getVippsExpressEnabled(sponsorId);
|
|
4890
|
+
if (express) {
|
|
4891
|
+
const res = await Vio.checkout.startVippsPayment(sponsorId, this.form, { express: true });
|
|
4892
|
+
if (res?.payment_url) return;
|
|
4893
|
+
}
|
|
4894
|
+
await Vio.checkout.startVippsPayment(sponsorId, this.form);
|
|
4895
|
+
}
|
|
4003
4896
|
async onApplePay() {
|
|
4004
4897
|
if (this.applePayInProgress) return;
|
|
4005
4898
|
this.applePayInProgress = true;
|
|
@@ -4014,6 +4907,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4014
4907
|
}
|
|
4015
4908
|
}
|
|
4016
4909
|
async onPay(method) {
|
|
4910
|
+
if (this.shippingBlocked) return;
|
|
4017
4911
|
this.paymentError = null;
|
|
4018
4912
|
this.paymentNotice = null;
|
|
4019
4913
|
if (method === "apple-pay") {
|
|
@@ -4032,24 +4926,35 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4032
4926
|
Vio.checkout.selectPaymentMethod("walley");
|
|
4033
4927
|
return;
|
|
4034
4928
|
}
|
|
4035
|
-
if (method === "
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
this.paymentError =
|
|
4929
|
+
if (method === "nexi") {
|
|
4930
|
+
Vio.checkout.selectPaymentMethod("nexi");
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
if (method !== "vipps") {
|
|
4934
|
+
const missing = !this.isAddressValid ? "Vennligst fyll ut alle feltene i leveringsadresse." : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode." : null;
|
|
4935
|
+
if (missing) {
|
|
4936
|
+
if (this.checkoutState?.paymentMethod !== method) {
|
|
4937
|
+
Vio.checkout.selectPaymentMethod(method);
|
|
4938
|
+
this.revealAddressForm();
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
this.paymentError = missing;
|
|
4048
4942
|
return;
|
|
4049
4943
|
}
|
|
4050
4944
|
}
|
|
4051
4945
|
Vio.checkout.selectPaymentMethod(method);
|
|
4946
|
+
if (method === "adyen") {
|
|
4947
|
+
this.adyenRequested = true;
|
|
4948
|
+
return;
|
|
4949
|
+
}
|
|
4052
4950
|
if (method === "stripe") {
|
|
4951
|
+
if (this.stripeMode === null) {
|
|
4952
|
+
this.stripeMode = await Vio.checkout.getStripeMode(this.checkoutState?.sponsorId);
|
|
4953
|
+
}
|
|
4954
|
+
if (this.stripeMode === "native") {
|
|
4955
|
+
this.stripeRequested = true;
|
|
4956
|
+
return;
|
|
4957
|
+
}
|
|
4053
4958
|
const selectedOpt = this.shippingList.find((s) => s.id === this.selectedShipping);
|
|
4054
4959
|
if (selectedOpt?.supplierId) {
|
|
4055
4960
|
try {
|
|
@@ -4065,15 +4970,21 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4065
4970
|
}
|
|
4066
4971
|
this.stripeLoading = true;
|
|
4067
4972
|
void Vio.checkout.startStripePayment(this.checkoutState?.sponsorId, this.form).catch((err) => {
|
|
4068
|
-
|
|
4973
|
+
if (typeof console !== "undefined") {
|
|
4974
|
+
console.warn("[VioCheckout] Stripe payment could not start:", err);
|
|
4975
|
+
}
|
|
4976
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
4069
4977
|
}).finally(() => {
|
|
4070
4978
|
this.stripeLoading = false;
|
|
4071
4979
|
});
|
|
4072
4980
|
}
|
|
4073
4981
|
if (method === "vipps") {
|
|
4074
4982
|
this.vippsLoading = true;
|
|
4075
|
-
void
|
|
4076
|
-
|
|
4983
|
+
void this.startVippsFromCheckout().catch((err) => {
|
|
4984
|
+
if (typeof console !== "undefined") {
|
|
4985
|
+
console.warn("[VioCheckout] Vipps payment could not start:", err);
|
|
4986
|
+
}
|
|
4987
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
4077
4988
|
}).finally(() => {
|
|
4078
4989
|
this.vippsLoading = false;
|
|
4079
4990
|
});
|
|
@@ -4087,6 +4998,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4087
4998
|
onCompleteOrder() {
|
|
4088
4999
|
const s = this.checkoutState;
|
|
4089
5000
|
if (!s || !s.paymentMethod) return;
|
|
5001
|
+
if (this.shippingBlocked) return;
|
|
4090
5002
|
if (s.paymentMethod !== "vipps" && !this.isAddressValid) {
|
|
4091
5003
|
this.paymentError = "Vennligst fyll ut alle feltene i leveringsadresse.";
|
|
4092
5004
|
return;
|
|
@@ -4107,13 +5019,19 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4107
5019
|
* the confirmation screen, emits `vio:payment-success`, and clears the
|
|
4108
5020
|
* paid sponsor's cart so the badge + drawer reflect the purchase.
|
|
4109
5021
|
*/
|
|
4110
|
-
confirmOrder(method, sponsorId, result) {
|
|
5022
|
+
confirmOrder(method, sponsorId, result, returnCheckoutId) {
|
|
4111
5023
|
const r = result ?? {};
|
|
5024
|
+
const shippingPrice = this.shippingMajor();
|
|
5025
|
+
const email = String(this.form.email ?? "").trim();
|
|
5026
|
+
const checkoutId = returnCheckoutId || this.checkoutState?.checkoutId;
|
|
5027
|
+
const providerOrderId = r.order?.orderId != null ? String(r.order.orderId) : "";
|
|
4112
5028
|
this.confirmedOrder = {
|
|
4113
5029
|
items: [...this.items],
|
|
4114
5030
|
currency: this.checkoutState?.currency ?? "",
|
|
4115
5031
|
total: typeof r.chargedTotal === "number" ? r.chargedTotal : this.checkoutState?.subtotal ?? 0,
|
|
4116
|
-
|
|
5032
|
+
...providerOrderId ? { providerRef: { label: `Ordre hos ${this.methodLabel(method)}`, value: providerOrderId } } : {},
|
|
5033
|
+
...shippingPrice > 0 ? { shipping: { name: this.summaryShippingName() || "Frakt", price: shippingPrice } } : {},
|
|
5034
|
+
...email ? { email } : {}
|
|
4117
5035
|
};
|
|
4118
5036
|
this.confirmedMethod = method;
|
|
4119
5037
|
this.orderConfirmed = true;
|
|
@@ -4122,10 +5040,40 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4122
5040
|
new CustomEvent("vio:payment-success", {
|
|
4123
5041
|
bubbles: true,
|
|
4124
5042
|
composed: true,
|
|
4125
|
-
|
|
5043
|
+
// `checkoutId` is Vio's own reference for the purchase — what the
|
|
5044
|
+
// redirect path already sends as `orderId`; analytics falls back to it
|
|
5045
|
+
// when the provider's result carries no order id.
|
|
5046
|
+
detail: { method, sponsorId, result, checkoutId }
|
|
4126
5047
|
})
|
|
4127
5048
|
);
|
|
4128
5049
|
Vio.cart.clearSponsorCart(sponsorId);
|
|
5050
|
+
void this.fillOrderNumber(checkoutId, sponsorId);
|
|
5051
|
+
}
|
|
5052
|
+
static {
|
|
5053
|
+
/** Waits between asks for the Vio order number: the order is often born a
|
|
5054
|
+
* moment after the payment (Vipps and Stripe through their webhook). */
|
|
5055
|
+
this.ORDER_NUMBER_DELAYS_MS = [0, 1500, 3e3, 5e3, 8e3];
|
|
5056
|
+
}
|
|
5057
|
+
/**
|
|
5058
|
+
* Puts the Vio order number (#4497) on the confirmation once the order
|
|
5059
|
+
* exists. Until then the line is simply not there — never the checkout's
|
|
5060
|
+
* uuid in its place, which is what it used to print. Stops if the shopper
|
|
5061
|
+
* closes the drawer or another confirmation takes its place.
|
|
5062
|
+
*/
|
|
5063
|
+
async fillOrderNumber(checkoutId, sponsorId) {
|
|
5064
|
+
this.confirmedCheckoutId = checkoutId || null;
|
|
5065
|
+
if (!checkoutId) return;
|
|
5066
|
+
const stillShowing = () => this.orderConfirmed && this.confirmedCheckoutId === checkoutId;
|
|
5067
|
+
for (const delayMs of _VioCheckout.ORDER_NUMBER_DELAYS_MS) {
|
|
5068
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
5069
|
+
if (!stillShowing()) return;
|
|
5070
|
+
const n = await Vio.checkout.getOrderNumber(checkoutId, sponsorId || void 0);
|
|
5071
|
+
if (!stillShowing()) return;
|
|
5072
|
+
if (n && this.confirmedOrder) {
|
|
5073
|
+
this.confirmedOrder = { ...this.confirmedOrder, orderId: String(n) };
|
|
5074
|
+
return;
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
4129
5077
|
}
|
|
4130
5078
|
methodLabel(method) {
|
|
4131
5079
|
switch (method) {
|
|
@@ -4139,6 +5087,11 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4139
5087
|
return "Qliro";
|
|
4140
5088
|
case "walley":
|
|
4141
5089
|
return "Walley";
|
|
5090
|
+
case "nexi":
|
|
5091
|
+
return "Nexi";
|
|
5092
|
+
// Adyen is not a name shoppers know; what they pick from is inside it.
|
|
5093
|
+
case "adyen":
|
|
5094
|
+
return "Kort, Vipps og flere";
|
|
4142
5095
|
case "vipps":
|
|
4143
5096
|
return "Vipps";
|
|
4144
5097
|
case "stripe":
|
|
@@ -4151,7 +5104,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4151
5104
|
}
|
|
4152
5105
|
/** True for methods that complete via a separate sheet/widget, not the CTA. */
|
|
4153
5106
|
isExpressMethod(method) {
|
|
4154
|
-
return method === "apple-pay" || method === "klarna" || method
|
|
5107
|
+
return method === "apple-pay" || method === "klarna" || isEmbeddedMethod(method ?? "") || // Adyen's Drop-in brings its own Pay button.
|
|
5108
|
+
isFormFirstWidgetMethod(method ?? "");
|
|
4155
5109
|
}
|
|
4156
5110
|
orderTotal() {
|
|
4157
5111
|
const s = this.checkoutState;
|
|
@@ -4162,11 +5116,33 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4162
5116
|
* express always has one; the normal flow has one once the backend
|
|
4163
5117
|
* shippings loaded (the choice IS persisted on the cart and charged). */
|
|
4164
5118
|
shippingMajor() {
|
|
5119
|
+
if (this.checkoutState?.paymentMethod === "qliro" && typeof this.qliroShipping?.price === "number") {
|
|
5120
|
+
return this.qliroShipping.price;
|
|
5121
|
+
}
|
|
5122
|
+
if (this.checkoutState?.paymentMethod === "nexi" && typeof this.nexiShipping?.shipping_price === "number") {
|
|
5123
|
+
return this.nexiShipping.shipping_price;
|
|
5124
|
+
}
|
|
5125
|
+
if (this.checkoutState?.paymentMethod === "kustom" && typeof this.kustomShipping?.price === "number") {
|
|
5126
|
+
return this.kustomShipping.price;
|
|
5127
|
+
}
|
|
4165
5128
|
if (!this.express && this.availableShippingsList.length === 0) return 0;
|
|
4166
5129
|
const o = this.shippingOption;
|
|
4167
5130
|
if (!o) return 0;
|
|
4168
5131
|
return o.priceMajor ?? o.price / 100;
|
|
4169
5132
|
}
|
|
5133
|
+
/** The rate the summary names — Qliro's own pick while Qliro is the method. */
|
|
5134
|
+
summaryShippingName() {
|
|
5135
|
+
if (this.checkoutState?.paymentMethod === "qliro" && this.qliroShipping) {
|
|
5136
|
+
const ref = this.qliroShipping.ref;
|
|
5137
|
+
const picked = ref ? this.availableShippingsList.find((o2) => o2.id === ref) : void 0;
|
|
5138
|
+
return picked ? picked.method || picked.name || "" : "";
|
|
5139
|
+
}
|
|
5140
|
+
if (this.checkoutState?.paymentMethod === "nexi" && this.nexiShipping) {
|
|
5141
|
+
return this.nexiShipping.shipping_name ?? "";
|
|
5142
|
+
}
|
|
5143
|
+
const o = this.shippingOption;
|
|
5144
|
+
return o ? o.method || o.name || "" : "";
|
|
5145
|
+
}
|
|
4170
5146
|
/** Total to charge — items + shipping (express). */
|
|
4171
5147
|
payTotalLabel() {
|
|
4172
5148
|
const s = this.checkoutState;
|
|
@@ -4179,7 +5155,9 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4179
5155
|
<div
|
|
4180
5156
|
class="modal ${this.express ? "as-side" : this.orderConfirmed ? "as-confirm" : ""}"
|
|
4181
5157
|
role="dialog"
|
|
5158
|
+
aria-modal="true"
|
|
4182
5159
|
aria-label=${this.heading}
|
|
5160
|
+
tabindex="-1"
|
|
4183
5161
|
>
|
|
4184
5162
|
<div class="handle" @click=${this.close} aria-hidden="true"></div>
|
|
4185
5163
|
<div class="topbar">
|
|
@@ -4188,81 +5166,280 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4188
5166
|
</div>
|
|
4189
5167
|
|
|
4190
5168
|
<div class="body">
|
|
4191
|
-
${this.orderConfirmed ? this.renderConfirmation() : this.express ? this.renderKlarnaExpress() : this.renderCheckoutBody()}
|
|
5169
|
+
${this.providerReceipt ? this.renderProviderReceipt() : this.orderConfirmed ? this.renderConfirmation() : this.verifyingReturn ? this.renderVerifying() : this.express ? this.renderKlarnaExpress() : this.renderCheckoutBody()}
|
|
4192
5170
|
</div>
|
|
4193
5171
|
</div>
|
|
4194
5172
|
`;
|
|
4195
5173
|
}
|
|
5174
|
+
/**
|
|
5175
|
+
* The provider's receipt on its own. It must not depend on the cart: the
|
|
5176
|
+
* payment step is rendered from the cart, and clearing the cart after a
|
|
5177
|
+
* purchase is what took Qliro's receipt away on 2026-09-08 and brought back
|
|
5178
|
+
* "how do you want to pay". The provider's container lives in the light DOM
|
|
5179
|
+
* and is projected here, untouched.
|
|
5180
|
+
*/
|
|
5181
|
+
renderProviderReceipt() {
|
|
5182
|
+
const slot = this.providerReceipt === "walley" ? "vio-walley" : this.providerReceipt === "kustom" ? "vio-kustom" : "vio-qliro";
|
|
5183
|
+
return html`
|
|
5184
|
+
<section class="section provider-receipt">
|
|
5185
|
+
<slot name=${slot}></slot>
|
|
5186
|
+
<button class="confirm-close" @click=${this.close}>Lukk</button>
|
|
5187
|
+
</section>
|
|
5188
|
+
`;
|
|
5189
|
+
}
|
|
5190
|
+
/**
|
|
5191
|
+
* A completed purchase whose receipt the PROVIDER shows. Same side effects as
|
|
5192
|
+
* confirmOrder — `vio:payment-success` for the host and analytics, the
|
|
5193
|
+
* sponsor's cart cleared — but the screen is the provider's receipt, not our
|
|
5194
|
+
* "Takk!": two receipts for one purchase is one too many.
|
|
5195
|
+
*/
|
|
5196
|
+
showProviderReceipt(method, sponsorId, result, returnCheckoutId) {
|
|
5197
|
+
this.providerReceipt = method;
|
|
5198
|
+
this.confirmedMethod = method;
|
|
5199
|
+
this.open = true;
|
|
5200
|
+
const checkoutId = returnCheckoutId || this.checkoutState?.checkoutId;
|
|
5201
|
+
this.dispatchEvent(
|
|
5202
|
+
new CustomEvent("vio:payment-success", {
|
|
5203
|
+
bubbles: true,
|
|
5204
|
+
composed: true,
|
|
5205
|
+
detail: { method, sponsorId, result, checkoutId }
|
|
5206
|
+
})
|
|
5207
|
+
);
|
|
5208
|
+
if (sponsorId) {
|
|
5209
|
+
try {
|
|
5210
|
+
Vio.cart.clearSponsorCart(sponsorId);
|
|
5211
|
+
} catch {
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
/** Back from the payment page, asking whether it was paid — and nothing
|
|
5216
|
+
* else on screen while we do. */
|
|
5217
|
+
renderVerifying() {
|
|
5218
|
+
return html`
|
|
5219
|
+
<section class="verifying" role="status" aria-live="polite">
|
|
5220
|
+
<span class="verifying-spinner" aria-hidden="true"></span>
|
|
5221
|
+
<p class="verifying-text">${this.verifyingReturn}</p>
|
|
5222
|
+
<p class="verifying-hint">Dette kan ta litt tid. Ikke lukk siden.</p>
|
|
5223
|
+
</section>
|
|
5224
|
+
`;
|
|
5225
|
+
}
|
|
4196
5226
|
renderConfirmation() {
|
|
4197
5227
|
const o = this.confirmedOrder;
|
|
5228
|
+
const email = o?.email || String(this.form.email ?? "").trim();
|
|
5229
|
+
const details = [];
|
|
5230
|
+
if (o?.orderId) details.push(["Ordrenummer", `#${o.orderId}`]);
|
|
5231
|
+
details.push(["Betalingsm\xE5te", o?.paidWith || this.methodLabel(this.confirmedMethod)]);
|
|
5232
|
+
if (o?.providerRef) details.push([o.providerRef.label, o.providerRef.value]);
|
|
5233
|
+
if (email) details.push(["Kvittering til", email]);
|
|
4198
5234
|
return html`
|
|
4199
5235
|
<section class="confirmation">
|
|
4200
|
-
<
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
5236
|
+
<header class="confirm-head">
|
|
5237
|
+
<div class="confirm-check" aria-hidden="true">✓</div>
|
|
5238
|
+
<div>
|
|
5239
|
+
<h3 class="confirm-title">Takk for bestillingen!</h3>
|
|
5240
|
+
<p class="confirm-text">
|
|
5241
|
+
Betalingen er bekreftet.${email ? html` Du får en ordrebekreftelse på e-post.` : ""}
|
|
5242
|
+
</p>
|
|
5243
|
+
</div>
|
|
5244
|
+
</header>
|
|
5245
|
+
|
|
5246
|
+
<dl class="confirm-details">
|
|
5247
|
+
${details.map(
|
|
5248
|
+
([k, v]) => html`<div><dt>${k}</dt><dd class=${k === o?.providerRef?.label ? "ref" : ""}>${v}</dd></div>`
|
|
5249
|
+
)}
|
|
5250
|
+
</dl>
|
|
5251
|
+
|
|
4206
5252
|
${o && o.items.length > 0 ? html`
|
|
4207
5253
|
<div class="confirm-summary">
|
|
5254
|
+
<div class="confirm-label">Din bestilling</div>
|
|
4208
5255
|
${o.items.map(
|
|
4209
5256
|
(it) => html`
|
|
4210
|
-
<div class="
|
|
4211
|
-
|
|
4212
|
-
<span
|
|
5257
|
+
<div class="confirm-line">
|
|
5258
|
+
${it.imageUrl ? html`<img class="confirm-thumb" src=${it.imageUrl} alt="" loading="lazy" />` : html`<span class="confirm-thumb" aria-hidden="true"></span>`}
|
|
5259
|
+
<span class="confirm-line-name">
|
|
5260
|
+
${it.brand ? html`<small>${it.brand}</small>` : ""}
|
|
5261
|
+
<span>${it.name}</span>
|
|
5262
|
+
<small>Antall: ${it.quantity}</small>
|
|
5263
|
+
</span>
|
|
5264
|
+
<span class="confirm-line-price">
|
|
5265
|
+
${formatPrice(it.unitPrice * it.quantity, it.currency)}
|
|
5266
|
+
</span>
|
|
4213
5267
|
</div>
|
|
4214
5268
|
`
|
|
4215
5269
|
)}
|
|
5270
|
+
${o.shipping ? html`
|
|
5271
|
+
<div class="order-row">
|
|
5272
|
+
<span>Frakt – ${o.shipping.name}</span>
|
|
5273
|
+
<span>${formatPrice(o.shipping.price, o.currency)}</span>
|
|
5274
|
+
</div>
|
|
5275
|
+
` : ""}
|
|
4216
5276
|
<div class="order-row total">
|
|
4217
5277
|
<span>Totalt</span><span>${formatPrice(o.total, o.currency)}</span>
|
|
4218
5278
|
</div>
|
|
4219
5279
|
</div>
|
|
4220
5280
|
` : ""}
|
|
4221
|
-
<button class="confirm-close" @click=${this.close}>
|
|
5281
|
+
<button class="confirm-close" @click=${this.close}>Fortsett å handle</button>
|
|
4222
5282
|
</section>
|
|
4223
5283
|
`;
|
|
4224
5284
|
}
|
|
5285
|
+
/**
|
|
5286
|
+
* Whether mounting an embedded checkout now would be starting a payment the
|
|
5287
|
+
* shopper asked for. Mounting CREATES an order at the provider (and a backend
|
|
5288
|
+
* checkout if there is none), so it must not happen on a confirmation return,
|
|
5289
|
+
* once this checkout's order is confirmed, or for a cart that cannot ship.
|
|
5290
|
+
*/
|
|
5291
|
+
mayStartEmbeddedPayment() {
|
|
5292
|
+
return !this.returningFrom && !this.orderConfirmed && !this.providerReceipt && !this.shippingBlocked && this.shippingsSettled();
|
|
5293
|
+
}
|
|
5294
|
+
/**
|
|
5295
|
+
* Whether we know enough about shipping to create an order. The provider
|
|
5296
|
+
* prices the order when it is created, so a first opening waits for the
|
|
5297
|
+
* shippings to answer: the cart may turn out unable to ship at all. A list
|
|
5298
|
+
* already on screen counts, and a later answer that blocks the cart takes
|
|
5299
|
+
* the widget down again (see `updated`).
|
|
5300
|
+
*/
|
|
5301
|
+
shippingsSettled() {
|
|
5302
|
+
return this.items.length === 0 || this.availableShippingsList.length > 0 || this.shippingsReadyFor === this.embedSession();
|
|
5303
|
+
}
|
|
5304
|
+
/** Why nothing can be paid: the cart's products cannot ship together. */
|
|
5305
|
+
renderShippingBlocked() {
|
|
5306
|
+
return html`<div class="payment-error" role="alert">${_VioCheckout.SHIPPING_BLOCKED_MESSAGE}</div>`;
|
|
5307
|
+
}
|
|
5308
|
+
/**
|
|
5309
|
+
* The checkout an embedded widget's order belongs to. Kustom, Qliro and
|
|
5310
|
+
* Walley price the order when it is created, so a widget kept into a NEW
|
|
5311
|
+
* checkout shows — and charges — the cart it was created for.
|
|
5312
|
+
*
|
|
5313
|
+
* Closing drops the widgets, but a checkout can start again without a
|
|
5314
|
+
* close: in Vev the cart view opens on top of the checkout and "Til kassen"
|
|
5315
|
+
* calls `Vio.checkout.open()` again (Angelo, 2026-09-14: quantities raised
|
|
5316
|
+
* in the cart view, Qliro kept the old total). Every `open()` is a new
|
|
5317
|
+
* session, so a widget from an earlier one starts over.
|
|
5318
|
+
*
|
|
5319
|
+
* 0.11.3 compared the cart LINES instead, and that was wrong: within one
|
|
5320
|
+
* session the backend rewrites them (saving the shipping re-reads the cart),
|
|
5321
|
+
* so the first Qliro event after that — the customer picking a shipping —
|
|
5322
|
+
* tore the widget down mid-purchase (Alan, 2026-09-14). The session does
|
|
5323
|
+
* not change under the customer's hands.
|
|
5324
|
+
*/
|
|
5325
|
+
embedSession() {
|
|
5326
|
+
const s = this.checkoutState;
|
|
5327
|
+
return `${s?.sponsorId ?? ""}|${s?.session ?? ""}`;
|
|
5328
|
+
}
|
|
4225
5329
|
/** Klarna Payments widget panel: shipping + category chips + widget + pay button. */
|
|
5330
|
+
/**
|
|
5331
|
+
* The cart a Kustom order describes. When it changes under a mounted
|
|
5332
|
+
* widget, the order is UPDATED in place (Kustom's rule) — see
|
|
5333
|
+
* syncKustomIfStale. The order is a photo of the lines, not of the form:
|
|
5334
|
+
* address and shipping live inside the widget.
|
|
5335
|
+
*/
|
|
5336
|
+
kustomCartKey() {
|
|
5337
|
+
return JSON.stringify([
|
|
5338
|
+
this.items.map((i) => [i.productId, i.variantId ?? "", i.quantity]),
|
|
5339
|
+
this.checkoutState?.subtotal ?? 0
|
|
5340
|
+
]);
|
|
5341
|
+
}
|
|
4226
5342
|
/** Mount the Kustom embedded checkout once it's the chosen method. */
|
|
4227
5343
|
async mountKustomIfNeeded() {
|
|
4228
5344
|
if (!this.open || !this.checkoutState) return;
|
|
4229
5345
|
if (this.checkoutState.paymentMethod !== "kustom") return;
|
|
4230
|
-
if (this.
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
5346
|
+
if (!this.mayStartEmbeddedPayment()) return;
|
|
5347
|
+
if (this.kustomMounting || this.kustomSyncing) return;
|
|
5348
|
+
if (this.kustomMountedOrderId) {
|
|
5349
|
+
if (this.kustomMountedFor === this.embedSession()) return;
|
|
5350
|
+
this.unmountKustom();
|
|
5351
|
+
}
|
|
5352
|
+
const container = this.lightContainer("vio-kustom-checkout-container", "vio-kustom");
|
|
5353
|
+
const mountedFor = this.embedSession();
|
|
5354
|
+
const mountedKey = this.kustomCartKey();
|
|
4236
5355
|
this.kustomMounting = true;
|
|
5356
|
+
this.kustomNotice = null;
|
|
5357
|
+
this.paymentError = null;
|
|
4237
5358
|
container.innerHTML = "";
|
|
4238
5359
|
try {
|
|
4239
5360
|
const order = await Vio.checkout.mountKustomCheckout(
|
|
4240
5361
|
container,
|
|
4241
|
-
this.checkoutState.sponsorId
|
|
5362
|
+
this.checkoutState.sponsorId,
|
|
5363
|
+
this.kustomHandlers()
|
|
4242
5364
|
);
|
|
4243
5365
|
this.kustomMountedOrderId = order.order_id;
|
|
5366
|
+
this.kustomMountedFor = mountedFor;
|
|
5367
|
+
this.kustomMountedKey = mountedKey;
|
|
5368
|
+
this.kustomShipping = order.shipping_name !== void 0 || order.shipping_price !== void 0 ? { name: order.shipping_name, price: order.shipping_price } : null;
|
|
5369
|
+
this.kustomTotal = typeof order.total_price === "number" ? order.total_price : null;
|
|
4244
5370
|
} catch (err) {
|
|
4245
5371
|
if (typeof console !== "undefined") {
|
|
4246
5372
|
console.warn("[VioCheckout] Kustom mount failed:", err);
|
|
4247
5373
|
}
|
|
4248
|
-
this.paymentError =
|
|
5374
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
4249
5375
|
Vio.checkout.selectPaymentMethod("");
|
|
4250
5376
|
} finally {
|
|
4251
5377
|
this.kustomMounting = false;
|
|
4252
5378
|
}
|
|
4253
5379
|
}
|
|
5380
|
+
/** What the widget tells us, kept only to show — never acted on server-side. */
|
|
5381
|
+
kustomHandlers() {
|
|
5382
|
+
return {
|
|
5383
|
+
onOrderTotalChange: (minor) => {
|
|
5384
|
+
this.kustomTotal = minor / 100;
|
|
5385
|
+
},
|
|
5386
|
+
onShippingOptionChange: (o) => {
|
|
5387
|
+
this.kustomShipping = {
|
|
5388
|
+
name: o?.name,
|
|
5389
|
+
price: typeof o?.price === "number" ? o.price / 100 : void 0
|
|
5390
|
+
};
|
|
5391
|
+
},
|
|
5392
|
+
onCannotComplete: () => {
|
|
5393
|
+
this.kustomNotice = "Betalingen kunne ikke gjennomf\xF8res med valgt metode. Velg en annen betalingsm\xE5te.";
|
|
5394
|
+
},
|
|
5395
|
+
onNetworkError: () => {
|
|
5396
|
+
this.kustomNotice = "Mistet kontakt med betalingsvinduet. Last inn siden p\xE5 nytt.";
|
|
5397
|
+
}
|
|
5398
|
+
};
|
|
5399
|
+
}
|
|
5400
|
+
/**
|
|
5401
|
+
* The cart changed under a mounted Kustom widget: suspend it, push the
|
|
5402
|
+
* cart into the same order, resume it. Never a second order, never a
|
|
5403
|
+
* widget showing a total the backend will not charge (the validation
|
|
5404
|
+
* callback would refuse it). A failed sync takes the widget down.
|
|
5405
|
+
*/
|
|
5406
|
+
syncKustomIfStale() {
|
|
5407
|
+
if (!this.kustomMountedOrderId || this.kustomMounting || this.kustomSyncing) return;
|
|
5408
|
+
if (this.providerReceipt || this.returningFrom) return;
|
|
5409
|
+
const key = this.kustomCartKey();
|
|
5410
|
+
if (key === this.kustomMountedKey) return;
|
|
5411
|
+
this.kustomSyncing = true;
|
|
5412
|
+
this.kustomMountedKey = key;
|
|
5413
|
+
void Vio.checkout.syncKustomOrder(this.checkoutState?.sponsorId).then((order) => {
|
|
5414
|
+
if (!order) throw new Error("no order");
|
|
5415
|
+
this.kustomMountedOrderId = order.order_id;
|
|
5416
|
+
this.kustomTotal = typeof order.total_price === "number" ? order.total_price : this.kustomTotal;
|
|
5417
|
+
}).catch((err) => {
|
|
5418
|
+
if (typeof console !== "undefined") {
|
|
5419
|
+
console.warn("[VioCheckout] Kustom sync failed:", err);
|
|
5420
|
+
}
|
|
5421
|
+
this.unmountKustom();
|
|
5422
|
+
this.kustomNotice = "Bestillingen ble endret. Last inn betalingen p\xE5 nytt.";
|
|
5423
|
+
}).finally(() => {
|
|
5424
|
+
this.kustomSyncing = false;
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
4254
5427
|
unmountKustom() {
|
|
4255
5428
|
this.kustomMountedOrderId = null;
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
5429
|
+
this.kustomMountedFor = null;
|
|
5430
|
+
this.kustomMountedKey = null;
|
|
5431
|
+
this.kustomShipping = null;
|
|
5432
|
+
this.kustomTotal = null;
|
|
5433
|
+
Vio.checkout.destroyKustomListeners();
|
|
5434
|
+
this.querySelector("#vio-kustom-checkout-container")?.remove();
|
|
4260
5435
|
}
|
|
4261
5436
|
renderKustomPanel() {
|
|
4262
5437
|
return html`
|
|
4263
5438
|
<div class="kustom-panel">
|
|
4264
5439
|
${this.kustomMounting ? html`<div style="font-size:13px;opacity:0.7;padding:8px 0;">Laster Kustom…</div>` : ""}
|
|
4265
|
-
|
|
5440
|
+
${this.kustomSyncing ? html`<div style="font-size:13px;opacity:0.7;padding:8px 0;">Oppdaterer bestillingen…</div>` : ""}
|
|
5441
|
+
${this.kustomNotice ? html`<div class="payment-notice">${this.kustomNotice}</div>` : ""}
|
|
5442
|
+
<slot name="vio-kustom"></slot>
|
|
4266
5443
|
</div>
|
|
4267
5444
|
`;
|
|
4268
5445
|
}
|
|
@@ -4292,9 +5469,15 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4292
5469
|
async mountQliroIfNeeded() {
|
|
4293
5470
|
if (!this.open || !this.checkoutState) return;
|
|
4294
5471
|
if (this.checkoutState.paymentMethod !== "qliro") return;
|
|
5472
|
+
if (!this.mayStartEmbeddedPayment()) return;
|
|
4295
5473
|
if (this.qliroMounting) return;
|
|
4296
|
-
|
|
4297
|
-
if (this.qliroMountedOrderId && container.childElementCount > 0)
|
|
5474
|
+
let container = this.lightContainer("vio-qliro-checkout-container", "vio-qliro");
|
|
5475
|
+
if (this.qliroMountedOrderId && container.childElementCount > 0) {
|
|
5476
|
+
if (this.qliroMountedFor === this.embedSession()) return;
|
|
5477
|
+
this.unmountQliro();
|
|
5478
|
+
container = this.lightContainer("vio-qliro-checkout-container", "vio-qliro");
|
|
5479
|
+
}
|
|
5480
|
+
const mountedFor = this.embedSession();
|
|
4298
5481
|
this.qliroMounting = true;
|
|
4299
5482
|
container.innerHTML = "";
|
|
4300
5483
|
try {
|
|
@@ -4303,11 +5486,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4303
5486
|
this.checkoutState.sponsorId
|
|
4304
5487
|
);
|
|
4305
5488
|
this.qliroMountedOrderId = order.order_id;
|
|
5489
|
+
this.qliroMountedFor = mountedFor;
|
|
4306
5490
|
} catch (err) {
|
|
4307
5491
|
if (typeof console !== "undefined") {
|
|
4308
5492
|
console.warn("[VioCheckout] Qliro mount failed:", err);
|
|
4309
5493
|
}
|
|
4310
|
-
this.paymentError =
|
|
5494
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
4311
5495
|
Vio.checkout.selectPaymentMethod("");
|
|
4312
5496
|
} finally {
|
|
4313
5497
|
this.qliroMounting = false;
|
|
@@ -4315,6 +5499,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4315
5499
|
}
|
|
4316
5500
|
unmountQliro() {
|
|
4317
5501
|
this.qliroMountedOrderId = null;
|
|
5502
|
+
this.qliroMountedFor = null;
|
|
5503
|
+
this.qliroShipping = null;
|
|
4318
5504
|
Vio.checkout.destroyQliroListeners();
|
|
4319
5505
|
this.querySelector("#vio-qliro-checkout-container")?.remove();
|
|
4320
5506
|
}
|
|
@@ -4335,9 +5521,15 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4335
5521
|
async mountWalleyIfNeeded() {
|
|
4336
5522
|
if (!this.open || !this.checkoutState) return;
|
|
4337
5523
|
if (this.checkoutState.paymentMethod !== "walley") return;
|
|
5524
|
+
if (!this.mayStartEmbeddedPayment()) return;
|
|
4338
5525
|
if (this.walleyMounting) return;
|
|
4339
|
-
|
|
4340
|
-
if (this.walleyMountedOrderId && container.childElementCount > 0)
|
|
5526
|
+
let container = this.lightContainer("vio-walley-checkout-container", "vio-walley");
|
|
5527
|
+
if (this.walleyMountedOrderId && container.childElementCount > 0) {
|
|
5528
|
+
if (this.walleyMountedFor === this.embedSession()) return;
|
|
5529
|
+
this.unmountWalley();
|
|
5530
|
+
container = this.lightContainer("vio-walley-checkout-container", "vio-walley");
|
|
5531
|
+
}
|
|
5532
|
+
const mountedFor = this.embedSession();
|
|
4341
5533
|
this.walleyMounting = true;
|
|
4342
5534
|
container.innerHTML = "";
|
|
4343
5535
|
try {
|
|
@@ -4345,28 +5537,18 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4345
5537
|
container,
|
|
4346
5538
|
this.checkoutState.sponsorId,
|
|
4347
5539
|
() => {
|
|
4348
|
-
this.
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
bubbles: true,
|
|
4352
|
-
composed: true
|
|
4353
|
-
})
|
|
4354
|
-
);
|
|
4355
|
-
const spId = this.checkoutState?.sponsorId;
|
|
4356
|
-
if (spId) {
|
|
4357
|
-
try {
|
|
4358
|
-
Vio.cart.clearSponsorCart(spId);
|
|
4359
|
-
} catch {
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
5540
|
+
this.showProviderReceipt("walley", this.checkoutState?.sponsorId ?? 0, {
|
|
5541
|
+
orderId: order.order_id
|
|
5542
|
+
});
|
|
4362
5543
|
}
|
|
4363
5544
|
);
|
|
4364
5545
|
this.walleyMountedOrderId = order.order_id;
|
|
5546
|
+
this.walleyMountedFor = mountedFor;
|
|
4365
5547
|
} catch (err) {
|
|
4366
5548
|
if (typeof console !== "undefined") {
|
|
4367
5549
|
console.warn("[VioCheckout] Walley mount failed:", err);
|
|
4368
5550
|
}
|
|
4369
|
-
this.paymentError =
|
|
5551
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
4370
5552
|
Vio.checkout.selectPaymentMethod("");
|
|
4371
5553
|
} finally {
|
|
4372
5554
|
this.walleyMounting = false;
|
|
@@ -4374,6 +5556,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4374
5556
|
}
|
|
4375
5557
|
unmountWalley() {
|
|
4376
5558
|
this.walleyMountedOrderId = null;
|
|
5559
|
+
this.walleyMountedFor = null;
|
|
4377
5560
|
this.querySelector("#vio-walley-checkout-container")?.remove();
|
|
4378
5561
|
}
|
|
4379
5562
|
renderWalleyPanel() {
|
|
@@ -4384,6 +5567,503 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4384
5567
|
</div>
|
|
4385
5568
|
`;
|
|
4386
5569
|
}
|
|
5570
|
+
/**
|
|
5571
|
+
* Mount the Nexi embedded checkout once it's the chosen method. Nexi's own
|
|
5572
|
+
* JS SDK renders into a LIGHT-DOM container (it refuses a shadow root);
|
|
5573
|
+
* completion is its `payment-completed` event — Nexi neither redirects nor
|
|
5574
|
+
* shows a receipt, so success lands in Vio's confirmation drawer. Shipping
|
|
5575
|
+
* is re-priced on `address-changed` (see the manager).
|
|
5576
|
+
*/
|
|
5577
|
+
async mountNexiIfNeeded() {
|
|
5578
|
+
if (!this.open || !this.checkoutState) return;
|
|
5579
|
+
if (this.checkoutState.paymentMethod !== "nexi") return;
|
|
5580
|
+
if (!this.mayStartEmbeddedPayment()) return;
|
|
5581
|
+
if (this.nexiMounting) return;
|
|
5582
|
+
let container = this.lightContainer("vio-nexi-checkout-container", "vio-nexi");
|
|
5583
|
+
if (this.nexiMountedOrderId && container.childElementCount > 0) {
|
|
5584
|
+
if (this.nexiMountedFor === this.embedSession()) return;
|
|
5585
|
+
this.unmountNexi();
|
|
5586
|
+
container = this.lightContainer("vio-nexi-checkout-container", "vio-nexi");
|
|
5587
|
+
}
|
|
5588
|
+
const mountedFor = this.embedSession();
|
|
5589
|
+
const resumePaymentId = this.nexiResumePaymentId;
|
|
5590
|
+
this.nexiResumePaymentId = null;
|
|
5591
|
+
this.nexiMounting = true;
|
|
5592
|
+
this.nexiShippingNotice = null;
|
|
5593
|
+
container.innerHTML = "";
|
|
5594
|
+
try {
|
|
5595
|
+
const order = await Vio.checkout.mountNexiCheckout(container, this.checkoutState.sponsorId, {
|
|
5596
|
+
onCompleted: (paid) => this.onNexiCompleted(paid.order_id),
|
|
5597
|
+
onShipping: (result, err) => {
|
|
5598
|
+
const wanted = this.nexiShipping?.shipping_id;
|
|
5599
|
+
const pickedNow = this.nexiPicking;
|
|
5600
|
+
this.nexiPicking = false;
|
|
5601
|
+
if (err || !result) {
|
|
5602
|
+
this.nexiShipping = null;
|
|
5603
|
+
this.nexiShippingNotice = "Kunne ikke beregne frakt. Pr\xF8v \xE5 endre adressen.";
|
|
5604
|
+
} else if (result.reason === "AWAITING_ADDRESS") {
|
|
5605
|
+
this.nexiShipping = result;
|
|
5606
|
+
this.nexiShippingNotice = null;
|
|
5607
|
+
} else if (!result.ok) {
|
|
5608
|
+
this.nexiShipping = null;
|
|
5609
|
+
this.nexiShippingNotice = "Vi kan dessverre ikke sende til dette landet.";
|
|
5610
|
+
} else {
|
|
5611
|
+
this.nexiShipping = result;
|
|
5612
|
+
if (result.repriced_at_pay) {
|
|
5613
|
+
this.nexiShippingNotice = "Frakten er oppdatert for leveringsadressen din. Sjekk totalen og trykk \xABBetal\xBB igjen.";
|
|
5614
|
+
} else {
|
|
5615
|
+
this.nexiShippingNotice = !pickedNow && wanted && result.shipping_id !== wanted ? `Valgt frakt leveres ikke til denne adressen. Vi har valgt ${result.shipping_name ?? "en annen"}.` : null;
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
}, resumePaymentId ? { paymentId: resumePaymentId } : void 0);
|
|
5620
|
+
this.nexiMountedOrderId = order.order_id;
|
|
5621
|
+
this.nexiMountedFor = mountedFor;
|
|
5622
|
+
} catch (err) {
|
|
5623
|
+
if (typeof console !== "undefined") {
|
|
5624
|
+
console.warn("[VioCheckout] Nexi mount failed:", err);
|
|
5625
|
+
}
|
|
5626
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
5627
|
+
Vio.checkout.selectPaymentMethod("");
|
|
5628
|
+
} finally {
|
|
5629
|
+
this.nexiMounting = false;
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
/** payment-completed: the reservation exists — confirm like the redirect methods do. */
|
|
5633
|
+
onNexiCompleted(paymentId) {
|
|
5634
|
+
const spId = this.checkoutState?.sponsorId ?? 0;
|
|
5635
|
+
const checkoutId = this.checkoutState?.checkoutId ?? "";
|
|
5636
|
+
const charged = this.nexiShipping?.ok ? this.nexiShipping.total_price : void 0;
|
|
5637
|
+
this.confirmOrder("nexi", spId, { chargedTotal: charged });
|
|
5638
|
+
if (this.confirmedOrder) {
|
|
5639
|
+
this.confirmedOrder = {
|
|
5640
|
+
...this.confirmedOrder,
|
|
5641
|
+
providerRef: { label: "Nexi betalings-ID", value: paymentId }
|
|
5642
|
+
};
|
|
5643
|
+
}
|
|
5644
|
+
this.unmountNexi();
|
|
5645
|
+
if (checkoutId) void this.addNexiPaymentDetails(checkoutId, spId, paymentId);
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5648
|
+
* How it was paid and where the receipt goes — Nexi knows, we ask. Its
|
|
5649
|
+
* payment details can lag the completion event by a moment, hence the
|
|
5650
|
+
* few retries. Best effort: the confirmation stands without them.
|
|
5651
|
+
*/
|
|
5652
|
+
async addNexiPaymentDetails(checkoutId, sponsorId, paymentId) {
|
|
5653
|
+
for (const delay of _VioCheckout.NEXI_DETAILS_DELAYS_MS) {
|
|
5654
|
+
if (delay) await new Promise((r) => setTimeout(r, delay));
|
|
5655
|
+
const o = await Vio.checkout.getNexiOrder(checkoutId, sponsorId).catch(() => null);
|
|
5656
|
+
const current = this.confirmedOrder;
|
|
5657
|
+
if (!current || current.providerRef?.value !== paymentId) return;
|
|
5658
|
+
if (!o) continue;
|
|
5659
|
+
const paidWith = o.payment_method ? o.card_last4 ? `${o.payment_method} \u2022\u2022\u2022\u2022 ${o.card_last4}` : o.payment_method : void 0;
|
|
5660
|
+
this.confirmedOrder = {
|
|
5661
|
+
...current,
|
|
5662
|
+
...paidWith ? { paidWith } : {},
|
|
5663
|
+
...o.email ? { email: o.email } : {}
|
|
5664
|
+
};
|
|
5665
|
+
if (paidWith) return;
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
static {
|
|
5669
|
+
this.NEXI_DETAILS_DELAYS_MS = [0, 1500, 3e3];
|
|
5670
|
+
}
|
|
5671
|
+
unmountNexi() {
|
|
5672
|
+
this.nexiMountedOrderId = null;
|
|
5673
|
+
this.nexiMountedFor = null;
|
|
5674
|
+
this.nexiShippingNotice = null;
|
|
5675
|
+
this.nexiShipping = null;
|
|
5676
|
+
this.nexiPicking = false;
|
|
5677
|
+
Vio.checkout.destroyNexi();
|
|
5678
|
+
this.querySelector("#vio-nexi-checkout-container")?.remove();
|
|
5679
|
+
}
|
|
5680
|
+
/**
|
|
5681
|
+
* Nexi has no shipping picker: once an address is typed in the widget, the
|
|
5682
|
+
* rates that reach it are listed here and the pick is pushed to the payment.
|
|
5683
|
+
* With a single rate there is nothing to choose, so nothing is shown.
|
|
5684
|
+
*/
|
|
5685
|
+
renderNexiShippingChoice() {
|
|
5686
|
+
const options = this.nexiShipping?.options ?? [];
|
|
5687
|
+
if (options.length < 2) return "";
|
|
5688
|
+
const currency = this.checkoutState?.currency || getGlobalCurrency();
|
|
5689
|
+
const current = this.nexiShipping?.shipping_id;
|
|
5690
|
+
return html`
|
|
5691
|
+
<div class="ship-select nexi-ship" role="radiogroup" aria-label="Frakt">
|
|
5692
|
+
${options.map(
|
|
5693
|
+
(o) => html`
|
|
5694
|
+
<button
|
|
5695
|
+
class="ship-opt ${current === o.id ? "active" : ""}"
|
|
5696
|
+
role="radio"
|
|
5697
|
+
aria-checked=${current === o.id}
|
|
5698
|
+
?disabled=${this.nexiPicking}
|
|
5699
|
+
@click=${() => void this.onPickNexiShipping(o.id)}
|
|
5700
|
+
>
|
|
5701
|
+
<span class="ship-meta"><b>${o.name}</b></span>
|
|
5702
|
+
<span class="ship-price">${formatPrice(o.price, currency)}</span>
|
|
5703
|
+
</button>
|
|
5704
|
+
`
|
|
5705
|
+
)}
|
|
5706
|
+
</div>
|
|
5707
|
+
`;
|
|
5708
|
+
}
|
|
5709
|
+
async onPickNexiShipping(id) {
|
|
5710
|
+
if (this.nexiPicking || id === this.nexiShipping?.shipping_id) return;
|
|
5711
|
+
this.nexiPicking = true;
|
|
5712
|
+
try {
|
|
5713
|
+
await Vio.checkout.pickNexiShipping(id);
|
|
5714
|
+
} finally {
|
|
5715
|
+
this.nexiPicking = false;
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
/* ── Adyen: our form first, then Adyen's Drop-in on a session ─────────── */
|
|
5719
|
+
/**
|
|
5720
|
+
* What an embedded payment is a photo OF — an Adyen session, a Stripe
|
|
5721
|
+
* PaymentIntent. When any of it changes underneath, that photo no longer
|
|
5722
|
+
* describes the purchase: it is dropped, and the shopper asks for the
|
|
5723
|
+
* payment step again.
|
|
5724
|
+
*/
|
|
5725
|
+
embedPurchaseKey() {
|
|
5726
|
+
return JSON.stringify([
|
|
5727
|
+
this.embedFormKey(),
|
|
5728
|
+
this.items.map((i) => [i.productId, i.variantId ?? "", i.quantity]),
|
|
5729
|
+
this.checkoutState?.subtotal ?? 0
|
|
5730
|
+
]);
|
|
5731
|
+
}
|
|
5732
|
+
/** The part of the photo the shopper types and picks. */
|
|
5733
|
+
embedFormKey() {
|
|
5734
|
+
const f = this.form;
|
|
5735
|
+
return JSON.stringify([
|
|
5736
|
+
f.firstName,
|
|
5737
|
+
f.lastName,
|
|
5738
|
+
f.email,
|
|
5739
|
+
f.address,
|
|
5740
|
+
f.postalCode,
|
|
5741
|
+
f.city,
|
|
5742
|
+
f.country ?? "",
|
|
5743
|
+
this.selectedShipping
|
|
5744
|
+
]);
|
|
5745
|
+
}
|
|
5746
|
+
/** A mounted session whose purchase changed is taken down — never patched. */
|
|
5747
|
+
dropStaleAdyen() {
|
|
5748
|
+
if (!this.adyenMounted || this.adyenMounting) return;
|
|
5749
|
+
if (this.adyenMountedKey === this.embedPurchaseKey()) return;
|
|
5750
|
+
this.unmountAdyen();
|
|
5751
|
+
this.adyenStale = true;
|
|
5752
|
+
}
|
|
5753
|
+
async mountAdyenIfNeeded() {
|
|
5754
|
+
if (!this.open || !this.checkoutState) return;
|
|
5755
|
+
if (this.checkoutState.paymentMethod !== "adyen") return;
|
|
5756
|
+
if (!this.mayStartEmbeddedPayment() || this.adyenSettled) return;
|
|
5757
|
+
if (this.adyenMounting) return;
|
|
5758
|
+
if (this.adyenMounted) {
|
|
5759
|
+
if (this.adyenMountedFor === this.embedSession()) return;
|
|
5760
|
+
this.unmountAdyen();
|
|
5761
|
+
}
|
|
5762
|
+
if (!this.adyenRequested || !this.isAddressValid || !this.hasSelectedShipping) return;
|
|
5763
|
+
this.adyenRequested = false;
|
|
5764
|
+
this.adyenMounting = true;
|
|
5765
|
+
this.adyenStale = false;
|
|
5766
|
+
this.paymentError = null;
|
|
5767
|
+
const spId = this.checkoutState.sponsorId;
|
|
5768
|
+
const mountedFor = this.embedSession();
|
|
5769
|
+
const formKey = this.embedFormKey();
|
|
5770
|
+
try {
|
|
5771
|
+
const selectedOpt = this.shippingList.find((o) => o.id === this.selectedShipping);
|
|
5772
|
+
if (selectedOpt?.supplierId) {
|
|
5773
|
+
await Vio.checkout.updateShippingsBySupplier(
|
|
5774
|
+
[{ shipping_id: selectedOpt.id, supplier_id: Number(selectedOpt.supplierId) }],
|
|
5775
|
+
spId
|
|
5776
|
+
);
|
|
5777
|
+
}
|
|
5778
|
+
await this.updateComplete;
|
|
5779
|
+
const container = this.lightContainer("vio-adyen-checkout-container", "vio-adyen");
|
|
5780
|
+
container.innerHTML = "";
|
|
5781
|
+
await Vio.checkout.mountAdyenCheckout(container, spId, this.form, {
|
|
5782
|
+
onCompleted: (payment, session) => this.onAdyenCompleted(spId, payment, session),
|
|
5783
|
+
onFailed: () => {
|
|
5784
|
+
this.unmountAdyen();
|
|
5785
|
+
this.paymentError = "Betalingen ble ikke gjennomf\xF8rt. Pr\xF8v igjen, eller velg en annen betalingsm\xE5te.";
|
|
5786
|
+
},
|
|
5787
|
+
onReplaced: (reason) => {
|
|
5788
|
+
this.unmountAdyen();
|
|
5789
|
+
this.adyenStale = true;
|
|
5790
|
+
if (reason === "ALREADY_PAID") {
|
|
5791
|
+
this.adyenSettled = true;
|
|
5792
|
+
this.paymentNotice = "Denne bestillingen er allerede betalt.";
|
|
5793
|
+
}
|
|
5794
|
+
},
|
|
5795
|
+
onError: (error) => {
|
|
5796
|
+
if (typeof console !== "undefined") console.warn("[VioCheckout] Adyen error:", error);
|
|
5797
|
+
}
|
|
5798
|
+
});
|
|
5799
|
+
if (this.embedSession() !== mountedFor || this.checkoutState?.paymentMethod !== "adyen" || !this.open) {
|
|
5800
|
+
this.unmountAdyen();
|
|
5801
|
+
return;
|
|
5802
|
+
}
|
|
5803
|
+
if (this.embedFormKey() !== formKey) {
|
|
5804
|
+
this.unmountAdyen();
|
|
5805
|
+
this.adyenStale = true;
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
this.adyenMounted = true;
|
|
5809
|
+
this.adyenMountedFor = mountedFor;
|
|
5810
|
+
this.adyenMountedKey = this.embedPurchaseKey();
|
|
5811
|
+
} catch (err) {
|
|
5812
|
+
this.unmountAdyen();
|
|
5813
|
+
this.paymentError = err instanceof AdyenOriginError ? "Betaling er ikke aktivert for denne siden enn\xE5. Ta kontakt med butikken." : friendlyPaymentError(errorText(err));
|
|
5814
|
+
if (typeof console !== "undefined") console.warn("[VioCheckout] Adyen mount failed:", err);
|
|
5815
|
+
} finally {
|
|
5816
|
+
this.adyenMounting = false;
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
onAdyenCompleted(sponsorId, payment, session) {
|
|
5820
|
+
const outcome = adyenReturnOutcome(payment);
|
|
5821
|
+
this.unmountAdyen();
|
|
5822
|
+
if (outcome === "paid") {
|
|
5823
|
+
this.adyenSettled = true;
|
|
5824
|
+
this.confirmOrder("adyen", sponsorId, {
|
|
5825
|
+
chargedTotal: payment?.total_price ?? session.total_price
|
|
5826
|
+
});
|
|
5827
|
+
this.addAdyenPaymentDetails(payment);
|
|
5828
|
+
} else if (outcome === "pending") {
|
|
5829
|
+
this.adyenSettled = true;
|
|
5830
|
+
this.paymentNotice = "Betalingen behandles fortsatt. Du f\xE5r ordrebekreftelse p\xE5 e-post n\xE5r den er gjennomf\xF8rt. Handlekurven er uendret.";
|
|
5831
|
+
} else {
|
|
5832
|
+
this.paymentError = "Betalingen ble ikke gjennomf\xF8rt. Pr\xF8v igjen, eller velg en annen betalingsm\xE5te.";
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
/** What Adyen told us about the payment, on Vio's confirmation. */
|
|
5836
|
+
addAdyenPaymentDetails(payment) {
|
|
5837
|
+
if (!this.confirmedOrder || !payment) return;
|
|
5838
|
+
this.confirmedOrder = {
|
|
5839
|
+
...this.confirmedOrder,
|
|
5840
|
+
...payment.psp_reference ? { providerRef: { label: "Betalingsreferanse", value: payment.psp_reference } } : {},
|
|
5841
|
+
...payment.payment_method ? { paidWith: payment.payment_method } : {},
|
|
5842
|
+
...payment.email && !this.confirmedOrder.email ? { email: payment.email } : {},
|
|
5843
|
+
...payment.shipping_name && !this.confirmedOrder.shipping ? { shipping: { name: payment.shipping_name, price: payment.shipping_price ?? 0 } } : {}
|
|
5844
|
+
};
|
|
5845
|
+
}
|
|
5846
|
+
unmountAdyen() {
|
|
5847
|
+
Vio.checkout.destroyAdyen();
|
|
5848
|
+
this.adyenMounted = false;
|
|
5849
|
+
this.adyenMountedFor = null;
|
|
5850
|
+
this.adyenMountedKey = null;
|
|
5851
|
+
this.adyenRequested = false;
|
|
5852
|
+
this.adyenStale = false;
|
|
5853
|
+
this.querySelector("#vio-adyen-checkout-container")?.remove();
|
|
5854
|
+
}
|
|
5855
|
+
renderAdyenPanel() {
|
|
5856
|
+
const ready = this.isAddressValid && this.hasSelectedShipping;
|
|
5857
|
+
const showButton = !this.adyenMounted && !this.adyenMounting && !this.adyenSettled;
|
|
5858
|
+
return html`
|
|
5859
|
+
<div class="adyen-panel">
|
|
5860
|
+
${this.adyenMounting ? html`<div style="font-size:13px;opacity:0.7;padding:8px 0;">Laster betaling…</div>` : ""}
|
|
5861
|
+
${this.adyenStale && showButton ? html`<div class="payment-notice">Bestillingen ble endret. Oppdater betalingen for å fortsette.</div>` : ""}
|
|
5862
|
+
${showButton ? html`
|
|
5863
|
+
<button
|
|
5864
|
+
class="payment-btn primary complete-cta"
|
|
5865
|
+
?disabled=${!ready}
|
|
5866
|
+
title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
|
|
5867
|
+
@click=${() => {
|
|
5868
|
+
this.adyenRequested = true;
|
|
5869
|
+
}}
|
|
5870
|
+
>
|
|
5871
|
+
${this.adyenStale ? "Oppdater betaling" : `G\xE5 til betaling \xB7 ${this.payTotalLabel()}`}
|
|
5872
|
+
</button>
|
|
5873
|
+
` : ""}
|
|
5874
|
+
<slot name="vio-adyen"></slot>
|
|
5875
|
+
</div>
|
|
5876
|
+
`;
|
|
5877
|
+
}
|
|
5878
|
+
/* ── Stripe, native: the Payment Element on our own page ──────────────── */
|
|
5879
|
+
/** A mounted Element whose purchase changed is taken down — never patched. */
|
|
5880
|
+
dropStaleStripe() {
|
|
5881
|
+
if (!this.stripeMounted || this.stripeMounting) return;
|
|
5882
|
+
if (this.stripeMountedKey === this.embedPurchaseKey()) return;
|
|
5883
|
+
this.unmountStripe();
|
|
5884
|
+
this.stripeStale = true;
|
|
5885
|
+
}
|
|
5886
|
+
async mountStripeIfNeeded() {
|
|
5887
|
+
if (!this.open || !this.checkoutState) return;
|
|
5888
|
+
if (this.checkoutState.paymentMethod !== "stripe" || this.stripeMode !== "native") return;
|
|
5889
|
+
if (!this.mayStartEmbeddedPayment() || this.stripeSettled) return;
|
|
5890
|
+
if (this.stripeMounting) return;
|
|
5891
|
+
if (this.stripeMounted) {
|
|
5892
|
+
if (this.stripeMountedFor === this.embedSession()) return;
|
|
5893
|
+
this.unmountStripe();
|
|
5894
|
+
}
|
|
5895
|
+
if (!this.stripeRequested || !this.isAddressValid || !this.hasSelectedShipping) return;
|
|
5896
|
+
this.stripeRequested = false;
|
|
5897
|
+
this.stripeMounting = true;
|
|
5898
|
+
this.stripeStale = false;
|
|
5899
|
+
this.paymentError = null;
|
|
5900
|
+
const spId = this.checkoutState.sponsorId;
|
|
5901
|
+
const mountedFor = this.embedSession();
|
|
5902
|
+
const formKey = this.embedFormKey();
|
|
5903
|
+
try {
|
|
5904
|
+
const selectedOpt = this.shippingList.find((o) => o.id === this.selectedShipping);
|
|
5905
|
+
if (selectedOpt?.supplierId) {
|
|
5906
|
+
await Vio.checkout.updateShippingsBySupplier(
|
|
5907
|
+
[{ shipping_id: selectedOpt.id, supplier_id: Number(selectedOpt.supplierId) }],
|
|
5908
|
+
spId
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
await this.updateComplete;
|
|
5912
|
+
const container = this.lightContainer("vio-stripe-checkout-container", "vio-stripe");
|
|
5913
|
+
container.innerHTML = "";
|
|
5914
|
+
this.stripeReady = false;
|
|
5915
|
+
const mounted = await Vio.checkout.mountStripeCheckout(container, spId, this.form, {
|
|
5916
|
+
// `mount()` resolves before Stripe paints anything — seconds before,
|
|
5917
|
+
// on a cold cache. Its own "ready" is what turns the spinner off.
|
|
5918
|
+
onReady: () => {
|
|
5919
|
+
this.stripeReady = true;
|
|
5920
|
+
},
|
|
5921
|
+
onLoadError: (message) => {
|
|
5922
|
+
this.unmountStripe();
|
|
5923
|
+
if (typeof console !== "undefined") {
|
|
5924
|
+
console.warn("[VioCheckout] Stripe Payment Element failed to load:", message);
|
|
5925
|
+
}
|
|
5926
|
+
this.paymentError = friendlyPaymentError(message, "stripe");
|
|
5927
|
+
}
|
|
5928
|
+
});
|
|
5929
|
+
if (this.embedSession() !== mountedFor || this.checkoutState?.paymentMethod !== "stripe" || !this.open) {
|
|
5930
|
+
this.unmountStripe();
|
|
5931
|
+
return;
|
|
5932
|
+
}
|
|
5933
|
+
if (this.embedFormKey() !== formKey) {
|
|
5934
|
+
this.unmountStripe();
|
|
5935
|
+
this.stripeStale = true;
|
|
5936
|
+
return;
|
|
5937
|
+
}
|
|
5938
|
+
this.stripeElement = mounted.handle;
|
|
5939
|
+
this.stripeCheckoutId = mounted.checkoutId;
|
|
5940
|
+
this.stripeMounted = true;
|
|
5941
|
+
this.stripeMountedFor = mountedFor;
|
|
5942
|
+
this.stripeMountedKey = this.embedPurchaseKey();
|
|
5943
|
+
} catch (err) {
|
|
5944
|
+
this.unmountStripe();
|
|
5945
|
+
this.paymentError = friendlyPaymentError(errorText(err));
|
|
5946
|
+
if (typeof console !== "undefined") console.warn("[VioCheckout] Stripe mount failed:", err);
|
|
5947
|
+
} finally {
|
|
5948
|
+
this.stripeMounting = false;
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
/**
|
|
5952
|
+
* Pay with the mounted Element. Stripe answers the BROWSER; the order is
|
|
5953
|
+
* created server-side by the `payment_intent.succeeded` webhook, so a
|
|
5954
|
+
* success is then waited for on the checkout itself.
|
|
5955
|
+
*/
|
|
5956
|
+
async onStripePay() {
|
|
5957
|
+
if (!this.stripeElement || this.stripePaying) return;
|
|
5958
|
+
const spId = this.checkoutState?.sponsorId ?? 0;
|
|
5959
|
+
const checkoutId = this.stripeCheckoutId;
|
|
5960
|
+
const charged = (this.checkoutState?.subtotal ?? 0) + this.shippingMajor();
|
|
5961
|
+
this.stripePaying = true;
|
|
5962
|
+
this.paymentError = null;
|
|
5963
|
+
try {
|
|
5964
|
+
const outcome = await this.stripeElement.confirm();
|
|
5965
|
+
if (outcome.status === "failed") {
|
|
5966
|
+
this.paymentError = outcome.message || "Betalingen ble ikke gjennomf\xF8rt. Pr\xF8v igjen, eller velg en annen betalingsm\xE5te.";
|
|
5967
|
+
return;
|
|
5968
|
+
}
|
|
5969
|
+
this.stripeSettled = true;
|
|
5970
|
+
this.unmountStripe();
|
|
5971
|
+
const settled = checkoutId ? await this.waitForStripeOrder(checkoutId, spId) : "pending";
|
|
5972
|
+
if (settled === "paid") {
|
|
5973
|
+
this.confirmOrder("stripe", spId, { chargedTotal: charged });
|
|
5974
|
+
} else if (settled === "duplicate") {
|
|
5975
|
+
this.stripeSettled = false;
|
|
5976
|
+
this.paymentError = CART_PAID_MESSAGE;
|
|
5977
|
+
} else if (settled === "failed") {
|
|
5978
|
+
this.stripeSettled = false;
|
|
5979
|
+
this.paymentError = "Betalingen ble avbrutt eller feilet. Vennligst pr\xF8v igjen.";
|
|
5980
|
+
} else {
|
|
5981
|
+
this.paymentNotice = "Betalingen behandles fortsatt. Du f\xE5r ordrebekreftelse p\xE5 e-post n\xE5r den er gjennomf\xF8rt. Handlekurven er uendret.";
|
|
5982
|
+
}
|
|
5983
|
+
} catch (err) {
|
|
5984
|
+
this.paymentError = `Betalingen kunne ikke fullf\xF8res: ${err instanceof Error ? err.message : String(err)}`;
|
|
5985
|
+
if (typeof console !== "undefined") console.warn("[VioCheckout] Stripe confirm failed:", err);
|
|
5986
|
+
} finally {
|
|
5987
|
+
this.stripePaying = false;
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
/**
|
|
5991
|
+
* Wait for Stripe's webhook to make the checkout paid — the same window the
|
|
5992
|
+
* redirect return uses, and ONLY the status: a Stripe checkout carries
|
|
5993
|
+
* `origin_payment_id` from the moment the intent is created, so that
|
|
5994
|
+
* pointer would read as paid before anyone paid.
|
|
5995
|
+
*/
|
|
5996
|
+
async waitForStripeOrder(checkoutId, sponsorId) {
|
|
5997
|
+
for (const delayMs of _VioCheckout.RETURN_VERIFY_DELAYS_MS) {
|
|
5998
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
5999
|
+
try {
|
|
6000
|
+
const checkout = await Vio.checkout.getCheckout(checkoutId, sponsorId || void 0);
|
|
6001
|
+
const status = String(checkout?.status ?? "").toLowerCase();
|
|
6002
|
+
if (checkout?.duplicate_of) return "duplicate";
|
|
6003
|
+
if (_VioCheckout.FAILED_STATUSES.has(status)) return "failed";
|
|
6004
|
+
if (_VioCheckout.PAID_STATUSES.has(status)) return "paid";
|
|
6005
|
+
} catch (err) {
|
|
6006
|
+
if (typeof console !== "undefined") {
|
|
6007
|
+
console.warn("[VioCheckout] Stripe order check failed:", err);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
return "pending";
|
|
6012
|
+
}
|
|
6013
|
+
unmountStripe() {
|
|
6014
|
+
Vio.checkout.destroyStripe();
|
|
6015
|
+
this.stripeReady = false;
|
|
6016
|
+
this.stripeElement = null;
|
|
6017
|
+
this.stripeCheckoutId = null;
|
|
6018
|
+
this.stripeMounted = false;
|
|
6019
|
+
this.stripeMountedFor = null;
|
|
6020
|
+
this.stripeMountedKey = null;
|
|
6021
|
+
this.stripeRequested = false;
|
|
6022
|
+
this.stripeStale = false;
|
|
6023
|
+
this.querySelector("#vio-stripe-checkout-container")?.remove();
|
|
6024
|
+
}
|
|
6025
|
+
renderStripePanel() {
|
|
6026
|
+
const ready = this.isAddressValid && this.hasSelectedShipping;
|
|
6027
|
+
const showButton = !this.stripeMounted && !this.stripeMounting && !this.stripeSettled;
|
|
6028
|
+
return html`
|
|
6029
|
+
<div class="stripe-panel">
|
|
6030
|
+
${this.stripeMounting || this.stripeMounted && !this.stripeReady ? html`<div style="font-size:13px;opacity:0.7;padding:8px 0;">Laster betaling…</div>` : ""}
|
|
6031
|
+
${this.stripeStale && showButton ? html`<div class="payment-notice">Bestillingen ble endret. Oppdater betalingen for å fortsette.</div>` : ""}
|
|
6032
|
+
${showButton ? html`
|
|
6033
|
+
<button
|
|
6034
|
+
class="payment-btn primary complete-cta"
|
|
6035
|
+
?disabled=${!ready}
|
|
6036
|
+
title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
|
|
6037
|
+
@click=${() => {
|
|
6038
|
+
this.stripeRequested = true;
|
|
6039
|
+
}}
|
|
6040
|
+
>
|
|
6041
|
+
${this.stripeStale ? "Oppdater betaling" : `G\xE5 til betaling \xB7 ${this.payTotalLabel()}`}
|
|
6042
|
+
</button>
|
|
6043
|
+
` : ""}
|
|
6044
|
+
<slot name="vio-stripe"></slot>
|
|
6045
|
+
${this.stripeMounted ? html`
|
|
6046
|
+
<button
|
|
6047
|
+
class="payment-btn primary complete-cta"
|
|
6048
|
+
?disabled=${this.stripePaying || !this.stripeReady}
|
|
6049
|
+
@click=${() => void this.onStripePay()}
|
|
6050
|
+
>
|
|
6051
|
+
${this.stripePaying ? "Betaler\u2026" : `Betal ${this.payTotalLabel()}`}
|
|
6052
|
+
</button>
|
|
6053
|
+
` : ""}
|
|
6054
|
+
</div>
|
|
6055
|
+
`;
|
|
6056
|
+
}
|
|
6057
|
+
renderNexiPanel() {
|
|
6058
|
+
return html`
|
|
6059
|
+
<div class="nexi-panel">
|
|
6060
|
+
${this.nexiMounting ? html`<div style="font-size:13px;opacity:0.7;padding:8px 0;">Laster Nexi…</div>` : ""}
|
|
6061
|
+
${this.renderNexiShippingChoice()}
|
|
6062
|
+
<slot name="vio-nexi"></slot>
|
|
6063
|
+
${this.nexiShippingNotice ? html`<div class="payment-notice">${this.nexiShippingNotice}</div>` : ""}
|
|
6064
|
+
</div>
|
|
6065
|
+
`;
|
|
6066
|
+
}
|
|
4387
6067
|
renderKlarnaPanel() {
|
|
4388
6068
|
const currency = this.checkoutState?.currency || getGlobalCurrency();
|
|
4389
6069
|
return html`
|
|
@@ -4433,7 +6113,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4433
6113
|
<button
|
|
4434
6114
|
class="payment-btn primary complete-cta"
|
|
4435
6115
|
@click=${() => void this.onKlarnaAuthorize()}
|
|
4436
|
-
?disabled=${this.klarnaAuthorizing || !this.klarnaHandle || this.klarnaMounting}
|
|
6116
|
+
?disabled=${this.klarnaAuthorizing || !this.klarnaHandle || this.klarnaMounting || !this.express && (!this.isAddressValid || !this.hasSelectedShipping)}
|
|
4437
6117
|
>
|
|
4438
6118
|
${this.klarnaAuthorizing ? "Behandler\u2026" : this.klarnaMounting ? "Klargj\xF8r Klarna\u2026" : `Betal ${this.payTotalLabel()} med Klarna`}
|
|
4439
6119
|
</button>
|
|
@@ -4483,7 +6163,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4483
6163
|
</div>
|
|
4484
6164
|
</div>
|
|
4485
6165
|
` : ""}
|
|
4486
|
-
${this.renderKlarnaPanel()}
|
|
6166
|
+
${this.shippingBlocked ? this.renderShippingBlocked() : this.renderKlarnaPanel()}
|
|
4487
6167
|
${this.paymentNotice ? html`<div class="payment-notice">${this.paymentNotice}</div>` : ""}
|
|
4488
6168
|
${this.paymentError ? html`<div class="payment-error">${this.paymentError}</div>` : ""}
|
|
4489
6169
|
</section>
|
|
@@ -4493,10 +6173,10 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4493
6173
|
const method = this.checkoutState?.paymentMethod;
|
|
4494
6174
|
const showCompleteCta = !!method && !this.isExpressMethod(method);
|
|
4495
6175
|
const isVipps = method === "vipps";
|
|
4496
|
-
const
|
|
6176
|
+
const needsOurForm = !!method && !collectsOwnAddress(method);
|
|
4497
6177
|
return html`
|
|
4498
|
-
${
|
|
4499
|
-
<section class="section">
|
|
6178
|
+
${needsOurForm ? html`
|
|
6179
|
+
<section class="section address-section">
|
|
4500
6180
|
<div class="section-label">Steg 1</div>
|
|
4501
6181
|
<h3 class="section-heading">Leveringsadresse</h3>
|
|
4502
6182
|
<div class="form-row">
|
|
@@ -4612,32 +6292,41 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4612
6292
|
` : ""}
|
|
4613
6293
|
|
|
4614
6294
|
<section class="section">
|
|
4615
|
-
<div class="section-label"
|
|
4616
|
-
|
|
6295
|
+
<div class="section-label">
|
|
6296
|
+
${needsOurForm ? "Steg 2" : "Betaling"}
|
|
6297
|
+
</div>
|
|
6298
|
+
${this.shippingBlocked ? this.renderShippingBlocked() : method ? html`
|
|
4617
6299
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
|
|
4618
6300
|
<h3 class="section-heading" style="margin:0;">
|
|
4619
6301
|
Betalingsmåte: <b>${this.methodLabel(method)}</b>
|
|
4620
6302
|
</h3>
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
6303
|
+
${(this.availableMethods?.length ?? 2) > 1 ? html`
|
|
6304
|
+
<button
|
|
6305
|
+
type="button"
|
|
6306
|
+
style="background:none; border:none; color:var(--vio-color-accent, #c14a3b); text-decoration:underline; font-size:13px; cursor:pointer;"
|
|
6307
|
+
@click=${() => {
|
|
4625
6308
|
this.unmountKlarna();
|
|
4626
6309
|
this.unmountKustom();
|
|
4627
6310
|
this.unmountQliro();
|
|
4628
6311
|
this.unmountWalley();
|
|
6312
|
+
this.unmountNexi();
|
|
6313
|
+
this.unmountAdyen();
|
|
4629
6314
|
Vio.checkout.selectPaymentMethod("");
|
|
4630
6315
|
}}
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
6316
|
+
>
|
|
6317
|
+
Endre
|
|
6318
|
+
</button>
|
|
6319
|
+
` : ""}
|
|
4634
6320
|
</div>
|
|
4635
6321
|
|
|
4636
6322
|
${method === "klarna" ? this.renderKlarnaPanel() : ""}
|
|
4637
6323
|
${method === "kustom" ? this.renderKustomPanel() : ""}
|
|
4638
6324
|
${method === "qliro" ? this.renderQliroPanel() : ""}
|
|
4639
6325
|
${method === "walley" ? this.renderWalleyPanel() : ""}
|
|
4640
|
-
${method === "
|
|
6326
|
+
${method === "nexi" ? this.renderNexiPanel() : ""}
|
|
6327
|
+
${method === "adyen" ? this.renderAdyenPanel() : ""}
|
|
6328
|
+
${method === "stripe" && this.stripeMode === "native" ? this.renderStripePanel() : ""}
|
|
6329
|
+
${method === "stripe" && this.stripeMode !== "native" ? html`
|
|
4641
6330
|
<button
|
|
4642
6331
|
class="payment-btn primary complete-cta"
|
|
4643
6332
|
@click=${() => void this.onPay("stripe")}
|
|
@@ -4676,19 +6365,25 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4676
6365
|
Betal ${this.payTotalLabel()} med ${this.methodLabel(method ?? null)}
|
|
4677
6366
|
</button>
|
|
4678
6367
|
` : ""}
|
|
4679
|
-
${
|
|
6368
|
+
${needsOurForm && !this.isAddressValid ? html`
|
|
4680
6369
|
<div style="font-size: 12px; color: var(--vio-color-accent, #c14a3b); text-align: center; margin-top: 8px;">
|
|
4681
6370
|
Vennligst fyll ut leveringsadresse for å fullføre betalingen
|
|
4682
6371
|
</div>
|
|
4683
|
-
` :
|
|
6372
|
+
` : needsOurForm && !this.hasSelectedShipping ? html`
|
|
4684
6373
|
<div style="font-size: 12px; color: var(--vio-color-accent, #c14a3b); text-align: center; margin-top: 8px;">
|
|
4685
6374
|
Vennligst velg en fraktmetode for å fullføre betalingen
|
|
4686
6375
|
</div>
|
|
4687
6376
|
` : ""}
|
|
4688
|
-
` : html`
|
|
6377
|
+
` : !this.paymentMethodsResolved ? html`
|
|
6378
|
+
<h3 class="section-heading">Velg betalingsmåte</h3>
|
|
6379
|
+
<div class="payment-loading">
|
|
6380
|
+
<span class="payment-spinner" aria-hidden="true"></span>
|
|
6381
|
+
<span>Henter betalingsmåter…</span>
|
|
6382
|
+
</div>
|
|
6383
|
+
` : html`
|
|
4689
6384
|
<h3 class="section-heading">Velg betalingsmåte</h3>
|
|
4690
6385
|
<div class="payment-grid">
|
|
4691
|
-
${this.methodEnabled("apple-pay") && this.applePayAvailable ? html`
|
|
6386
|
+
${this.methodEnabled("apple-pay") && this.applePayAvailable && !this.stripeOwnsWallets() ? html`
|
|
4692
6387
|
<button
|
|
4693
6388
|
class="payment-btn primary"
|
|
4694
6389
|
@click=${() => this.onPay("apple-pay")}
|
|
@@ -4726,15 +6421,26 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4726
6421
|
<span style="font-weight:800;font-size:16px;letter-spacing:-0.01em">Walley</span>
|
|
4727
6422
|
</button>
|
|
4728
6423
|
` : ""}
|
|
4729
|
-
${this.methodEnabled("
|
|
4730
|
-
<button class="payment-btn" @click=${() => this.onPay("
|
|
4731
|
-
<span style="
|
|
6424
|
+
${this.methodEnabled("nexi") ? html`
|
|
6425
|
+
<button class="payment-btn" @click=${() => this.onPay("nexi")}>
|
|
6426
|
+
<span style="font-weight:800;font-size:16px;letter-spacing:-0.01em">Nexi</span>
|
|
4732
6427
|
</button>
|
|
4733
6428
|
` : ""}
|
|
4734
|
-
${this.methodEnabled("
|
|
4735
|
-
<
|
|
4736
|
-
|
|
4737
|
-
</
|
|
6429
|
+
${this.methodEnabled("adyen") ? html`
|
|
6430
|
+
<button class="payment-btn" @click=${() => this.onPay("adyen")}>
|
|
6431
|
+
<span style="font-weight:700;font-size:15px;letter-spacing:-0.01em">${this.methodLabel("adyen")}</span>
|
|
6432
|
+
</button>
|
|
6433
|
+
` : ""}
|
|
6434
|
+
${this.methodEnabled("vipps") ? html`
|
|
6435
|
+
<button class="payment-btn" @click=${() => this.onPay("vipps")} aria-label="Vipps">
|
|
6436
|
+
${this.vippsOfficial ? html`<vipps-mobilepay-button
|
|
6437
|
+
brand="vipps"
|
|
6438
|
+
variant="primary"
|
|
6439
|
+
rounded="true"
|
|
6440
|
+
compact="true"
|
|
6441
|
+
style="pointer-events:none"
|
|
6442
|
+
></vipps-mobilepay-button>` : html`<span style="color:#ff5b24;font-weight:800;font-size:17px;letter-spacing:-0.02em">vipps</span>`}
|
|
6443
|
+
</button>
|
|
4738
6444
|
` : ""}
|
|
4739
6445
|
</div>
|
|
4740
6446
|
`}
|
|
@@ -4765,7 +6471,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4765
6471
|
<span>Sum</span><span>${this.orderTotal()}</span>
|
|
4766
6472
|
</div>
|
|
4767
6473
|
<div class="order-row">
|
|
4768
|
-
<span>Frakt${this.shippingMajor() > 0 && this.
|
|
6474
|
+
<span>Frakt${this.shippingMajor() > 0 && this.summaryShippingName() ? ` \u2013 ${this.summaryShippingName()}` : ""}</span>
|
|
4769
6475
|
<span>${this.shippingMajor() > 0 ? formatPrice(this.shippingMajor(), this.checkoutState?.currency) : this.shippingLabel}</span>
|
|
4770
6476
|
</div>
|
|
4771
6477
|
<div class="order-row total">
|
|
@@ -4808,6 +6514,9 @@ __decorateClass([
|
|
|
4808
6514
|
__decorateClass([
|
|
4809
6515
|
state()
|
|
4810
6516
|
], _VioCheckout.prototype, "orderConfirmed", 2);
|
|
6517
|
+
__decorateClass([
|
|
6518
|
+
state()
|
|
6519
|
+
], _VioCheckout.prototype, "providerReceipt", 2);
|
|
4811
6520
|
__decorateClass([
|
|
4812
6521
|
state()
|
|
4813
6522
|
], _VioCheckout.prototype, "confirmedMethod", 2);
|
|
@@ -4817,6 +6526,9 @@ __decorateClass([
|
|
|
4817
6526
|
__decorateClass([
|
|
4818
6527
|
state()
|
|
4819
6528
|
], _VioCheckout.prototype, "paymentNotice", 2);
|
|
6529
|
+
__decorateClass([
|
|
6530
|
+
state()
|
|
6531
|
+
], _VioCheckout.prototype, "verifyingReturn", 2);
|
|
4820
6532
|
__decorateClass([
|
|
4821
6533
|
state()
|
|
4822
6534
|
], _VioCheckout.prototype, "confirmedOrder", 2);
|
|
@@ -4838,15 +6550,84 @@ __decorateClass([
|
|
|
4838
6550
|
__decorateClass([
|
|
4839
6551
|
state()
|
|
4840
6552
|
], _VioCheckout.prototype, "availableMethods", 2);
|
|
6553
|
+
__decorateClass([
|
|
6554
|
+
state()
|
|
6555
|
+
], _VioCheckout.prototype, "vippsOfficial", 2);
|
|
4841
6556
|
__decorateClass([
|
|
4842
6557
|
state()
|
|
4843
6558
|
], _VioCheckout.prototype, "kustomMounting", 2);
|
|
6559
|
+
__decorateClass([
|
|
6560
|
+
state()
|
|
6561
|
+
], _VioCheckout.prototype, "kustomSyncing", 2);
|
|
6562
|
+
__decorateClass([
|
|
6563
|
+
state()
|
|
6564
|
+
], _VioCheckout.prototype, "kustomShipping", 2);
|
|
6565
|
+
__decorateClass([
|
|
6566
|
+
state()
|
|
6567
|
+
], _VioCheckout.prototype, "kustomTotal", 2);
|
|
6568
|
+
__decorateClass([
|
|
6569
|
+
state()
|
|
6570
|
+
], _VioCheckout.prototype, "kustomNotice", 2);
|
|
4844
6571
|
__decorateClass([
|
|
4845
6572
|
state()
|
|
4846
6573
|
], _VioCheckout.prototype, "qliroMounting", 2);
|
|
4847
6574
|
__decorateClass([
|
|
4848
6575
|
state()
|
|
4849
6576
|
], _VioCheckout.prototype, "walleyMounting", 2);
|
|
6577
|
+
__decorateClass([
|
|
6578
|
+
state()
|
|
6579
|
+
], _VioCheckout.prototype, "nexiMounting", 2);
|
|
6580
|
+
__decorateClass([
|
|
6581
|
+
state()
|
|
6582
|
+
], _VioCheckout.prototype, "nexiShippingNotice", 2);
|
|
6583
|
+
__decorateClass([
|
|
6584
|
+
state()
|
|
6585
|
+
], _VioCheckout.prototype, "nexiShipping", 2);
|
|
6586
|
+
__decorateClass([
|
|
6587
|
+
state()
|
|
6588
|
+
], _VioCheckout.prototype, "nexiPicking", 2);
|
|
6589
|
+
__decorateClass([
|
|
6590
|
+
state()
|
|
6591
|
+
], _VioCheckout.prototype, "adyenMounting", 2);
|
|
6592
|
+
__decorateClass([
|
|
6593
|
+
state()
|
|
6594
|
+
], _VioCheckout.prototype, "adyenRequested", 2);
|
|
6595
|
+
__decorateClass([
|
|
6596
|
+
state()
|
|
6597
|
+
], _VioCheckout.prototype, "adyenMounted", 2);
|
|
6598
|
+
__decorateClass([
|
|
6599
|
+
state()
|
|
6600
|
+
], _VioCheckout.prototype, "adyenStale", 2);
|
|
6601
|
+
__decorateClass([
|
|
6602
|
+
state()
|
|
6603
|
+
], _VioCheckout.prototype, "adyenSettled", 2);
|
|
6604
|
+
__decorateClass([
|
|
6605
|
+
state()
|
|
6606
|
+
], _VioCheckout.prototype, "stripeMode", 2);
|
|
6607
|
+
__decorateClass([
|
|
6608
|
+
state()
|
|
6609
|
+
], _VioCheckout.prototype, "stripeRequested", 2);
|
|
6610
|
+
__decorateClass([
|
|
6611
|
+
state()
|
|
6612
|
+
], _VioCheckout.prototype, "stripeMounting", 2);
|
|
6613
|
+
__decorateClass([
|
|
6614
|
+
state()
|
|
6615
|
+
], _VioCheckout.prototype, "stripeMounted", 2);
|
|
6616
|
+
__decorateClass([
|
|
6617
|
+
state()
|
|
6618
|
+
], _VioCheckout.prototype, "stripeStale", 2);
|
|
6619
|
+
__decorateClass([
|
|
6620
|
+
state()
|
|
6621
|
+
], _VioCheckout.prototype, "stripeSettled", 2);
|
|
6622
|
+
__decorateClass([
|
|
6623
|
+
state()
|
|
6624
|
+
], _VioCheckout.prototype, "stripePaying", 2);
|
|
6625
|
+
__decorateClass([
|
|
6626
|
+
state()
|
|
6627
|
+
], _VioCheckout.prototype, "stripeReady", 2);
|
|
6628
|
+
__decorateClass([
|
|
6629
|
+
state()
|
|
6630
|
+
], _VioCheckout.prototype, "qliroShipping", 2);
|
|
4850
6631
|
__decorateClass([
|
|
4851
6632
|
state()
|
|
4852
6633
|
], _VioCheckout.prototype, "stripeLoading", 2);
|
|
@@ -4856,6 +6637,15 @@ __decorateClass([
|
|
|
4856
6637
|
__decorateClass([
|
|
4857
6638
|
state()
|
|
4858
6639
|
], _VioCheckout.prototype, "form", 2);
|
|
6640
|
+
__decorateClass([
|
|
6641
|
+
state()
|
|
6642
|
+
], _VioCheckout.prototype, "paymentMethodsResolved", 2);
|
|
6643
|
+
__decorateClass([
|
|
6644
|
+
state()
|
|
6645
|
+
], _VioCheckout.prototype, "shippingBlocked", 2);
|
|
6646
|
+
__decorateClass([
|
|
6647
|
+
state()
|
|
6648
|
+
], _VioCheckout.prototype, "shippingsReadyFor", 2);
|
|
4859
6649
|
var VioCheckout = _VioCheckout;
|
|
4860
6650
|
|
|
4861
6651
|
// src/ui/elements.ts
|
|
@@ -4875,5 +6665,5 @@ function registerVioElements() {
|
|
|
4875
6665
|
}
|
|
4876
6666
|
|
|
4877
6667
|
export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
|
|
4878
|
-
//# sourceMappingURL=chunk-
|
|
4879
|
-
//# sourceMappingURL=chunk-
|
|
6668
|
+
//# sourceMappingURL=chunk-MRJR6DG5.js.map
|
|
6669
|
+
//# sourceMappingURL=chunk-MRJR6DG5.js.map
|