@fire-spark/payments 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ type AllocateTenderInput = {
9
9
  sequence: number;
10
10
  method: PaymentMethod;
11
11
  allocation: TenderAllocationInput | null;
12
- /** Value a REDEMPTION tender covers (`details.points_value`). */
12
+ /** Value a REDEMPTION tender covers (`details.amount`). */
13
13
  redemption_amount?: number;
14
14
  };
15
15
  type AllocateTendersInput = {
@@ -472,16 +472,14 @@ type RedemptionDetails = Extract<TenderDetails, {
472
472
  /** One reward inside a grouped REDEMPTION tender (entry totals). */
473
473
  type RedemptionReward = RedemptionDetails["rewards"][number];
474
474
  /**
475
- * Payment redeem as stored in `orders.redeem`. `amount` and `points` are
476
- * totals of the entry (every unit); `quantity` is informational.
475
+ * Payment redeem as stored in `orders.redeem`. `amount` is the total of
476
+ * the entry (every unit); `quantity` is informational.
477
477
  */
478
478
  type PaymentRedeemLike = {
479
479
  reward_id: string;
480
480
  quantity?: number;
481
481
  payment_id?: string | null;
482
482
  amount?: number;
483
- points?: number;
484
- program?: string;
485
483
  };
486
484
  /** Payment redeems of one `payment_id`, ready to seed its tender. */
487
485
  type RedemptionGroup = {
@@ -491,7 +489,7 @@ type RedemptionGroup = {
491
489
  details: RedemptionDetails;
492
490
  };
493
491
  /**
494
- * Points / balance paid with a REDEMPTION tender, not a discount (D16):
492
+ * Reward (voucher, balance…) paid with a REDEMPTION tender, not a discount (D16):
495
493
  * it never discounts lines nor has a `redemption_steps` entry.
496
494
  */
497
495
  declare const isPaymentRedeem: (redeem: {
@@ -501,6 +499,20 @@ declare const isPaymentRedeem: (redeem: {
501
499
  declare const discountRedeems: <T extends {
502
500
  payment_id?: string | null;
503
501
  }>(redeem: readonly T[]) => T[];
502
+ /** Order fields payment redeems live in: the order and its root lines. */
503
+ type PaymentRedeemSource<T extends PaymentRedeemLike> = {
504
+ redeem?: readonly T[] | null;
505
+ lines?: readonly {
506
+ parent_uid?: string | null;
507
+ redeem?: readonly T[];
508
+ }[];
509
+ };
510
+ /**
511
+ * Every payment redeem of the order: order-level ones first, then the ones
512
+ * kept on a root line (a voucher stored with the product it pays for).
513
+ * Both are paid by REDEMPTION tenders; child-line redeems never apply.
514
+ */
515
+ declare const orderPaymentRedeems: <T extends PaymentRedeemLike>(order: PaymentRedeemSource<T>) => T[];
504
516
  /**
505
517
  * Payment redeems pay the whole order, so no real tender is needed: the
506
518
  * intent is only REDEMPTION tenders and the order advances on its own.
@@ -515,16 +527,16 @@ declare const paymentRedeemsCoverTotal: (redeem: readonly PaymentRedeemLike[], t
515
527
  */
516
528
  declare const groupPaymentRedeems: (redeem: readonly PaymentRedeemLike[]) => RedemptionGroup[];
517
529
  /**
518
- * REDEMPTION `details` in the grouped shape, or null for other methods.
519
- * Single-reward details written before grouping read as one reward.
530
+ * REDEMPTION `details` in the current shape, or null for other methods.
531
+ * Details persisted earlier (one `reward_id`, or money as `points_value`)
532
+ * read as the same shape; their old keys are dropped.
520
533
  */
521
534
  declare const redemptionDetailsOf: (details: unknown) => RedemptionDetails | null;
522
535
  /** Rewards of a REDEMPTION tender; empty for other methods. */
523
536
  declare const redemptionRewardsOf: (details: unknown) => {
524
537
  reward_id: string;
525
538
  quantity: number;
526
- points: number;
527
- points_value: number;
539
+ amount: number;
528
540
  }[];
529
541
  //#endregion
530
542
  //#region src/reference.d.ts
@@ -610,7 +622,7 @@ declare const primaryTender: <T extends TenderLike>(intent: {
610
622
  primary_tender_id: string | null;
611
623
  tenders: readonly T[];
612
624
  } | null | undefined) => T | null;
613
- /** A REDEMPTION tender whose points are held for the order (L2 / D30). */
625
+ /** A REDEMPTION tender whose reward is held for the order (L2 / D30). */
614
626
  declare const isRedemptionReserved: (tender: TenderLike) => boolean;
615
627
  /** Paid, or a reserved REDEMPTION (confirmed later, §3.3). */
616
628
  declare const isTenderSettled: (tender: TenderLike) => boolean;
@@ -621,7 +633,7 @@ declare const isIntentCovered: (intent: IntentLike | null | undefined, orderTota
621
633
  /**
622
634
  * Order `payment_status` from the live tenders of the current intent
623
635
  * (D32). Reserved REDEMPTION tenders count as settled, so a redemption-only
624
- * intent is `PAID` once its points are held (D30).
636
+ * intent is `PAID` once its rewards are held (D30).
625
637
  */
626
638
  declare const derivePaymentStatus: (intent: IntentLike | null | undefined) => PaymentStatus;
627
639
  /** Live tenders a `PAYING` node waits for (§7.3); no scope = all. */
@@ -688,4 +700,4 @@ declare const scopedPaymentStorage: (storage: PaymentStorage, paymentId: string)
688
700
  /** For tests and web previews; apps provide AsyncStorage or localStorage. */
689
701
  declare const memoryPaymentStorage: () => PaymentStorage;
690
702
  //#endregion
691
- export { ACTIVE_INTENT_STATUSES, AllocateTenderInput, AllocateTendersInput, AllocateTendersResult, AllocatedTender, AllocationError, CURRENCY_DECIMALS, ChargeConversion, DEFAULT_PAYMENT_POLICY, DELIVERY_FEE_BASED_ON, DIGITAL_COLLECT_AT, type DeviceField, MergePaymentTenderOutcome, MergePaymentTenderResult, NEXT_STATUSES, PARTIAL_APPROVAL_ERROR, PAYMENT_ERROR_CODES, PAYMENT_ERROR_DEFAULTS, type PaymentAction, type PaymentAdapter, type PaymentAdapterCapabilities, type PaymentAmount, type PaymentCardType, type PaymentCollectAt, type PaymentContext, type PaymentCustomer, type PaymentEntryMode, type PaymentEnvironment, PaymentError, PaymentErrorCode, PaymentErrorDictionary, PaymentErrorEntry, PaymentErrorReason, type PaymentIntentStatus, type PaymentMetadata, type PaymentMetadataPayment, type PaymentOperation, type PaymentPolicy, PaymentRedeemLike, type PaymentResult, type PaymentReversal, type PaymentServer, type PaymentStorage, PaymentTenderUpdate, type PaymentUI, REDEMPTION_METHOD, RawPaymentError, RedemptionDetails, RedemptionGroup, RedemptionReward, RulesTender, StandardPaymentError, TERMINAL_TENDER_STATUSES, TIP_FEE_BASED_ON, type TaxBreakdown, TaxLine, type TerminalContext, type TerminalOperation, type TerminalOperations, type TerminalResult, ValidateTendersResult, activeIntent, allocateTenders, amountTolerance, batchCloseDay, countTenderItems, createAttemptReference, createPaymentError, currencyDecimals, currentIntent, defaultReversalFor, definePaymentErrors, derivePaymentStatus, discountRedeems, formatPaymentError, groupPaymentRedeems, isBatchCloseDue, isDeliveryFee, isIntentCovered, isMixedTenders, isPaymentError, isPaymentRedeem, isRealPaymentMethod, isRedemptionReserved, isScopePaid, isTenderSettled, isTerminalLocked, isTipFee, liveRealTenders, liveTenders, memoryPaymentStorage, mergePaymentTender, missingDeviceFields, nextIntentStatus, paymentRedeemsCoverTotal, primaryTender, primaryTenderOf, prorateMinor, realTenders, redemptionDetailsOf, redemptionRewardsOf, resolvePaymentPolicy, resolveProviderEnvironment, roundTo, scopedPaymentStorage, taxBreakdown, tendersCoverage, tendersInScope, toChargeAmount, toPaymentAmount, toPaymentError, validateTendersAgainstRules, withTerminalLock };
703
+ export { ACTIVE_INTENT_STATUSES, AllocateTenderInput, AllocateTendersInput, AllocateTendersResult, AllocatedTender, AllocationError, CURRENCY_DECIMALS, ChargeConversion, DEFAULT_PAYMENT_POLICY, DELIVERY_FEE_BASED_ON, DIGITAL_COLLECT_AT, type DeviceField, MergePaymentTenderOutcome, MergePaymentTenderResult, NEXT_STATUSES, PARTIAL_APPROVAL_ERROR, PAYMENT_ERROR_CODES, PAYMENT_ERROR_DEFAULTS, type PaymentAction, type PaymentAdapter, type PaymentAdapterCapabilities, type PaymentAmount, type PaymentCardType, type PaymentCollectAt, type PaymentContext, type PaymentCustomer, type PaymentEntryMode, type PaymentEnvironment, PaymentError, PaymentErrorCode, PaymentErrorDictionary, PaymentErrorEntry, PaymentErrorReason, type PaymentIntentStatus, type PaymentMetadata, type PaymentMetadataPayment, type PaymentOperation, type PaymentPolicy, PaymentRedeemLike, PaymentRedeemSource, type PaymentResult, type PaymentReversal, type PaymentServer, type PaymentStorage, PaymentTenderUpdate, type PaymentUI, REDEMPTION_METHOD, RawPaymentError, RedemptionDetails, RedemptionGroup, RedemptionReward, RulesTender, StandardPaymentError, TERMINAL_TENDER_STATUSES, TIP_FEE_BASED_ON, type TaxBreakdown, TaxLine, type TerminalContext, type TerminalOperation, type TerminalOperations, type TerminalResult, ValidateTendersResult, activeIntent, allocateTenders, amountTolerance, batchCloseDay, countTenderItems, createAttemptReference, createPaymentError, currencyDecimals, currentIntent, defaultReversalFor, definePaymentErrors, derivePaymentStatus, discountRedeems, formatPaymentError, groupPaymentRedeems, isBatchCloseDue, isDeliveryFee, isIntentCovered, isMixedTenders, isPaymentError, isPaymentRedeem, isRealPaymentMethod, isRedemptionReserved, isScopePaid, isTenderSettled, isTerminalLocked, isTipFee, liveRealTenders, liveTenders, memoryPaymentStorage, mergePaymentTender, missingDeviceFields, nextIntentStatus, orderPaymentRedeems, paymentRedeemsCoverTotal, primaryTender, primaryTenderOf, prorateMinor, realTenders, redemptionDetailsOf, redemptionRewardsOf, resolvePaymentPolicy, resolveProviderEnvironment, roundTo, scopedPaymentStorage, taxBreakdown, tendersCoverage, tendersInScope, toChargeAmount, toPaymentAmount, toPaymentError, validateTendersAgainstRules, withTerminalLock };
package/dist/index.js CHANGED
@@ -137,7 +137,7 @@ const primaryTender = (intent) => {
137
137
  const id = intent.primary_tender_id;
138
138
  return intent.tenders.find((tender) => tender.id === id) ?? null;
139
139
  };
140
- /** A REDEMPTION tender whose points are held for the order (L2 / D30). */
140
+ /** A REDEMPTION tender whose reward is held for the order (L2 / D30). */
141
141
  const isRedemptionReserved = (tender) => {
142
142
  if (isRealPaymentMethod(tender.method)) return false;
143
143
  return typeof tender.metadata?.reserved_at === "string";
@@ -167,7 +167,7 @@ const isIntentCovered = (intent, orderTotal) => {
167
167
  /**
168
168
  * Order `payment_status` from the live tenders of the current intent
169
169
  * (D32). Reserved REDEMPTION tenders count as settled, so a redemption-only
170
- * intent is `PAID` once its points are held (D30).
170
+ * intent is `PAID` once its rewards are held (D30).
171
171
  */
172
172
  const derivePaymentStatus = (intent) => {
173
173
  const tenders = liveTenders(intent);
@@ -1174,7 +1174,7 @@ const batchCloseDay = (now) => localDay(now);
1174
1174
  const SUM_DECIMALS = 6;
1175
1175
  const add = (a, b) => roundTo(a + b, SUM_DECIMALS);
1176
1176
  /**
1177
- * Points / balance paid with a REDEMPTION tender, not a discount (D16):
1177
+ * Reward (voucher, balance…) paid with a REDEMPTION tender, not a discount (D16):
1178
1178
  * it never discounts lines nor has a `redemption_steps` entry.
1179
1179
  */
1180
1180
  const isPaymentRedeem = (redeem) => {
@@ -1185,6 +1185,15 @@ const discountRedeems = (redeem) => {
1185
1185
  return redeem.filter((item) => !isPaymentRedeem(item));
1186
1186
  };
1187
1187
  /**
1188
+ * Every payment redeem of the order: order-level ones first, then the ones
1189
+ * kept on a root line (a voucher stored with the product it pays for).
1190
+ * Both are paid by REDEMPTION tenders; child-line redeems never apply.
1191
+ */
1192
+ const orderPaymentRedeems = (order) => {
1193
+ const onLines = (order.lines ?? []).filter((line) => line.parent_uid == null).flatMap((line) => line.redeem ?? []);
1194
+ return [...order.redeem ?? [], ...onLines].filter(isPaymentRedeem);
1195
+ };
1196
+ /**
1188
1197
  * Payment redeems pay the whole order, so no real tender is needed: the
1189
1198
  * intent is only REDEMPTION tenders and the order advances on its own.
1190
1199
  */
@@ -1197,32 +1206,22 @@ const paymentRedeemsCoverTotal = (redeem, totals) => {
1197
1206
  return roundTo(paid, decimals) >= roundTo(totals.total, decimals);
1198
1207
  };
1199
1208
  const rewardOf = (redeem) => {
1200
- const r1 = {
1201
- reward_id: redeem.reward_id,
1202
- quantity: redeem.quantity ?? 1
1203
- };
1204
- const r2 = {
1205
- points: redeem.points ?? 0,
1206
- points_value: redeem.amount ?? 0
1207
- };
1208
1209
  return {
1209
- ...r1,
1210
- ...r2
1210
+ reward_id: redeem.reward_id,
1211
+ quantity: redeem.quantity ?? 1,
1212
+ amount: redeem.amount ?? 0
1211
1213
  };
1212
1214
  };
1213
- const detailsOf = (rewards, program, loyalty_transaction_id) => {
1215
+ const detailsOf = (rewards, loyalty_transaction_id) => {
1214
1216
  const d1 = {
1215
1217
  method: REDEMPTION_METHOD,
1216
1218
  rewards,
1217
- points: rewards.reduce((sum, item) => add(sum, item.points), 0),
1218
- points_value: rewards.reduce((sum, item) => add(sum, item.points_value), 0)
1219
+ amount: rewards.reduce((sum, item) => add(sum, item.amount), 0)
1219
1220
  };
1220
- const d2 = program === void 0 ? {} : { program };
1221
- const d3 = loyalty_transaction_id ? { loyalty_transaction_id } : {};
1221
+ const d2 = loyalty_transaction_id ? { loyalty_transaction_id } : {};
1222
1222
  return {
1223
1223
  ...d1,
1224
- ...d2,
1225
- ...d3
1224
+ ...d2
1226
1225
  };
1227
1226
  };
1228
1227
  /**
@@ -1239,11 +1238,10 @@ const groupPaymentRedeems = (redeem) => {
1239
1238
  groups.set(payment_id, list);
1240
1239
  }
1241
1240
  return [...groups].map(([payment_id, items]) => {
1242
- const program = items.find((item) => item.program)?.program;
1243
- const details = detailsOf(items.map(rewardOf), program);
1241
+ const details = detailsOf(items.map(rewardOf));
1244
1242
  return {
1245
1243
  payment_id,
1246
- amount: details.points_value,
1244
+ amount: details.amount,
1247
1245
  details
1248
1246
  };
1249
1247
  });
@@ -1251,27 +1249,41 @@ const groupPaymentRedeems = (redeem) => {
1251
1249
  const isRecord = (value) => {
1252
1250
  return typeof value === "object" && value !== null;
1253
1251
  };
1252
+ const numberOr = (value, fallback) => {
1253
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1254
+ };
1255
+ /** Money of a persisted reward: `amount`, or `points_value` before it. */
1256
+ const rewardAmountOf = (reward) => {
1257
+ return numberOr(reward.amount, numberOr(reward.points_value, 0));
1258
+ };
1259
+ const persistedRewardOf = (value) => {
1260
+ if (!isRecord(value) || typeof value.reward_id !== "string") return null;
1261
+ const quantity = numberOr(value.quantity, 1);
1262
+ return {
1263
+ reward_id: value.reward_id,
1264
+ quantity,
1265
+ amount: rewardAmountOf(value)
1266
+ };
1267
+ };
1254
1268
  /**
1255
- * REDEMPTION `details` in the grouped shape, or null for other methods.
1256
- * Single-reward details written before grouping read as one reward.
1269
+ * REDEMPTION `details` in the current shape, or null for other methods.
1270
+ * Details persisted earlier (one `reward_id`, or money as `points_value`)
1271
+ * read as the same shape; their old keys are dropped.
1257
1272
  */
1258
1273
  const redemptionDetailsOf = (details) => {
1259
1274
  if (!isRecord(details) || details.method !== "REDEMPTION") return null;
1260
- if (Array.isArray(details.rewards)) return details;
1261
- if (typeof details.reward_id !== "string") return null;
1262
- const legacy = details;
1263
- const r1 = {
1264
- reward_id: legacy.reward_id,
1275
+ const id = details.loyalty_transaction_id;
1276
+ const reference = typeof id === "string" ? id : void 0;
1277
+ if (Array.isArray(details.rewards)) {
1278
+ const rewards = details.rewards.map(persistedRewardOf);
1279
+ if (rewards.some((reward) => reward === null)) return null;
1280
+ return detailsOf(rewards, reference);
1281
+ }
1282
+ const reward = persistedRewardOf({
1283
+ ...details,
1265
1284
  quantity: 1
1266
- };
1267
- const r2 = { points: legacy.points ?? 0 };
1268
- const reward = {
1269
- ...r1,
1270
- ...r2,
1271
- points_value: legacy.points_value ?? 0
1272
- };
1273
- const id = legacy.loyalty_transaction_id;
1274
- return detailsOf([reward], legacy.program, id);
1285
+ });
1286
+ return reward ? detailsOf([reward], reference) : null;
1275
1287
  };
1276
1288
  /** Rewards of a REDEMPTION tender; empty for other methods. */
1277
1289
  const redemptionRewardsOf = (details) => {
@@ -1375,4 +1387,4 @@ const memoryPaymentStorage = () => {
1375
1387
  };
1376
1388
  };
1377
1389
  //#endregion
1378
- export { ACTIVE_INTENT_STATUSES, CURRENCY_DECIMALS, DEFAULT_PAYMENT_POLICY, DELIVERY_FEE_BASED_ON, DIGITAL_COLLECT_AT, NEXT_STATUSES, PARTIAL_APPROVAL_ERROR, PAYMENT_ERROR_CODES, PAYMENT_ERROR_DEFAULTS, PaymentError, REDEMPTION_METHOD, TERMINAL_TENDER_STATUSES, TIP_FEE_BASED_ON, activeIntent, allocateTenders, amountTolerance, batchCloseDay, countTenderItems, createAttemptReference, createPaymentError, currencyDecimals, currentIntent, defaultReversalFor, definePaymentErrors, derivePaymentStatus, discountRedeems, formatPaymentError, groupPaymentRedeems, isBatchCloseDue, isDeliveryFee, isIntentCovered, isMixedTenders, isPaymentError, isPaymentRedeem, isRealPaymentMethod, isRedemptionReserved, isScopePaid, isTenderSettled, isTerminalLocked, isTipFee, liveRealTenders, liveTenders, memoryPaymentStorage, mergePaymentTender, missingDeviceFields, nextIntentStatus, paymentRedeemsCoverTotal, primaryTender, primaryTenderOf, prorateMinor, realTenders, redemptionDetailsOf, redemptionRewardsOf, resolvePaymentPolicy, resolveProviderEnvironment, roundTo, scopedPaymentStorage, taxBreakdown, tendersCoverage, tendersInScope, toChargeAmount, toPaymentAmount, toPaymentError, validateTendersAgainstRules, withTerminalLock };
1390
+ export { ACTIVE_INTENT_STATUSES, CURRENCY_DECIMALS, DEFAULT_PAYMENT_POLICY, DELIVERY_FEE_BASED_ON, DIGITAL_COLLECT_AT, NEXT_STATUSES, PARTIAL_APPROVAL_ERROR, PAYMENT_ERROR_CODES, PAYMENT_ERROR_DEFAULTS, PaymentError, REDEMPTION_METHOD, TERMINAL_TENDER_STATUSES, TIP_FEE_BASED_ON, activeIntent, allocateTenders, amountTolerance, batchCloseDay, countTenderItems, createAttemptReference, createPaymentError, currencyDecimals, currentIntent, defaultReversalFor, definePaymentErrors, derivePaymentStatus, discountRedeems, formatPaymentError, groupPaymentRedeems, isBatchCloseDue, isDeliveryFee, isIntentCovered, isMixedTenders, isPaymentError, isPaymentRedeem, isRealPaymentMethod, isRedemptionReserved, isScopePaid, isTenderSettled, isTerminalLocked, isTipFee, liveRealTenders, liveTenders, memoryPaymentStorage, mergePaymentTender, missingDeviceFields, nextIntentStatus, orderPaymentRedeems, paymentRedeemsCoverTotal, primaryTender, primaryTenderOf, prorateMinor, realTenders, redemptionDetailsOf, redemptionRewardsOf, resolvePaymentPolicy, resolveProviderEnvironment, roundTo, scopedPaymentStorage, taxBreakdown, tendersCoverage, tendersInScope, toChargeAmount, toPaymentAmount, toPaymentError, validateTendersAgainstRules, withTerminalLock };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fire-spark/payments",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Fire Spark payments contract: adapters, errors, amounts and policy",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "@fire-spark/types": "^0.3.4"
20
+ "@fire-spark/types": "^0.4.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "tsdown": "^0.22.14",