@blamejs/blamejs-shop 0.0.72 → 0.0.75
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/CHANGELOG.md +6 -0
- package/lib/announcement-bar.js +753 -0
- package/lib/banner-ab-tests.js +806 -0
- package/lib/bin-locations.js +791 -0
- package/lib/blog-articles.js +1173 -0
- package/lib/carrier-accounts.js +805 -0
- package/lib/cart-recovery.js +1133 -0
- package/lib/category-navigation.js +934 -0
- package/lib/consent-ledger.js +539 -0
- package/lib/customer-impersonation.js +743 -0
- package/lib/customer-merge.js +879 -0
- package/lib/demand-forecast.js +1121 -0
- package/lib/dispute-resolution.js +886 -0
- package/lib/email-ab-tests.js +918 -0
- package/lib/email-engagement-score.js +649 -0
- package/lib/event-log.js +713 -0
- package/lib/fulfillment-sla.js +791 -0
- package/lib/index.js +41 -0
- package/lib/inventory-audits.js +852 -0
- package/lib/line-gift-wrap.js +430 -0
- package/lib/marketing-budget.js +792 -0
- package/lib/operator-activity-feed.js +977 -0
- package/lib/operator-approvals.js +942 -0
- package/lib/operator-help-center.js +1020 -0
- package/lib/operator-inbox.js +889 -0
- package/lib/operator-sessions.js +701 -0
- package/lib/order-exchanges.js +602 -0
- package/lib/product-compare.js +804 -0
- package/lib/pwa-manifest.js +1005 -0
- package/lib/referral-leaderboard.js +612 -0
- package/lib/sales-tax-filings.js +807 -0
- package/lib/search-ranking.js +859 -0
- package/lib/shipping-insurance.js +757 -0
- package/lib/shrinkage-report.js +1182 -0
- package/lib/sidebar-widgets.js +952 -0
- package/lib/smart-restocking.js +1048 -0
- package/lib/stock-receipts.js +834 -0
- package/lib/subscription-analytics.js +1032 -0
- package/lib/suggestion-box.js +921 -0
- package/lib/tax-remittance.js +625 -0
- package/lib/vendor-invoices.js +1021 -0
- package/lib/winback-campaigns.js +1350 -0
- package/lib/wishlist-digest.js +1133 -0
- package/package.json +1 -1
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module shop.shippingInsurance
|
|
4
|
+
* @title Shipping insurance — per-shipment third-party parcel insurance
|
|
5
|
+
*
|
|
6
|
+
* @intro
|
|
7
|
+
* Per-shipment insurance via Shipsurance / Loop / U-PIC / Route.
|
|
8
|
+
* The customer opts in for high-value packages at checkout; the
|
|
9
|
+
* storefront calls `quoteInsurance` against the operator-registered
|
|
10
|
+
* provider to get the premium, then `purchaseInsurance` to mint the
|
|
11
|
+
* row + snapshot the premium. The carrier never sees the
|
|
12
|
+
* insurance — it's a parallel agreement between the customer, the
|
|
13
|
+
* operator, and the insurer.
|
|
14
|
+
*
|
|
15
|
+
* The framework does NOT call Shipsurance / Loop / U-PIC / Route
|
|
16
|
+
* APIs directly. Each insurer has its own auth + endpoint + claim
|
|
17
|
+
* portal; the operator's worker drives the integration and calls
|
|
18
|
+
* back into this primitive (`purchaseInsurance` with the minted
|
|
19
|
+
* external_policy_id; `markClaimApproved` / `markClaimDenied`
|
|
20
|
+
* with the insurer's decision). The primitive owns the lifecycle
|
|
21
|
+
* bookkeeping, the premium-quote math, and the claim FSM.
|
|
22
|
+
*
|
|
23
|
+
* Premium math: `premium = max(premium_min_minor,
|
|
24
|
+
* ceil(declared_value_minor * premium_rate_bps / 10000))`. Basis
|
|
25
|
+
* points (1/100ths of a percent) carry zero floating-point
|
|
26
|
+
* surprise — the calculation is integer-only and rounds up on
|
|
27
|
+
* half-cent so the insurer is never short. The provider's floor
|
|
28
|
+
* (`premium_min_minor`) prevents a $1 declared parcel from
|
|
29
|
+
* computing a 0-cent premium.
|
|
30
|
+
*
|
|
31
|
+
* Insurance FSM:
|
|
32
|
+
*
|
|
33
|
+
* active --cancelInsurance--> cancelled (terminal)
|
|
34
|
+
* active --(post claim_window)--> expired (terminal)
|
|
35
|
+
*
|
|
36
|
+
* Claim FSM:
|
|
37
|
+
*
|
|
38
|
+
* filed --markClaimApproved--> approved (terminal)
|
|
39
|
+
* filed --markClaimDenied--> denied (terminal)
|
|
40
|
+
*
|
|
41
|
+
* Filing a claim requires the parent insurance to be `active` AND
|
|
42
|
+
* the current monotonic clock to be on/before
|
|
43
|
+
* `claim_window_ends_at`. Once the window elapses the insurance is
|
|
44
|
+
* `expired` and no new claims may be filed; existing `filed` claims
|
|
45
|
+
* are still resolvable (the insurer's review may take longer than
|
|
46
|
+
* the customer's filing window).
|
|
47
|
+
*
|
|
48
|
+
* Composes:
|
|
49
|
+
* - `b.guardUuid` — strict UUID gate on every order_id /
|
|
50
|
+
* shipment_id / customer_id / insurance_id /
|
|
51
|
+
* claim_id at the entry point.
|
|
52
|
+
* - `b.uuid.v7` — row ids (lexicographic + monotonic so
|
|
53
|
+
* ties on created_at still sort
|
|
54
|
+
* deterministically).
|
|
55
|
+
* - `shippingLabels` (optional) — when wired, `quoteInsurance({
|
|
56
|
+
* shipment_id })` uses `labelsForShipment`
|
|
57
|
+
* to verify the shipment has a label
|
|
58
|
+
* before quoting. Absent, callers may
|
|
59
|
+
* quote against any shipment_id (the
|
|
60
|
+
* operator's storefront is responsible
|
|
61
|
+
* for the existence check).
|
|
62
|
+
*
|
|
63
|
+
* Surface:
|
|
64
|
+
* - defineProvider({ code, name, premium_rate_bps,
|
|
65
|
+
* premium_min_minor, min_declared_value_minor,
|
|
66
|
+
* max_declared_value_minor, claim_window_days,
|
|
67
|
+
* currency, active? })
|
|
68
|
+
* - quoteInsurance({ provider_code, declared_value_minor,
|
|
69
|
+
* currency, shipment_id? })
|
|
70
|
+
* - purchaseInsurance({ provider_code, shipment_id, order_id,
|
|
71
|
+
* customer_id, declared_value_minor, currency,
|
|
72
|
+
* external_policy_id? })
|
|
73
|
+
* - fileClaim({ insurance_id, claim_type, claimed_amount_minor,
|
|
74
|
+
* evidence? })
|
|
75
|
+
* - markClaimApproved({ claim_id, payout_minor })
|
|
76
|
+
* - markClaimDenied({ claim_id, denial_reason })
|
|
77
|
+
* - getInsurance(insurance_id)
|
|
78
|
+
* - insurancesForOrder(order_id)
|
|
79
|
+
* - claimsForInsurance(insurance_id)
|
|
80
|
+
* - metricsForProvider({ provider_code, from, to })
|
|
81
|
+
*
|
|
82
|
+
* Storage:
|
|
83
|
+
* - insurance_providers, shipping_insurances, insurance_claims
|
|
84
|
+
* (migration `0166_shipping_insurance.sql`).
|
|
85
|
+
*
|
|
86
|
+
* Monotonic clock: operator-driven FSM transitions can land in the
|
|
87
|
+
* same millisecond on fast machines (markClaimApproved on a freshly
|
|
88
|
+
* filed claim in a test, for instance). Bumping by 1ms on a tie
|
|
89
|
+
* keeps the timeline strictly increasing so a sort-by-timestamp
|
|
90
|
+
* read returns the events in the order they were issued.
|
|
91
|
+
*
|
|
92
|
+
* @primitive shippingInsurance
|
|
93
|
+
* @related b.guardUuid, b.uuid, shippingLabels, orderTracking, payment
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
var bShop;
|
|
97
|
+
function _b() {
|
|
98
|
+
if (!bShop) bShop = require("./index");
|
|
99
|
+
return bShop.framework;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---- constants ---------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
var CODE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
105
|
+
var CURRENCY_RE = /^[A-Z]{3}$/;
|
|
106
|
+
var EXTERNAL_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._\-:/]{0,127}$/;
|
|
107
|
+
var DENIAL_REASON_RE = /^[A-Za-z0-9][A-Za-z0-9 _.,'\-]{0,279}$/;
|
|
108
|
+
var MAX_NAME_LEN = 200;
|
|
109
|
+
var MAX_AMOUNT_MINOR = 1000000000; // $10,000,000.00 — sane upper cap
|
|
110
|
+
var MAX_RATE_BPS = 10000; // 100% — exclusive ceiling
|
|
111
|
+
var MAX_CLAIM_DAYS = 3650; // 10 years — sane upper cap
|
|
112
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
113
|
+
|
|
114
|
+
var CLAIM_TYPES = Object.freeze(["lost", "damaged", "stolen", "not_delivered"]);
|
|
115
|
+
var INSURANCE_STATUSES = Object.freeze(["active", "cancelled", "expired"]);
|
|
116
|
+
var CLAIM_STATUSES = Object.freeze(["filed", "approved", "denied"]);
|
|
117
|
+
|
|
118
|
+
// ---- monotonic clock ---------------------------------------------------
|
|
119
|
+
//
|
|
120
|
+
// Operator-driven FSM transitions can land in the same millisecond on
|
|
121
|
+
// fast machines (markClaimApproved on a freshly filed claim in a test,
|
|
122
|
+
// for instance). Bumping by 1ms on a tie keeps the timeline strictly
|
|
123
|
+
// increasing so a sort-by-timestamp read returns the events in the
|
|
124
|
+
// order they were issued.
|
|
125
|
+
|
|
126
|
+
var _lastTs = 0;
|
|
127
|
+
function _now() {
|
|
128
|
+
var t = Date.now();
|
|
129
|
+
if (t <= _lastTs) { t = _lastTs + 1; }
|
|
130
|
+
_lastTs = t;
|
|
131
|
+
return t;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---- validators --------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
function _uuid(s, label) {
|
|
137
|
+
try {
|
|
138
|
+
return _b().guardUuid.sanitize(s, { profile: "strict" });
|
|
139
|
+
} catch (e) {
|
|
140
|
+
throw new TypeError("shippingInsurance: " + label + " — " + (e && e.message || "invalid UUID"));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function _code(s, label) {
|
|
144
|
+
if (typeof s !== "string" || !CODE_RE.test(s)) {
|
|
145
|
+
throw new TypeError("shippingInsurance: " + (label || "code") +
|
|
146
|
+
" must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/ (alnum + . _ -, 1..64 chars)");
|
|
147
|
+
}
|
|
148
|
+
return s;
|
|
149
|
+
}
|
|
150
|
+
function _name(s) {
|
|
151
|
+
if (typeof s !== "string" || s.length === 0 || s.length > MAX_NAME_LEN) {
|
|
152
|
+
throw new TypeError("shippingInsurance: name must be a non-empty string <= " + MAX_NAME_LEN + " chars");
|
|
153
|
+
}
|
|
154
|
+
return s;
|
|
155
|
+
}
|
|
156
|
+
function _currency(s) {
|
|
157
|
+
if (typeof s !== "string" || !CURRENCY_RE.test(s)) {
|
|
158
|
+
throw new TypeError("shippingInsurance: currency must be a 3-letter uppercase ISO-4217 code");
|
|
159
|
+
}
|
|
160
|
+
return s;
|
|
161
|
+
}
|
|
162
|
+
function _amountMinor(n, label) {
|
|
163
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > MAX_AMOUNT_MINOR) {
|
|
164
|
+
throw new TypeError("shippingInsurance: " + label + " must be a positive integer in [1, " + MAX_AMOUNT_MINOR + "]");
|
|
165
|
+
}
|
|
166
|
+
return n;
|
|
167
|
+
}
|
|
168
|
+
function _amountMinorNonNeg(n, label) {
|
|
169
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 0 || n > MAX_AMOUNT_MINOR) {
|
|
170
|
+
throw new TypeError("shippingInsurance: " + label + " must be a non-negative integer in [0, " + MAX_AMOUNT_MINOR + "]");
|
|
171
|
+
}
|
|
172
|
+
return n;
|
|
173
|
+
}
|
|
174
|
+
function _rateBps(n) {
|
|
175
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 0 || n > MAX_RATE_BPS) {
|
|
176
|
+
throw new TypeError("shippingInsurance: premium_rate_bps must be an integer in [0, " + MAX_RATE_BPS + "] (basis points)");
|
|
177
|
+
}
|
|
178
|
+
return n;
|
|
179
|
+
}
|
|
180
|
+
function _claimDays(n) {
|
|
181
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > MAX_CLAIM_DAYS) {
|
|
182
|
+
throw new TypeError("shippingInsurance: claim_window_days must be an integer in [1, " + MAX_CLAIM_DAYS + "]");
|
|
183
|
+
}
|
|
184
|
+
return n;
|
|
185
|
+
}
|
|
186
|
+
function _claimType(s) {
|
|
187
|
+
if (typeof s !== "string" || CLAIM_TYPES.indexOf(s) === -1) {
|
|
188
|
+
throw new TypeError("shippingInsurance: claim_type must be one of " + CLAIM_TYPES.join(", "));
|
|
189
|
+
}
|
|
190
|
+
return s;
|
|
191
|
+
}
|
|
192
|
+
function _externalPolicyId(s) {
|
|
193
|
+
if (typeof s !== "string" || !EXTERNAL_ID_RE.test(s)) {
|
|
194
|
+
throw new TypeError("shippingInsurance: external_policy_id must match /^[A-Za-z0-9][A-Za-z0-9._\\-:/]{0,127}$/");
|
|
195
|
+
}
|
|
196
|
+
return s;
|
|
197
|
+
}
|
|
198
|
+
function _denialReason(s) {
|
|
199
|
+
if (typeof s !== "string" || !DENIAL_REASON_RE.test(s)) {
|
|
200
|
+
throw new TypeError("shippingInsurance: denial_reason must match /^[A-Za-z0-9][A-Za-z0-9 _.,'\\-]{0,279}$/");
|
|
201
|
+
}
|
|
202
|
+
return s;
|
|
203
|
+
}
|
|
204
|
+
function _evidence(obj) {
|
|
205
|
+
if (obj == null) return {};
|
|
206
|
+
if (typeof obj !== "object" || Array.isArray(obj)) {
|
|
207
|
+
throw new TypeError("shippingInsurance: evidence must be a JSON-serialisable object");
|
|
208
|
+
}
|
|
209
|
+
try { JSON.parse(JSON.stringify(obj)); }
|
|
210
|
+
catch (_e) { throw new TypeError("shippingInsurance: evidence must be JSON-serialisable"); }
|
|
211
|
+
return obj;
|
|
212
|
+
}
|
|
213
|
+
function _epochMs(n, label) {
|
|
214
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 0) {
|
|
215
|
+
throw new TypeError("shippingInsurance: " + label + " must be a non-negative integer (epoch ms)");
|
|
216
|
+
}
|
|
217
|
+
return n;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- premium math -------------------------------------------------------
|
|
221
|
+
//
|
|
222
|
+
// premium = max(premium_min_minor,
|
|
223
|
+
// ceil(declared_value_minor * premium_rate_bps / 10000))
|
|
224
|
+
// Integer-only; rounds up on half-cent so the insurer is never short.
|
|
225
|
+
|
|
226
|
+
function _computePremium(declaredValueMinor, premiumRateBps, premiumMinMinor) {
|
|
227
|
+
var product = declaredValueMinor * premiumRateBps;
|
|
228
|
+
var quotient = Math.floor(product / 10000);
|
|
229
|
+
if (quotient * 10000 < product) { quotient += 1; }
|
|
230
|
+
if (quotient < premiumMinMinor) { quotient = premiumMinMinor; }
|
|
231
|
+
return quotient;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---- hydration ---------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
function _hydrateProvider(row) {
|
|
237
|
+
if (!row) return null;
|
|
238
|
+
return {
|
|
239
|
+
code: row.code,
|
|
240
|
+
name: row.name,
|
|
241
|
+
premium_rate_bps: Number(row.premium_rate_bps),
|
|
242
|
+
premium_min_minor: Number(row.premium_min_minor),
|
|
243
|
+
min_declared_value_minor: Number(row.min_declared_value_minor),
|
|
244
|
+
max_declared_value_minor: Number(row.max_declared_value_minor),
|
|
245
|
+
claim_window_days: Number(row.claim_window_days),
|
|
246
|
+
currency: row.currency,
|
|
247
|
+
active: Number(row.active) === 1,
|
|
248
|
+
archived_at: row.archived_at == null ? null : Number(row.archived_at),
|
|
249
|
+
created_at: Number(row.created_at),
|
|
250
|
+
updated_at: Number(row.updated_at),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function _hydrateInsurance(row) {
|
|
255
|
+
if (!row) return null;
|
|
256
|
+
return {
|
|
257
|
+
id: row.id,
|
|
258
|
+
provider_code: row.provider_code,
|
|
259
|
+
shipment_id: row.shipment_id,
|
|
260
|
+
order_id: row.order_id,
|
|
261
|
+
customer_id: row.customer_id,
|
|
262
|
+
declared_value_minor: Number(row.declared_value_minor),
|
|
263
|
+
premium_minor: Number(row.premium_minor),
|
|
264
|
+
currency: row.currency,
|
|
265
|
+
external_policy_id: row.external_policy_id == null ? null : row.external_policy_id,
|
|
266
|
+
status: row.status,
|
|
267
|
+
claim_window_ends_at: Number(row.claim_window_ends_at),
|
|
268
|
+
cancelled_at: row.cancelled_at == null ? null : Number(row.cancelled_at),
|
|
269
|
+
expired_at: row.expired_at == null ? null : Number(row.expired_at),
|
|
270
|
+
created_at: Number(row.created_at),
|
|
271
|
+
updated_at: Number(row.updated_at),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function _hydrateClaim(row) {
|
|
276
|
+
if (!row) return null;
|
|
277
|
+
var evidence;
|
|
278
|
+
try { evidence = JSON.parse(row.evidence_json || "{}"); }
|
|
279
|
+
catch (_e) { evidence = {}; }
|
|
280
|
+
return {
|
|
281
|
+
id: row.id,
|
|
282
|
+
insurance_id: row.insurance_id,
|
|
283
|
+
claim_type: row.claim_type,
|
|
284
|
+
status: row.status,
|
|
285
|
+
claimed_amount_minor: Number(row.claimed_amount_minor),
|
|
286
|
+
payout_minor: row.payout_minor == null ? null : Number(row.payout_minor),
|
|
287
|
+
denial_reason: row.denial_reason == null ? null : row.denial_reason,
|
|
288
|
+
evidence: evidence,
|
|
289
|
+
filed_at: Number(row.filed_at),
|
|
290
|
+
resolved_at: row.resolved_at == null ? null : Number(row.resolved_at),
|
|
291
|
+
updated_at: Number(row.updated_at),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---- factory -----------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
function create(opts) {
|
|
298
|
+
opts = opts || {};
|
|
299
|
+
var query = opts.query;
|
|
300
|
+
if (!query) {
|
|
301
|
+
query = function (sql, params) { return _b().externalDb.query(sql, params); };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// shippingLabels is optional — when wired, `quoteInsurance({
|
|
305
|
+
// shipment_id })` uses `labelsForShipment` to verify the shipment
|
|
306
|
+
// has a label before quoting. Refuses at boot when the handle is
|
|
307
|
+
// supplied without the expected verb so a typo doesn't degrade
|
|
308
|
+
// silently to "no shipment-existence check."
|
|
309
|
+
var shippingLabelsHandle = opts.shippingLabels || null;
|
|
310
|
+
if (shippingLabelsHandle && typeof shippingLabelsHandle.labelsForShipment !== "function") {
|
|
311
|
+
throw new TypeError("shippingInsurance.create: opts.shippingLabels must expose a labelsForShipment(shipment_id) method");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function _getProviderRow(code) {
|
|
315
|
+
var r = await query("SELECT * FROM insurance_providers WHERE code = ?1", [code]);
|
|
316
|
+
return r.rows.length ? r.rows[0] : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function _getInsuranceRow(id) {
|
|
320
|
+
var r = await query("SELECT * FROM shipping_insurances WHERE id = ?1", [id]);
|
|
321
|
+
return r.rows.length ? r.rows[0] : null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function _getClaimRow(id) {
|
|
325
|
+
var r = await query("SELECT * FROM insurance_claims WHERE id = ?1", [id]);
|
|
326
|
+
return r.rows.length ? r.rows[0] : null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function _getInsuranceByProviderShipment(providerCode, shipmentId) {
|
|
330
|
+
var r = await query(
|
|
331
|
+
"SELECT * FROM shipping_insurances WHERE provider_code = ?1 AND shipment_id = ?2",
|
|
332
|
+
[providerCode, shipmentId],
|
|
333
|
+
);
|
|
334
|
+
return r.rows.length ? r.rows[0] : null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Register a third-party insurer. Upsert semantics on `code` — re-
|
|
338
|
+
// defining the same code updates the row in place (operator
|
|
339
|
+
// changes premium rate / window without invalidating prior
|
|
340
|
+
// insurances; existing insurances carry the snapshotted premium
|
|
341
|
+
// from purchase time so the customer's rate doesn't shift under
|
|
342
|
+
// them). Reactivates an archived provider by clearing archived_at.
|
|
343
|
+
async function defineProvider(input) {
|
|
344
|
+
if (!input || typeof input !== "object") {
|
|
345
|
+
throw new TypeError("shippingInsurance.defineProvider: input object required");
|
|
346
|
+
}
|
|
347
|
+
var code = _code(input.code, "code");
|
|
348
|
+
_name(input.name);
|
|
349
|
+
_rateBps(input.premium_rate_bps);
|
|
350
|
+
_amountMinorNonNeg(input.premium_min_minor, "premium_min_minor");
|
|
351
|
+
_amountMinorNonNeg(input.min_declared_value_minor, "min_declared_value_minor");
|
|
352
|
+
_amountMinor(input.max_declared_value_minor, "max_declared_value_minor");
|
|
353
|
+
if (input.max_declared_value_minor < input.min_declared_value_minor) {
|
|
354
|
+
throw new TypeError("shippingInsurance.defineProvider: max_declared_value_minor must be >= min_declared_value_minor");
|
|
355
|
+
}
|
|
356
|
+
_claimDays(input.claim_window_days);
|
|
357
|
+
_currency(input.currency);
|
|
358
|
+
var active = input.active === false ? 0 : 1;
|
|
359
|
+
|
|
360
|
+
var now = _now();
|
|
361
|
+
var existing = await _getProviderRow(code);
|
|
362
|
+
if (existing) {
|
|
363
|
+
await query(
|
|
364
|
+
"UPDATE insurance_providers SET name = ?1, premium_rate_bps = ?2, " +
|
|
365
|
+
"premium_min_minor = ?3, min_declared_value_minor = ?4, " +
|
|
366
|
+
"max_declared_value_minor = ?5, claim_window_days = ?6, currency = ?7, " +
|
|
367
|
+
"active = ?8, archived_at = NULL, updated_at = ?9 WHERE code = ?10",
|
|
368
|
+
[
|
|
369
|
+
input.name, input.premium_rate_bps, input.premium_min_minor,
|
|
370
|
+
input.min_declared_value_minor, input.max_declared_value_minor,
|
|
371
|
+
input.claim_window_days, input.currency, active, now, code,
|
|
372
|
+
],
|
|
373
|
+
);
|
|
374
|
+
} else {
|
|
375
|
+
await query(
|
|
376
|
+
"INSERT INTO insurance_providers (code, name, premium_rate_bps, " +
|
|
377
|
+
"premium_min_minor, min_declared_value_minor, max_declared_value_minor, " +
|
|
378
|
+
"claim_window_days, currency, active, created_at, updated_at) " +
|
|
379
|
+
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?10)",
|
|
380
|
+
[
|
|
381
|
+
code, input.name, input.premium_rate_bps, input.premium_min_minor,
|
|
382
|
+
input.min_declared_value_minor, input.max_declared_value_minor,
|
|
383
|
+
input.claim_window_days, input.currency, active, now,
|
|
384
|
+
],
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return _hydrateProvider(await _getProviderRow(code));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Quote a premium against a provider's published rate. Pure
|
|
391
|
+
// function over the provider row + declared value — does not
|
|
392
|
+
// write. Refuses on archived / inactive providers, currency
|
|
393
|
+
// mismatch, declared-value floor/ceiling violation. When
|
|
394
|
+
// shippingLabels is wired and `shipment_id` is supplied, also
|
|
395
|
+
// verifies the shipment has at least one label (the storefront's
|
|
396
|
+
// checkout flow always has minted a label before offering
|
|
397
|
+
// insurance; the gate catches stray callers).
|
|
398
|
+
async function quoteInsurance(input) {
|
|
399
|
+
if (!input || typeof input !== "object") {
|
|
400
|
+
throw new TypeError("shippingInsurance.quoteInsurance: input object required");
|
|
401
|
+
}
|
|
402
|
+
var providerCode = _code(input.provider_code, "provider_code");
|
|
403
|
+
_amountMinor(input.declared_value_minor, "declared_value_minor");
|
|
404
|
+
var currency = _currency(input.currency);
|
|
405
|
+
|
|
406
|
+
var prov = await _getProviderRow(providerCode);
|
|
407
|
+
if (!prov) {
|
|
408
|
+
throw new TypeError("shippingInsurance.quoteInsurance: provider_code " +
|
|
409
|
+
JSON.stringify(providerCode) + " not found");
|
|
410
|
+
}
|
|
411
|
+
if (!Number(prov.active) || prov.archived_at != null) {
|
|
412
|
+
throw new TypeError("shippingInsurance.quoteInsurance: provider_code " +
|
|
413
|
+
JSON.stringify(providerCode) + " is not active");
|
|
414
|
+
}
|
|
415
|
+
if (prov.currency !== currency) {
|
|
416
|
+
throw new TypeError("shippingInsurance.quoteInsurance: currency " + currency +
|
|
417
|
+
" does not match provider currency " + prov.currency);
|
|
418
|
+
}
|
|
419
|
+
if (input.declared_value_minor < Number(prov.min_declared_value_minor)) {
|
|
420
|
+
throw new TypeError("shippingInsurance.quoteInsurance: declared_value_minor " +
|
|
421
|
+
input.declared_value_minor + " is below provider floor " + prov.min_declared_value_minor);
|
|
422
|
+
}
|
|
423
|
+
if (input.declared_value_minor > Number(prov.max_declared_value_minor)) {
|
|
424
|
+
throw new TypeError("shippingInsurance.quoteInsurance: declared_value_minor " +
|
|
425
|
+
input.declared_value_minor + " exceeds provider ceiling " + prov.max_declared_value_minor);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (input.shipment_id != null) {
|
|
429
|
+
var shipmentId = _uuid(input.shipment_id, "shipment_id");
|
|
430
|
+
if (shippingLabelsHandle) {
|
|
431
|
+
var labels = await shippingLabelsHandle.labelsForShipment(shipmentId);
|
|
432
|
+
if (!labels || !labels.length) {
|
|
433
|
+
throw new TypeError("shippingInsurance.quoteInsurance: shipment " +
|
|
434
|
+
shipmentId + " has no shipping label — mint a label before quoting insurance");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
var premium = _computePremium(
|
|
440
|
+
input.declared_value_minor,
|
|
441
|
+
Number(prov.premium_rate_bps),
|
|
442
|
+
Number(prov.premium_min_minor),
|
|
443
|
+
);
|
|
444
|
+
return {
|
|
445
|
+
provider_code: providerCode,
|
|
446
|
+
declared_value_minor: input.declared_value_minor,
|
|
447
|
+
premium_minor: premium,
|
|
448
|
+
currency: currency,
|
|
449
|
+
claim_window_days: Number(prov.claim_window_days),
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Mint a per-shipment insurance row. Snapshots the premium at
|
|
454
|
+
// purchase time so a later provider-rate shift doesn't change
|
|
455
|
+
// what the customer pays. Refuses when an insurance for the same
|
|
456
|
+
// (provider, shipment) already exists (the UNIQUE constraint would
|
|
457
|
+
// also catch this; the app-layer check produces a friendlier error
|
|
458
|
+
// shape). external_policy_id is optional at purchase — the
|
|
459
|
+
// operator's worker may call back to attach it after the
|
|
460
|
+
// provider's API returns.
|
|
461
|
+
async function purchaseInsurance(input) {
|
|
462
|
+
if (!input || typeof input !== "object") {
|
|
463
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: input object required");
|
|
464
|
+
}
|
|
465
|
+
var providerCode = _code(input.provider_code, "provider_code");
|
|
466
|
+
var shipmentId = _uuid(input.shipment_id, "shipment_id");
|
|
467
|
+
var orderId = _uuid(input.order_id, "order_id");
|
|
468
|
+
var customerId = _uuid(input.customer_id, "customer_id");
|
|
469
|
+
_amountMinor(input.declared_value_minor, "declared_value_minor");
|
|
470
|
+
var currency = _currency(input.currency);
|
|
471
|
+
var externalPolicyId = null;
|
|
472
|
+
if (input.external_policy_id != null) {
|
|
473
|
+
externalPolicyId = _externalPolicyId(input.external_policy_id);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
var prov = await _getProviderRow(providerCode);
|
|
477
|
+
if (!prov) {
|
|
478
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: provider_code " +
|
|
479
|
+
JSON.stringify(providerCode) + " not found");
|
|
480
|
+
}
|
|
481
|
+
if (!Number(prov.active) || prov.archived_at != null) {
|
|
482
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: provider_code " +
|
|
483
|
+
JSON.stringify(providerCode) + " is not active");
|
|
484
|
+
}
|
|
485
|
+
if (prov.currency !== currency) {
|
|
486
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: currency " + currency +
|
|
487
|
+
" does not match provider currency " + prov.currency);
|
|
488
|
+
}
|
|
489
|
+
if (input.declared_value_minor < Number(prov.min_declared_value_minor)) {
|
|
490
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: declared_value_minor " +
|
|
491
|
+
input.declared_value_minor + " is below provider floor " + prov.min_declared_value_minor);
|
|
492
|
+
}
|
|
493
|
+
if (input.declared_value_minor > Number(prov.max_declared_value_minor)) {
|
|
494
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: declared_value_minor " +
|
|
495
|
+
input.declared_value_minor + " exceeds provider ceiling " + prov.max_declared_value_minor);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
var existing = await _getInsuranceByProviderShipment(providerCode, shipmentId);
|
|
499
|
+
if (existing) {
|
|
500
|
+
throw new TypeError("shippingInsurance.purchaseInsurance: shipment " + shipmentId +
|
|
501
|
+
" is already insured through " + providerCode +
|
|
502
|
+
" (insurance_id=" + existing.id + ")");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
var premium = _computePremium(
|
|
506
|
+
input.declared_value_minor,
|
|
507
|
+
Number(prov.premium_rate_bps),
|
|
508
|
+
Number(prov.premium_min_minor),
|
|
509
|
+
);
|
|
510
|
+
var now = _now();
|
|
511
|
+
var windowEnds = now + Number(prov.claim_window_days) * DAY_MS;
|
|
512
|
+
var id = _b().uuid.v7();
|
|
513
|
+
await query(
|
|
514
|
+
"INSERT INTO shipping_insurances (id, provider_code, shipment_id, order_id, " +
|
|
515
|
+
"customer_id, declared_value_minor, premium_minor, currency, external_policy_id, " +
|
|
516
|
+
"status, claim_window_ends_at, created_at, updated_at) " +
|
|
517
|
+
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 'active', ?10, ?11, ?11)",
|
|
518
|
+
[
|
|
519
|
+
id, providerCode, shipmentId, orderId, customerId,
|
|
520
|
+
input.declared_value_minor, premium, currency, externalPolicyId,
|
|
521
|
+
windowEnds, now,
|
|
522
|
+
],
|
|
523
|
+
);
|
|
524
|
+
return _hydrateInsurance(await _getInsuranceRow(id));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// File a claim against an active insurance. Refuses on cancelled /
|
|
528
|
+
// expired insurances, on a claim_window that has elapsed, and on
|
|
529
|
+
// any claim_type outside the closed set. `evidence` is a JSON
|
|
530
|
+
// object captured at filing time — typically photo URLs, carrier
|
|
531
|
+
// scan reports, signature attestations; the primitive only gates
|
|
532
|
+
// JSON round-trip, not the body shape (insurers vary).
|
|
533
|
+
async function fileClaim(input) {
|
|
534
|
+
if (!input || typeof input !== "object") {
|
|
535
|
+
throw new TypeError("shippingInsurance.fileClaim: input object required");
|
|
536
|
+
}
|
|
537
|
+
var insuranceId = _uuid(input.insurance_id, "insurance_id");
|
|
538
|
+
var claimType = _claimType(input.claim_type);
|
|
539
|
+
_amountMinor(input.claimed_amount_minor, "claimed_amount_minor");
|
|
540
|
+
var evidence = _evidence(input.evidence);
|
|
541
|
+
|
|
542
|
+
var ins = await _getInsuranceRow(insuranceId);
|
|
543
|
+
if (!ins) {
|
|
544
|
+
throw new TypeError("shippingInsurance.fileClaim: insurance " + insuranceId + " not found");
|
|
545
|
+
}
|
|
546
|
+
var now = _now();
|
|
547
|
+
// Auto-expire on read — a claim_window that elapsed without any
|
|
548
|
+
// operator activity should land as `expired` rather than silently
|
|
549
|
+
// accept a stale claim. We flip the status here so the refusal
|
|
550
|
+
// message is accurate; the (terminal) expired status is the same
|
|
551
|
+
// a separate sweeper would produce.
|
|
552
|
+
if (ins.status === "active" && now > Number(ins.claim_window_ends_at)) {
|
|
553
|
+
await query(
|
|
554
|
+
"UPDATE shipping_insurances SET status = 'expired', expired_at = ?1, updated_at = ?1 WHERE id = ?2",
|
|
555
|
+
[now, insuranceId],
|
|
556
|
+
);
|
|
557
|
+
ins = await _getInsuranceRow(insuranceId);
|
|
558
|
+
}
|
|
559
|
+
if (ins.status !== "active") {
|
|
560
|
+
throw new TypeError("shippingInsurance.fileClaim: insurance " + insuranceId +
|
|
561
|
+
" is " + ins.status + ", only active insurances accept new claims");
|
|
562
|
+
}
|
|
563
|
+
if (input.claimed_amount_minor > Number(ins.declared_value_minor)) {
|
|
564
|
+
throw new TypeError("shippingInsurance.fileClaim: claimed_amount_minor " +
|
|
565
|
+
input.claimed_amount_minor + " exceeds declared_value_minor " +
|
|
566
|
+
ins.declared_value_minor);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
var id = _b().uuid.v7();
|
|
570
|
+
await query(
|
|
571
|
+
"INSERT INTO insurance_claims (id, insurance_id, claim_type, status, " +
|
|
572
|
+
"claimed_amount_minor, evidence_json, filed_at, updated_at) " +
|
|
573
|
+
"VALUES (?1, ?2, ?3, 'filed', ?4, ?5, ?6, ?6)",
|
|
574
|
+
[id, insuranceId, claimType, input.claimed_amount_minor, JSON.stringify(evidence), now],
|
|
575
|
+
);
|
|
576
|
+
return _hydrateClaim(await _getClaimRow(id));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// filed -> approved. Stamps payout_minor (which may differ from
|
|
580
|
+
// the customer's claimed_amount_minor — the insurer's adjuster may
|
|
581
|
+
// pay less than the requested amount per the provider's depreciation
|
|
582
|
+
// schedule). Refuses on already-terminal rows.
|
|
583
|
+
async function markClaimApproved(input) {
|
|
584
|
+
if (!input || typeof input !== "object") {
|
|
585
|
+
throw new TypeError("shippingInsurance.markClaimApproved: input object required");
|
|
586
|
+
}
|
|
587
|
+
var claimId = _uuid(input.claim_id, "claim_id");
|
|
588
|
+
_amountMinor(input.payout_minor, "payout_minor");
|
|
589
|
+
|
|
590
|
+
var claim = await _getClaimRow(claimId);
|
|
591
|
+
if (!claim) {
|
|
592
|
+
throw new TypeError("shippingInsurance.markClaimApproved: claim " + claimId + " not found");
|
|
593
|
+
}
|
|
594
|
+
if (claim.status !== "filed") {
|
|
595
|
+
throw new TypeError("shippingInsurance.markClaimApproved: claim " + claimId +
|
|
596
|
+
" is " + claim.status + ", only filed claims can move to approved");
|
|
597
|
+
}
|
|
598
|
+
var ins = await _getInsuranceRow(claim.insurance_id);
|
|
599
|
+
if (input.payout_minor > Number(ins.declared_value_minor)) {
|
|
600
|
+
throw new TypeError("shippingInsurance.markClaimApproved: payout_minor " +
|
|
601
|
+
input.payout_minor + " exceeds declared_value_minor " +
|
|
602
|
+
ins.declared_value_minor + " on the parent insurance");
|
|
603
|
+
}
|
|
604
|
+
var now = _now();
|
|
605
|
+
await query(
|
|
606
|
+
"UPDATE insurance_claims SET status = 'approved', payout_minor = ?1, " +
|
|
607
|
+
"resolved_at = ?2, updated_at = ?2 WHERE id = ?3",
|
|
608
|
+
[input.payout_minor, now, claimId],
|
|
609
|
+
);
|
|
610
|
+
return _hydrateClaim(await _getClaimRow(claimId));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// filed -> denied. Captures the operator-supplied denial_reason
|
|
614
|
+
// (insurer-facing prose the customer reads on the resolution
|
|
615
|
+
// notice). Refuses on already-terminal rows.
|
|
616
|
+
async function markClaimDenied(input) {
|
|
617
|
+
if (!input || typeof input !== "object") {
|
|
618
|
+
throw new TypeError("shippingInsurance.markClaimDenied: input object required");
|
|
619
|
+
}
|
|
620
|
+
var claimId = _uuid(input.claim_id, "claim_id");
|
|
621
|
+
var denialReason = _denialReason(input.denial_reason);
|
|
622
|
+
|
|
623
|
+
var claim = await _getClaimRow(claimId);
|
|
624
|
+
if (!claim) {
|
|
625
|
+
throw new TypeError("shippingInsurance.markClaimDenied: claim " + claimId + " not found");
|
|
626
|
+
}
|
|
627
|
+
if (claim.status !== "filed") {
|
|
628
|
+
throw new TypeError("shippingInsurance.markClaimDenied: claim " + claimId +
|
|
629
|
+
" is " + claim.status + ", only filed claims can move to denied");
|
|
630
|
+
}
|
|
631
|
+
var now = _now();
|
|
632
|
+
await query(
|
|
633
|
+
"UPDATE insurance_claims SET status = 'denied', denial_reason = ?1, " +
|
|
634
|
+
"resolved_at = ?2, updated_at = ?2 WHERE id = ?3",
|
|
635
|
+
[denialReason, now, claimId],
|
|
636
|
+
);
|
|
637
|
+
return _hydrateClaim(await _getClaimRow(claimId));
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async function getInsurance(insuranceId) {
|
|
641
|
+
var id = _uuid(insuranceId, "insurance_id");
|
|
642
|
+
return _hydrateInsurance(await _getInsuranceRow(id));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Per-order list. An order may fan out to multiple shipments and
|
|
646
|
+
// multiple insurances per shipment (belt-and-braces overlays
|
|
647
|
+
// through different providers), so this read is naturally a list.
|
|
648
|
+
// Ordered by created_at ASC, id ASC for deterministic iteration.
|
|
649
|
+
async function insurancesForOrder(orderId) {
|
|
650
|
+
var id = _uuid(orderId, "order_id");
|
|
651
|
+
var rows = (await query(
|
|
652
|
+
"SELECT * FROM shipping_insurances WHERE order_id = ?1 ORDER BY created_at ASC, id ASC",
|
|
653
|
+
[id],
|
|
654
|
+
)).rows;
|
|
655
|
+
return rows.map(_hydrateInsurance);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async function claimsForInsurance(insuranceId) {
|
|
659
|
+
var id = _uuid(insuranceId, "insurance_id");
|
|
660
|
+
var rows = (await query(
|
|
661
|
+
"SELECT * FROM insurance_claims WHERE insurance_id = ?1 ORDER BY filed_at ASC, id ASC",
|
|
662
|
+
[id],
|
|
663
|
+
)).rows;
|
|
664
|
+
return rows.map(_hydrateClaim);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Provider-level rollup over [from, to]. Counts active /
|
|
668
|
+
// cancelled / expired insurances, total premium collected, claim
|
|
669
|
+
// counts by status, and total approved payout. Operator dashboard
|
|
670
|
+
// "Shipsurance performance this month" reads from here.
|
|
671
|
+
async function metricsForProvider(input) {
|
|
672
|
+
if (!input || typeof input !== "object") {
|
|
673
|
+
throw new TypeError("shippingInsurance.metricsForProvider: input object required");
|
|
674
|
+
}
|
|
675
|
+
var providerCode = _code(input.provider_code, "provider_code");
|
|
676
|
+
var from = _epochMs(input.from, "from");
|
|
677
|
+
var to = _epochMs(input.to, "to");
|
|
678
|
+
if (from > to) {
|
|
679
|
+
throw new TypeError("shippingInsurance.metricsForProvider: from must be <= to");
|
|
680
|
+
}
|
|
681
|
+
var insRows = (await query(
|
|
682
|
+
"SELECT status, premium_minor FROM shipping_insurances " +
|
|
683
|
+
"WHERE provider_code = ?1 AND created_at >= ?2 AND created_at <= ?3",
|
|
684
|
+
[providerCode, from, to],
|
|
685
|
+
)).rows;
|
|
686
|
+
var activeCount = 0;
|
|
687
|
+
var cancelledCount = 0;
|
|
688
|
+
var expiredCount = 0;
|
|
689
|
+
var totalPremium = 0;
|
|
690
|
+
for (var i = 0; i < insRows.length; i += 1) {
|
|
691
|
+
var r = insRows[i];
|
|
692
|
+
totalPremium += Number(r.premium_minor) || 0;
|
|
693
|
+
if (r.status === "active") { activeCount += 1; }
|
|
694
|
+
else if (r.status === "cancelled") { cancelledCount += 1; }
|
|
695
|
+
else if (r.status === "expired") { expiredCount += 1; }
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
var claimRows = (await query(
|
|
699
|
+
"SELECT c.status AS status, c.payout_minor AS payout_minor " +
|
|
700
|
+
"FROM insurance_claims c " +
|
|
701
|
+
"JOIN shipping_insurances i ON i.id = c.insurance_id " +
|
|
702
|
+
"WHERE i.provider_code = ?1 AND c.filed_at >= ?2 AND c.filed_at <= ?3",
|
|
703
|
+
[providerCode, from, to],
|
|
704
|
+
)).rows;
|
|
705
|
+
var filedCount = 0;
|
|
706
|
+
var approvedCount = 0;
|
|
707
|
+
var deniedCount = 0;
|
|
708
|
+
var totalPayout = 0;
|
|
709
|
+
for (var j = 0; j < claimRows.length; j += 1) {
|
|
710
|
+
var c = claimRows[j];
|
|
711
|
+
if (c.status === "filed") { filedCount += 1; }
|
|
712
|
+
else if (c.status === "approved") { approvedCount += 1; totalPayout += Number(c.payout_minor) || 0; }
|
|
713
|
+
else if (c.status === "denied") { deniedCount += 1; }
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
provider_code: providerCode,
|
|
718
|
+
from: from,
|
|
719
|
+
to: to,
|
|
720
|
+
active_count: activeCount,
|
|
721
|
+
cancelled_count: cancelledCount,
|
|
722
|
+
expired_count: expiredCount,
|
|
723
|
+
total_premium_minor: totalPremium,
|
|
724
|
+
claims_filed_count: filedCount,
|
|
725
|
+
claims_approved_count: approvedCount,
|
|
726
|
+
claims_denied_count: deniedCount,
|
|
727
|
+
total_payout_minor: totalPayout,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
CLAIM_TYPES: CLAIM_TYPES,
|
|
733
|
+
INSURANCE_STATUSES: INSURANCE_STATUSES,
|
|
734
|
+
CLAIM_STATUSES: CLAIM_STATUSES,
|
|
735
|
+
|
|
736
|
+
defineProvider: defineProvider,
|
|
737
|
+
quoteInsurance: quoteInsurance,
|
|
738
|
+
purchaseInsurance: purchaseInsurance,
|
|
739
|
+
fileClaim: fileClaim,
|
|
740
|
+
markClaimApproved: markClaimApproved,
|
|
741
|
+
markClaimDenied: markClaimDenied,
|
|
742
|
+
getInsurance: getInsurance,
|
|
743
|
+
insurancesForOrder: insurancesForOrder,
|
|
744
|
+
claimsForInsurance: claimsForInsurance,
|
|
745
|
+
metricsForProvider: metricsForProvider,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
module.exports = {
|
|
750
|
+
create: create,
|
|
751
|
+
CLAIM_TYPES: CLAIM_TYPES,
|
|
752
|
+
INSURANCE_STATUSES: INSURANCE_STATUSES,
|
|
753
|
+
CLAIM_STATUSES: CLAIM_STATUSES,
|
|
754
|
+
MAX_AMOUNT_MINOR: MAX_AMOUNT_MINOR,
|
|
755
|
+
MAX_RATE_BPS: MAX_RATE_BPS,
|
|
756
|
+
MAX_CLAIM_DAYS: MAX_CLAIM_DAYS,
|
|
757
|
+
};
|