@grupolapa/desarrollos-sdk 0.1.0 → 0.3.1
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 +139 -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,65 @@ 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
|
+
Building a partner website that hosts the configurable cotizador? Read this
|
|
38
|
+
single self-contained guide; it needs no other document:
|
|
39
|
+
|
|
40
|
+
```txt
|
|
41
|
+
https://grupolapa.com/api/public/v1/desarrollos/partner-agent.json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
It covers environment setup, the journey call order, every error code, the
|
|
45
|
+
frozen UONDR embed protocol `1.0` contract, and the host page requirements.
|
|
46
|
+
|
|
47
|
+
Maintaining UONDR itself uses the cross-project contract reference:
|
|
48
|
+
|
|
49
|
+
```txt
|
|
50
|
+
https://grupolapa.com/api/public/v1/desarrollos/uondr-agent.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Embed Protocol
|
|
54
|
+
|
|
55
|
+
`@grupolapa/desarrollos-sdk/embed` carries the iframe contract shared with
|
|
56
|
+
UONDR. `UONDR_EMBED_PROTOCOL_CONTRACT` is a frozen literal mirrored
|
|
57
|
+
byte-for-byte in the UONDR repository and verified by contract tests in both;
|
|
58
|
+
`UONDR_EMBED_PROTOCOL_CONTRACT_DIGEST` lets a consumer confirm it matches the
|
|
59
|
+
deployed guide.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import {
|
|
63
|
+
createUondrCommand,
|
|
64
|
+
postUondrCommand,
|
|
65
|
+
UondrEventGuard,
|
|
66
|
+
UONDR_EMBED_PROTOCOL_CONTRACT,
|
|
67
|
+
UONDR_EMBED_PROTOCOL_CONTRACT_DIGEST,
|
|
68
|
+
} from "@grupolapa/desarrollos-sdk/embed";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Never post to a wildcard target origin. Derive it from the `embedUrl` Lapa
|
|
72
|
+
returns, and note that the configurator resolves its parent origin from
|
|
73
|
+
`document.referrer`, so the host page must not send a strict `Referrer-Policy`.
|
|
74
|
+
|
|
28
75
|
## Browser Reads and Quotes
|
|
29
76
|
|
|
30
77
|
```ts
|
|
@@ -79,6 +126,96 @@ const quote =
|
|
|
79
126
|
: null;
|
|
80
127
|
```
|
|
81
128
|
|
|
129
|
+
## Browser Journey Flow
|
|
130
|
+
|
|
131
|
+
The journey client is browser-only and does not accept a Lapa API key. Create a
|
|
132
|
+
journey with the integration's publishable key, then authenticate every
|
|
133
|
+
journey-scoped call with the returned opaque token. The optional token on
|
|
134
|
+
`createJourney` resumes the same journey when it is still valid.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { createDesarrollosClient } from "@grupolapa/desarrollos-sdk";
|
|
138
|
+
|
|
139
|
+
const client = createDesarrollosClient({
|
|
140
|
+
baseUrl: "https://grupolapa.com/api/public/v1/desarrollos",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const created = await client.createJourney({
|
|
144
|
+
integrationKey: "lapa_partner_public_key",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const journeyId = created.journey.id;
|
|
148
|
+
const journeyToken = created.journeyToken;
|
|
149
|
+
|
|
150
|
+
await client.updateJourneySelection({
|
|
151
|
+
journeyId,
|
|
152
|
+
journeyToken,
|
|
153
|
+
selection: { unitId: "unit_1" },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const { session } = await client.createJourneyConfiguratorSession({
|
|
157
|
+
journeyId,
|
|
158
|
+
journeyToken,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Mount session.embedUrl in the UONDR iframe and wait for its completed event.
|
|
162
|
+
await client.completeJourneyConfiguration({
|
|
163
|
+
journeyId,
|
|
164
|
+
journeyToken,
|
|
165
|
+
configuration: {
|
|
166
|
+
configurationId: "configuration_01",
|
|
167
|
+
revision: 1,
|
|
168
|
+
hash: "sha256-canonical-configuration-hash",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const quotedJourney = await client.createJourneyQuote({
|
|
173
|
+
journeyId,
|
|
174
|
+
journeyToken,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Always render the authoritative quote returned by Lapa.
|
|
178
|
+
const quote = quotedJourney.quote;
|
|
179
|
+
|
|
180
|
+
await client.updateJourneyLead({
|
|
181
|
+
journeyId,
|
|
182
|
+
journeyToken,
|
|
183
|
+
lead: {
|
|
184
|
+
name: "Ada Lovelace",
|
|
185
|
+
phone: "+529991234567",
|
|
186
|
+
email: "ada@example.com",
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const turnstileToken = "token-returned-by-the-turnstile-widget";
|
|
191
|
+
const checkoutJourney = await client.createJourneyCheckout({
|
|
192
|
+
journeyId,
|
|
193
|
+
journeyToken,
|
|
194
|
+
checkout: {
|
|
195
|
+
idempotencyKey: crypto.randomUUID(),
|
|
196
|
+
turnstileToken,
|
|
197
|
+
payment: {
|
|
198
|
+
provider: "stripe",
|
|
199
|
+
returnUrl: "https://partner.example.com/confirmation",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (checkoutJourney.checkout?.checkoutUrl) {
|
|
205
|
+
window.location.assign(checkoutJourney.checkout.checkoutUrl);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Persist only the versioned opaque `journeyToken` in browser storage. Keep PII,
|
|
210
|
+
configuration snapshots, quote amounts, and payment-provider data out of local
|
|
211
|
+
storage. Visible step and unit state can live in URL search parameters; tokens
|
|
212
|
+
and prices must not.
|
|
213
|
+
|
|
214
|
+
Journey mutations return the authoritative aggregate journey state, including
|
|
215
|
+
configuration, quote, hold, checkout, payment-conflict, and draft-operation
|
|
216
|
+
status. Client quote helpers remain provisional and must not replace the Lapa
|
|
217
|
+
quote returned by `createJourneyQuote`.
|
|
218
|
+
|
|
82
219
|
## Trusted Server Flow
|
|
83
220
|
|
|
84
221
|
Keep `lapa_dev_<prefix>_<secret>` API keys on a server. Use the trusted server
|
|
@@ -145,7 +282,7 @@ if (map) {
|
|
|
145
282
|
## Exports
|
|
146
283
|
|
|
147
284
|
- `@grupolapa/desarrollos-sdk`: browser-safe client, public types, and
|
|
148
|
-
`DesarrollosApiError
|
|
285
|
+
`DesarrollosApiError`, including the token-authenticated journey methods.
|
|
149
286
|
- `@grupolapa/desarrollos-sdk/server`: trusted server API-key client.
|
|
150
287
|
- `@grupolapa/desarrollos-sdk/quote`: quote, selection, payment, inventory,
|
|
151
288
|
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;
|