@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.
@@ -0,0 +1,35 @@
1
+ import { Money } from './types';
2
+
3
+ /**
4
+ * Convert a major-unit decimal (e.g. `12.34`) to a `Money` object in minor units.
5
+ * Uses the currency's declared decimals to avoid float drift; rounds half-away-from-zero.
6
+ */
7
+ export declare function toMoney(major: number, currency: string): Money;
8
+ /** Inverse of {@link toMoney}. */
9
+ export declare function fromMoney(m: Money): number;
10
+ export declare function addMoney(a: Money, b: Money): Money;
11
+ export declare function subtractMoney(a: Money, b: Money): Money;
12
+ /** Multiply by a scalar (rounded half-away-from-zero). */
13
+ export declare function multiplyMoney(a: Money, factor: number): Money;
14
+ export declare function compareMoney(a: Money, b: Money): number;
15
+ export declare function isZero(m: Money): boolean;
16
+ export declare function isNegative(m: Money): boolean;
17
+ export declare function negateMoney(m: Money): Money;
18
+ /**
19
+ * Locale-aware Intl-based formatter. Falls back to `${symbol}${amount}` if Intl rejects.
20
+ */
21
+ export declare function formatMoney(m: Money, opts?: {
22
+ locale?: string;
23
+ /** Override decimals (default = catalog decimals). */
24
+ decimals?: number;
25
+ /** Force symbol display vs ISO code. Default `'symbol'`. */
26
+ currencyDisplay?: 'symbol' | 'code' | 'name' | 'narrowSymbol';
27
+ }): string;
28
+ /**
29
+ * Parse a localized money string back into a `Money` object.
30
+ *
31
+ * Best-effort: strips currency symbols / whitespace / NBSP, treats the **last**
32
+ * `.` or `,` as the decimal separator, all earlier separators as thousands grouping.
33
+ * Returns `null` on irrecoverable input.
34
+ */
35
+ export declare function parseMoney(input: string, currency: string): Money | null;
@@ -0,0 +1,84 @@
1
+ import { PaymentRequest, PaymentResponse } from './types';
2
+
3
+ /**
4
+ * Provider-agnostic payment processor abstraction.
5
+ *
6
+ * Wrap any PSP (Stripe, PayPal, Adyen, Square, Mollie, BLIK / PayU, Przelewy24,
7
+ * Braintree, Razorpay, …) behind this interface. UI components depend only on the
8
+ * shape, not on any specific SDK. Real implementations call processor SDKs / REST APIs.
9
+ *
10
+ * The bundled adapters in this file are **scaffolds** — they describe the contract
11
+ * each integrator must satisfy and provide working in-memory `'mock'` mode so the UI
12
+ * can be exercised end-to-end without credentials.
13
+ */
14
+ export interface PaymentProcessor {
15
+ /** Stable identifier (`stripe`, `paypal`, `adyen`, `square`, `mollie`, `payu`, …). */
16
+ readonly id: string;
17
+ /** Display name. */
18
+ readonly name: string;
19
+ /** Currencies the processor accepts (uppercase ISO 4217). `'*'` = all. */
20
+ readonly supportedCurrencies: string[] | '*';
21
+ /** Minimum charge in minor units, per currency. Use for client-side guard. */
22
+ readonly minimums?: Record<string, number>;
23
+ /**
24
+ * Authorize a payment. Returns an authorization id; capture in a follow-up call.
25
+ * Implementations MAY auto-capture if the processor's API does so by default.
26
+ */
27
+ authorize(req: PaymentRequest): Promise<PaymentResponse>;
28
+ /** Capture a previously authorized payment. */
29
+ capture(authorizationId: string, amount?: PaymentRequest['amount']): Promise<PaymentResponse>;
30
+ /** Cancel an unauthorized / authorized but uncaptured payment. */
31
+ cancel(paymentId: string): Promise<PaymentResponse>;
32
+ /** Refund a captured payment (full or partial). */
33
+ refund(paymentId: string, amount?: PaymentRequest['amount']): Promise<PaymentResponse>;
34
+ }
35
+ /**
36
+ * Mock processor — accepts everything, returns deterministic ids. Use in tests / demos.
37
+ */
38
+ export declare class MockPaymentProcessor implements PaymentProcessor {
39
+ readonly id: string;
40
+ readonly name: string;
41
+ readonly supportedCurrencies: string[] | '*';
42
+ readonly minimums?: Record<string, number>;
43
+ private counter;
44
+ constructor(opts?: {
45
+ id?: string;
46
+ name?: string;
47
+ supportedCurrencies?: string[] | '*';
48
+ });
49
+ private newId;
50
+ authorize(req: PaymentRequest): Promise<PaymentResponse>;
51
+ capture(authorizationId: string, amount?: PaymentRequest['amount']): Promise<PaymentResponse>;
52
+ cancel(paymentId: string): Promise<PaymentResponse>;
53
+ refund(paymentId: string, amount?: PaymentRequest['amount']): Promise<PaymentResponse>;
54
+ }
55
+ export interface ProcessorExecutor {
56
+ authorize(req: PaymentRequest): Promise<PaymentResponse>;
57
+ capture(authorizationId: string, amount?: PaymentRequest['amount']): Promise<PaymentResponse>;
58
+ cancel(paymentId: string): Promise<PaymentResponse>;
59
+ refund(paymentId: string, amount?: PaymentRequest['amount']): Promise<PaymentResponse>;
60
+ }
61
+ /** Stripe (https://stripe.com). */
62
+ export declare function makeStripeAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
63
+ /** PayPal (https://www.paypal.com). */
64
+ export declare function makePayPalAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
65
+ /** Adyen (https://www.adyen.com). */
66
+ export declare function makeAdyenAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
67
+ /** Square (https://squareup.com). */
68
+ export declare function makeSquareAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
69
+ /** Mollie (https://www.mollie.com) — strong EU coverage including iDEAL, Bancontact. */
70
+ export declare function makeMollieAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
71
+ /** Braintree (https://www.braintreepayments.com). */
72
+ export declare function makeBraintreeAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
73
+ /** Razorpay (https://razorpay.com) — India / South Asia. */
74
+ export declare function makeRazorpayAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
75
+ /** PayU (https://corporate.payu.com) — covers BLIK / Przelewy24 / card in PL/CZ/RO/HU. */
76
+ export declare function makePayUAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
77
+ /** Przelewy24 (https://www.przelewy24.pl) — Polish bank-transfer aggregator + BLIK. */
78
+ export declare function makePrzelewy24Adapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
79
+ /** Klarna (https://www.klarna.com) — pay-later / instalments. */
80
+ export declare function makeKlarnaAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
81
+ /** Coinbase Commerce — crypto payments (BTC, ETH, USDC, …). */
82
+ export declare function makeCoinbaseCommerceAdapter(executor?: Partial<ProcessorExecutor>): PaymentProcessor;
83
+ /** Convenient registry of every bundled adapter (mock by default). */
84
+ export declare const NICE_PAYMENT_PROCESSORS: Record<string, () => PaymentProcessor>;
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .nice-money{font-variant-numeric:tabular-nums}.nice-money--negative .nice-money__value{color:var(--nice-color-danger, #dc2626)}.nice-money__converted{margin-left:.25em;opacity:.7;font-size:.875em}.nice-currency-selector{display:inline-flex;align-items:center;gap:.25rem}.nice-currency-selector__select{font:inherit;padding:.375rem .5rem;border:1px solid var(--nice-color-border, #d1d5db);border-radius:var(--nice-radius-sm, 4px);background:var(--nice-color-bg, #fff);color:inherit}.nice-currency-selector__search{font:inherit;padding:.25rem .5rem;border:1px solid var(--nice-color-border, #d1d5db);border-radius:var(--nice-radius-sm, 4px);width:8rem}.nice-currency-selector--compact .nice-currency-selector__search{display:none}.nice-money-input{display:flex;flex-direction:column;gap:.25rem}.nice-money-input__label{font-size:.875rem;font-weight:500}.nice-money-input__required{color:var(--nice-color-danger, #dc2626);margin-left:.125rem}.nice-money-input__row{display:flex;align-items:stretch;gap:.25rem;border:1px solid var(--nice-color-border, #d1d5db);border-radius:var(--nice-radius-md, 6px);background:var(--nice-color-bg, #fff);padding:.125rem}.nice-money-input--error .nice-money-input__row{border-color:var(--nice-color-danger, #dc2626)}.nice-money-input--disabled{opacity:.6;pointer-events:none}.nice-money-input__currency .nice-currency-selector__select{border:none;background:transparent;padding:.5rem}.nice-money-input__symbol{display:inline-flex;align-items:center;padding:0 .5rem;color:var(--nice-color-muted, #6b7280);font-weight:600}.nice-money-input__amount{flex:1;border:none;background:transparent;font:inherit;padding:.5rem;outline:none;font-variant-numeric:tabular-nums;text-align:right;min-width:0}.nice-money-input__step{border:none;background:var(--nice-color-bg-soft, #f3f4f6);border-radius:var(--nice-radius-sm, 4px);width:2rem;font-size:1.125rem;cursor:pointer}.nice-money-input__step:disabled{opacity:.4;cursor:not-allowed}.nice-money-input--sm .nice-money-input__amount{padding:.25rem;font-size:.875rem}.nice-money-input--lg .nice-money-input__amount{padding:.75rem;font-size:1.125rem}.nice-money-input__preview{font-size:.8125rem;color:var(--nice-color-muted, #6b7280)}.nice-money-input__error{font-size:.8125rem;color:var(--nice-color-danger, #dc2626)}.nice-money-input__helper{font-size:.8125rem;color:var(--nice-color-muted, #6b7280)}.nice-credit-card{display:flex;flex-direction:column;gap:.5rem}.nice-credit-card__row{display:flex;flex-direction:column;gap:.25rem}.nice-credit-card__row--split{flex-direction:row;gap:.5rem}.nice-credit-card__row--split .nice-credit-card__field{flex:1}.nice-credit-card__field{display:flex;flex-direction:column;gap:.125rem}.nice-credit-card__label{font-size:.8125rem;font-weight:500;color:var(--nice-color-muted, #4b5563)}.nice-credit-card__input-wrapper{position:relative;display:flex;align-items:center}.nice-credit-card__input{font:inherit;font-variant-numeric:tabular-nums;padding:.5rem .75rem;border:1px solid var(--nice-color-border, #d1d5db);border-radius:var(--nice-radius-md, 6px);background:var(--nice-color-bg, #fff);width:100%;outline:none}.nice-credit-card__input:focus{border-color:var(--nice-color-primary, #2563eb);box-shadow:0 0 0 2px var(--nice-color-primary-soft, #bfdbfe)}.nice-credit-card__input[aria-invalid=true]{border-color:var(--nice-color-danger, #dc2626)}.nice-credit-card__brand{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);font-size:.75rem;font-weight:700;letter-spacing:.05em;padding:.125rem .375rem;border-radius:var(--nice-radius-sm, 4px);background:var(--nice-color-bg-soft, #f3f4f6);color:var(--nice-color-muted, #4b5563);pointer-events:none}.nice-credit-card__brand--visa{background:#1a1f71;color:#fff}.nice-credit-card__brand--mastercard{background:#eb001b;color:#fff}.nice-credit-card__brand--amex{background:#2e77bb;color:#fff}.nice-credit-card__brand--discover{background:#ff6000;color:#fff}.nice-credit-card__error{font-size:.75rem;color:var(--nice-color-danger, #dc2626)}.nice-payment-form{display:flex;flex-direction:column;gap:.75rem;max-width:28rem}.nice-payment-form__total{display:flex;justify-content:space-between;align-items:baseline;padding:.75rem 1rem;background:var(--nice-color-bg-soft, #f9fafb);border-radius:var(--nice-radius-md, 6px);font-size:1.125rem;font-weight:600}.nice-payment-form__total-label{color:var(--nice-color-muted, #6b7280);font-size:.875rem;font-weight:500}.nice-payment-form__description{font-size:.875rem;color:var(--nice-color-muted, #4b5563);margin:0}.nice-payment-form__warning,.nice-payment-form__error{padding:.5rem .75rem;border-radius:var(--nice-radius-md, 6px);font-size:.875rem}.nice-payment-form__warning{background:var(--nice-color-warning-soft, #fef3c7);color:var(--nice-color-warning-fg, #92400e)}.nice-payment-form__error{background:var(--nice-color-danger-soft, #fee2e2);color:var(--nice-color-danger-fg, #991b1b)}.nice-payment-form__submit{font:inherit;font-weight:600;padding:.75rem 1rem;border:none;border-radius:var(--nice-radius-md, 6px);background:var(--nice-color-primary, #2563eb);color:var(--nice-color-primary-fg, #fff);cursor:pointer}.nice-payment-form__submit:disabled{opacity:.5;cursor:not-allowed}.nice-payment-form__processor{text-align:center;font-size:.75rem;color:var(--nice-color-muted, #6b7280);margin:0}.nice-price-tag{display:inline-flex;align-items:baseline;gap:.5rem;font-variant-numeric:tabular-nums}.nice-price-tag__amount{font-weight:700}.nice-price-tag--sm{font-size:.875rem}.nice-price-tag--md{font-size:1rem}.nice-price-tag--lg{font-size:1.25rem}.nice-price-tag--xl{font-size:1.75rem}.nice-price-tag__period{font-size:.75em;color:var(--nice-color-muted, #6b7280);margin-left:.125em}.nice-price-tag__original{color:var(--nice-color-muted, #9ca3af);font-size:.875em}.nice-price-tag__discount{background:var(--nice-color-success, #16a34a);color:var(--nice-color-success-fg, #fff);border-radius:var(--nice-radius-sm, 4px);padding:.125em .375em;font-size:.75em;font-weight:700}.nice-currency-converter{display:flex;flex-direction:column;gap:.5rem}.nice-currency-converter__output{display:flex;align-items:center;gap:.5rem;padding:.5rem;border:1px solid var(--nice-color-border, #d1d5db);border-radius:var(--nice-radius-md, 6px)}.nice-currency-converter__amount{font-weight:600;font-variant-numeric:tabular-nums;margin-left:auto}.nice-currency-converter__rate{font-size:.8125rem;color:var(--nice-color-muted, #6b7280);margin:0}.nice-currency-converter__inverse{opacity:.7}.nice-currency-converter__error{color:var(--nice-color-danger, #dc2626);font-size:.8125rem}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Money domain types and core utilities.
3
+ *
4
+ * Money is stored as integer **minor units** (e.g. cents) to avoid floating-point error.
5
+ * `100 USD` = `{ amount: 10000, currency: 'USD' }`.
6
+ */
7
+ export interface Money {
8
+ /** Amount in minor units (integer). */
9
+ amount: number;
10
+ /** ISO 4217 alphabetic code, e.g. `USD`, `EUR`, `JPY`. */
11
+ currency: string;
12
+ }
13
+ export interface CurrencyDef {
14
+ /** ISO 4217 alphabetic code. */
15
+ code: string;
16
+ /** ISO 4217 numeric code (3-digit). */
17
+ numeric: number;
18
+ /** Common symbol, e.g. `$`, `€`, `zł`. */
19
+ symbol: string;
20
+ /** English name. */
21
+ name: string;
22
+ /** Number of fractional decimals (ISO 4217 e), default 2. JPY=0, BHD=3. */
23
+ decimals: number;
24
+ /** Whether the symbol is placed before the amount in en-US (heuristic only — Intl handles real cases). */
25
+ symbolFirst?: boolean;
26
+ }
27
+ export type CardBrand = 'visa' | 'mastercard' | 'amex' | 'discover' | 'diners' | 'jcb' | 'unionpay' | 'maestro' | 'mir' | 'elo' | 'hipercard' | 'unknown';
28
+ export interface CreditCardData {
29
+ number: string;
30
+ /** MM/YY or MM/YYYY. Stored normalised as `MM/YY`. */
31
+ expiry: string;
32
+ cvc: string;
33
+ holderName?: string;
34
+ postalCode?: string;
35
+ }
36
+ export interface CreditCardValidation {
37
+ brand: CardBrand;
38
+ numberValid: boolean;
39
+ expiryValid: boolean;
40
+ cvcValid: boolean;
41
+ holderValid: boolean;
42
+ isValid: boolean;
43
+ errors: Partial<Record<keyof CreditCardData, string>>;
44
+ }
45
+ export interface ExchangeRate {
46
+ base: string;
47
+ quote: string;
48
+ /** rate such that `1 base = rate quote`. */
49
+ rate: number;
50
+ /** ISO timestamp of the quote. */
51
+ asOf: string;
52
+ }
53
+ export type PaymentStatus = 'idle' | 'validating' | 'authorizing' | 'authorized' | 'capturing' | 'captured' | 'failed' | 'cancelled' | 'refunded';
54
+ export interface PaymentRequest {
55
+ amount: Money;
56
+ description?: string;
57
+ customer?: {
58
+ id?: string;
59
+ email?: string;
60
+ name?: string;
61
+ };
62
+ metadata?: Record<string, string>;
63
+ /** Card for direct authorization. Token-based flows ignore this. */
64
+ card?: CreditCardData;
65
+ /** Pre-tokenized payment method (Stripe `pm_…`, PayPal order id, …). */
66
+ paymentMethodToken?: string;
67
+ /** Idempotency key — most processors recommend it. */
68
+ idempotencyKey?: string;
69
+ }
70
+ export interface PaymentResponse {
71
+ id: string;
72
+ status: PaymentStatus;
73
+ processor: string;
74
+ /** Processor-native raw response for debugging / receipts. */
75
+ raw?: unknown;
76
+ errorCode?: string;
77
+ errorMessage?: string;
78
+ /** Authorized / captured amount (may differ from request, e.g. partial capture). */
79
+ amount?: Money;
80
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@nice2dev/ui-money",
3
+ "version": "1.0.10",
4
+ "description": "Nice2Dev Money & Payments — Money / MoneyInput / CurrencySelector / CreditCardInput / PaymentForm + ISO 4217 catalog, Luhn validation, exchange-rate & payment-processor adapters",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./style.css": "./dist/style.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "LICENSE",
20
+ "CHANGELOG.md",
21
+ "README.md"
22
+ ],
23
+ "sideEffects": [
24
+ "*.css"
25
+ ],
26
+ "scripts": {
27
+ "dev": "vite",
28
+ "build": "tsc -p tsconfig.build.json && vite build",
29
+ "test": "vitest run --passWithNoTests",
30
+ "test:watch": "vitest",
31
+ "test:coverage": "vitest run --coverage",
32
+ "typecheck": "tsc --noEmit",
33
+ "prepublishOnly": "npm run build"
34
+ },
35
+ "peerDependencies": {
36
+ "@nice2dev/ui-core": "^1.0.10",
37
+ "react": ">=17.0.0",
38
+ "react-dom": ">=17.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@nice2dev/ui-core": "^1.0.10",
42
+ "@testing-library/jest-dom": "^6.9.1",
43
+ "@testing-library/react": "^14.0.0",
44
+ "@types/react": "^18.2.0",
45
+ "@types/react-dom": "^18.2.0",
46
+ "@vitejs/plugin-react": "^4.2.0",
47
+ "react": "^18.2.0",
48
+ "react-dom": "^18.2.0",
49
+ "typescript": "^5.3.0",
50
+ "vite": "^5.0.0",
51
+ "vite-plugin-dts": "^3.7.0",
52
+ "vitest": "^4.1.0"
53
+ },
54
+ "keywords": [
55
+ "nice2dev",
56
+ "money",
57
+ "currency",
58
+ "payment",
59
+ "credit-card",
60
+ "stripe",
61
+ "paypal",
62
+ "adyen",
63
+ "exchange-rate",
64
+ "iso-4217",
65
+ "luhn",
66
+ "react"
67
+ ],
68
+ "author": "Nice2Dev Team",
69
+ "license": "SEE LICENSE IN LICENSE"
70
+ }