@wix/himalaya-cli 0.1550.0 → 0.1552.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/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +2 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +24 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/order-refs.d.ts +12 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forms/src/extensions.d.ts +83 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +146 -23
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +8 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +147 -24
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forms.mjs +19 -0
- package/dist/cli.mjs +2489 -2173
- package/fixtures/wix-canned.json +14 -7
- package/guides/getting-started.md +17 -1
- package/guides/headless-loop.md +7 -1
- package/guides/himi-skill.md +17 -1
- package/guides/himi-test.md +95 -7
- package/guides/iap-pricing.md +1 -1
- package/guides/iap-products.md +2 -2
- package/guides/iap-screen-adoption.md +1 -1
- package/guides/index.json +1 -1
- package/guides/qa-builds.md +56 -1
- package/guides/sdk-reference.json +82 -4
- package/guides/skill.md +16 -0
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +13 -0
- package/templates/beauty-salon/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/branded-lite/config.ts +13 -0
- package/templates/branded-lite/dev/index.ts +1338 -2011
- package/templates/branded-lite/tier5-src/_shared/branded-form.ts +262 -0
- package/templates/branded-lite/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-form/index.ts +399 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-forms/index.ts +22 -7
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/community-club/config.ts +13 -0
- package/templates/community-club/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/community-club/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/events-meetups/config.ts +13 -0
- package/templates/events-meetups/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/gym-fitness/config.ts +13 -0
- package/templates/gym-fitness/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/nonprofit-cause/config.ts +13 -0
- package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/podcast-creator/config.ts +13 -0
- package/templates/podcast-creator/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/portfolio-creative/config.ts +13 -0
- package/templates/portfolio-creative/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/restaurant/config.ts +13 -0
- package/templates/restaurant/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/restaurant/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/retail-store/config.ts +13 -0
- package/templates/retail-store/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/yoga-studio/config.ts +13 -0
- package/templates/yoga-studio/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
- package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +1 -1
- package/templates/branded-lite/tier5-src/wix-spaces-form-detail/index.ts +0 -30
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// The Wix Forms schema → what the Branded Lite Form screen can render and send.
|
|
2
|
+
//
|
|
3
|
+
// Pure functions, so the hermetic tests drive them without a worker. The source for every
|
|
4
|
+
// rule here is Wix's "About Form Fields" and "About Submission Values" articles
|
|
5
|
+
// (dev.wix.com/docs/api-reference/crm/forms/…):
|
|
6
|
+
// • `formFields` is an unordered set; the display order is `steps[].layout.<bp>.items`
|
|
7
|
+
// sorted by row, then column (a breakpoint may be missing, so fall back across them).
|
|
8
|
+
// • `identifier`, not `componentType`, names the field: TEXT_INPUT serves a short answer,
|
|
9
|
+
// a long answer, a link, an email, an address and the contact names.
|
|
10
|
+
// • a checkbox label is Ricos rich content; every other label is a plain string.
|
|
11
|
+
// • a submission is keyed by each field's `target`, and its value shape follows `inputType`.
|
|
12
|
+
// A field the app cannot fill (file, signature, payment, appointment, a multi-line address,
|
|
13
|
+
// image choice, rating, time) or any conditional rule makes the whole form "unsupported":
|
|
14
|
+
// a hidden required field would fail the whole send, so the screen offers the website instead.
|
|
15
|
+
import { ricosToPlainText } from "@himalaya/wix-rich-content";
|
|
16
|
+
|
|
17
|
+
export type FieldKind = "text" | "area" | "email" | "phone" | "number" | "url" | "date" | "checkbox" | "choice" | "multi" | "display";
|
|
18
|
+
|
|
19
|
+
export interface FormOption { value: string; label: string }
|
|
20
|
+
|
|
21
|
+
export interface FormFieldModel {
|
|
22
|
+
id: string;
|
|
23
|
+
target: string;
|
|
24
|
+
kind: FieldKind;
|
|
25
|
+
label: string;
|
|
26
|
+
required: boolean;
|
|
27
|
+
placeholder: string;
|
|
28
|
+
help: string;
|
|
29
|
+
options: FormOption[];
|
|
30
|
+
/** `inputOptions.contactMapping.contactField` — EMAIL, FIRST_NAME, … — for member prefill. */
|
|
31
|
+
contactField: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FormModel {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
/** Field ids per page, in display order. A form with no layout is one page in array order. */
|
|
38
|
+
pages: string[][];
|
|
39
|
+
fields: Record<string, FormFieldModel>;
|
|
40
|
+
submitText: string;
|
|
41
|
+
nextText: string;
|
|
42
|
+
backText: string;
|
|
43
|
+
thankYou: string;
|
|
44
|
+
/** Something on the form the app cannot fill: show the website notice. */
|
|
45
|
+
unsupported: boolean;
|
|
46
|
+
/** The form stopped taking submissions (its deadline passed). */
|
|
47
|
+
closed: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type Obj = Record<string, unknown>;
|
|
51
|
+
const obj = (v: unknown): Obj => (v && typeof v === "object" && !Array.isArray(v) ? v as Obj : {});
|
|
52
|
+
const arr = (v: unknown): unknown[] => (Array.isArray(v) ? v : []);
|
|
53
|
+
const str = (v: unknown): string => (typeof v === "string" ? v : "");
|
|
54
|
+
|
|
55
|
+
/** Ricos rich content (or a plain string) → plain text, through the shared Ricos walker. */
|
|
56
|
+
export function plainText(v: unknown): string {
|
|
57
|
+
if (typeof v === "string") return v.trim();
|
|
58
|
+
return ricosToPlainText(obj(v) as never).trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const TEXT_IDENTIFIERS = new Set([
|
|
62
|
+
"TEXT_INPUT", "CONTACTS_FIRST_NAME", "CONTACTS_LAST_NAME", "CONTACTS_COMPANY", "CONTACTS_POSITION",
|
|
63
|
+
"CONTACTS_TAX_ID", "CONTACTS_ADDRESS",
|
|
64
|
+
]);
|
|
65
|
+
const KIND_BY_IDENTIFIER: Record<string, FieldKind> = {
|
|
66
|
+
TEXT_AREA: "area", CONTACTS_EMAIL: "email", CONTACTS_PHONE: "phone", NUMBER_INPUT: "number", URL_INPUT: "url",
|
|
67
|
+
CONTACTS_BIRTHDATE: "date", DATE_INPUT: "date", DATE_PICKER: "date",
|
|
68
|
+
CHECKBOX: "checkbox", CONTACTS_SUBSCRIBE: "checkbox",
|
|
69
|
+
RADIO_GROUP: "choice", DROPDOWN: "choice", CHECKBOX_GROUP: "multi", TAGS: "multi",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** The `inputType` options object for an input field: `stringOptions`, `booleanOptions`, … */
|
|
73
|
+
function typeOptions(input: Obj): Obj {
|
|
74
|
+
const t = str(input.inputType).toLowerCase().replace(/_([a-z])/g, (_m, c: string) => c.toUpperCase());
|
|
75
|
+
return obj(input[`${t}Options`]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The component options object: `textInputOptions`, `radioGroupOptions`, … */
|
|
79
|
+
function componentOptions(typeOpts: Obj): Obj {
|
|
80
|
+
const c = str(typeOpts.componentType).toLowerCase().replace(/_([a-z])/g, (_m, ch: string) => ch.toUpperCase());
|
|
81
|
+
return obj(typeOpts[`${c}Options`]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One `formFields` entry → its model, `null` for the submit button / a hidden field, or
|
|
85
|
+
* `"unsupported"` for a field the app cannot fill. */
|
|
86
|
+
export function parseField(raw: unknown): FormFieldModel | null | "unsupported" {
|
|
87
|
+
const f = obj(raw);
|
|
88
|
+
const id = str(f.id).toLowerCase();
|
|
89
|
+
// A hidden field the visitor cannot fill, but Wix requires, would fail every send.
|
|
90
|
+
if (f.hidden === true) return f.fieldType === "INPUT" && obj(f.inputOptions).required === true ? "unsupported" : null;
|
|
91
|
+
if (f.fieldType === "DISPLAY") {
|
|
92
|
+
const d = obj(f.displayOptions);
|
|
93
|
+
if (d.displayFieldType !== "RICH_CONTENT") return null;
|
|
94
|
+
const label = plainText(obj(d.richContentOptions).richContent);
|
|
95
|
+
return label ? { id, target: "", kind: "display", label, required: false, placeholder: "", help: "", options: [], contactField: "" } : null;
|
|
96
|
+
}
|
|
97
|
+
if (f.fieldType !== "INPUT") return "unsupported";
|
|
98
|
+
const identifier = str(f.identifier);
|
|
99
|
+
const kind: FieldKind | undefined = TEXT_IDENTIFIERS.has(identifier) ? "text" : KIND_BY_IDENTIFIER[identifier];
|
|
100
|
+
const input = obj(f.inputOptions);
|
|
101
|
+
const target = str(input.target);
|
|
102
|
+
if (!kind || !target) return "unsupported";
|
|
103
|
+
const opts = componentOptions(typeOptions(input));
|
|
104
|
+
const options = (kind === "choice" || kind === "multi")
|
|
105
|
+
? arr(opts.options).map((o) => ({ value: str(obj(o).value), label: str(obj(o).label) || str(obj(o).value) })).filter((o) => o.value)
|
|
106
|
+
: [];
|
|
107
|
+
if ((kind === "choice" || kind === "multi") && !options.length) return "unsupported";
|
|
108
|
+
return {
|
|
109
|
+
id,
|
|
110
|
+
target,
|
|
111
|
+
kind,
|
|
112
|
+
label: plainText(opts.label),
|
|
113
|
+
required: input.required === true,
|
|
114
|
+
placeholder: str(opts.placeholder).trim(),
|
|
115
|
+
help: plainText(opts.description),
|
|
116
|
+
options,
|
|
117
|
+
contactField: str(obj(input.contactMapping).contactField),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Field ids of one step, in display order (row, then column). */
|
|
122
|
+
function stepFieldIds(step: unknown): string[] {
|
|
123
|
+
const layout = obj(obj(step).layout);
|
|
124
|
+
const bp = [layout.small, layout.medium, layout.large].map(obj).find((b) => arr(b.items).length) ?? {};
|
|
125
|
+
return arr(bp.items)
|
|
126
|
+
.map(obj)
|
|
127
|
+
.sort((a, b) => (Number(a.row) - Number(b.row)) || (Number(a.column) - Number(b.column)))
|
|
128
|
+
.map((i) => str(i.fieldId).toLowerCase())
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function parseForm(raw: unknown, now: number = Date.now()): FormModel {
|
|
133
|
+
const form = obj(obj(raw).form ?? raw);
|
|
134
|
+
const fields: Record<string, FormFieldModel> = {};
|
|
135
|
+
let unsupported = arr(form.formRules).length > 0 || arr(form.rules).length > 0;
|
|
136
|
+
let submitText = "", nextText = "", backText = "";
|
|
137
|
+
const arrayOrder: string[] = [];
|
|
138
|
+
for (const rawField of arr(form.formFields)) {
|
|
139
|
+
const f = obj(rawField);
|
|
140
|
+
const nav = obj(obj(f.displayOptions).pageNavigationOptions);
|
|
141
|
+
// A form has one navigation field per step and `formFields` is unordered: keep the first
|
|
142
|
+
// non-empty text of each kind, so another step's blank value never erases it.
|
|
143
|
+
if (obj(f.displayOptions).displayFieldType === "PAGE_NAVIGATION") {
|
|
144
|
+
submitText ||= str(nav.submitText).trim(); nextText ||= str(nav.nextPageText).trim(); backText ||= str(nav.previousPageText).trim();
|
|
145
|
+
}
|
|
146
|
+
const model = parseField(rawField);
|
|
147
|
+
if (model === "unsupported") { unsupported = true; continue; }
|
|
148
|
+
if (model) { fields[model.id] = model; arrayOrder.push(model.id); }
|
|
149
|
+
}
|
|
150
|
+
const steps = arr(form.steps);
|
|
151
|
+
const placed = steps.filter((st) => obj(st).hidden !== true).map((st) => stepFieldIds(st).filter((id) => fields[id]));
|
|
152
|
+
// A hidden step's fields are placed too — on no page. Adding them to the last page would show
|
|
153
|
+
// the visitor fields the owner hid (and block the send on a hidden required one).
|
|
154
|
+
const hiddenStepIds = steps.filter((st) => obj(st).hidden === true).flatMap(stepFieldIds);
|
|
155
|
+
const seen = new Set([...placed.flat(), ...hiddenStepIds]);
|
|
156
|
+
// …and a required field on a hidden step is the same dead end.
|
|
157
|
+
if (hiddenStepIds.some((id) => fields[id]?.required)) unsupported = true;
|
|
158
|
+
// A field missing from the layout still renders, on the last page, rather than vanishing.
|
|
159
|
+
const unplaced = arrayOrder.filter((id) => !seen.has(id));
|
|
160
|
+
const pages = placed.filter((p) => p.length);
|
|
161
|
+
if (unplaced.length) pages.length ? pages[pages.length - 1].push(...unplaced) : pages.push(unplaced);
|
|
162
|
+
const hasInput = Object.values(fields).some((f) => f.kind !== "display");
|
|
163
|
+
const deadline = Date.parse(str(obj(form.limitationRule).dateTimeDeadline));
|
|
164
|
+
const thanks = obj(obj(form.submitSettings).thankYouMessageOptions);
|
|
165
|
+
return {
|
|
166
|
+
id: str(form.id),
|
|
167
|
+
title: str(form.name).trim(),
|
|
168
|
+
pages: pages.length ? pages : [[]],
|
|
169
|
+
fields,
|
|
170
|
+
submitText, nextText, backText,
|
|
171
|
+
thankYou: plainText(thanks.richContent),
|
|
172
|
+
unsupported: unsupported || !hasInput,
|
|
173
|
+
closed: Number.isFinite(deadline) && deadline <= now,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** A typed number. A locale decimal keyboard types "1,5": one comma and no dot is a decimal comma. */
|
|
178
|
+
export function toNumber(text: string): number {
|
|
179
|
+
const t = text.trim();
|
|
180
|
+
return Number(/^[+-]?\d*,\d+$/.test(t) ? t.replace(",", ".") : t);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** A form a visitor can fill has at least one input field. The demo site has a form with
|
|
184
|
+
* no name and no fields; listing it opened a screen with nothing to fill. */
|
|
185
|
+
export function hasInputFields(form: unknown): boolean {
|
|
186
|
+
return arr(obj(form).formFields).some((f) => obj(f).fieldType === "INPUT");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** What the screen holds for one field. */
|
|
190
|
+
export interface FieldValue { text: string; checked: boolean; selected: string[] }
|
|
191
|
+
|
|
192
|
+
export const EMPTY_VALUE: FieldValue = { text: "", checked: false, selected: [] };
|
|
193
|
+
|
|
194
|
+
function isEmpty(f: FormFieldModel, v: FieldValue): boolean {
|
|
195
|
+
if (f.kind === "checkbox") return !v.checked;
|
|
196
|
+
if (f.kind === "choice" || f.kind === "multi") return v.selected.length === 0;
|
|
197
|
+
return v.text.trim() === "";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export type FieldProblem = "required" | "email" | "number" | "url";
|
|
201
|
+
|
|
202
|
+
/** The checks the app can make before Wix sees the values. Wix's own validation (format,
|
|
203
|
+
* length, allowed values) runs after, through validateFormSubmission. */
|
|
204
|
+
export function localProblems(fields: FormFieldModel[], values: Record<string, FieldValue>): Record<string, FieldProblem> {
|
|
205
|
+
const out: Record<string, FieldProblem> = {};
|
|
206
|
+
for (const f of fields) {
|
|
207
|
+
if (f.kind === "display") continue;
|
|
208
|
+
const v = values[f.target] ?? EMPTY_VALUE;
|
|
209
|
+
if (isEmpty(f, v)) { if (f.required) out[f.target] = "required"; continue; }
|
|
210
|
+
const t = v.text.trim();
|
|
211
|
+
if (f.kind === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)) out[f.target] = "email";
|
|
212
|
+
else if (f.kind === "number" && !Number.isFinite(toNumber(t))) out[f.target] = "number";
|
|
213
|
+
else if (f.kind === "url" && !/^(https?:\/\/)?[^\s.]+\.[^\s]+$/i.test(t)) out[f.target] = "url";
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The `submissions` map: keyed by target, shaped by input type, empty optional values left out. */
|
|
219
|
+
export function submissionValues(fields: FormFieldModel[], values: Record<string, FieldValue>): Record<string, unknown> {
|
|
220
|
+
const out: Record<string, unknown> = {};
|
|
221
|
+
for (const f of fields) {
|
|
222
|
+
if (f.kind === "display") continue;
|
|
223
|
+
const v = values[f.target] ?? EMPTY_VALUE;
|
|
224
|
+
if (f.kind === "checkbox") { out[f.target] = v.checked; continue; }
|
|
225
|
+
if (f.kind === "multi") { if (v.selected.length) out[f.target] = [...v.selected]; continue; }
|
|
226
|
+
if (f.kind === "choice") { if (v.selected[0]) out[f.target] = v.selected[0]; continue; }
|
|
227
|
+
const t = v.text.trim();
|
|
228
|
+
if (!t) continue;
|
|
229
|
+
out[f.target] = f.kind === "number" ? toNumber(t) : t;
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Wix's per-field validation errors (`submissionValidationErrors.errors[]`, in a
|
|
235
|
+
* validateFormSubmission answer or in a rejected createSubmission body) → target → errorType. */
|
|
236
|
+
export function serverProblems(answerOrError: unknown): Record<string, string> {
|
|
237
|
+
let source: unknown = answerOrError;
|
|
238
|
+
if (answerOrError instanceof Error || typeof answerOrError === "string") {
|
|
239
|
+
const msg = String((answerOrError as Error)?.message ?? answerOrError);
|
|
240
|
+
const json = msg.slice(msg.indexOf("{"));
|
|
241
|
+
try { source = JSON.parse(json); } catch { return {}; }
|
|
242
|
+
}
|
|
243
|
+
const out: Record<string, string> = {};
|
|
244
|
+
const visit = (v: unknown): void => {
|
|
245
|
+
if (Array.isArray(v)) { v.forEach(visit); return; }
|
|
246
|
+
const o = obj(v);
|
|
247
|
+
for (const e of arr(obj(o.submissionValidationErrors).errors)) {
|
|
248
|
+
const path = str(obj(e).errorPath), type = str(obj(e).errorType);
|
|
249
|
+
if (path && !out[path]) out[path] = type || "INVALID";
|
|
250
|
+
}
|
|
251
|
+
for (const k of ["violationErrors", "details", "applicationError", "data", "validationError"]) if (k in o) visit(o[k]);
|
|
252
|
+
};
|
|
253
|
+
visit(source);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** A unix-seconds date (the DatePicker's `args.value`) → the `YYYY-MM-DD` Wix's DATE format takes. */
|
|
258
|
+
export function isoDate(unixSeconds: number): string {
|
|
259
|
+
const d = new Date(unixSeconds * 1000);
|
|
260
|
+
const p = (n: number) => String(n).padStart(2, "0");
|
|
261
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
262
|
+
}
|
|
@@ -111,7 +111,7 @@ export function createAttendancePurchase(options: { kind: "bookingsService" | "e
|
|
|
111
111
|
const classified = classifyForApple(metadata, currency);
|
|
112
112
|
const candidate = platform === "ios" && classified === "appleIap";
|
|
113
113
|
const price = iapPriceFields(commerce.pricing.view(reference), iapPriceCopy(prefix));
|
|
114
|
-
const copy =
|
|
114
|
+
const copy = iapPurchaseCopy(prefix);
|
|
115
115
|
const purchase = commerce.purchaseFields(reference, copy);
|
|
116
116
|
const apple = candidate || (platform === "ios" && classified !== "wixPayments" && purchase.appleRoute);
|
|
117
117
|
const enabled = attendanceCheckoutRoute(metadata, currency, platform) === "appleIap";
|
|
@@ -20,7 +20,7 @@ export function iapPriceCopy(prefix: string): IapPriceCopy {
|
|
|
20
20
|
* `iapPurchaseFields`. Customer copy only: no backend names, ids or diagnostics. */
|
|
21
21
|
export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
|
|
22
22
|
return {
|
|
23
|
-
buy: copyParam(`${prefix}.buyCta`, "
|
|
23
|
+
buy: copyParam(`${prefix}.buyCta`, "Buy now"),
|
|
24
24
|
processing: copyParam(`${prefix}.purchaseProcessingText`, "Processing your purchase…"),
|
|
25
25
|
awaitingApproval: copyParam(`${prefix}.purchaseAwaitingApprovalText`, "Waiting for approval"),
|
|
26
26
|
unavailable: copyParam(`${prefix}.purchaseTemporarilyUnavailableText`, "Purchasing is temporarily unavailable."),
|