@grupolapa/desarrollos-sdk 0.1.0 → 0.3.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 CHANGED
@@ -13,18 +13,37 @@ npm install @grupolapa/desarrollos-sdk
13
13
  pnpm add @grupolapa/desarrollos-sdk
14
14
  ```
15
15
 
16
+ ## API Discovery Endpoints
17
+
16
18
  The public API base URL is:
17
19
 
18
20
  ```txt
19
21
  https://grupolapa.com/api/public/v1/desarrollos
20
22
  ```
21
23
 
22
- Agent-readable integration instructions are available at:
24
+ Agents can fetch integration instructions, endpoint descriptions, auth rules,
25
+ and copy-paste examples from:
23
26
 
24
27
  ```txt
25
28
  https://grupolapa.com/api/public/v1/desarrollos/agent.json
26
29
  ```
27
30
 
31
+ OpenAPI metadata is available at:
32
+
33
+ ```txt
34
+ https://grupolapa.com/api/public/v1/desarrollos/openapi.json
35
+ ```
36
+
37
+ UONDR and external configurable-cotizador agents should use the dedicated
38
+ cross-project guide:
39
+
40
+ ```txt
41
+ https://grupolapa.com/api/public/v1/desarrollos/uondr-agent.json
42
+ ```
43
+
44
+ It distinguishes already available Lapa and SDK tools from the required UONDR
45
+ partner APIs, iframe protocol, and production website work.
46
+
28
47
  ## Browser Reads and Quotes
29
48
 
30
49
  ```ts
@@ -79,6 +98,96 @@ const quote =
79
98
  : null;
80
99
  ```
81
100
 
101
+ ## Browser Journey Flow
102
+
103
+ The journey client is browser-only and does not accept a Lapa API key. Create a
104
+ journey with the integration's publishable key, then authenticate every
105
+ journey-scoped call with the returned opaque token. The optional token on
106
+ `createJourney` resumes the same journey when it is still valid.
107
+
108
+ ```ts
109
+ import { createDesarrollosClient } from "@grupolapa/desarrollos-sdk";
110
+
111
+ const client = createDesarrollosClient({
112
+ baseUrl: "https://grupolapa.com/api/public/v1/desarrollos",
113
+ });
114
+
115
+ const created = await client.createJourney({
116
+ integrationKey: "lapa_partner_public_key",
117
+ });
118
+
119
+ const journeyId = created.journey.id;
120
+ const journeyToken = created.journeyToken;
121
+
122
+ await client.updateJourneySelection({
123
+ journeyId,
124
+ journeyToken,
125
+ selection: { unitId: "unit_1" },
126
+ });
127
+
128
+ const { session } = await client.createJourneyConfiguratorSession({
129
+ journeyId,
130
+ journeyToken,
131
+ });
132
+
133
+ // Mount session.embedUrl in the UONDR iframe and wait for its completed event.
134
+ await client.completeJourneyConfiguration({
135
+ journeyId,
136
+ journeyToken,
137
+ configuration: {
138
+ configurationId: "configuration_01",
139
+ revision: 1,
140
+ hash: "sha256-canonical-configuration-hash",
141
+ },
142
+ });
143
+
144
+ const quotedJourney = await client.createJourneyQuote({
145
+ journeyId,
146
+ journeyToken,
147
+ });
148
+
149
+ // Always render the authoritative quote returned by Lapa.
150
+ const quote = quotedJourney.quote;
151
+
152
+ await client.updateJourneyLead({
153
+ journeyId,
154
+ journeyToken,
155
+ lead: {
156
+ name: "Ada Lovelace",
157
+ phone: "+529991234567",
158
+ email: "ada@example.com",
159
+ },
160
+ });
161
+
162
+ const turnstileToken = "token-returned-by-the-turnstile-widget";
163
+ const checkoutJourney = await client.createJourneyCheckout({
164
+ journeyId,
165
+ journeyToken,
166
+ checkout: {
167
+ idempotencyKey: crypto.randomUUID(),
168
+ turnstileToken,
169
+ payment: {
170
+ provider: "stripe",
171
+ returnUrl: "https://partner.example.com/confirmation",
172
+ },
173
+ },
174
+ });
175
+
176
+ if (checkoutJourney.checkout?.checkoutUrl) {
177
+ window.location.assign(checkoutJourney.checkout.checkoutUrl);
178
+ }
179
+ ```
180
+
181
+ Persist only the versioned opaque `journeyToken` in browser storage. Keep PII,
182
+ configuration snapshots, quote amounts, and payment-provider data out of local
183
+ storage. Visible step and unit state can live in URL search parameters; tokens
184
+ and prices must not.
185
+
186
+ Journey mutations return the authoritative aggregate journey state, including
187
+ configuration, quote, hold, checkout, payment-conflict, and draft-operation
188
+ status. Client quote helpers remain provisional and must not replace the Lapa
189
+ quote returned by `createJourneyQuote`.
190
+
82
191
  ## Trusted Server Flow
83
192
 
84
193
  Keep `lapa_dev_<prefix>_<secret>` API keys on a server. Use the trusted server
@@ -145,7 +254,7 @@ if (map) {
145
254
  ## Exports
146
255
 
147
256
  - `@grupolapa/desarrollos-sdk`: browser-safe client, public types, and
148
- `DesarrollosApiError`.
257
+ `DesarrollosApiError`, including the token-authenticated journey methods.
149
258
  - `@grupolapa/desarrollos-sdk/server`: trusted server API-key client.
150
259
  - `@grupolapa/desarrollos-sdk/quote`: quote, selection, payment, inventory,
151
260
  formatting, Entrada/action, and product helpers.
@@ -1,6 +1,7 @@
1
1
  import { getFinanceRulesRentalRateForUnit, getUnitEstimatedRentalAnnualMXN, getUnitEstimatedRentalMonthlyMXN, getUnitDeliveryMonths, getUnitListPriceMXN, isCashbackScheme, isInstallmentScheme, MAX_CASHBACK_MONTHS, } from "./payment-schemes.js";
2
2
  import { getInstallmentAllocation, getInstallmentDiscountPct, INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT, INSTALLMENT_DEFAULT_MONTHLY_PCT, } from "./installment-allocation.js";
3
3
  import { getEscalatedAnnualAmountMXN, isCorporateRentalUnit, } from "./corporate-rent.js";
4
+ import { CASHBACK_PAYMENT_OPTION_STAGED, normalizeCashbackPaymentOptionId, UONDR_CASHBACK_STAGED_COMPLETED_RATE, UONDR_CASHBACK_STAGED_DELIVERY_PCT, UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT, UONDR_CASHBACK_STAGED_INITIAL_RATE, UONDR_CASHBACK_STAGED_MONTHLY_COUNT, UONDR_CASHBACK_STAGED_MONTHLY_PAYMENT_PCT, UONDR_CASHBACK_STAGED_MONTHLY_PCT, } from "./cashback-payment-options.js";
4
5
  import { applyIvaModeMXN } from "./tax.js";
5
6
  function getVisibleTimelineRows(rows, deferredPaymentMonths, deliveryMonths, isCashback, cashbackMonths) {
6
7
  const visibleMonths = new Set([
@@ -35,33 +36,49 @@ export function calculateQuote(unit, input, scheme, rules) {
35
36
  ? Math.min(deliveryMonths, MAX_CASHBACK_MONTHS)
36
37
  : 0;
37
38
  const isCashbackCapped = isCashback && cashbackMonths < deliveryMonths;
39
+ const preDeliveryPaymentMonths = Math.max(deliveryMonths - 1, 0);
40
+ const cashbackPaymentOption = normalizeCashbackPaymentOptionId(input.cashbackPaymentOption);
41
+ const isStagedCashback = isCashback && cashbackPaymentOption === CASHBACK_PAYMENT_OPTION_STAGED;
42
+ const stagedCashbackMonthlyPaymentCount = isStagedCashback
43
+ ? Math.min(UONDR_CASHBACK_STAGED_MONTHLY_COUNT, preDeliveryPaymentMonths)
44
+ : 0;
45
+ const stagedCashbackMonthlyPaymentPct = stagedCashbackMonthlyPaymentCount *
46
+ UONDR_CASHBACK_STAGED_MONTHLY_PAYMENT_PCT;
47
+ const stagedCashbackMonthlyPctRolledToDelivery = isStagedCashback
48
+ ? UONDR_CASHBACK_STAGED_MONTHLY_PCT - stagedCashbackMonthlyPaymentPct
49
+ : 0;
38
50
  const installmentAllocation = isInstallmentScheme(scheme)
39
51
  ? getInstallmentAllocation(input.installmentDownPaymentPct ?? INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT, input.installmentMonthlyPct ?? INSTALLMENT_DEFAULT_MONTHLY_PCT)
40
52
  : null;
41
53
  const downPaymentPct = isCashback
42
- ? input.downPaymentPct
54
+ ? isStagedCashback
55
+ ? UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT
56
+ : input.downPaymentPct
43
57
  : (installmentAllocation?.downPaymentPct ?? 0);
44
58
  const downPaymentAmountMXN = listPriceMXN * (downPaymentPct / 100);
45
59
  const balanceAmountMXN = listPriceMXN - downPaymentAmountMXN;
46
- const cashbackMonthlyMXN = isCashback
47
- ? (downPaymentAmountMXN * scheme.cashbackRate) / 12
48
- : 0;
49
- const cashbackTotalMXN = cashbackMonthlyMXN * cashbackMonths;
50
60
  const rawMonthlyPaymentPct = installmentAllocation?.monthlyPct ?? 0;
51
61
  const rawDeferredPaymentPct = installmentAllocation?.deferredPct ?? 0;
52
- const preDeliveryPaymentMonths = Math.max(deliveryMonths - 1, 0);
53
62
  const deferredPaymentsCount = installmentAllocation && rawDeferredPaymentPct > 0
54
63
  ? Math.floor(preDeliveryPaymentMonths / 12)
55
64
  : 0;
56
65
  const deferredPaymentMonths = Array.from({ length: deferredPaymentsCount }, (_, index) => (index + 1) * 12);
57
66
  const deferredPaymentMonthSet = new Set(deferredPaymentMonths);
58
- const monthlyPaymentMonths = installmentAllocation && rawMonthlyPaymentPct > 0
59
- ? Array.from({ length: preDeliveryPaymentMonths }, (_, index) => index + 1).filter((month) => !deferredPaymentMonthSet.has(month))
60
- : [];
67
+ const monthlyPaymentMonths = isStagedCashback
68
+ ? Array.from({ length: stagedCashbackMonthlyPaymentCount }, (_, index) => index + 1)
69
+ : installmentAllocation && rawMonthlyPaymentPct > 0
70
+ ? Array.from({ length: preDeliveryPaymentMonths }, (_, index) => index + 1).filter((month) => !deferredPaymentMonthSet.has(month))
71
+ : [];
61
72
  const monthlyPaymentCount = monthlyPaymentMonths.length;
62
73
  const monthlyPaymentMonthSet = new Set(monthlyPaymentMonths);
63
- const monthlyPaymentPct = monthlyPaymentCount > 0 ? rawMonthlyPaymentPct : 0;
64
- const monthlyPctRolledToDelivery = rawMonthlyPaymentPct - monthlyPaymentPct;
74
+ const monthlyPaymentPct = isStagedCashback
75
+ ? stagedCashbackMonthlyPaymentPct
76
+ : monthlyPaymentCount > 0
77
+ ? rawMonthlyPaymentPct
78
+ : 0;
79
+ const monthlyPctRolledToDelivery = isStagedCashback
80
+ ? stagedCashbackMonthlyPctRolledToDelivery
81
+ : rawMonthlyPaymentPct - monthlyPaymentPct;
65
82
  const monthlyPaymentTotalMXN = listPriceMXN * (monthlyPaymentPct / 100);
66
83
  const monthlyPaymentAmountMXN = monthlyPaymentCount > 0 ? monthlyPaymentTotalMXN / monthlyPaymentCount : 0;
67
84
  const deferredPaymentPct = deferredPaymentsCount > 0 ? rawDeferredPaymentPct : 0;
@@ -74,10 +91,36 @@ export function calculateQuote(unit, input, scheme, rules) {
74
91
  ? installmentAllocation.deliveryPct +
75
92
  monthlyPctRolledToDelivery +
76
93
  deferredPctRolledToDelivery
77
- : 100 - downPaymentPct;
94
+ : isStagedCashback
95
+ ? UONDR_CASHBACK_STAGED_DELIVERY_PCT + monthlyPctRolledToDelivery
96
+ : 100 - downPaymentPct;
78
97
  const deliveryPaymentAmountMXN = installmentAllocation
79
98
  ? listPriceMXN * (deliveryPaymentPct / 100)
80
- : balanceAmountMXN;
99
+ : isStagedCashback
100
+ ? listPriceMXN * (deliveryPaymentPct / 100)
101
+ : balanceAmountMXN;
102
+ function getCashbackForMonthMXN(month) {
103
+ if (!isCashback || month < 1 || month > cashbackMonths) {
104
+ return 0;
105
+ }
106
+ if (isStagedCashback) {
107
+ const rate = month <= UONDR_CASHBACK_STAGED_MONTHLY_COUNT
108
+ ? UONDR_CASHBACK_STAGED_INITIAL_RATE
109
+ : UONDR_CASHBACK_STAGED_COMPLETED_RATE;
110
+ const basePct = month <= UONDR_CASHBACK_STAGED_MONTHLY_COUNT
111
+ ? UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT
112
+ : UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT +
113
+ UONDR_CASHBACK_STAGED_MONTHLY_PCT;
114
+ return (listPriceMXN * (basePct / 100) * rate) / 12;
115
+ }
116
+ return (downPaymentAmountMXN * scheme.cashbackRate) / 12;
117
+ }
118
+ const cashbackMonthlyMXN = isCashback
119
+ ? Math.max(...Array.from({ length: cashbackMonths }, (_, index) => getCashbackForMonthMXN(index + 1)), 0)
120
+ : 0;
121
+ const cashbackTotalMXN = isCashback
122
+ ? Array.from({ length: cashbackMonths }, (_, index) => getCashbackForMonthMXN(index + 1)).reduce((total, amount) => total + amount, 0)
123
+ : 0;
81
124
  const installmentDiscountPct = installmentAllocation
82
125
  ? getInstallmentDiscountPct(installmentAllocation.downPaymentPct)
83
126
  : 0;
@@ -99,6 +142,10 @@ export function calculateQuote(unit, input, scheme, rules) {
99
142
  const milestones = {};
100
143
  if (isCashback) {
101
144
  milestones[1] = rules.milestones.cashbackStart;
145
+ if (isStagedCashback &&
146
+ cashbackMonths > UONDR_CASHBACK_STAGED_MONTHLY_COUNT) {
147
+ milestones[UONDR_CASHBACK_STAGED_MONTHLY_COUNT + 1] = "Cashback 6%";
148
+ }
102
149
  milestones[Math.round(deliveryMonths / 2)] = rules.milestones.midpoint;
103
150
  if (isCashbackCapped) {
104
151
  milestones[cashbackMonths] = "Último cashback";
@@ -137,13 +184,18 @@ export function calculateQuote(unit, input, scheme, rules) {
137
184
  const assetValueMXN = listPriceMXN * Math.pow(1 + plusvaliaMonthlyRate, month);
138
185
  const plusvaliaDeltaMXN = assetValueMXN - previousAssetValueMXN;
139
186
  accumulatedPlusvaliaMXN += plusvaliaDeltaMXN;
140
- const cashbackForMonthMXN = isCashback && month <= cashbackMonths ? cashbackMonthlyMXN : 0;
187
+ const cashbackForMonthMXN = getCashbackForMonthMXN(month);
141
188
  let paymentMXN = 0;
142
189
  let paymentLabel = "—";
143
190
  if (isCashback) {
191
+ const hasMonthlyPaymentForMonth = monthlyPaymentPct > 0 && monthlyPaymentMonthSet.has(month);
192
+ if (hasMonthlyPaymentForMonth) {
193
+ paymentMXN += adjustedMonthlyAmountMXN;
194
+ paymentLabel = "Mensualidad cashback";
195
+ }
144
196
  if (month === deliveryMonths) {
145
- paymentMXN = balanceAmountMXN;
146
- paymentLabel = "Saldo contra entrega";
197
+ paymentMXN += deliveryPaymentAmountMXN;
198
+ paymentLabel = `Saldo contra entrega ${deliveryPaymentPct}%`;
147
199
  }
148
200
  }
149
201
  else if (installmentAllocation) {
@@ -228,9 +280,18 @@ export function calculateQuote(unit, input, scheme, rules) {
228
280
  valueMXN: -downPaymentAmountMXN,
229
281
  tone: "negative",
230
282
  },
283
+ ...(monthlyPaymentPct > 0
284
+ ? [
285
+ {
286
+ label: `Mensualidades (${monthlyPaymentPct}%)`,
287
+ valueMXN: -monthlyPaymentTotalMXN,
288
+ tone: "negative",
289
+ },
290
+ ]
291
+ : []),
231
292
  {
232
- label: `Saldo contra entrega (${100 - downPaymentPct}%)`,
233
- valueMXN: -balanceAmountMXN,
293
+ label: `Contra entrega (${deliveryPaymentPct}%)`,
294
+ valueMXN: -deliveryPaymentAmountMXN,
234
295
  tone: "negative",
235
296
  },
236
297
  {
@@ -342,7 +403,11 @@ export function calculateQuote(unit, input, scheme, rules) {
342
403
  return {
343
404
  unit,
344
405
  scheme,
345
- input,
406
+ input: {
407
+ ...input,
408
+ downPaymentPct,
409
+ cashbackPaymentOption,
410
+ },
346
411
  listPriceMXN,
347
412
  deliveryMonths,
348
413
  isCashback,
@@ -0,0 +1,28 @@
1
+ import type { CashbackPaymentOptionId, CashbackPaymentScheme, RangeRule } from "./types.js";
2
+ export declare const CASHBACK_PAYMENT_OPTION_DIRECT = "direct";
3
+ export declare const CASHBACK_PAYMENT_OPTION_STAGED = "staged";
4
+ export declare const UONDR_CASHBACK_DIRECT_DOWN_PAYMENT_PCT = 60;
5
+ export declare const UONDR_CASHBACK_DIRECT_MIN_DOWN_PAYMENT_PCT = 60;
6
+ export declare const UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT = 30;
7
+ export declare const UONDR_CASHBACK_STAGED_MONTHLY_PCT = 30;
8
+ export declare const UONDR_CASHBACK_STAGED_MONTHLY_COUNT = 6;
9
+ export declare const UONDR_CASHBACK_STAGED_MONTHLY_PAYMENT_PCT: number;
10
+ export declare const UONDR_CASHBACK_STAGED_DELIVERY_PCT = 40;
11
+ export declare const UONDR_CASHBACK_STAGED_INITIAL_RATE = 0.04;
12
+ export declare const UONDR_CASHBACK_STAGED_COMPLETED_RATE = 0.06;
13
+ export declare const CASHBACK_PAYMENT_OPTIONS: readonly [{
14
+ readonly id: "direct";
15
+ readonly label: "8% - 60/40";
16
+ readonly name: "Cashback directo";
17
+ readonly description: "60% de enganche y 40% contra entrega.";
18
+ }, {
19
+ readonly id: "staged";
20
+ readonly label: "4-6% 30/30/40";
21
+ readonly name: "Cashback por etapas";
22
+ readonly description: "30% de enganche, 30% en 6 meses y 40% contra entrega.";
23
+ }];
24
+ export declare function isCashbackPaymentOptionId(value: unknown): value is CashbackPaymentOptionId;
25
+ export declare function normalizeCashbackPaymentOptionId(value: unknown): CashbackPaymentOptionId;
26
+ export declare function getUondrCashbackDirectDownPaymentRange(scheme: CashbackPaymentScheme): RangeRule;
27
+ export declare function getUondrCashbackOptionDownPaymentPct(scheme: CashbackPaymentScheme, optionId: CashbackPaymentOptionId, requestedPct?: number): number;
28
+ export declare function getUondrCashbackOptionMonthlyPct(optionId: CashbackPaymentOptionId): 0 | 30;
@@ -0,0 +1,53 @@
1
+ import { clampToRange } from "./payment-schemes.js";
2
+ export const CASHBACK_PAYMENT_OPTION_DIRECT = "direct";
3
+ export const CASHBACK_PAYMENT_OPTION_STAGED = "staged";
4
+ export const UONDR_CASHBACK_DIRECT_DOWN_PAYMENT_PCT = 60;
5
+ export const UONDR_CASHBACK_DIRECT_MIN_DOWN_PAYMENT_PCT = 60;
6
+ export const UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT = 30;
7
+ export const UONDR_CASHBACK_STAGED_MONTHLY_PCT = 30;
8
+ export const UONDR_CASHBACK_STAGED_MONTHLY_COUNT = 6;
9
+ export const UONDR_CASHBACK_STAGED_MONTHLY_PAYMENT_PCT = UONDR_CASHBACK_STAGED_MONTHLY_PCT / UONDR_CASHBACK_STAGED_MONTHLY_COUNT;
10
+ export const UONDR_CASHBACK_STAGED_DELIVERY_PCT = 40;
11
+ export const UONDR_CASHBACK_STAGED_INITIAL_RATE = 0.04;
12
+ export const UONDR_CASHBACK_STAGED_COMPLETED_RATE = 0.06;
13
+ export const CASHBACK_PAYMENT_OPTIONS = [
14
+ {
15
+ id: CASHBACK_PAYMENT_OPTION_DIRECT,
16
+ label: "8% - 60/40",
17
+ name: "Cashback directo",
18
+ description: "60% de enganche y 40% contra entrega.",
19
+ },
20
+ {
21
+ id: CASHBACK_PAYMENT_OPTION_STAGED,
22
+ label: "4-6% 30/30/40",
23
+ name: "Cashback por etapas",
24
+ description: "30% de enganche, 30% en 6 meses y 40% contra entrega.",
25
+ },
26
+ ];
27
+ export function isCashbackPaymentOptionId(value) {
28
+ return (value === CASHBACK_PAYMENT_OPTION_DIRECT ||
29
+ value === CASHBACK_PAYMENT_OPTION_STAGED);
30
+ }
31
+ export function normalizeCashbackPaymentOptionId(value) {
32
+ return isCashbackPaymentOptionId(value)
33
+ ? value
34
+ : CASHBACK_PAYMENT_OPTION_DIRECT;
35
+ }
36
+ export function getUondrCashbackDirectDownPaymentRange(scheme) {
37
+ return {
38
+ min: UONDR_CASHBACK_DIRECT_MIN_DOWN_PAYMENT_PCT,
39
+ max: Math.max(scheme.downPaymentRange.max, UONDR_CASHBACK_DIRECT_DOWN_PAYMENT_PCT),
40
+ step: scheme.downPaymentRange.step,
41
+ };
42
+ }
43
+ export function getUondrCashbackOptionDownPaymentPct(scheme, optionId, requestedPct) {
44
+ if (optionId === CASHBACK_PAYMENT_OPTION_STAGED) {
45
+ return UONDR_CASHBACK_STAGED_DOWN_PAYMENT_PCT;
46
+ }
47
+ return clampToRange(requestedPct ?? UONDR_CASHBACK_DIRECT_DOWN_PAYMENT_PCT, getUondrCashbackDirectDownPaymentRange(scheme));
48
+ }
49
+ export function getUondrCashbackOptionMonthlyPct(optionId) {
50
+ return optionId === CASHBACK_PAYMENT_OPTION_STAGED
51
+ ? UONDR_CASHBACK_STAGED_MONTHLY_PCT
52
+ : 0;
53
+ }
@@ -0,0 +1,124 @@
1
+ export declare const UONDR_EMBED_PROTOCOL_CONTRACT: {
2
+ readonly version: "1.0";
3
+ readonly frozenOn: "2026-07-26";
4
+ readonly owner: "uondr";
5
+ readonly summary: "Bidirectional postMessage contract between a partner website (host) and the UONDR configurator iframe. The host never calls a UONDR HTTP API; this channel plus the Lapa journey API is the entire integration surface.";
6
+ readonly sources: {
7
+ readonly hostCommand: "lapa.partner";
8
+ readonly configuratorEvent: "uondr.configurator";
9
+ };
10
+ readonly commandTypes: readonly ["initialize", "restrict_designs", "restore", "complete", "cancel"];
11
+ readonly eventTypes: readonly ["ready", "changed", "completed", "cancelled", "resize", "error"];
12
+ readonly commandEnvelope: {
13
+ readonly requiredKeys: readonly ["source", "protocolVersion", "sessionId", "type"];
14
+ readonly optionalKeys: readonly ["requestId", "payload"];
15
+ readonly unknownKeysRejected: true;
16
+ };
17
+ readonly eventEnvelope: {
18
+ readonly requiredKeys: readonly ["source", "protocolVersion", "sessionId", "sequence", "type"];
19
+ readonly optionalKeys: readonly ["configuration", "payload", "requestId"];
20
+ readonly unknownKeysRejected: false;
21
+ };
22
+ readonly limits: {
23
+ readonly sessionIdMaxLength: 256;
24
+ readonly requestIdMaxLength: 128;
25
+ readonly commandFingerprintMaxLength: 16384;
26
+ readonly commandHistoryMax: 256;
27
+ readonly earlyCommandQueueMax: 32;
28
+ };
29
+ readonly commands: {
30
+ readonly initialize: {
31
+ readonly order: "First command. Sent once, only after the ready event.";
32
+ readonly payload: {
33
+ readonly allowedDesignIds: readonly ["<stable-design-id>"];
34
+ readonly externalMode: true;
35
+ readonly hidePricing: true;
36
+ };
37
+ readonly rules: readonly ["externalMode and hidePricing must both be literal true.", "allowedDesignIds must be a non-empty array of unique, non-empty strings with no leading or trailing whitespace, and a subset of the session scope.", "A second initialize is rejected."];
38
+ };
39
+ readonly restrict_designs: {
40
+ readonly order: "After initialize. Narrows the visible design set.";
41
+ readonly payload: {
42
+ readonly allowedDesignIds: readonly ["<stable-design-id>"];
43
+ };
44
+ readonly rules: readonly ["allowedDesignIds must be a subset of the session scope; the configurator intersects rather than widens."];
45
+ };
46
+ readonly restore: {
47
+ readonly order: "After initialize, when Lapa reports an active configuration.";
48
+ readonly payload: {
49
+ readonly configuration: {
50
+ readonly configurationId: "<stable-configuration-id>";
51
+ readonly hash: "<canonical-hash>";
52
+ readonly revision: 1;
53
+ };
54
+ };
55
+ readonly rules: readonly ["revision must be a safe integer >= 1 and strictly greater than the last accepted restore revision.", "configurationId and hash must be strings."];
56
+ };
57
+ readonly complete: {
58
+ readonly order: "After initialize, when the buyer confirms the configuration.";
59
+ readonly payload: {};
60
+ readonly rules: readonly ["The configurator persists an immutable revision and answers with a completed event."];
61
+ };
62
+ readonly cancel: {
63
+ readonly order: "After initialize. Terminal.";
64
+ readonly payload: {};
65
+ readonly rules: readonly ["The configurator accepts no further commands on this session."];
66
+ };
67
+ };
68
+ readonly events: {
69
+ readonly ready: {
70
+ readonly order: "Emitted unprompted at sequence 0 as soon as the session validates. Wait for it before sending initialize.";
71
+ readonly payload: {
72
+ readonly allowedDesignIds: readonly ["<stable-design-id>"];
73
+ readonly capabilities: {
74
+ readonly commands: readonly ["initialize", "restrict_designs", "restore", "complete", "cancel"];
75
+ readonly externalMode: true;
76
+ readonly hidePricing: true;
77
+ };
78
+ };
79
+ };
80
+ readonly changed: {
81
+ readonly order: "Emitted on every buyer selection change after initialize.";
82
+ readonly payload: {
83
+ readonly designId: "<stable-design-id>";
84
+ readonly materialSelectionCount: 0;
85
+ readonly modifierSelectionCount: 0;
86
+ };
87
+ readonly rules: readonly ["Carries no monetary value. Never derive a price or total from this event."];
88
+ };
89
+ readonly completed: {
90
+ readonly order: "Emitted once in response to complete.";
91
+ readonly configuration: {
92
+ readonly configurationId: "<stable-configuration-id>";
93
+ readonly hash: "<canonical-hash>";
94
+ readonly revision: 1;
95
+ };
96
+ readonly rules: readonly ["Carries only the stable reference. Submit those three fields to Lapa; Lapa fetches and validates the immutable snapshot server-to-server.", "No snapshot, no internal identifier, and no pricing is exposed to the host."];
97
+ };
98
+ readonly cancelled: {
99
+ readonly order: "Emitted in response to cancel. Terminal.";
100
+ readonly payload: {
101
+ readonly reason: "<optional-reason-code>";
102
+ };
103
+ };
104
+ readonly resize: {
105
+ readonly order: "Emitted whenever the configurator content height changes.";
106
+ readonly payload: {
107
+ readonly height: 720;
108
+ };
109
+ readonly rules: readonly ["Apply to the iframe height. The configurator never resizes itself."];
110
+ };
111
+ readonly error: {
112
+ readonly order: "Emitted on a recoverable configurator failure.";
113
+ readonly payload: {
114
+ readonly code: "external_configurator_error";
115
+ readonly message: "<safe-buyer-facing-message>";
116
+ readonly retryable: true;
117
+ };
118
+ };
119
+ };
120
+ readonly hostRules: readonly ["Mount the embedUrl returned by Lapa unchanged. Never construct a UONDR URL yourself.", "Derive the target origin with new URL(embedUrl).origin and pass it to every postMessage. Never use '*'.", "Accept an event only when event.origin equals that origin, event.source is the iframe contentWindow, source is 'uondr.configurator', protocolVersion is '1.0', sessionId matches, and sequence is strictly greater than the last accepted sequence.", "The configurator resolves the parent origin from document.referrer and fails closed when it is absent. The host page must send a referrer: do not set Referrer-Policy to no-referrer, same-origin, origin-when-cross-origin, or strict-origin on the page that mounts the iframe.", "Send each command at most once. Duplicate command bodies and duplicate requestIds are dropped.", "Treat completed and cancelled as terminal; request a new session to configure again."];
121
+ readonly configuratorRules: readonly ["Accept a command only when event.origin equals the session parentOrigin and event.source is the parent window.", "Reject every command that arrives before initialize.", "Emit one monotonically increasing sequence per session, starting at 0.", "Hide every monetary amount, currency label, and financing surface in external mode.", "Fail closed on expired, revoked, cross-session, malformed, stale, or replayed input."];
122
+ };
123
+ export declare const UONDR_EMBED_PROTOCOL_CONTRACT_DIGEST = "sha256:f8c00ed49a9364a2f5b35d9073779767b744639e1b7d943ac669e60473080316";
124
+ export declare function canonicalProtocolContractJson(): string;