@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 +111 -2
- package/dist/calculate-quote.js +84 -19
- package/dist/cashback-payment-options.d.ts +28 -0
- package/dist/cashback-payment-options.js +53 -0
- package/dist/embed-protocol-contract.d.ts +124 -0
- package/dist/embed-protocol-contract.js +194 -0
- package/dist/embed-protocol.d.ts +55 -0
- package/dist/embed-protocol.js +70 -0
- package/dist/http.d.ts +28 -1
- package/dist/http.js +40 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +15 -0
- package/dist/payment-schemes.d.ts +12 -1
- package/dist/payment-schemes.js +56 -0
- package/dist/query-state.d.ts +2 -2
- package/dist/query-state.js +40 -5
- package/dist/quote.d.ts +1 -0
- package/dist/quote.js +1 -0
- package/dist/selection-state.d.ts +17 -2
- package/dist/selection-state.js +47 -18
- package/dist/social-quote.d.ts +2 -2
- package/dist/social-quote.js +20 -5
- package/dist/types.d.ts +144 -0
- package/package.json +8 -3
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
|
-
|
|
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.
|
package/dist/calculate-quote.js
CHANGED
|
@@ -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
|
-
?
|
|
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 =
|
|
59
|
-
? Array.from({ length:
|
|
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 =
|
|
64
|
-
|
|
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
|
-
:
|
|
94
|
+
: isStagedCashback
|
|
95
|
+
? UONDR_CASHBACK_STAGED_DELIVERY_PCT + monthlyPctRolledToDelivery
|
|
96
|
+
: 100 - downPaymentPct;
|
|
78
97
|
const deliveryPaymentAmountMXN = installmentAllocation
|
|
79
98
|
? listPriceMXN * (deliveryPaymentPct / 100)
|
|
80
|
-
:
|
|
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 =
|
|
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
|
|
146
|
-
paymentLabel =
|
|
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: `
|
|
233
|
-
valueMXN: -
|
|
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;
|