@nice2dev/ui-money 1.0.10

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 ADDED
@@ -0,0 +1,124 @@
1
+ # @nice2dev/ui-money
2
+
3
+ Money, currency, credit-card and payment React components for the Nice2Dev monorepo.
4
+ First-class integrations: ISO 4217 catalog (60+ currencies + crypto), Luhn / brand detection, exchange-rate providers, payment-processor adapters.
5
+
6
+ ```bash
7
+ npm install @nice2dev/ui-money
8
+ ```
9
+
10
+ ## Components
11
+
12
+ | Component | Purpose |
13
+ | ----------------------- | --------------------------------------------------------------------------- |
14
+ | `NiceMoney` | Read-only formatted amount with optional FX preview. |
15
+ | `NiceMoneyInput` | Amount input + integrated currency selector + steppers + live preview. |
16
+ | `NiceCurrencySelector` | Searchable, optgroup-aware ISO-4217 picker with `recents`. |
17
+ | `NiceCreditCardInput` | Card number + expiry + CVC + holder + postal. Brand detection, Luhn. |
18
+ | `NicePaymentForm` | Composite checkout form driven by any `PaymentProcessor` adapter. |
19
+ | `NicePriceTag` | Price with strike-through, discount badge, period suffix (subscriptions). |
20
+ | `NiceCurrencyConverter` | Live source ↔ target conversion widget driven by an `ExchangeRateProvider`. |
21
+
22
+ ## Money domain
23
+
24
+ Amounts are always stored as **integer minor units** (cents, satoshi, …) — never floats:
25
+
26
+ ```ts
27
+ import { toMoney, fromMoney, addMoney, formatMoney } from '@nice2dev/ui-money';
28
+
29
+ const price = toMoney(12.34, 'USD'); // { amount: 1234, currency: 'USD' }
30
+ const tax = toMoney(0.99, 'USD');
31
+ const total = addMoney(price, tax); // { amount: 1333, currency: 'USD' }
32
+
33
+ formatMoney(total, { locale: 'en-US' }); // "$13.33"
34
+ formatMoney(total, { locale: 'de-DE' }); // "13,33 $"
35
+ ```
36
+
37
+ JPY (0 decimals) and BHD (3 decimals) are handled automatically via the catalog.
38
+
39
+ ## Exchange-rate providers
40
+
41
+ Built-in adapters, all behind one `ExchangeRateProvider` interface:
42
+
43
+ ```ts
44
+ import {
45
+ StaticExchangeRateProvider,
46
+ FrankfurterProvider, // free, ECB-derived, no key
47
+ ExchangeRateHostProvider, // free, aggregated, no key
48
+ OpenExchangeRatesProvider, // requires app id
49
+ convertMoney,
50
+ } from '@nice2dev/ui-money';
51
+
52
+ const fx = new FrankfurterProvider({ cacheTtl: 60_000 });
53
+ const eur = await convertMoney(toMoney(100, 'USD'), 'EUR', fx);
54
+ ```
55
+
56
+ Implement `ExchangeRateProvider` to plug in Fixer, CurrencyAPI, Wise, your own backend, etc.
57
+
58
+ ## Payment-processor adapters
59
+
60
+ Provider-agnostic `PaymentProcessor` interface with bundled scaffolds for:
61
+
62
+ - **Stripe** · **PayPal** · **Adyen** · **Square** · **Mollie**
63
+ - **Braintree** · **Razorpay** · **Klarna**
64
+ - **PayU** · **Przelewy24** (Polish market: BLIK, Przelewy24)
65
+ - **Coinbase Commerce** (BTC, ETH, USDC, USDT)
66
+ - `MockPaymentProcessor` (tests / demos)
67
+
68
+ Each adapter is a scaffold — pass an `executor` that calls the real SDK / REST API:
69
+
70
+ ```ts
71
+ import { makeStripeAdapter } from '@nice2dev/ui-money';
72
+
73
+ const stripe = makeStripeAdapter({
74
+ authorize: async (req) => {
75
+ // call Stripe SDK / REST here, return PaymentResponse
76
+ },
77
+ capture: async (id, amount) => {
78
+ /* … */
79
+ },
80
+ cancel: async (id) => {
81
+ /* … */
82
+ },
83
+ refund: async (id, amount) => {
84
+ /* … */
85
+ },
86
+ });
87
+ ```
88
+
89
+ ## Usage
90
+
91
+ ```tsx
92
+ import { NicePaymentForm, toMoney, MockPaymentProcessor } from '@nice2dev/ui-money';
93
+ import '@nice2dev/ui-money/style.css';
94
+
95
+ export default function Checkout() {
96
+ return (
97
+ <NicePaymentForm
98
+ processor={new MockPaymentProcessor()}
99
+ amount={toMoney(49.99, 'USD')}
100
+ description="Pro plan, billed monthly"
101
+ onSuccess={(r) => console.log('paid!', r.id)}
102
+ />
103
+ );
104
+ }
105
+ ```
106
+
107
+ ## Hooks
108
+
109
+ ```ts
110
+ useNiceMoney(initial, locale?) // reactive Money + helpers
111
+ useNiceExchangeRate(source, target, fx) // live conversion subscription
112
+ useNicePayment(processor) // authorize / capture / refund
113
+ ```
114
+
115
+ ## Why not just floats?
116
+
117
+ Floating-point arithmetic loses pennies (`0.1 + 0.2 !== 0.3`). Storing minor units as
118
+ integers makes every arithmetic op exact, every comparison stable, and every
119
+ serialization round-trip lossless. `toMoney` / `fromMoney` are the only places where a
120
+ decimal touches a `number`.
121
+
122
+ ## License
123
+
124
+ See repository [LICENSE](../../LICENSE).
@@ -0,0 +1,14 @@
1
+ import { CurrencyDef } from './types';
2
+
3
+ /**
4
+ * ISO 4217 currency catalog.
5
+ *
6
+ * Curated subset of the most commonly used currencies (top ~60 by GDP / trade volume).
7
+ * The full ISO 4217 list contains ~180 active codes; consumers needing the long tail
8
+ * can extend `NICE_CURRENCY_CATALOG` at runtime.
9
+ */
10
+ export declare const NICE_CURRENCY_CATALOG: CurrencyDef[];
11
+ /** Lookup a currency definition by ISO 4217 code (case-insensitive). */
12
+ export declare function findCurrency(code: string): CurrencyDef | undefined;
13
+ /** Register / override a currency definition at runtime (e.g. private settlement codes). */
14
+ export declare function registerCurrency(def: CurrencyDef): void;
@@ -0,0 +1,26 @@
1
+ import { NiceFormFieldProps } from '@nice2dev/ui-core';
2
+ import { default as React } from 'react';
3
+ import { CardBrand, CreditCardData, CreditCardValidation } from '../types';
4
+
5
+ export interface NiceCreditCardInputProps extends Omit<NiceFormFieldProps, 'displayMode' | 'submitOnEnter' | 'onSubmit' | 'showKeyboardHints' | 'name'> {
6
+ /** Controlled value. Internally normalised. */
7
+ value?: CreditCardData;
8
+ /** Fires on every change with the latest data + validation summary. */
9
+ onChange?: (value: CreditCardData, validation: CreditCardValidation) => void;
10
+ /** Show cardholder name field. Default `true`. */
11
+ showHolderName?: boolean;
12
+ /** Require cardholder name in validation. Default same as `showHolderName`. */
13
+ requireHolderName?: boolean;
14
+ /** Show postal / ZIP field. Default `false`. */
15
+ showPostalCode?: boolean;
16
+ /** Restrict accepted brands (validation only — input is never blocked). */
17
+ acceptedBrands?: CardBrand[];
18
+ /** Render a small brand logo to the right of the number. Default `true`. */
19
+ showBrandIcon?: boolean;
20
+ }
21
+ /**
22
+ * {@link NiceCreditCardInput} — credit-card form fragment with brand detection,
23
+ * Luhn validation, expiry / CVC sanity checks. PCI scope reduction: pair with a
24
+ * tokenizing iframe (Stripe Elements, Adyen secured fields, …) for real charges.
25
+ */
26
+ export declare const NiceCreditCardInput: React.ForwardRefExoticComponent<NiceCreditCardInputProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,26 @@
1
+ import { default as React } from 'react';
2
+ import { ExchangeRateProvider } from '../exchange';
3
+ import { Money } from '../types';
4
+
5
+ export interface NiceCurrencyConverterProps {
6
+ /** Source money. */
7
+ source: Money;
8
+ /** Setter for the source. */
9
+ onSourceChange: (m: Money) => void;
10
+ /** Target currency code. */
11
+ targetCurrency: string;
12
+ /** Setter for target currency. */
13
+ onTargetCurrencyChange: (code: string) => void;
14
+ /** Provider used to fetch the rate (Frankfurter / OpenExchangeRates / static / …). */
15
+ provider: ExchangeRateProvider;
16
+ /** Locale for formatted preview. */
17
+ locale?: string;
18
+ /** Show the live `1 X = Y Z` rate line. Default `true`. */
19
+ showRateLine?: boolean;
20
+ className?: string;
21
+ style?: React.CSSProperties;
22
+ }
23
+ /**
24
+ * {@link NiceCurrencyConverter} — live source ↔ target conversion widget.
25
+ */
26
+ export declare const NiceCurrencyConverter: React.ForwardRefExoticComponent<NiceCurrencyConverterProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,31 @@
1
+ import { NiceFormFieldProps } from '@nice2dev/ui-core';
2
+ import { default as React } from 'react';
3
+ import { CurrencyDef } from '../types';
4
+
5
+ export interface NiceCurrencySelectorProps extends Omit<NiceFormFieldProps, 'displayMode' | 'submitOnEnter' | 'onSubmit' | 'showKeyboardHints'> {
6
+ /** Selected ISO 4217 code. */
7
+ value?: string;
8
+ /** Fires on change. */
9
+ onChange?: (code: string, def: CurrencyDef) => void;
10
+ /** Override the catalog (e.g. limit to a few). */
11
+ options?: CurrencyDef[];
12
+ /** Codes to show pinned at the top. */
13
+ recents?: string[];
14
+ /** Show a search input. Default `true` if `options.length > 8`. */
15
+ searchable?: boolean;
16
+ /** Show ISO code next to the name. Default `true`. */
17
+ showCode?: boolean;
18
+ /** Show currency symbol. Default `true`. */
19
+ showSymbol?: boolean;
20
+ /** Compact mode = symbol + code only (no full name). */
21
+ compact?: boolean;
22
+ 'aria-label'?: string;
23
+ placeholder?: string;
24
+ }
25
+ /**
26
+ * {@link NiceCurrencySelector} — accessible, searchable ISO-4217 picker.
27
+ *
28
+ * Native `<select>` (no portal, screen-reader friendly, mobile sheet on iOS/Android).
29
+ * Use as the building block for `NiceMoneyInput`, `NicePaymentForm`, etc.
30
+ */
31
+ export declare const NiceCurrencySelector: React.ForwardRefExoticComponent<NiceCurrencySelectorProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,30 @@
1
+ import { NiceBaseProps } from '@nice2dev/ui-core';
2
+ import { ExchangeRateProvider } from '../exchange';
3
+ import { Money } from '../types';
4
+
5
+ export interface NiceMoneyProps extends NiceBaseProps {
6
+ /** Money in minor units (preferred). */
7
+ value?: Money | null;
8
+ /** Convenience: pass major-unit number + currency. */
9
+ amount?: number;
10
+ currency?: string;
11
+ /** Override locale (e.g. `pl-PL`). */
12
+ locale?: string;
13
+ /** `'symbol' | 'code' | 'name' | 'narrowSymbol'`. Default `'symbol'`. */
14
+ display?: 'symbol' | 'code' | 'name' | 'narrowSymbol';
15
+ /** Show negative amounts with a class for styling (e.g. red). */
16
+ highlightNegative?: boolean;
17
+ /** Render zero as a placeholder string. */
18
+ zeroPlaceholder?: string;
19
+ /** When set, renders a converted preview underneath. */
20
+ convertTo?: string;
21
+ /** Required when `convertTo` is set. */
22
+ exchangeProvider?: ExchangeRateProvider;
23
+ 'aria-label'?: string;
24
+ }
25
+ /**
26
+ * {@link NiceMoney} — read-only formatted money display with optional FX preview.
27
+ */
28
+ export declare const NiceMoney: import('react').ForwardRefExoticComponent<NiceMoneyProps & import('react').RefAttributes<HTMLSpanElement>>;
29
+ /** Sortable list of all known currency codes — handy for selectors. */
30
+ export declare const NICE_CURRENCY_CODES: string[];
@@ -0,0 +1,43 @@
1
+ import { NiceFormFieldProps } from '@nice2dev/ui-core';
2
+ import { default as React } from 'react';
3
+ import { Money } from '../types';
4
+
5
+ export interface NiceMoneyInputProps extends Omit<NiceFormFieldProps, 'displayMode'> {
6
+ /** Controlled value (preferred). Storage-safe minor units. */
7
+ value?: Money | null;
8
+ /** Fires on every keystroke that produces a valid amount. */
9
+ onChange?: (value: Money | null) => void;
10
+ /** Selected currency. Mirrors `value.currency` when both supplied. */
11
+ currency?: string;
12
+ /** Fires when user changes currency from the inline selector. */
13
+ onCurrencyChange?: (code: string) => void;
14
+ /** Currencies offered in the inline selector. */
15
+ currencies?: string[];
16
+ /** Hide the inline currency selector (e.g. when fixed by context). */
17
+ hideCurrencySelector?: boolean;
18
+ /** Min / max validation in major units (e.g. `0.50`). */
19
+ min?: number;
20
+ max?: number;
21
+ /** Step in major units. Default = 1 / 10^decimals. */
22
+ step?: number;
23
+ /** Locale for formatted preview. */
24
+ locale?: string;
25
+ /** Placeholder. */
26
+ placeholder?: string;
27
+ /** Show the formatted preview below the input. Default `true`. */
28
+ showFormattedPreview?: boolean;
29
+ /** When `true`, renders quick increment / decrement buttons. */
30
+ showSteppers?: boolean;
31
+ /** Bubble up blur / focus for form-library integrations. */
32
+ onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
33
+ onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
34
+ }
35
+ /**
36
+ * {@link NiceMoneyInput} — amount + currency input with locale-aware formatting,
37
+ * minor-unit safe storage, integrated `NiceCurrencySelector`, optional steppers
38
+ * and a formatted preview.
39
+ *
40
+ * Internally drives parsing through {@link parseMoney} and {@link toMoney} so
41
+ * values are always stored as integer minor units.
42
+ */
43
+ export declare const NiceMoneyInput: React.ForwardRefExoticComponent<NiceMoneyInputProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,47 @@
1
+ import { default as React } from 'react';
2
+ import { PaymentProcessor } from '../processors';
3
+ import { Money, PaymentRequest, PaymentResponse } from '../types';
4
+
5
+ export interface NicePaymentFormProps {
6
+ /** Active payment processor adapter. */
7
+ processor: PaymentProcessor;
8
+ /** Pre-filled / locked amount. If editable, pass `editableAmount`. */
9
+ amount: Money;
10
+ /** When `true`, customer can change the amount (donations, top-ups). */
11
+ editableAmount?: boolean;
12
+ /** Pre-filled customer info. */
13
+ customer?: PaymentRequest['customer'];
14
+ /** Allowed currencies if the amount is editable. */
15
+ currencies?: string[];
16
+ /** Description shown to customer + sent to processor. */
17
+ description?: string;
18
+ /** Metadata forwarded to the processor. */
19
+ metadata?: Record<string, string>;
20
+ /** Auto-capture after successful authorization. Default `true`. */
21
+ autoCapture?: boolean;
22
+ /** Submit button label. */
23
+ submitLabel?: React.ReactNode;
24
+ /** Fires on successful authorization (and capture if `autoCapture`). */
25
+ onSuccess?: (response: PaymentResponse) => void;
26
+ /** Fires on any failure. */
27
+ onError?: (error: Error) => void;
28
+ /** Optional render slot for terms acceptance / consents. */
29
+ consents?: React.ReactNode;
30
+ className?: string;
31
+ style?: React.CSSProperties;
32
+ }
33
+ /**
34
+ * {@link NicePaymentForm} — composite checkout form: amount → card → confirm.
35
+ *
36
+ * Handles the full UI lifecycle:
37
+ * 1. Amount + currency (locked or editable).
38
+ * 2. Credit card (with brand detection, Luhn, expiry, CVC).
39
+ * 3. Validation (currency support, processor minimums, card validity).
40
+ * 4. Submission via the injected `PaymentProcessor` (uses {@link useNicePayment}).
41
+ * 5. Status display (idle / authorizing / authorized / captured / failed).
42
+ *
43
+ * The processor is fully pluggable — pass `MockPaymentProcessor` in tests, a real
44
+ * Stripe / PayPal / Adyen / PayU adapter in production. The form never speaks to a
45
+ * specific PSP directly.
46
+ */
47
+ export declare const NicePaymentForm: React.ForwardRefExoticComponent<NicePaymentFormProps & React.RefAttributes<HTMLFormElement>>;
@@ -0,0 +1,26 @@
1
+ import { NiceBaseProps } from '@nice2dev/ui-core';
2
+ import { default as React } from 'react';
3
+ import { Money } from '../types';
4
+
5
+ export interface NicePriceTagProps extends NiceBaseProps {
6
+ /** Current (sale / final) price. */
7
+ price: Money;
8
+ /** Original price — when set and higher, rendered struck-through. */
9
+ originalPrice?: Money;
10
+ /** Subscription / time period appended after the amount, e.g. `'/mo'`, `'/yr'`. */
11
+ period?: string;
12
+ /** Extra label, e.g. `'starting at'`, `'from'`. */
13
+ prefix?: React.ReactNode;
14
+ /** Suffix slot, e.g. tax notice. */
15
+ suffix?: React.ReactNode;
16
+ /** Show calculated `% off` badge when both prices given. Default `true`. */
17
+ showDiscountBadge?: boolean;
18
+ size?: 'sm' | 'md' | 'lg' | 'xl';
19
+ /** Locale override. */
20
+ locale?: string;
21
+ }
22
+ /**
23
+ * {@link NicePriceTag} — display price with optional original-price strike-through,
24
+ * percent-off badge, period suffix (subscriptions), prefix / suffix slots.
25
+ */
26
+ export declare const NicePriceTag: React.ForwardRefExoticComponent<NicePriceTagProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,24 @@
1
+ import { CardBrand, CreditCardData, CreditCardValidation } from './types';
2
+
3
+ /**
4
+ * Detect the card brand from the BIN (Bank Identification Number, first 6 digits).
5
+ * Heuristic ranges based on the public registries; not exhaustive.
6
+ */
7
+ export declare function detectCardBrand(rawNumber: string): CardBrand;
8
+ /** Luhn / mod-10 checksum. */
9
+ export declare function luhnCheck(rawNumber: string): boolean;
10
+ /** Format `4111111111111111` → `4111 1111 1111 1111` (Amex → 4-6-5 grouping). */
11
+ export declare function formatCardNumber(rawNumber: string, brand?: CardBrand): string;
12
+ /** Mask all but the last 4 digits: `**** **** **** 1234`. */
13
+ export declare function maskCardNumber(rawNumber: string, brand?: CardBrand): string;
14
+ /** Normalise expiry input into `MM/YY`. Returns `null` on invalid month. */
15
+ export declare function normaliseExpiry(input: string): string | null;
16
+ /** True if `MM/YY` (or `MM/YYYY`) is in the future (end-of-month). */
17
+ export declare function isExpiryValid(input: string, now?: Date): boolean;
18
+ /** Required CVC length per brand (Amex = 4, others = 3). */
19
+ export declare function expectedCvcLength(brand: CardBrand): number;
20
+ /** Full validation summary for a credit card. */
21
+ export declare function validateCreditCard(card: CreditCardData, opts?: {
22
+ now?: Date;
23
+ requireHolder?: boolean;
24
+ }): CreditCardValidation;
@@ -0,0 +1,81 @@
1
+ import { ExchangeRate, Money } from './types';
2
+
3
+ /**
4
+ * Provider-agnostic exchange-rate source.
5
+ *
6
+ * Implementations can wrap any FX API (ECB, Open Exchange Rates, Fixer, Frankfurter,
7
+ * CurrencyAPI, …) — the consumer only depends on this interface.
8
+ */
9
+ export interface ExchangeRateProvider {
10
+ /** Stable identifier for telemetry / cache scoping. */
11
+ readonly id: string;
12
+ /**
13
+ * Fetch the rate `1 base = ? quote`. Implementations should cache.
14
+ */
15
+ getRate(base: string, quote: string): Promise<ExchangeRate>;
16
+ /**
17
+ * Optional: bulk fetch all rates against a single base — drastically reduces requests
18
+ * for converters that price one input against many outputs.
19
+ */
20
+ getRates?(base: string, quotes: string[]): Promise<ExchangeRate[]>;
21
+ }
22
+ /**
23
+ * Hard-coded provider — useful for tests, demos, offline mode, and as a fallback when
24
+ * a network provider rate-limits.
25
+ */
26
+ export declare class StaticExchangeRateProvider implements ExchangeRateProvider {
27
+ private readonly rates;
28
+ readonly id = "static";
29
+ constructor(rates: ExchangeRate[]);
30
+ getRate(base: string, quote: string): Promise<ExchangeRate>;
31
+ }
32
+ interface HttpProviderOpts {
33
+ /** Override `fetch` for tests / SSR. */
34
+ fetchImpl?: typeof fetch;
35
+ /** TTL for in-memory cache, milliseconds. Default 60_000 (1 min). */
36
+ cacheTtl?: number;
37
+ }
38
+ interface CacheEntry {
39
+ expires: number;
40
+ rate: ExchangeRate;
41
+ }
42
+ declare abstract class CachingHttpProvider implements ExchangeRateProvider {
43
+ abstract readonly id: string;
44
+ protected readonly cache: Map<string, CacheEntry>;
45
+ protected readonly fetchImpl: typeof fetch;
46
+ protected readonly ttl: number;
47
+ constructor(opts?: HttpProviderOpts);
48
+ protected cacheKey(base: string, quote: string): string;
49
+ protected getCached(base: string, quote: string): ExchangeRate | null;
50
+ protected setCached(rate: ExchangeRate): void;
51
+ abstract getRate(base: string, quote: string): Promise<ExchangeRate>;
52
+ }
53
+ /**
54
+ * Frankfurter (https://www.frankfurter.app) — free, ECB-derived, no API key.
55
+ */
56
+ export declare class FrankfurterProvider extends CachingHttpProvider {
57
+ readonly id = "frankfurter";
58
+ constructor(opts?: HttpProviderOpts);
59
+ getRate(base: string, quote: string): Promise<ExchangeRate>;
60
+ }
61
+ /**
62
+ * Open Exchange Rates (https://openexchangerates.org) — requires API key.
63
+ * Quotes against USD; cross-rates derived locally.
64
+ */
65
+ export declare class OpenExchangeRatesProvider extends CachingHttpProvider {
66
+ private readonly appId;
67
+ readonly id = "openexchangerates";
68
+ constructor(appId: string, opts?: HttpProviderOpts);
69
+ getRate(base: string, quote: string): Promise<ExchangeRate>;
70
+ }
71
+ /**
72
+ * exchangerate.host — free, no key required, ECB+open-source aggregated.
73
+ */
74
+ export declare class ExchangeRateHostProvider extends CachingHttpProvider {
75
+ readonly id = "exchangerate.host";
76
+ constructor(opts?: HttpProviderOpts);
77
+ getRate(base: string, quote: string): Promise<ExchangeRate>;
78
+ }
79
+ /** Convert a money amount using a provider (single round-trip). */
80
+ export declare function convertMoney(source: Money, targetCurrency: string, provider: ExchangeRateProvider): Promise<Money>;
81
+ export {};
@@ -0,0 +1,42 @@
1
+ import { ExchangeRateProvider } from './exchange';
2
+ import { PaymentProcessor } from './processors';
3
+ import { Money, PaymentRequest, PaymentResponse, PaymentStatus } from './types';
4
+
5
+ /**
6
+ * Reactive wrapper around a `Money` value with helpers and locale formatting.
7
+ */
8
+ export declare function useNiceMoney(initial: Money | {
9
+ major: number;
10
+ currency: string;
11
+ }, locale?: string): {
12
+ money: Money;
13
+ setMoney: import('react').Dispatch<import('react').SetStateAction<Money>>;
14
+ setMajor: (major: number) => void;
15
+ setCurrency: (code: string) => void;
16
+ add: (other: Money) => void;
17
+ subtract: (other: Money) => void;
18
+ scale: (factor: number) => void;
19
+ formatted: string;
20
+ major: number;
21
+ };
22
+ /**
23
+ * Subscribe to live exchange-rate conversion. Re-fetches on currency changes.
24
+ */
25
+ export declare function useNiceExchangeRate(source: Money | null, targetCurrency: string, provider: ExchangeRateProvider): {
26
+ converted: Money | null;
27
+ loading: boolean;
28
+ error: Error | null;
29
+ };
30
+ /**
31
+ * Drive a payment lifecycle (`authorize` → optional `capture` → terminal status).
32
+ */
33
+ export declare function useNicePayment(processor: PaymentProcessor): {
34
+ status: PaymentStatus;
35
+ response: PaymentResponse | null;
36
+ error: Error | null;
37
+ authorize: (req: PaymentRequest) => Promise<PaymentResponse>;
38
+ capture: (id?: string, amount?: PaymentRequest["amount"]) => Promise<PaymentResponse>;
39
+ cancel: (id?: string) => Promise<PaymentResponse>;
40
+ refund: (id?: string, amount?: PaymentRequest["amount"]) => Promise<PaymentResponse>;
41
+ reset: () => void;
42
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var Xe=Object.defineProperty;var We=(r,e,n)=>e in r?Xe(r,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):r[e]=n;var F=(r,e,n)=>We(r,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react"),c=require("react/jsx-runtime"),ne=[{code:"USD",numeric:840,symbol:"$",name:"US Dollar",decimals:2,symbolFirst:!0},{code:"EUR",numeric:978,symbol:"€",name:"Euro",decimals:2,symbolFirst:!0},{code:"GBP",numeric:826,symbol:"£",name:"Pound Sterling",decimals:2,symbolFirst:!0},{code:"JPY",numeric:392,symbol:"¥",name:"Japanese Yen",decimals:0,symbolFirst:!0},{code:"CHF",numeric:756,symbol:"Fr",name:"Swiss Franc",decimals:2},{code:"CAD",numeric:124,symbol:"CA$",name:"Canadian Dollar",decimals:2,symbolFirst:!0},{code:"AUD",numeric:36,symbol:"A$",name:"Australian Dollar",decimals:2,symbolFirst:!0},{code:"NZD",numeric:554,symbol:"NZ$",name:"New Zealand Dollar",decimals:2,symbolFirst:!0},{code:"CNY",numeric:156,symbol:"¥",name:"Chinese Yuan Renminbi",decimals:2,symbolFirst:!0},{code:"HKD",numeric:344,symbol:"HK$",name:"Hong Kong Dollar",decimals:2,symbolFirst:!0},{code:"SGD",numeric:702,symbol:"S$",name:"Singapore Dollar",decimals:2,symbolFirst:!0},{code:"PLN",numeric:985,symbol:"zł",name:"Polish Złoty",decimals:2},{code:"CZK",numeric:203,symbol:"Kč",name:"Czech Koruna",decimals:2},{code:"HUF",numeric:348,symbol:"Ft",name:"Hungarian Forint",decimals:2},{code:"RON",numeric:946,symbol:"lei",name:"Romanian Leu",decimals:2},{code:"BGN",numeric:975,symbol:"лв",name:"Bulgarian Lev",decimals:2},{code:"HRK",numeric:191,symbol:"kn",name:"Croatian Kuna",decimals:2},{code:"DKK",numeric:208,symbol:"kr",name:"Danish Krone",decimals:2},{code:"SEK",numeric:752,symbol:"kr",name:"Swedish Krona",decimals:2},{code:"NOK",numeric:578,symbol:"kr",name:"Norwegian Krone",decimals:2},{code:"ISK",numeric:352,symbol:"kr",name:"Icelandic Króna",decimals:0},{code:"RUB",numeric:643,symbol:"₽",name:"Russian Ruble",decimals:2},{code:"UAH",numeric:980,symbol:"₴",name:"Ukrainian Hryvnia",decimals:2},{code:"TRY",numeric:949,symbol:"₺",name:"Turkish Lira",decimals:2,symbolFirst:!0},{code:"MXN",numeric:484,symbol:"Mex$",name:"Mexican Peso",decimals:2,symbolFirst:!0},{code:"BRL",numeric:986,symbol:"R$",name:"Brazilian Real",decimals:2,symbolFirst:!0},{code:"ARS",numeric:32,symbol:"AR$",name:"Argentine Peso",decimals:2,symbolFirst:!0},{code:"CLP",numeric:152,symbol:"CL$",name:"Chilean Peso",decimals:0,symbolFirst:!0},{code:"COP",numeric:170,symbol:"CO$",name:"Colombian Peso",decimals:2,symbolFirst:!0},{code:"PEN",numeric:604,symbol:"S/",name:"Peruvian Sol",decimals:2,symbolFirst:!0},{code:"UYU",numeric:858,symbol:"$U",name:"Uruguayan Peso",decimals:2,symbolFirst:!0},{code:"INR",numeric:356,symbol:"₹",name:"Indian Rupee",decimals:2,symbolFirst:!0},{code:"IDR",numeric:360,symbol:"Rp",name:"Indonesian Rupiah",decimals:2,symbolFirst:!0},{code:"KRW",numeric:410,symbol:"₩",name:"South Korean Won",decimals:0,symbolFirst:!0},{code:"MYR",numeric:458,symbol:"RM",name:"Malaysian Ringgit",decimals:2,symbolFirst:!0},{code:"PHP",numeric:608,symbol:"₱",name:"Philippine Peso",decimals:2,symbolFirst:!0},{code:"THB",numeric:764,symbol:"฿",name:"Thai Baht",decimals:2,symbolFirst:!0},{code:"VND",numeric:704,symbol:"₫",name:"Vietnamese Đồng",decimals:0},{code:"TWD",numeric:901,symbol:"NT$",name:"New Taiwan Dollar",decimals:2,symbolFirst:!0},{code:"PKR",numeric:586,symbol:"₨",name:"Pakistani Rupee",decimals:2},{code:"BDT",numeric:50,symbol:"৳",name:"Bangladeshi Taka",decimals:2},{code:"LKR",numeric:144,symbol:"Rs",name:"Sri Lankan Rupee",decimals:2},{code:"AED",numeric:784,symbol:"د.إ",name:"UAE Dirham",decimals:2},{code:"SAR",numeric:682,symbol:"ر.س",name:"Saudi Riyal",decimals:2},{code:"QAR",numeric:634,symbol:"ر.ق",name:"Qatari Riyal",decimals:2},{code:"KWD",numeric:414,symbol:"د.ك",name:"Kuwaiti Dinar",decimals:3},{code:"BHD",numeric:48,symbol:".د.ب",name:"Bahraini Dinar",decimals:3},{code:"OMR",numeric:512,symbol:"ر.ع",name:"Omani Rial",decimals:3},{code:"JOD",numeric:400,symbol:"د.أ",name:"Jordanian Dinar",decimals:3},{code:"ILS",numeric:376,symbol:"₪",name:"Israeli New Shekel",decimals:2,symbolFirst:!0},{code:"EGP",numeric:818,symbol:"E£",name:"Egyptian Pound",decimals:2,symbolFirst:!0},{code:"ZAR",numeric:710,symbol:"R",name:"South African Rand",decimals:2,symbolFirst:!0},{code:"NGN",numeric:566,symbol:"₦",name:"Nigerian Naira",decimals:2,symbolFirst:!0},{code:"KES",numeric:404,symbol:"KSh",name:"Kenyan Shilling",decimals:2},{code:"GHS",numeric:936,symbol:"₵",name:"Ghanaian Cedi",decimals:2},{code:"MAD",numeric:504,symbol:"د.م.",name:"Moroccan Dirham",decimals:2},{code:"TND",numeric:788,symbol:"د.ت",name:"Tunisian Dinar",decimals:3},{code:"BTC",numeric:0,symbol:"₿",name:"Bitcoin",decimals:8,symbolFirst:!0},{code:"ETH",numeric:0,symbol:"Ξ",name:"Ether",decimals:8,symbolFirst:!0},{code:"USDC",numeric:0,symbol:"USDC",name:"USD Coin",decimals:6},{code:"USDT",numeric:0,symbol:"USDT",name:"Tether",decimals:6}],je=new Map(ne.map(r=>[r.code,r]));function O(r){return je.get(r.toUpperCase())}function en(r){je.set(r.code.toUpperCase(),r);const e=ne.findIndex(n=>n.code===r.code);e>=0?ne[e]=r:ne.push(r)}function W(r,e){const n=O(e),t=(n==null?void 0:n.decimals)??2,a=Math.pow(10,t);return{amount:(r<0?-1:1)*Math.round(Math.abs(r)*a),currency:e.toUpperCase()}}function de(r){const e=O(r.currency),n=(e==null?void 0:e.decimals)??2;return r.amount/Math.pow(10,n)}function nn(r,e){return r.currency.toUpperCase()===e.currency.toUpperCase()}function me(r,e,n){if(!nn(r,e))throw new Error(`Cannot ${n} different currencies: ${r.currency} vs ${e.currency}`)}function Se(r,e){return me(r,e,"add"),{amount:r.amount+e.amount,currency:r.currency}}function Me(r,e){return me(r,e,"subtract"),{amount:r.amount-e.amount,currency:r.currency}}function ve(r,e){return{amount:(r.amount*e<0?-1:1)*Math.round(Math.abs(r.amount*e)),currency:r.currency}}function rn(r,e){return me(r,e,"compare"),r.amount-e.amount}function tn(r){return r.amount===0}function an(r){return r.amount<0}function cn(r){return{amount:-r.amount,currency:r.currency}}function V(r,e={}){const n=O(r.currency),t=e.decimals??(n==null?void 0:n.decimals)??2,a=r.amount/Math.pow(10,t);try{return new Intl.NumberFormat(e.locale,{style:"currency",currency:r.currency,minimumFractionDigits:t,maximumFractionDigits:t,currencyDisplay:e.currencyDisplay??"symbol"}).format(a)}catch{const s=(n==null?void 0:n.symbol)??r.currency,l=a.toFixed(t);return n!=null&&n.symbolFirst?`${s}${l}`:`${l} ${s}`}}function Re(r,e){if(!r)return null;let n=r.replace(/[^\d.,\-]/g,"").trim();if(!n||n==="-")return null;const t=n.startsWith("-");t&&(n=n.slice(1));const a=n.lastIndexOf("."),s=n.lastIndexOf(","),l=Math.max(a,s);let d,u="";l===-1?d=n:(d=n.slice(0,l).replace(/[.,]/g,""),u=n.slice(l+1).replace(/[.,]/g,""));const o=`${d}.${u||"0"}`,p=parseFloat(o);return Number.isFinite(p)?W(t?-p:p,e):null}function ae(r){const e=(r||"").replace(/\D/g,"");return e?/^4/.test(e)?"visa":/^(5[1-5]|2(2[2-9]|[3-6]\d|7[01]|720))/.test(e)?"mastercard":/^3[47]/.test(e)?"amex":/^(6011|65|64[4-9]|622(12[6-9]|1[3-9]\d|[2-8]\d\d|9[01]\d|92[0-5]))/.test(e)?"discover":/^3(0[0-5]|[68])/.test(e)?"diners":/^35(2[89]|[3-8])/.test(e)?"jcb":/^62/.test(e)?"unionpay":/^(50|5[6-9]|6[0-9])/.test(e)?"maestro":/^(2200|2201|2202|2203|2204)/.test(e)?"mir":/^(4011|4312|4389|4514|4576|5041|5066|5067|509|6277|6362|6363|650|6516|6550)/.test(e)?"elo":/^(606282|3841)/.test(e)?"hipercard":"unknown":"unknown"}function ye(r){const e=(r||"").replace(/\D/g,"");if(e.length<12||e.length>19)return!1;let n=0,t=!1;for(let a=e.length-1;a>=0;a--){let s=e.charCodeAt(a)-48;t&&(s*=2,s>9&&(s-=9)),n+=s,t=!t}return n%10===0}function $e(r,e){const n=(r||"").replace(/\D/g,""),t=e??ae(n);return t==="amex"?[n.slice(0,4),n.slice(4,10),n.slice(10,15)].filter(Boolean).join(" "):t==="diners"?[n.slice(0,4),n.slice(4,10),n.slice(10,14)].filter(Boolean).join(" "):(n.match(/.{1,4}/g)??[]).join(" ").trim()}function sn(r,e){const n=(r||"").replace(/\D/g,"");if(n.length<4)return n;const t=n.slice(-4),a="*".repeat(n.length-4)+t,s=e??ae(n);return s==="amex"?[a.slice(0,4),a.slice(4,10),a.slice(10,15)].filter(Boolean).join(" "):s==="diners"?[a.slice(0,4),a.slice(4,10),a.slice(10,14)].filter(Boolean).join(" "):(a.match(/.{1,4}/g)??[]).join(" ").trim()}function pe(r){const e=(r||"").replace(/\D/g,"");if(e.length<3)return null;const n=e.slice(0,2),t=e.length>=4?e.slice(-2):e.slice(2,4),a=parseInt(n,10);return!Number.isFinite(a)||a<1||a>12?null:`${n}/${t}`}function fe(r,e=new Date){const n=pe(r);if(!n)return!1;const[t,a]=n.split("/"),s=parseInt(t,10),l=2e3+parseInt(a,10);return new Date(l,s,0,23,59,59,999).getTime()>=e.getTime()}function ee(r){return r==="amex"?4:3}function le(r,e={}){var o;const n={},t=ae(r.number),a=ye(r.number);a||(n.number="Invalid card number");const s=fe(r.expiry,e.now);s||(n.expiry="Invalid or expired");const d=(r.cvc||"").replace(/\D/g,"").length===ee(t);d||(n.cvc=`CVC must be ${ee(t)} digits`);const u=!e.requireHolder||(((o=r.holderName)==null?void 0:o.trim().length)??0)>=2;return u||(n.holderName="Cardholder name required"),{brand:t,numberValid:a,expiryValid:s,cvcValid:d,holderValid:u,isValid:a&&s&&d&&u,errors:n}}class on{constructor(e){F(this,"id","static");this.rates=e}async getRate(e,n){const t=e.toUpperCase(),a=n.toUpperCase();if(t===a)return{base:t,quote:a,rate:1,asOf:new Date().toISOString()};const s=this.rates.find(o=>o.base===t&&o.quote===a);if(s)return s;const l=this.rates.find(o=>o.base===a&&o.quote===t);if(l)return{base:t,quote:a,rate:1/l.rate,asOf:l.asOf};const d=this.rates.find(o=>o.base===t&&o.quote==="USD")??this.rates.find(o=>o.base==="USD"&&o.quote===t),u=this.rates.find(o=>o.base==="USD"&&o.quote===a)??this.rates.find(o=>o.base===a&&o.quote==="USD");if(d&&u){const o=d.base==="USD"?1/d.rate:d.rate,p=u.base==="USD"?u.rate:1/u.rate;return{base:t,quote:a,rate:o*p,asOf:new Date().toISOString()}}throw new Error(`No FX rate for ${t}/${a}`)}}class he{constructor(e={}){F(this,"cache",new Map);F(this,"fetchImpl");F(this,"ttl");this.fetchImpl=e.fetchImpl??fetch,this.ttl=e.cacheTtl??6e4}cacheKey(e,n){return`${this.id}:${e.toUpperCase()}/${n.toUpperCase()}`}getCached(e,n){const t=this.cacheKey(e,n),a=this.cache.get(t);return a&&a.expires>Date.now()?a.rate:(a&&this.cache.delete(t),null)}setCached(e){this.cache.set(this.cacheKey(e.base,e.quote),{expires:Date.now()+this.ttl,rate:e})}}class ln extends he{constructor(n={}){super(n);F(this,"id","frankfurter")}async getRate(n,t){const a=n.toUpperCase(),s=t.toUpperCase();if(a===s)return{base:a,quote:s,rate:1,asOf:new Date().toISOString()};const l=this.getCached(a,s);if(l)return l;const d=`https://api.frankfurter.app/latest?from=${a}&to=${s}`,u=await this.fetchImpl(d);if(!u.ok)throw new Error(`Frankfurter HTTP ${u.status}`);const o=await u.json(),p=o.rates[s];if(p==null)throw new Error(`Frankfurter: no rate for ${a}/${s}`);const h={base:a,quote:s,rate:p,asOf:o.date};return this.setCached(h),h}}class un extends he{constructor(n,t={}){super(t);F(this,"id","openexchangerates");this.appId=n}async getRate(n,t){const a=n.toUpperCase(),s=t.toUpperCase();if(a===s)return{base:a,quote:s,rate:1,asOf:new Date().toISOString()};const l=this.getCached(a,s);if(l)return l;const d=`https://openexchangerates.org/api/latest.json?app_id=${this.appId}`,u=await this.fetchImpl(d);if(!u.ok)throw new Error(`OpenExchangeRates HTTP ${u.status}`);const o=await u.json(),p=o.rates[a],h=o.rates[s];if(p==null||h==null)throw new Error(`OpenExchangeRates: missing rate for ${a} or ${s}`);const y={base:a,quote:s,rate:h/p,asOf:new Date(o.timestamp*1e3).toISOString()};return this.setCached(y),y}}class dn extends he{constructor(n={}){super(n);F(this,"id","exchangerate.host")}async getRate(n,t){var y;const a=n.toUpperCase(),s=t.toUpperCase();if(a===s)return{base:a,quote:s,rate:1,asOf:new Date().toISOString()};const l=this.getCached(a,s);if(l)return l;const d=`https://api.exchangerate.host/convert?from=${a}&to=${s}`,u=await this.fetchImpl(d);if(!u.ok)throw new Error(`exchangerate.host HTTP ${u.status}`);const o=await u.json(),p=((y=o.info)==null?void 0:y.rate)??o.result,h={base:a,quote:s,rate:p,asOf:o.date};return this.setCached(h),h}}async function Ue(r,e,n){const t=e.toUpperCase();if(r.currency.toUpperCase()===t)return r;const a=O(r.currency),s=O(t),l=(a==null?void 0:a.decimals)??2,d=(s==null?void 0:s.decimals)??2,{rate:u}=await n.getRate(r.currency,t),p=r.amount/Math.pow(10,l)*u;return{currency:t,amount:Math.round(p*Math.pow(10,d))}}class Ee{constructor(e={}){F(this,"id");F(this,"name");F(this,"supportedCurrencies");F(this,"minimums");F(this,"counter",0);this.id=e.id??"mock",this.name=e.name??"Mock Processor",this.supportedCurrencies=e.supportedCurrencies??"*"}newId(e){return this.counter+=1,`${e}_${Date.now().toString(36)}_${this.counter}`}async authorize(e){return{id:this.newId("auth"),status:"authorized",processor:this.id,amount:e.amount,raw:{mock:!0,request:e}}}async capture(e,n){return{id:e.replace("auth_","cap_"),status:"captured",processor:this.id,amount:n}}async cancel(e){return{id:e,status:"cancelled",processor:this.id}}async refund(e,n){return{id:e.replace(/^[a-z]+_/,"ref_"),status:"refunded",processor:this.id,amount:n}}}function H(r,e,n,t,a){const s=l=>{const d=t==null?void 0:t[l];return d||(async()=>{throw new Error(`[ui-money] ${e} adapter is not wired. Pass an \`executor.${String(l)}\` implementation that calls the ${e} SDK.`)})};return{id:r,name:e,supportedCurrencies:n,minimums:a,authorize:s("authorize"),capture:s("capture"),cancel:s("cancel"),refund:s("refund")}}function ke(r){return H("stripe","Stripe","*",r,{USD:50,EUR:50,GBP:30,JPY:50,AUD:50,CAD:50,CHF:50,DKK:250,NOK:300,SEK:300,PLN:200})}function Pe(r){return H("paypal","PayPal",["USD","EUR","GBP","AUD","CAD","JPY","CHF","NZD","SEK","NOK","DKK","PLN","CZK","HUF","BRL","MXN","HKD","SGD","TWD","THB","PHP","ILS"],r)}function Ie(r){return H("adyen","Adyen","*",r)}function Ae(r){return H("square","Square",["USD","CAD","GBP","AUD","JPY","EUR"],r)}function Fe(r){return H("mollie","Mollie","*",r)}function Oe(r){return H("braintree","Braintree","*",r)}function Ke(r){return H("razorpay","Razorpay",["INR","USD","EUR","GBP","AED"],r)}function Te(r){return H("payu","PayU",["PLN","EUR","USD","CZK","HUF","RON","BGN","TRY"],r)}function Be(r){return H("przelewy24","Przelewy24",["PLN","EUR"],r)}function qe(r){return H("klarna","Klarna",["USD","EUR","GBP","SEK","NOK","DKK","CHF","AUD","CAD","NZD","PLN"],r)}function Le(r){return H("coinbase-commerce","Coinbase Commerce",["BTC","ETH","USDC","USDT","USD","EUR"],r)}const mn={mock:()=>new Ee,stripe:()=>ke(),paypal:()=>Pe(),adyen:()=>Ie(),square:()=>Ae(),mollie:()=>Fe(),braintree:()=>Oe(),razorpay:()=>Ke(),payu:()=>Te(),przelewy24:()=>Be(),klarna:()=>qe(),"coinbase-commerce":()=>Le()};function yn(r,e){const n=i.useMemo(()=>"amount"in r?r:W(r.major,r.currency),[]),[t,a]=i.useState(n),s=i.useCallback(y=>a(m=>W(y,m.currency)),[]),l=i.useCallback(y=>{a(m=>({...m,currency:y.toUpperCase()}))},[]),d=i.useCallback(y=>a(m=>Se(m,y)),[]),u=i.useCallback(y=>a(m=>Me(m,y)),[]),o=i.useCallback(y=>a(m=>ve(m,y)),[]),p=i.useMemo(()=>V(t,{locale:e}),[t,e]),h=i.useMemo(()=>de(t),[t]);return{money:t,setMoney:a,setMajor:s,setCurrency:l,add:d,subtract:u,scale:o,formatted:p,major:h}}function oe(r,e,n){const[t,a]=i.useState(null),[s,l]=i.useState(null),[d,u]=i.useState(!1),o=i.useRef(0);return i.useEffect(()=>{if(!r){a(null);return}const p=++o.current;u(!0),l(null),Ue(r,e,n).then(h=>{o.current===p&&a(h)}).catch(h=>{o.current===p&&(l(h instanceof Error?h:new Error(String(h))),a(null))}).finally(()=>{o.current===p&&u(!1)})},[r==null?void 0:r.amount,r==null?void 0:r.currency,e,n]),{converted:t,loading:d,error:s}}function He(r){const[e,n]=i.useState("idle"),[t,a]=i.useState(null),[s,l]=i.useState(null),d=i.useCallback(()=>{n("idle"),a(null),l(null)},[]),u=i.useCallback(async y=>{n("authorizing"),l(null);try{const m=await r.authorize(y);return a(m),n(m.status),m}catch(m){const _=m instanceof Error?m:new Error(String(m));throw l(_),n("failed"),_}},[r]),o=i.useCallback(async(y,m)=>{const _=y??(t==null?void 0:t.id);if(!_)throw new Error("No authorization id to capture");n("capturing");try{const x=await r.capture(_,m);return a(x),n(x.status),x}catch(x){const b=x instanceof Error?x:new Error(String(x));throw l(b),n("failed"),b}},[r,t==null?void 0:t.id]),p=i.useCallback(async y=>{const m=y??(t==null?void 0:t.id);if(!m)throw new Error("No payment id to cancel");const _=await r.cancel(m);return a(_),n(_.status),_},[r,t==null?void 0:t.id]),h=i.useCallback(async(y,m)=>{const _=y??(t==null?void 0:t.id);if(!_)throw new Error("No payment id to refund");const x=await r.refund(_,m);return a(x),n(x.status),x},[r,t==null?void 0:t.id]);return{status:e,response:t,error:s,authorize:u,capture:o,cancel:p,refund:h,reset:d}}const ue=i.forwardRef(function({value:e,amount:n,currency:t,locale:a,display:s="symbol",highlightNegative:l=!0,zeroPlaceholder:d,convertTo:u,exchangeProvider:o,className:p,style:h,"aria-label":y},m){const _=i.useMemo(()=>{if(e)return e;if(n!=null&&t){const $=O(t),U=($==null?void 0:$.decimals)??2;return{amount:Math.round(n*Math.pow(10,U)),currency:t.toUpperCase()}}return null},[e,n,t]),x=i.useMemo(()=>_?_.amount===0&&d!=null?d:V(_,{locale:a,currencyDisplay:s}):"",[_,a,s,d]),{converted:b}=oe(u&&o?_:null,u??"",o);if(!_)return null;const T=_.amount<0,B="nice-money"+(l&&T?" nice-money--negative":"")+(p?` ${p}`:"");return c.jsxs("span",{ref:m,className:B,style:h,"aria-label":y,children:[c.jsx("span",{className:"nice-money__value",children:x}),b&&c.jsxs("span",{className:"nice-money__converted","aria-label":`Converted to ${b.currency}`,children:[" ≈ ",V(b,{locale:a})]})]})}),pn=ne.map(r=>r.code).sort(),be=i.forwardRef(function({value:e,onChange:n,options:t=ne,recents:a=[],searchable:s,showCode:l=!0,showSymbol:d=!0,compact:u=!1,disabled:o,readOnly:p,loading:h,accessMode:y,label:m,helperText:_,error:x,required:b,size:T="md",labelPlacement:B="top",labelWidth:$,controlWidth:U,className:Z,style:J,id:E,name:R,title:X,"data-testid":v,"aria-label":z,placeholder:P},D){if(y==="hidden")return null;const K=o||y==="disabled"||h,Q=p||y==="readOnly",k=i.useId(),q=E??`nice-cs-${k}`,[Y,L]=i.useState(""),w=s??t.length>8,g=i.useMemo(()=>{if(!Y.trim())return t;const f=Y.trim().toLowerCase();return t.filter(S=>S.code.toLowerCase().includes(f)||S.name.toLowerCase().includes(f)||S.symbol.toLowerCase().includes(f))},[t,Y]),j=i.useMemo(()=>{const f=new Set;return a.map(S=>O(S)).filter(S=>!!S&&!f.has(S.code)&&!!f.add(S.code))},[a]),A=i.useCallback(f=>{const S=f.target.value,ce=O(S);ce&&(n==null||n(S,ce))},[n]),re=f=>{if(u)return`${d?f.symbol+" ":""}${f.code}`;const S=[];return d&&S.push(f.symbol),l&&S.push(f.code),S.push("—",f.name),S.join(" ")},N=`nice-currency-selector nice-currency-selector--${T} nice-currency-selector--label-${B}`+(u?" nice-currency-selector--compact":"")+(x?" nice-currency-selector--error":"")+(K?" nice-currency-selector--disabled":"")+(Z?` ${Z}`:""),C=B==="left"&&$!=null?{width:typeof $=="number"?`${$}px`:$,flex:"none"}:void 0,I=U!=null?{width:typeof U=="number"?`${U}px`:U,flex:"none"}:void 0;return c.jsxs("div",{ref:D,className:N,style:J,title:X,"data-testid":v,children:[m&&c.jsxs("label",{htmlFor:q,className:"nice-currency-selector__label",style:C,children:[m,b&&c.jsx("span",{className:"nice-currency-selector__required","aria-hidden":!0,children:"*"})]}),c.jsxs("div",{className:"nice-currency-selector__inner",style:I,children:[w&&c.jsx("input",{type:"search",className:"nice-currency-selector__search",placeholder:"Search currency…",value:Y,onChange:f=>L(f.target.value),disabled:K||Q,"aria-label":"Search currency"}),c.jsxs("select",{id:q,name:R,className:"nice-currency-selector__select",value:e??"",onChange:A,disabled:K||Q,required:b,"aria-invalid":!!x,"aria-label":z??(typeof m=="string"?m:"Currency"),children:[!e&&c.jsx("option",{value:"",children:P??"Select currency…"}),j.length>0&&c.jsx("optgroup",{label:"Recent",children:j.map(f=>c.jsx("option",{value:f.code,children:re(f)},`r-${f.code}`))}),c.jsx("optgroup",{label:j.length>0?"All":"",children:g.map(f=>c.jsx("option",{value:f.code,children:re(f)},f.code))})]})]}),(x||_)&&c.jsx("span",{className:x?"nice-currency-selector__error":"nice-currency-selector__helper",children:x??_})]})}),_e=i.forwardRef(function({value:e,onChange:n,currency:t,onCurrencyChange:a,currencies:s,hideCurrencySelector:l,min:d,max:u,step:o,locale:p,placeholder:h,label:y,helperText:m,error:_,size:x="md",disabled:b,readOnly:T,required:B,loading:$,accessMode:U,labelPlacement:Z="top",errorPlacement:J="bottom",labelWidth:E,controlWidth:R,submitOnEnter:X,onSubmit:v,showFormattedPreview:z=!0,showSteppers:P=!1,className:D,style:K,id:Q,name:k,title:q,"data-testid":Y,onBlur:L,onFocus:w},g){const j=b||U==="disabled"||$,A=T||U==="readOnly";if(U==="hidden")return null;const re=i.useId(),N=Q??`nice-money-${re}`,C=((e==null?void 0:e.currency)??t??"USD").toUpperCase(),I=O(C),f=(I==null?void 0:I.decimals)??2,S=o??1/Math.pow(10,f),[ce,se]=i.useState(()=>e?(e.amount/Math.pow(10,f)).toFixed(f):""),[xe,Ne]=i.useState(!1);i.useEffect(()=>{if(xe)return;if(e==null){se("");return}const M=O(e.currency),G=(M==null?void 0:M.decimals)??2;se((e.amount/Math.pow(10,G)).toFixed(G))},[e,xe]);const Ce=i.useCallback(M=>{const G=Re(M,C);if(!G){n==null||n(null);return}const ie=G.amount/Math.pow(10,f);if(d!=null&&ie<d){n==null||n(W(d,C));return}if(u!=null&&ie>u){n==null||n(W(u,C));return}n==null||n(G)},[C,f,d,u,n]),Ye=i.useCallback(M=>{const G=M.target.value;se(G),Ce(G)},[Ce]),ge=i.useCallback(M=>{const ie=(e?e.amount/Math.pow(10,f):0)+M,Qe=Math.min(u??1/0,Math.max(d??-1/0,ie));n==null||n(W(Qe,C))},[e,f,d,u,C,n]),we=i.useMemo(()=>e?V(e,{locale:p}):"",[e,p]),te=typeof _=="string"?_:void 0,Ge=`nice-money-input nice-money-input--${x} nice-money-input--label-${Z} nice-money-input--error-${J}`+(te?" nice-money-input--error":"")+(j?" nice-money-input--disabled":"")+($?" nice-money-input--loading":"")+(D?` ${D}`:""),Ve=M=>{X&&M.key==="Enter"&&v&&(M.preventDefault(),v())},Ze=Z==="left"&&E!=null?{width:typeof E=="number"?`${E}px`:E,flex:"none"}:void 0,Je=R!=null?{width:typeof R=="number"?`${R}px`:R,flex:"none"}:void 0;return c.jsxs("div",{ref:g,className:Ge,style:K,title:q,"data-testid":Y,children:[y&&c.jsxs("label",{htmlFor:N,className:"nice-money-input__label",style:Ze,children:[y,B&&c.jsx("span",{className:"nice-money-input__required","aria-hidden":!0,children:"*"})]}),c.jsxs("div",{className:"nice-money-input__row",style:Je,children:[!l&&c.jsx(be,{value:C,compact:!0,options:s?s.map(O).filter(M=>!!M):void 0,onChange:M=>a==null?void 0:a(M),disabled:j||A,className:"nice-money-input__currency","aria-label":"Currency"}),l&&c.jsx("span",{className:"nice-money-input__symbol","aria-hidden":!0,children:(I==null?void 0:I.symbol)??C}),P&&c.jsx("button",{type:"button",className:"nice-money-input__step nice-money-input__step--down",onClick:()=>ge(-S),disabled:j||A||d!=null&&e!=null&&e.amount/Math.pow(10,f)<=d,"aria-label":"Decrease amount",tabIndex:-1,children:"−"}),c.jsx("input",{id:N,name:k,type:"text",inputMode:"decimal",autoComplete:"transaction-amount",className:"nice-money-input__amount",value:ce,onChange:Ye,onKeyDown:Ve,onFocus:M=>{Ne(!0),w==null||w(M)},onBlur:M=>{Ne(!1),e&&se((e.amount/Math.pow(10,f)).toFixed(f)),L==null||L(M)},placeholder:h??`0${f>0?"."+"0".repeat(f):""}`,disabled:j,readOnly:A,required:B,"aria-invalid":!!te,"aria-describedby":te||m?`${N}-help`:void 0}),P&&c.jsx("button",{type:"button",className:"nice-money-input__step nice-money-input__step--up",onClick:()=>ge(S),disabled:j||A||u!=null&&e!=null&&e.amount/Math.pow(10,f)>=u,"aria-label":"Increase amount",tabIndex:-1,children:"+"}),$&&c.jsx("span",{className:"nice-money-input__spinner","aria-hidden":!0,children:"⟳"})]}),z&&we&&c.jsx("span",{className:"nice-money-input__preview","aria-live":"polite",children:we}),(te||m)&&c.jsx("span",{id:`${N}-help`,className:te?"nice-money-input__error":"nice-money-input__helper",children:te??m})]})}),fn={number:"",expiry:"",cvc:""},ze=i.forwardRef(function({value:e=fn,onChange:n,showHolderName:t=!0,requireHolderName:a,showPostalCode:s=!1,acceptedBrands:l,showBrandIcon:d=!0,size:u="md",disabled:o,readOnly:p,loading:h,accessMode:y,label:m,helperText:_,error:x,required:b,className:T,style:B,id:$,title:U,"data-testid":Z},J){if(y==="hidden")return null;const E=o||y==="disabled"||h,R=p||y==="readOnly",X=i.useId(),v=$??`nice-cc-${X}`,[z,P]=i.useState({number:!1,expiry:!1,cvc:!1,holder:!1,postal:!1}),D=i.useMemo(()=>ae(e.number),[e.number]),K=i.useMemo(()=>le(e,{requireHolder:a??t}),[e,a,t]),Q=!l||l.includes(D)||D==="unknown",k=i.useCallback(N=>{const C={...e,...N},I=le(C,{requireHolder:a??t});n==null||n(C,I)},[e,a,t,n]),q=i.useCallback(N=>{const C=N.target.value.replace(/\D/g,"").slice(0,19);k({number:C})},[k]),Y=i.useCallback(N=>{const C=N.target.value.replace(/\D/g,"").slice(0,4);let I=C;C.length>=3?I=`${C.slice(0,2)}/${C.slice(2)}`:C.length===2&&(I=`${C}/`),k({expiry:I})},[k]),L=i.useCallback(N=>{const C=N.target.value.replace(/\D/g,"").slice(0,ee(D));k({cvc:C})},[D,k]),w=i.useMemo(()=>$e(e.number,D),[e.number,D]),g=z.number&&e.number.length>0&&(ye(e.number)?Q?void 0:"Card brand not accepted":K.errors.number),j=z.expiry&&e.expiry.length>0&&!fe(e.expiry)?K.errors.expiry:void 0,A=z.cvc&&e.cvc.length>0&&e.cvc.length!==ee(D)?K.errors.cvc:void 0,re=`nice-credit-card nice-credit-card--${u}`+(E?" nice-credit-card--disabled":"")+(h?" nice-credit-card--loading":"")+(x?" nice-credit-card--error":"")+(T?` ${T}`:"");return c.jsxs("div",{ref:J,className:re,style:B,title:U,"data-testid":Z,children:[m&&c.jsxs("div",{className:"nice-credit-card__title",children:[m,b&&c.jsx("span",{className:"nice-credit-card__required","aria-hidden":!0,children:"*"})]}),c.jsx("div",{className:"nice-credit-card__row",children:c.jsxs("label",{className:"nice-credit-card__field nice-credit-card__field--number",children:[c.jsx("span",{className:"nice-credit-card__label",children:"Card number"}),c.jsxs("span",{className:"nice-credit-card__input-wrapper",children:[c.jsx("input",{id:`${v}-number`,type:"text",inputMode:"numeric",autoComplete:"cc-number",placeholder:D==="amex"?"3782 822463 10005":"1234 1234 1234 1234",value:w,onChange:q,onBlur:()=>P(N=>({...N,number:!0})),disabled:E,readOnly:R,"aria-invalid":!!g,"aria-describedby":g?`${v}-number-err`:void 0,className:"nice-credit-card__input",maxLength:D==="amex"?17:23}),d&&c.jsx(hn,{brand:D})]}),g&&c.jsx("span",{id:`${v}-number-err`,className:"nice-credit-card__error",children:g})]})}),c.jsxs("div",{className:"nice-credit-card__row nice-credit-card__row--split",children:[c.jsxs("label",{className:"nice-credit-card__field",children:[c.jsx("span",{className:"nice-credit-card__label",children:"Expiry"}),c.jsx("input",{id:`${v}-expiry`,type:"text",inputMode:"numeric",autoComplete:"cc-exp",placeholder:"MM/YY",value:e.expiry,onChange:Y,onBlur:()=>{P(C=>({...C,expiry:!0}));const N=pe(e.expiry);N&&N!==e.expiry&&k({expiry:N})},disabled:E,readOnly:R,"aria-invalid":!!j,"aria-describedby":j?`${v}-expiry-err`:void 0,className:"nice-credit-card__input",maxLength:5}),j&&c.jsx("span",{id:`${v}-expiry-err`,className:"nice-credit-card__error",children:j})]}),c.jsxs("label",{className:"nice-credit-card__field",children:[c.jsx("span",{className:"nice-credit-card__label",children:D==="amex"?"CID":"CVC"}),c.jsx("input",{id:`${v}-cvc`,type:"text",inputMode:"numeric",autoComplete:"cc-csc",placeholder:"•".repeat(ee(D)),value:e.cvc,onChange:L,onBlur:()=>P(N=>({...N,cvc:!0})),disabled:E,readOnly:R,"aria-invalid":!!A,"aria-describedby":A?`${v}-cvc-err`:void 0,className:"nice-credit-card__input",maxLength:ee(D)}),A&&c.jsx("span",{id:`${v}-cvc-err`,className:"nice-credit-card__error",children:A})]})]}),t&&c.jsx("div",{className:"nice-credit-card__row",children:c.jsxs("label",{className:"nice-credit-card__field",children:[c.jsx("span",{className:"nice-credit-card__label",children:"Cardholder name"}),c.jsx("input",{id:`${v}-holder`,type:"text",autoComplete:"cc-name",placeholder:"Jane Doe",value:e.holderName??"",onChange:N=>k({holderName:N.target.value}),onBlur:()=>P(N=>({...N,holder:!0})),disabled:E,readOnly:R,className:"nice-credit-card__input",maxLength:120})]})}),s&&c.jsx("div",{className:"nice-credit-card__row",children:c.jsxs("label",{className:"nice-credit-card__field",children:[c.jsx("span",{className:"nice-credit-card__label",children:"Postal code"}),c.jsx("input",{id:`${v}-postal`,type:"text",autoComplete:"postal-code",value:e.postalCode??"",onChange:N=>k({postalCode:N.target.value}),onBlur:()=>P(N=>({...N,postal:!0})),disabled:E,readOnly:R,className:"nice-credit-card__input",maxLength:16})]})}),(x||_)&&c.jsx("div",{className:x?"nice-credit-card__form-error":"nice-credit-card__helper",role:x?"alert":void 0,children:x??_})]})}),De={visa:"VISA",mastercard:"MasterCard",amex:"AMEX",discover:"Discover",diners:"Diners",jcb:"JCB",unionpay:"UnionPay",maestro:"Maestro",mir:"MIR",elo:"Elo",hipercard:"Hipercard",unknown:""};function hn({brand:r}){return r==="unknown"?null:c.jsx("span",{className:`nice-credit-card__brand nice-credit-card__brand--${r}`,"aria-label":De[r],children:De[r]})}const bn=i.forwardRef(function({processor:e,amount:n,editableAmount:t,customer:a,currencies:s,description:l,metadata:d,autoCapture:u=!0,submitLabel:o="Pay",onSuccess:p,onError:h,consents:y,className:m,style:_},x){const[b,T]=i.useState(n),[B,$]=i.useState(n.currency),[U,Z]=i.useState({number:"",expiry:"",cvc:""}),[J,E]=i.useState(null),[R,X]=i.useState(!1),[v,z]=i.useState(null),P=He(e),D=i.useMemo(()=>e.supportedCurrencies==="*"?!0:e.supportedCurrencies.includes(b.currency.toUpperCase()),[e,b.currency]),K=i.useMemo(()=>{var g;const w=(g=e.minimums)==null?void 0:g[b.currency.toUpperCase()];return w==null||b.amount>=w},[e,b]),Q=i.useMemo(()=>{var g;const w=(g=e.minimums)==null?void 0:g[b.currency.toUpperCase()];return w==null?null:{amount:w,currency:b.currency}},[e,b.currency]),k=!!(J!=null&&J.isValid),q=!R&&D&&K&&k,Y=i.useCallback(async w=>{if(w.preventDefault(),!!q){z(null),X(!0);try{const g={amount:b,card:U,customer:a,description:l,metadata:d,idempotencyKey:_n()},j=await P.authorize(g);if(u&&j.status==="authorized"){const A=await P.capture(j.id,b);p==null||p(A)}else p==null||p(j)}catch(g){const j=g instanceof Error?g:new Error(String(g));z(j.message),h==null||h(j)}finally{X(!1)}}},[q,b,U,a,l,d,u,P,p,h]),L=O(b.currency);return c.jsxs("form",{ref:x,className:"nice-payment-form"+(m?` ${m}`:""),style:_,onSubmit:Y,"aria-busy":R,children:[c.jsx("div",{className:"nice-payment-form__amount",children:t?c.jsx(_e,{label:"Amount",value:b,currency:B,currencies:s,onChange:w=>w&&T(w),onCurrencyChange:w=>{$(w),T(g=>({...g,currency:w}))},disabled:R,required:!0,showFormattedPreview:!0}):c.jsxs("div",{className:"nice-payment-form__total",children:[c.jsx("span",{className:"nice-payment-form__total-label",children:"Total"}),c.jsx(ue,{value:b})]})}),l&&c.jsx("p",{className:"nice-payment-form__description",children:l}),c.jsx("div",{className:"nice-payment-form__card",children:c.jsx(ze,{value:U,onChange:(w,g)=>{Z(w),E(g)},disabled:R})}),!D&&c.jsxs("div",{className:"nice-payment-form__warning",role:"alert",children:[e.name," does not accept ",(L==null?void 0:L.name)??b.currency,"."]}),!K&&Q&&c.jsxs("div",{className:"nice-payment-form__warning",role:"alert",children:["Minimum charge for ",e.name," is ",c.jsx(ue,{value:Q}),"."]}),y&&c.jsx("div",{className:"nice-payment-form__consents",children:y}),v&&c.jsx("div",{className:"nice-payment-form__error",role:"alert",children:v}),c.jsx("button",{type:"submit",className:"nice-payment-form__submit",disabled:!q,"aria-disabled":!q,children:R?"Processing…":o}),c.jsxs("p",{className:"nice-payment-form__processor",children:["Secured by ",c.jsx("strong",{children:e.name})]})]})});function _n(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`idem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`}const xn=i.forwardRef(function({price:e,originalPrice:n,period:t,prefix:a,suffix:s,showDiscountBadge:l=!0,size:d="md",locale:u,className:o,style:p},h){const y=n&&n.currency===e.currency&&n.amount>e.amount,m=i.useMemo(()=>!y||!n?null:Math.round((n.amount-e.amount)/n.amount*100),[y,n,e.amount]),_=`nice-price-tag nice-price-tag--${d}`+(o?` ${o}`:"");return c.jsxs("div",{ref:h,className:_,style:p,children:[a&&c.jsx("span",{className:"nice-price-tag__prefix",children:a}),c.jsxs("span",{className:"nice-price-tag__main",children:[c.jsx("span",{className:"nice-price-tag__amount",children:V(e,{locale:u})}),t&&c.jsx("span",{className:"nice-price-tag__period",children:t})]}),y&&n&&c.jsx("span",{className:"nice-price-tag__original","aria-label":"Original price",children:c.jsx("s",{children:V(n,{locale:u})})}),y&&l&&m!=null&&m>0&&c.jsxs("span",{className:"nice-price-tag__discount","aria-label":`${m} percent off`,children:["−",m,"%"]}),s&&c.jsx("span",{className:"nice-price-tag__suffix",children:s})]})}),Nn=i.forwardRef(function({source:e,onSourceChange:n,targetCurrency:t,onTargetCurrencyChange:a,provider:s,locale:l,showRateLine:d=!0,className:u,style:o},p){const{converted:h,loading:y,error:m}=oe(e,t,s),_=i.useMemo(()=>({amount:Math.pow(10,2),currency:e.currency}),[e.currency]),{converted:x}=oe(d?_:null,t,s);return c.jsxs("div",{ref:p,className:"nice-currency-converter"+(u?` ${u}`:""),style:o,children:[c.jsx("div",{className:"nice-currency-converter__row",children:c.jsx(_e,{label:"From",value:e,onChange:b=>b&&n(b),currency:e.currency,onCurrencyChange:b=>n({...e,currency:b}),showFormattedPreview:!1})}),c.jsxs("div",{className:"nice-currency-converter__row",children:[c.jsx("label",{className:"nice-currency-converter__label",children:"To"}),c.jsxs("div",{className:"nice-currency-converter__output",children:[c.jsx(be,{value:t,onChange:b=>a(b),compact:!0}),c.jsx("span",{className:"nice-currency-converter__amount",children:y?"…":m?"—":h?V(h,{locale:l}):""})]})]}),d&&x&&c.jsxs("p",{className:"nice-currency-converter__rate",children:["1 ",e.currency," = ",V(x,{locale:l})," ",c.jsxs("span",{className:"nice-currency-converter__inverse",children:["· 1 ",t," ="," ",V(W(1/de(x),e.currency),{locale:l})]})]}),m&&c.jsx("p",{className:"nice-currency-converter__error",role:"alert",children:m.message})]})});exports.ExchangeRateHostProvider=dn;exports.FrankfurterProvider=ln;exports.MockPaymentProcessor=Ee;exports.NICE_CURRENCY_CATALOG=ne;exports.NICE_CURRENCY_CODES=pn;exports.NICE_PAYMENT_PROCESSORS=mn;exports.NiceCreditCardInput=ze;exports.NiceCurrencyConverter=Nn;exports.NiceCurrencySelector=be;exports.NiceMoney=ue;exports.NiceMoneyInput=_e;exports.NicePaymentForm=bn;exports.NicePriceTag=xn;exports.OpenExchangeRatesProvider=un;exports.StaticExchangeRateProvider=on;exports.addMoney=Se;exports.compareMoney=rn;exports.convertMoney=Ue;exports.detectCardBrand=ae;exports.expectedCvcLength=ee;exports.findCurrency=O;exports.formatCardNumber=$e;exports.formatMoney=V;exports.fromMoney=de;exports.isExpiryValid=fe;exports.isNegative=an;exports.isZero=tn;exports.luhnCheck=ye;exports.makeAdyenAdapter=Ie;exports.makeBraintreeAdapter=Oe;exports.makeCoinbaseCommerceAdapter=Le;exports.makeKlarnaAdapter=qe;exports.makeMollieAdapter=Fe;exports.makePayPalAdapter=Pe;exports.makePayUAdapter=Te;exports.makePrzelewy24Adapter=Be;exports.makeRazorpayAdapter=Ke;exports.makeSquareAdapter=Ae;exports.makeStripeAdapter=ke;exports.maskCardNumber=sn;exports.multiplyMoney=ve;exports.negateMoney=cn;exports.normaliseExpiry=pe;exports.parseMoney=Re;exports.registerCurrency=en;exports.subtractMoney=Me;exports.toMoney=W;exports.useNiceExchangeRate=oe;exports.useNiceMoney=yn;exports.useNicePayment=He;exports.validateCreditCard=le;