diff --git a/scripts/temp-module-filter.ts b/scripts/temp-module-filter.ts index 8acc3cefd3c..66123f9d932 100644 --- a/scripts/temp-module-filter.ts +++ b/scripts/temp-module-filter.ts @@ -8,6 +8,7 @@ export const ALLOWED_MODULES = new Set([ 'DatabaseModule', 'DatatypeModule', 'DateModule', + 'FinanceModule', 'FoodModule', 'GitModule', 'HackerModule', diff --git a/src/definitions/finance.ts b/src/definitions/finance.ts index 8353cbfb126..1372b6b5dc4 100644 --- a/src/definitions/finance.ts +++ b/src/definitions/finance.ts @@ -1,4 +1,4 @@ -import type { Currency } from '../modules/finance'; +import type { Currency } from '../modules/finance/currency'; import type { LocaleEntry } from './definitions'; /** * The possible definitions related to finance. diff --git a/src/index.ts b/src/index.ts index 9712425a08d..9db2bc7a461 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,25 +59,20 @@ export type { DatabaseModule } from './modules/database'; export type { DatatypeModule } from './modules/datatype'; export type { DateModule, SimpleDateModule } from './modules/date'; export type { + BitcoinAddressFamilyType, + BitcoinNetworkType, Currency, FinanceModule, VatNumberCountryCode, } from './modules/finance'; -export { - BitcoinAddressFamily, - BitcoinNetwork, -} from './modules/finance/_bitcoin'; -export type { - BitcoinAddressFamilyType, - BitcoinNetworkType, -} from './modules/finance/_bitcoin'; +export { BitcoinAddressFamily, BitcoinNetwork } from './modules/finance'; export type { FoodModule } from './modules/food'; export type { GitModule } from './modules/git'; export type { HackerModule } from './modules/hacker'; export type { HelpersModule, SimpleHelpersModule } from './modules/helpers'; export type { ImageModule } from './modules/image'; export { IPv4Network } from './modules/internet'; -export type { IPv4NetworkType, InternetModule } from './modules/internet'; +export type { InternetModule, IPv4NetworkType } from './modules/internet'; export type { LocationModule, SimpleLocationModule } from './modules/location'; export type { LoremModule } from './modules/lorem'; export type { MedicalModule } from './modules/medical'; diff --git a/src/modules/finance/_bitcoin.ts b/src/modules/finance/_bitcoin.ts deleted file mode 100644 index ab8af2e812e..00000000000 --- a/src/modules/finance/_bitcoin.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Casing, NumberRange } from '../../utils/types'; - -/** - * The bitcoin address families. - */ -export enum BitcoinAddressFamily { - Legacy = 'legacy', - Segwit = 'segwit', - Bech32 = 'bech32', - Taproot = 'taproot', -} - -/** - * The bitcoin address families. - */ -export type BitcoinAddressFamilyType = `${BitcoinAddressFamily}`; - -/** - * The different bitcoin networks. - */ -export enum BitcoinNetwork { - Mainnet = 'mainnet', - Testnet = 'testnet', -} - -/** - * The different bitcoin networks. - */ -export type BitcoinNetworkType = `${BitcoinNetwork}`; - -type BitcoinAddressOptions = { - prefix: Record; - length: NumberRange; - casing: Casing; - exclude: string; -}; - -export const BitcoinAddressSpecs: Record< - BitcoinAddressFamilyType, - BitcoinAddressOptions -> = { - [BitcoinAddressFamily.Legacy]: { - prefix: { [BitcoinNetwork.Mainnet]: '1', [BitcoinNetwork.Testnet]: 'm' }, - length: { min: 26, max: 34 }, - casing: 'mixed', - exclude: '0OIl', - }, - [BitcoinAddressFamily.Segwit]: { - prefix: { [BitcoinNetwork.Mainnet]: '3', [BitcoinNetwork.Testnet]: '2' }, - length: { min: 26, max: 34 }, - casing: 'mixed', - exclude: '0OIl', - }, - [BitcoinAddressFamily.Bech32]: { - prefix: { - [BitcoinNetwork.Mainnet]: 'bc1', - [BitcoinNetwork.Testnet]: 'tb1', - }, - length: { min: 42, max: 42 }, - casing: 'lower', - exclude: '1bBiIoO', - }, - [BitcoinAddressFamily.Taproot]: { - prefix: { - [BitcoinNetwork.Mainnet]: 'bc1p', - [BitcoinNetwork.Testnet]: 'tb1p', - }, - length: { min: 62, max: 62 }, - casing: 'lower', - exclude: '1bBiIoO', - }, -}; diff --git a/src/modules/finance/_iban.ts b/src/modules/finance/_iban-lib.ts similarity index 99% rename from src/modules/finance/_iban.ts rename to src/modules/finance/_iban-lib.ts index c4ca0e7f1fc..b897f80bf42 100644 --- a/src/modules/finance/_iban.ts +++ b/src/modules/finance/_iban-lib.ts @@ -13,7 +13,12 @@ interface Iban { toDigitString: (str: string) => string; } -const iban: Iban = { +/** + * The internal data required to generate IBANs. + * + * @internal + */ +export const ibanLib: Iban = { alpha: [ 'A', 'B', @@ -1422,5 +1427,3 @@ const iban: Iban = { String((match.toUpperCase().codePointAt(0) ?? Number.NaN) - 55) ), }; - -export default iban; diff --git a/src/modules/finance/account-name.ts b/src/modules/finance/account-name.ts new file mode 100644 index 00000000000..10a97cffdde --- /dev/null +++ b/src/modules/finance/account-name.ts @@ -0,0 +1,21 @@ +import type { FakerCore } from '../../core'; +import { arrayElement } from '../helpers/array-element'; + +/** + * Generates a random account name. + * + * @param fakerCore The FakerCore to use. + * + * @example + * accountName(fakerCore) // 'Personal Loan Account' + * + * @since 11.0.0 + * + * @experimental + */ +export function accountName(fakerCore: FakerCore): string { + return [ + arrayElement(fakerCore, fakerCore.locale.finance.account_type), + 'Account', + ].join(' '); +} diff --git a/src/modules/finance/account-number.ts b/src/modules/finance/account-number.ts new file mode 100644 index 00000000000..e3704b98071 --- /dev/null +++ b/src/modules/finance/account-number.ts @@ -0,0 +1,118 @@ +import type { FakerCore } from '../../core'; +import { numeric } from '../string/numeric'; + +/** + * Generates a random account number. + * + * @param fakerCore The FakerCore to use. + * @param length The length of the account number. Defaults to `8`. + * + * @see stringNumeric(fakerCore): For generating the number with greater control. + * + * @example + * accountNumber(fakerCore) // '92842238' + * accountNumber(fakerCore, 5) // '32564' + * + * @since 11.0.0 + * + * @experimental + */ +export function accountNumber(fakerCore: FakerCore, length?: number): string; +/** + * Generates a random account number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object. + * @param options.length The length of the account number. Defaults to `8`. + * + * @see stringNumeric(fakerCore): For generating the number with greater control. + * + * @example + * accountNumber(fakerCore) // '92842238' + * accountNumber(fakerCore, { length: 5 }) // '32564' + * + * @since 11.0.0 + * + * @experimental + */ +export function accountNumber( + fakerCore: FakerCore, + options?: { + /** + * The length of the account number. + * + * @default 8 + */ + length?: number; + } +): string; +/** + * Generates a random account number. + * + * @param fakerCore The FakerCore to use. + * @param optionsOrLength An options object or the length of the account number. + * @param optionsOrLength.length The length of the account number. Defaults to `8`. + * + * @see stringNumeric(fakerCore): For generating the number with greater control. + * + * @example + * accountNumber(fakerCore) // '92842238' + * accountNumber(fakerCore, 5) // '28736' + * accountNumber(fakerCore, { length: 5 }) // '32564' + * + * @since 11.0.0 + * + * @experimental + */ +export function accountNumber( + fakerCore: FakerCore, + optionsOrLength?: + | number + | { + /** + * The length of the account number. + * + * @default 8 + */ + length?: number; + } +): string; +/** + * Generates a random account number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object or the length of the account number. + * @param options.length The length of the account number. Defaults to `8`. + * + * @see stringNumeric(fakerCore): For generating the number with greater control. + * + * @example + * accountNumber(fakerCore) // '92842238' + * accountNumber(fakerCore, 5) // '28736' + * accountNumber(fakerCore, { length: 5 }) // '32564' + * + * @since 11.0.0 + * + * @experimental + */ +export function accountNumber( + fakerCore: FakerCore, + options: + | number + | { + /** + * The length of the account number. + * + * @default 8 + */ + length?: number; + } = {} +): string { + if (typeof options === 'number') { + options = { length: options }; + } + + const { length = 8 } = options; + + return numeric(fakerCore, { length, allowLeadingZeros: true }); +} diff --git a/src/modules/finance/amount.ts b/src/modules/finance/amount.ts new file mode 100644 index 00000000000..226c73e044f --- /dev/null +++ b/src/modules/finance/amount.ts @@ -0,0 +1,82 @@ +import type { FakerCore } from '../../core'; +import { float } from '../number/float'; + +/** + * Generates a random amount between the given bounds (inclusive). + * + * @param fakerCore The FakerCore to use. + * @param options An options object. + * @param options.min The lower bound for the amount. Defaults to `0`. + * @param options.max The upper bound for the amount. Defaults to `1000`. + * @param options.dec The number of decimal places for the amount. Defaults to `2`. + * @param options.symbol The symbol used to prefix the amount. Defaults to `''`. + * @param options.autoFormat If true this method will use `Number.toLocaleString()`. Otherwise it will use `Number.toFixed()`. + * + * @see numberFloat(fakerCore): For generating the amount with greater control. + * + * @example + * amount(fakerCore) // '617.87' + * amount(fakerCore, { min: 5, max: 10 }) // '5.53' + * amount(fakerCore, { min: 5, max: 10, dec: 0 }) // '8' + * amount(fakerCore, { min: 5, max: 10, dec: 2, symbol: '$' }) // '$5.85' + * amount(fakerCore, { min: 5, max: 10, dec: 5, symbol: '', autoFormat: true }) // '9,75067' + * + * @since 11.0.0 + * + * @experimental + */ +export function amount( + fakerCore: FakerCore, + options: { + /** + * The lower bound for the amount. + * + * @default 0 + */ + min?: number; + /** + * The upper bound for the amount. + * + * @default 1000 + */ + max?: number; + /** + * The number of decimal places for the amount. + * + * @default 2 + */ + dec?: number; + /** + * The symbol used to prefix the amount. + * + * @default '' + */ + symbol?: string; + /** + * If true this method will use `Number.toLocaleString()`. Otherwise it will use `Number.toFixed()`. + * + * @default false + */ + autoFormat?: boolean; + } = {} +): string { + const { + autoFormat = false, + dec = 2, + max = 1000, + min = 0, + symbol = '', + } = options; + + const randValue = float(fakerCore, { + max, + min, + fractionDigits: dec, + }); + + const formattedString = autoFormat + ? randValue.toLocaleString(undefined, { minimumFractionDigits: dec }) + : randValue.toFixed(dec); + + return symbol + formattedString; +} diff --git a/src/modules/finance/bic.ts b/src/modules/finance/bic.ts new file mode 100644 index 00000000000..2f865339b7f --- /dev/null +++ b/src/modules/finance/bic.ts @@ -0,0 +1,53 @@ +import type { FakerCore } from '../../core'; +import { boolean } from '../datatype/boolean'; +import { arrayElement } from '../helpers/array-element'; +import { alpha } from '../string/alpha'; +import { alphanumeric } from '../string/alphanumeric'; +import { ibanLib } from './_iban-lib'; + +/** + * Generates a random SWIFT/BIC code based on the [ISO-9362](https://en.wikipedia.org/wiki/ISO_9362) format. + * + * @param fakerCore The FakerCore to use. + * @param options Options object. + * @param options.includeBranchCode Whether to include a three-digit branch code at the end of the generated code. Defaults to a random boolean value. + * + * @example + * bic(fakerCore) // 'WYAUPGX1' + * bic(fakerCore, { includeBranchCode: true }) // 'KCAUPGR1432' + * bic(fakerCore, { includeBranchCode: false }) // 'XDAFQGT7' + * + * @since 11.0.0 + * + * @experimental + */ +export function bic( + fakerCore: FakerCore, + options: { + /** + * Whether to include a three-digit branch code at the end of the generated code. + * + * @default datatypeBoolean(fakerCore) + */ + includeBranchCode?: boolean; + } = {} +): string { + const { includeBranchCode = boolean(fakerCore) } = options; + + const bankIdentifier = alpha(fakerCore, { + length: 4, + casing: 'upper', + }); + const countryCode = arrayElement(fakerCore, ibanLib.iso3166); + const locationCode = alphanumeric(fakerCore, { + length: 2, + casing: 'upper', + }); + const branchCode = includeBranchCode + ? boolean(fakerCore) + ? alphanumeric(fakerCore, { length: 3, casing: 'upper' }) + : 'XXX' + : ''; + + return `${bankIdentifier}${countryCode}${locationCode}${branchCode}`; +} diff --git a/src/modules/finance/bitcoin-address.ts b/src/modules/finance/bitcoin-address.ts new file mode 100644 index 00000000000..0cf754b91ae --- /dev/null +++ b/src/modules/finance/bitcoin-address.ts @@ -0,0 +1,127 @@ +import type { FakerCore } from '../../core'; +import type { Casing, NumberRange } from '../../utils/types'; +import { enumValue } from '../helpers/enum-value'; +import { int } from '../number/int'; +import { alphanumeric } from '../string/alphanumeric'; + +/** + * The bitcoin address families. + */ +export enum BitcoinAddressFamily { + Legacy = 'legacy', + Segwit = 'segwit', + Bech32 = 'bech32', + Taproot = 'taproot', +} + +/** + * The bitcoin address families. + */ +export type BitcoinAddressFamilyType = `${BitcoinAddressFamily}`; + +/** + * The different bitcoin networks. + */ +export enum BitcoinNetwork { + Mainnet = 'mainnet', + Testnet = 'testnet', +} + +/** + * The different bitcoin networks. + */ +export type BitcoinNetworkType = `${BitcoinNetwork}`; + +type BitcoinAddressOptions = { + prefix: Record; + length: NumberRange; + casing: Casing; + exclude: string; +}; + +export const BitcoinAddressSpecs: Record< + BitcoinAddressFamilyType, + BitcoinAddressOptions +> = { + [BitcoinAddressFamily.Legacy]: { + prefix: { [BitcoinNetwork.Mainnet]: '1', [BitcoinNetwork.Testnet]: 'm' }, + length: { min: 26, max: 34 }, + casing: 'mixed', + exclude: '0OIl', + }, + [BitcoinAddressFamily.Segwit]: { + prefix: { [BitcoinNetwork.Mainnet]: '3', [BitcoinNetwork.Testnet]: '2' }, + length: { min: 26, max: 34 }, + casing: 'mixed', + exclude: '0OIl', + }, + [BitcoinAddressFamily.Bech32]: { + prefix: { + [BitcoinNetwork.Mainnet]: 'bc1', + [BitcoinNetwork.Testnet]: 'tb1', + }, + length: { min: 42, max: 42 }, + casing: 'lower', + exclude: '1bBiIoO', + }, + [BitcoinAddressFamily.Taproot]: { + prefix: { + [BitcoinNetwork.Mainnet]: 'bc1p', + [BitcoinNetwork.Testnet]: 'tb1p', + }, + length: { min: 62, max: 62 }, + casing: 'lower', + exclude: '1bBiIoO', + }, +}; + +/** + * Generates a random Bitcoin address. + * + * @param fakerCore The FakerCore to use. + * @param options An optional options object. + * @param options.type The bitcoin address type (`'legacy'`, `'segwit'`, `'bech32'` or `'taproot'`). Defaults to a random address type. + * @param options.network The bitcoin network (`'mainnet'` or `'testnet'`). Defaults to `'mainnet'`. + * + * @example + * bitcoinAddress(fakerCore) // '1TeZEFLmGPLEQrSRdAcnZLoWwYeiHwmRog' + * bitcoinAddress(fakerCore, { type: 'bech32' }) // 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' + * bitcoinAddress(fakerCore, { type: 'bech32', network: 'testnet' }) // 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx' + * + * @since 11.0.0 + * + * @experimental + */ +export function bitcoinAddress( + fakerCore: FakerCore, + options: { + /** + * The bitcoin address type (`'legacy'`, `'segwit'`, `'bech32'` or `'taproot'`). + * + * @default helpersEnumValue(fakerCore, BitcoinAddressFamily) + */ + type?: BitcoinAddressFamilyType; + /** + * The bitcoin network (`'mainnet'` or `'testnet'`). + * + * @default 'mainnet' + */ + network?: BitcoinNetworkType; + } = {} +): string { + const { + type = enumValue(fakerCore, BitcoinAddressFamily), + network = BitcoinNetwork.Mainnet, + } = options; + const addressSpec = BitcoinAddressSpecs[type]; + const addressPrefix = addressSpec.prefix[network]; + const addressLength = int(fakerCore, addressSpec.length); + + const address = alphanumeric(fakerCore, { + length: addressLength - addressPrefix.length, + casing: addressSpec.casing, + exclude: addressSpec.exclude, + }); + + return addressPrefix + address; +} diff --git a/src/modules/finance/credit-card-cvv.ts b/src/modules/finance/credit-card-cvv.ts new file mode 100644 index 00000000000..a11898b5150 --- /dev/null +++ b/src/modules/finance/credit-card-cvv.ts @@ -0,0 +1,18 @@ +import type { FakerCore } from '../../core'; +import { numeric } from '../string/numeric'; + +/** + * Generates a random credit card CVV. + * + * @param fakerCore The FakerCore to use. + * + * @example + * creditCardCVV(fakerCore) // '506' + * + * @since 11.0.0 + * + * @experimental + */ +export function creditCardCVV(fakerCore: FakerCore): string { + return numeric(fakerCore, { length: 3, allowLeadingZeros: true }); +} diff --git a/src/modules/finance/credit-card-issuer.ts b/src/modules/finance/credit-card-issuer.ts new file mode 100644 index 00000000000..42bd34be544 --- /dev/null +++ b/src/modules/finance/credit-card-issuer.ts @@ -0,0 +1,18 @@ +import type { FakerCore } from '../../core'; +import { objectKey } from '../helpers/object-key'; + +/** + * Returns a random credit card issuer. + * + * @param fakerCore The FakerCore to use. + * + * @example + * creditCardIssuer(fakerCore) // 'discover' + * + * @since 11.0.0 + * + * @experimental + */ +export function creditCardIssuer(fakerCore: FakerCore): string { + return objectKey(fakerCore, fakerCore.locale.finance.credit_card) as string; +} diff --git a/src/modules/finance/credit-card-number.ts b/src/modules/finance/credit-card-number.ts new file mode 100644 index 00000000000..1e470b51f7b --- /dev/null +++ b/src/modules/finance/credit-card-number.ts @@ -0,0 +1,132 @@ +import type { FakerCore } from '../../core'; +import { arrayElement } from '../helpers/array-element'; +import { objectValue } from '../helpers/object-value'; +import { replaceCreditCardSymbols } from '../helpers/replace-credit-card-symbols'; + +/** + * Generates a random credit card number. + * + * @param fakerCore The FakerCore to use. + * @param issuer The name of the issuer (case-insensitive) or the format used to generate one. + * + * @example + * creditCardNumber(fakerCore) // '4427163488662' + * creditCardNumber(fakerCore, 'visa') // '4882664999007' + * creditCardNumber(fakerCore, '63[7-9]#-####-####-###L') // '6375-3265-4676-6646' + * + * @since 11.0.0 + * + * @experimental + */ +export function creditCardNumber(fakerCore: FakerCore, issuer?: string): string; +/** + * Generates a random credit card number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object. + * @param options.issuer The name of the issuer (case-insensitive) or the format used to generate one. Defaults to `''`. + * + * @example + * creditCardNumber(fakerCore) // '4427163488662' + * creditCardNumber(fakerCore, { issuer: 'visa' }) // '4882664999007' + * creditCardNumber(fakerCore, { issuer: '63[7-9]#-####-####-###L' }) // '6375-3265-4676-6646' + * + * @since 11.0.0 + * + * @experimental + */ +export function creditCardNumber( + fakerCore: FakerCore, + options?: { + /** + * The name of the issuer (case-insensitive) or the format used to generate one. + * + * @default '' + */ + issuer?: string; + } +): string; +/** + * Generates a random credit card number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object, the issuer or a custom format. + * @param options.issuer The name of the issuer (case-insensitive) or the format used to generate one. Defaults to `''`. + * + * @example + * creditCardNumber(fakerCore) // '4427163488662' + * creditCardNumber(fakerCore, { issuer: 'visa' }) // '4882664999007' + * creditCardNumber(fakerCore, { issuer: '63[7-9]#-####-####-###L' }) // '6375-3265-4676-6646' + * creditCardNumber(fakerCore, 'visa') // '1226423499765' + * + * @since 11.0.0 + * + * @experimental + */ +export function creditCardNumber( + fakerCore: FakerCore, + options?: + | string + | { + /** + * The name of the issuer (case-insensitive) or the format used to generate one. + * + * @default '' + */ + issuer?: string; + } +): string; +/** + * Generates a random credit card number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object, the issuer or a custom format. + * @param options.issuer The name of the issuer (case-insensitive) or the format used to generate one. + * + * @example + * creditCardNumber(fakerCore) // '4427163488662' + * creditCardNumber(fakerCore, { issuer: 'visa' }) // '4882664999007' + * creditCardNumber(fakerCore, { issuer: '63[7-9]#-####-####-###L' }) // '6375-3265-4676-6646' + * creditCardNumber(fakerCore, 'visa') // '1226423499765' + * + * @since 11.0.0 + * + * @experimental + */ +export function creditCardNumber( + fakerCore: FakerCore, + options: + | string + | { + /** + * The name of the issuer (case-insensitive) or the format used to generate one. + * + * @default '' + */ + issuer?: string; + } = {} +): string { + if (typeof options === 'string') { + options = { issuer: options }; + } + + const { issuer = '' } = options; + + let format: string; + const localeFormat = fakerCore.locale.finance.credit_card; + const normalizedIssuer = issuer.toLowerCase(); + if (normalizedIssuer in localeFormat) { + format = arrayElement(fakerCore, localeFormat[normalizedIssuer]); + } else if (issuer.includes('#')) { + // The user chose an optional scheme + format = issuer; + } else { + // Choose a random issuer + // Credit cards are in an object structure + const formats = objectValue(fakerCore, localeFormat); // There could be multiple formats + format = arrayElement(fakerCore, formats); + } + + format = format.replaceAll('/', ''); + return replaceCreditCardSymbols(fakerCore, format); +} diff --git a/src/modules/finance/currency-code.ts b/src/modules/finance/currency-code.ts new file mode 100644 index 00000000000..1b6ecd49e9b --- /dev/null +++ b/src/modules/finance/currency-code.ts @@ -0,0 +1,19 @@ +import type { FakerCore } from '../../core'; +import { currency } from './currency'; + +/** + * Returns a random currency code. + * (The short text/abbreviation for the currency (e.g. `US Dollar` -> `USD`)) + * + * @param fakerCore The FakerCore to use. + * + * @example + * currencyCode(fakerCore) // 'USD' + * + * @since 11.0.0 + * + * @experimental + */ +export function currencyCode(fakerCore: FakerCore): string { + return currency(fakerCore).code; +} diff --git a/src/modules/finance/currency-name.ts b/src/modules/finance/currency-name.ts new file mode 100644 index 00000000000..1eed24382d5 --- /dev/null +++ b/src/modules/finance/currency-name.ts @@ -0,0 +1,18 @@ +import type { FakerCore } from '../../core'; +import { currency } from './currency'; + +/** + * Returns a random currency name. + * + * @param fakerCore The FakerCore to use. + * + * @example + * currencyName(fakerCore) // 'US Dollar' + * + * @since 11.0.0 + * + * @experimental + */ +export function currencyName(fakerCore: FakerCore): string { + return currency(fakerCore).name; +} diff --git a/src/modules/finance/currency-numeric-code.ts b/src/modules/finance/currency-numeric-code.ts new file mode 100644 index 00000000000..0b2af2cd8b9 --- /dev/null +++ b/src/modules/finance/currency-numeric-code.ts @@ -0,0 +1,19 @@ +import type { FakerCore } from '../../core'; +import { currency } from './currency'; + +/** + * Returns a random currency numeric code. + * (The ISO 4217 numerical code for a currency (e.g. `US Dollar` -> `840` )) + * + * @param fakerCore The FakerCore to use. + * + * @example + * currencyNumericCode(fakerCore) // '840' + * + * @since 11.0.0 + * + * @experimental + */ +export function currencyNumericCode(fakerCore: FakerCore): string { + return currency(fakerCore).numericCode; +} diff --git a/src/modules/finance/currency-symbol.ts b/src/modules/finance/currency-symbol.ts new file mode 100644 index 00000000000..5fdb88891c6 --- /dev/null +++ b/src/modules/finance/currency-symbol.ts @@ -0,0 +1,31 @@ +import type { FakerCore } from '../../core'; +import { FakerError } from '../../errors/faker-error'; +import { arrayElement } from '../helpers/array-element'; + +/** + * Returns a random currency symbol. + * + * @param fakerCore The FakerCore to use. + * + * @throws {FakerError} If no currency in the locale data has a symbol. + * + * @example + * currencySymbol(fakerCore) // '$' + * + * @since 11.0.0 + * + * @experimental + */ +export function currencySymbol(fakerCore: FakerCore): string { + const currenciesWithSymbols = fakerCore.locale.finance.currency.filter( + (currency) => currency.symbol.length > 0 + ); + + if (currenciesWithSymbols.length === 0) { + throw new FakerError( + 'Cannot get currency symbol from dataset with no currency symbols.' + ); + } + + return arrayElement(fakerCore, currenciesWithSymbols).symbol; +} diff --git a/src/modules/finance/currency.ts b/src/modules/finance/currency.ts new file mode 100644 index 00000000000..84d7524bfda --- /dev/null +++ b/src/modules/finance/currency.ts @@ -0,0 +1,48 @@ +import type { FakerCore } from '../../core'; +import { arrayElement } from '../helpers/array-element'; + +/** + * The possible definitions related to currency entries. + */ +export interface Currency { + /** + * The full name for the currency (e.g. `US Dollar`). + */ + name: string; + + /** + * The code/short text/abbreviation for the currency (e.g. `USD`). + */ + code: string; + + /** + * The symbol for the currency (e.g. `$`). + */ + symbol: string; + + /** + * The ISO 4217 numeric code for the currency (e.g. `840`). + */ + numericCode: string; +} + +/** + * Returns a random currency object, containing `code`, `name`, `symbol`, and `numericCode` properties. + * + * @param fakerCore The FakerCore to use. + * + * @see currencyCode(fakerCore): For generating specifically the currency code. + * @see currencyName(fakerCore): For generating specifically the currency name. + * @see currencySymbol(fakerCore): For generating specifically the currency symbol. + * @see currencyNumericCode(fakerCore): For generating specifically the currency numeric code. + * + * @example + * currency(fakerCore) // { code: 'USD', name: 'US Dollar', symbol: '$', numericCode: '840' } + * + * @since 11.0.0 + * + * @experimental + */ +export function currency(fakerCore: FakerCore): Currency { + return arrayElement(fakerCore, fakerCore.locale.finance.currency); +} diff --git a/src/modules/finance/ethereum-address.ts b/src/modules/finance/ethereum-address.ts new file mode 100644 index 00000000000..1eca42258c6 --- /dev/null +++ b/src/modules/finance/ethereum-address.ts @@ -0,0 +1,23 @@ +import type { FakerCore } from '../../core'; +import { hexadecimal } from '../string/hexadecimal'; + +/** + * Creates a random, non-checksum Ethereum address. + * + * To generate a checksummed Ethereum address (with specific per character casing), wrap this method in a custom method and use third-party libraries to transform the result. + * + * @param fakerCore The FakerCore to use. + * + * @example + * ethereumAddress(fakerCore) // '0xf03dfeecbafc5147241cc4c4ca20b3c9dfd04c4a' + * + * @since 11.0.0 + * + * @experimental + */ +export function ethereumAddress(fakerCore: FakerCore): string { + return hexadecimal(fakerCore, { + length: 40, + casing: 'lower', + }); +} diff --git a/src/modules/finance/iban.ts b/src/modules/finance/iban.ts new file mode 100644 index 00000000000..ba960c55dd4 --- /dev/null +++ b/src/modules/finance/iban.ts @@ -0,0 +1,115 @@ +import type { FakerCore } from '../../core'; +import { FakerError } from '../../errors/faker-error'; +import { boolean } from '../datatype/boolean'; +import { arrayElement } from '../helpers/array-element'; +import { int } from '../number/int'; +import { ibanLib } from './_iban-lib'; + +/** + * Puts a space after every 4 characters. + * + * @internal + * + * @param iban The iban to pretty print. + */ +export function prettyPrintIban(iban: string): string { + let pretty = ''; + for (let i = 0; i < iban.length; i += 4) { + pretty += `${iban.substring(i, i + 4)} `; + } + + return pretty.trimEnd(); +} + +/** + * Generates a random IBAN. + * + * Please note that the generated IBAN might be invalid due to randomly generated bank codes/other country specific validation rules. + * + * @param fakerCore The FakerCore to use. + * @param options An options object. + * @param options.formatted Return a formatted version of the generated IBAN. Defaults to `false`. + * @param options.countryCode The country code from which you want to generate an IBAN, if none is provided a random country will be used. + * + * @throws {FakerError} Will throw an error if the passed country code is not supported. + * + * @example + * iban(fakerCore) // 'TR736918640040966092800056' + * iban(fakerCore, { formatted: true }) // 'FR20 8008 2330 8984 74S3 Z620 224' + * iban(fakerCore, { formatted: true, countryCode: 'DE' }) // 'DE84 1022 7075 0900 1170 01' + * + * @since 11.0.0 + * + * @experimental + */ +export function iban( + fakerCore: FakerCore, + options: { + /** + * Return a formatted version of the generated IBAN. + * + * @default false + */ + formatted?: boolean; + /** + * The country code from which you want to generate an IBAN, + * if none is provided a random country will be used. + */ + countryCode?: string; + } = {} +): string { + const { countryCode, formatted = false } = options; + + const ibanFormat = countryCode + ? ibanLib.formats.find((f) => f.country === countryCode) + : arrayElement(fakerCore, ibanLib.formats); + + if (!ibanFormat) { + throw new FakerError(`Country code ${countryCode} not supported.`); + } + + let s = ''; + let count = 0; + for (const bban of ibanFormat.bban) { + let c = bban.count; + count += bban.count; + while (c > 0) { + if (bban.type === 'a') { + s += arrayElement(fakerCore, ibanLib.alpha); + } else if (bban.type === 'c') { + if (boolean(fakerCore, 0.8)) { + s += int(fakerCore, 9); + } else { + s += arrayElement(fakerCore, ibanLib.alpha); + } + } else { + if (c >= 3 && boolean(fakerCore, 0.3)) { + if (boolean(fakerCore)) { + s += arrayElement(fakerCore, ibanLib.pattern100); + c -= 2; + } else { + s += arrayElement(fakerCore, ibanLib.pattern10); + c--; + } + } else { + s += int(fakerCore, 9); + } + } + + c--; + } + + s = s.substring(0, count); + } + + let checksum: string | number = + 98 - ibanLib.mod97(ibanLib.toDigitString(`${s}${ibanFormat.country}00`)); + + if (checksum < 10) { + checksum = `0${checksum}`; + } + + const result = `${ibanFormat.country}${checksum}${s}`; + + return formatted ? prettyPrintIban(result) : result; +} diff --git a/src/modules/finance/index.ts b/src/modules/finance/index.ts index 30d9c039790..51d8261b841 100644 --- a/src/modules/finance/index.ts +++ b/src/modules/finance/index.ts @@ -1,2 +1,8 @@ +export { BitcoinAddressFamily, BitcoinNetwork } from './bitcoin-address'; +export type { + BitcoinAddressFamilyType, + BitcoinNetworkType, +} from './bitcoin-address'; +export type { Currency } from './currency'; export * from './module'; -export type { VatNumberCountryCode } from './_vat-number'; +export type { VatNumberCountryCode } from './vat-number'; diff --git a/src/modules/finance/litecoin-address.ts b/src/modules/finance/litecoin-address.ts new file mode 100644 index 00000000000..6121de362e1 --- /dev/null +++ b/src/modules/finance/litecoin-address.ts @@ -0,0 +1,29 @@ +import type { FakerCore } from '../../core'; +import { int } from '../number/int'; +import { fromCharacters } from '../string/from-characters'; + +/** + * Generates a random Litecoin address. + * + * @param fakerCore The FakerCore to use. + * + * @example + * litecoinAddress(fakerCore) // 'MoQaSTGWBRXkWfyxKbNKuPrAWGELzcW' + * + * @since 11.0.0 + * + * @experimental + */ +export function litecoinAddress(fakerCore: FakerCore): string { + const addressLength = int(fakerCore, { min: 26, max: 33 }); + + const address = + fromCharacters(fakerCore, 'LM3') + + fromCharacters( + fakerCore, + '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', + addressLength - 1 + ); + + return address; +} diff --git a/src/modules/finance/module.ts b/src/modules/finance/module.ts index 2e2cb4bc5a7..3218bc21470 100644 --- a/src/modules/finance/module.ts +++ b/src/modules/finance/module.ts @@ -1,55 +1,31 @@ -import { FakerError } from '../../errors/faker-error'; import { ModuleBase } from '../../internal/module-base'; -import type { BitcoinAddressFamilyType, BitcoinNetworkType } from './_bitcoin'; -import { - BitcoinAddressFamily, - BitcoinAddressSpecs, - BitcoinNetwork, -} from './_bitcoin'; -import iban from './_iban'; -import type { VatNumberCountryCode } from './_vat-number'; -import { vatNumberCountryCodes, vatNumberFormats } from './_vat-number'; - -/** - * The possible definitions related to currency entries. - */ -export interface Currency { - /** - * The full name for the currency (e.g. `US Dollar`). - */ - name: string; - - /** - * The code/short text/abbreviation for the currency (e.g. `USD`). - */ - code: string; - - /** - * The symbol for the currency (e.g. `$`). - */ - symbol: string; - - /** - * The ISO 4217 numeric code for the currency (e.g. `840`). - */ - numericCode: string; -} - -/** - * Puts a space after every 4 characters. - * - * @internal - * - * @param iban The iban to pretty print. - */ -export function prettyPrintIban(iban: string): string { - let pretty = ''; - for (let i = 0; i < iban.length; i += 4) { - pretty += `${iban.substring(i, i + 4)} `; - } - - return pretty.trimEnd(); -} +import { accountName as financeAccountName } from './account-name'; +import { accountNumber as financeAccountNumber } from './account-number'; +import { amount as financeAmount } from './amount'; +import { bic as financeBic } from './bic'; +import type { + BitcoinAddressFamilyType, + BitcoinNetworkType, +} from './bitcoin-address'; +import { bitcoinAddress as financeBitcoinAddress } from './bitcoin-address'; +import { creditCardCVV as financeCreditCardCVV } from './credit-card-cvv'; +import { creditCardIssuer as financeCreditCardIssuer } from './credit-card-issuer'; +import { creditCardNumber as financeCreditCardNumber } from './credit-card-number'; +import type { Currency } from './currency'; +import { currency as financeCurrency } from './currency'; +import { currencyCode as financeCurrencyCode } from './currency-code'; +import { currencyName as financeCurrencyName } from './currency-name'; +import { currencyNumericCode as financeCurrencyNumericCode } from './currency-numeric-code'; +import { currencySymbol as financeCurrencySymbol } from './currency-symbol'; +import { ethereumAddress as financeEthereumAddress } from './ethereum-address'; +import { iban as financeIban } from './iban'; +import { litecoinAddress as financeLitecoinAddress } from './litecoin-address'; +import { pin as financePin } from './pin'; +import { routingNumber as financeRoutingNumber } from './routing-number'; +import { transactionDescription as financeTransactionDescription } from './transaction-description'; +import { transactionType as financeTransactionType } from './transaction-type'; +import type { VatNumberCountryCode } from './vat-number'; +import { vatNumber as financeVatNumber } from './vat-number'; /** * Module to generate finance and money related entries. @@ -67,6 +43,11 @@ export function prettyPrintIban(iban: string): string { * For blockchain related methods, use: [`bitcoinAddress()`](https://fakerjs.dev/api/finance.html#bitcoinaddress), [`ethereumAddress()`](https://fakerjs.dev/api/finance.html#ethereumaddress) and [`litecoinAddress()`](https://fakerjs.dev/api/finance.html#litecoinaddress). */ export class FinanceModule extends ModuleBase { + /* + * The class body is automatically generated. + * Run 'pnpm run generate:module-tree finance' to update the methods from their respective files. + */ + /** * Generates a random account number. * @@ -157,13 +138,7 @@ export class FinanceModule extends ModuleBase { length?: number; } = {} ): string { - if (typeof options === 'number') { - options = { length: options }; - } - - const { length = 8 } = options; - - return this.faker.string.numeric({ length, allowLeadingZeros: true }); + return financeAccountNumber(this.faker.fakerCore, options); } /** @@ -175,12 +150,7 @@ export class FinanceModule extends ModuleBase { * @since 2.0.1 */ accountName(): string { - return [ - this.faker.helpers.arrayElement( - this.faker.definitions.finance.account_type - ), - 'Account', - ].join(' '); + return financeAccountName(this.faker.fakerCore); } /** @@ -192,27 +162,7 @@ export class FinanceModule extends ModuleBase { * @since 5.0.0 */ routingNumber(): string { - const federalReserveRoutingSymbol = this.faker.helpers.arrayElement( - this.faker.definitions.finance.federal_reserve_routing_symbol - ); - - const institutionIdentifier = this.faker.string.numeric({ - length: 4, - allowLeadingZeros: true, - }); - - const routingNumber = federalReserveRoutingSymbol + institutionIdentifier; - - // Modules 10 straight summation. - let sum = 0; - - for (let i = 0; i < routingNumber.length; i += 3) { - sum += Number(routingNumber[i]) * 3; - sum += Number(routingNumber[i + 1]) * 7; - sum += Number(routingNumber[i + 2]) || 0; - } - - return `${routingNumber}${Math.ceil(sum / 10) * 10 - sum}`; + return financeRoutingNumber(this.faker.fakerCore); } /** @@ -270,25 +220,7 @@ export class FinanceModule extends ModuleBase { autoFormat?: boolean; } = {} ): string { - const { - autoFormat = false, - dec = 2, - max = 1000, - min = 0, - symbol = '', - } = options; - - const randValue = this.faker.number.float({ - max, - min, - fractionDigits: dec, - }); - - const formattedString = autoFormat - ? randValue.toLocaleString(undefined, { minimumFractionDigits: dec }) - : randValue.toFixed(dec); - - return symbol + formattedString; + return financeAmount(this.faker.fakerCore, options); } /** @@ -300,9 +232,7 @@ export class FinanceModule extends ModuleBase { * @since 2.0.1 */ transactionType(): string { - return this.faker.helpers.arrayElement( - this.faker.definitions.finance.transaction_type - ); + return financeTransactionType(this.faker.fakerCore); } /** @@ -319,9 +249,7 @@ export class FinanceModule extends ModuleBase { * @since 8.0.0 */ currency(): Currency { - return this.faker.helpers.arrayElement( - this.faker.definitions.finance.currency - ); + return financeCurrency(this.faker.fakerCore); } /** @@ -334,7 +262,7 @@ export class FinanceModule extends ModuleBase { * @since 2.0.1 */ currencyCode(): string { - return this.currency().code; + return financeCurrencyCode(this.faker.fakerCore); } /** @@ -346,7 +274,7 @@ export class FinanceModule extends ModuleBase { * @since 2.0.1 */ currencyName(): string { - return this.currency().name; + return financeCurrencyName(this.faker.fakerCore); } /** @@ -360,18 +288,7 @@ export class FinanceModule extends ModuleBase { * @since 2.0.1 */ currencySymbol(): string { - const currenciesWithSymbols = - this.faker.definitions.finance.currency.filter( - (currency) => currency.symbol.length > 0 - ); - - if (currenciesWithSymbols.length === 0) { - throw new FakerError( - 'Cannot get currency symbol from dataset with no currency symbols.' - ); - } - - return this.faker.helpers.arrayElement(currenciesWithSymbols).symbol; + return financeCurrencySymbol(this.faker.fakerCore); } /** @@ -384,7 +301,7 @@ export class FinanceModule extends ModuleBase { * @since 9.6.0 */ currencyNumericCode(): string { - return this.currency().numericCode; + return financeCurrencyNumericCode(this.faker.fakerCore); } /** @@ -417,21 +334,7 @@ export class FinanceModule extends ModuleBase { network?: BitcoinNetworkType; } = {} ): string { - const { - type = this.faker.helpers.enumValue(BitcoinAddressFamily), - network = BitcoinNetwork.Mainnet, - } = options; - const addressSpec = BitcoinAddressSpecs[type]; - const addressPrefix = addressSpec.prefix[network]; - const addressLength = this.faker.number.int(addressSpec.length); - - const address = this.faker.string.alphanumeric({ - length: addressLength - addressPrefix.length, - casing: addressSpec.casing, - exclude: addressSpec.exclude, - }); - - return addressPrefix + address; + return financeBitcoinAddress(this.faker.fakerCore, options); } /** @@ -443,16 +346,7 @@ export class FinanceModule extends ModuleBase { * @since 5.0.0 */ litecoinAddress(): string { - const addressLength = this.faker.number.int({ min: 26, max: 33 }); - - const address = - this.faker.string.fromCharacters('LM3') + - this.faker.string.fromCharacters( - '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', - addressLength - 1 - ); - - return address; + return financeLitecoinAddress(this.faker.fakerCore); } /** @@ -541,29 +435,7 @@ export class FinanceModule extends ModuleBase { issuer?: string; } = {} ): string { - if (typeof options === 'string') { - options = { issuer: options }; - } - - const { issuer = '' } = options; - - let format: string; - const localeFormat = this.faker.definitions.finance.credit_card; - const normalizedIssuer = issuer.toLowerCase(); - if (normalizedIssuer in localeFormat) { - format = this.faker.helpers.arrayElement(localeFormat[normalizedIssuer]); - } else if (issuer.includes('#')) { - // The user chose an optional scheme - format = issuer; - } else { - // Choose a random issuer - // Credit cards are in an object structure - const formats = this.faker.helpers.objectValue(localeFormat); // There could be multiple formats - format = this.faker.helpers.arrayElement(formats); - } - - format = format.replaceAll('/', ''); - return this.faker.helpers.replaceCreditCardSymbols(format); + return financeCreditCardNumber(this.faker.fakerCore, options); } /** @@ -575,7 +447,7 @@ export class FinanceModule extends ModuleBase { * @since 5.0.0 */ creditCardCVV(): string { - return this.faker.string.numeric({ length: 3, allowLeadingZeros: true }); + return financeCreditCardCVV(this.faker.fakerCore); } /** @@ -587,9 +459,7 @@ export class FinanceModule extends ModuleBase { * @since 6.3.0 */ creditCardIssuer(): string { - return this.faker.helpers.objectKey( - this.faker.definitions.finance.credit_card - ) as string; + return financeCreditCardIssuer(this.faker.fakerCore); } /** @@ -690,17 +560,7 @@ export class FinanceModule extends ModuleBase { length?: number; } = {} ): string { - if (typeof options === 'number') { - options = { length: options }; - } - - const { length = 4 } = options; - - if (length < 1) { - throw new FakerError('minimum length is 1'); - } - - return this.faker.string.numeric({ length, allowLeadingZeros: true }); + return financePin(this.faker.fakerCore, options); } /** @@ -714,11 +574,7 @@ export class FinanceModule extends ModuleBase { * @since 5.0.0 */ ethereumAddress(): string { - const address = this.faker.string.hexadecimal({ - length: 40, - casing: 'lower', - }); - return address; + return financeEthereumAddress(this.faker.fakerCore); } /** @@ -754,60 +610,7 @@ export class FinanceModule extends ModuleBase { countryCode?: string; } = {} ): string { - const { countryCode, formatted = false } = options; - - const ibanFormat = countryCode - ? iban.formats.find((f) => f.country === countryCode) - : this.faker.helpers.arrayElement(iban.formats); - - if (!ibanFormat) { - throw new FakerError(`Country code ${countryCode} not supported.`); - } - - let s = ''; - let count = 0; - for (const bban of ibanFormat.bban) { - let c = bban.count; - count += bban.count; - while (c > 0) { - if (bban.type === 'a') { - s += this.faker.helpers.arrayElement(iban.alpha); - } else if (bban.type === 'c') { - if (this.faker.datatype.boolean(0.8)) { - s += this.faker.number.int(9); - } else { - s += this.faker.helpers.arrayElement(iban.alpha); - } - } else { - if (c >= 3 && this.faker.datatype.boolean(0.3)) { - if (this.faker.datatype.boolean()) { - s += this.faker.helpers.arrayElement(iban.pattern100); - c -= 2; - } else { - s += this.faker.helpers.arrayElement(iban.pattern10); - c--; - } - } else { - s += this.faker.number.int(9); - } - } - - c--; - } - - s = s.substring(0, count); - } - - let checksum: string | number = - 98 - iban.mod97(iban.toDigitString(`${s}${ibanFormat.country}00`)); - - if (checksum < 10) { - checksum = `0${checksum}`; - } - - const result = `${ibanFormat.country}${checksum}${s}`; - - return formatted ? prettyPrintIban(result) : result; + return financeIban(this.faker.fakerCore, options); } /** @@ -833,24 +636,7 @@ export class FinanceModule extends ModuleBase { includeBranchCode?: boolean; } = {} ): string { - const { includeBranchCode = this.faker.datatype.boolean() } = options; - - const bankIdentifier = this.faker.string.alpha({ - length: 4, - casing: 'upper', - }); - const countryCode = this.faker.helpers.arrayElement(iban.iso3166); - const locationCode = this.faker.string.alphanumeric({ - length: 2, - casing: 'upper', - }); - const branchCode = includeBranchCode - ? this.faker.datatype.boolean() - ? this.faker.string.alphanumeric({ length: 3, casing: 'upper' }) - : 'XXX' - : ''; - - return `${bankIdentifier}${countryCode}${locationCode}${branchCode}`; + return financeBic(this.faker.fakerCore, options); } /** @@ -890,21 +676,7 @@ export class FinanceModule extends ModuleBase { countryCode?: VatNumberCountryCode; } = {} ): string { - const { - countryCode = this.faker.helpers.arrayElement(vatNumberCountryCodes), - } = options; - - const pattern = vatNumberFormats[countryCode]; - - if (pattern == null) { - throw new FakerError(`Country code ${countryCode} not supported.`); - } - - return this.faker.helpers.fromRegExp( - typeof pattern === 'string' - ? pattern - : this.faker.helpers.arrayElement(pattern) - ); + return financeVatNumber(this.faker.fakerCore, options); } /** @@ -917,8 +689,6 @@ export class FinanceModule extends ModuleBase { * @since 5.1.0 */ transactionDescription(): string { - return this.faker.helpers.fake( - this.faker.definitions.finance.transaction_description_pattern - ); + return financeTransactionDescription(this.faker.fakerCore); } } diff --git a/src/modules/finance/pin.ts b/src/modules/finance/pin.ts new file mode 100644 index 00000000000..d01b2450090 --- /dev/null +++ b/src/modules/finance/pin.ts @@ -0,0 +1,131 @@ +import type { FakerCore } from '../../core'; +import { FakerError } from '../../errors/faker-error'; +import { numeric } from '../string/numeric'; + +/** + * Generates a random PIN number. + * + * @param fakerCore The FakerCore to use. + * @param length The length of the PIN to generate. Defaults to `4`. + * + * @throws {FakerError} Will throw an error if length is less than 1. + * + * @see stringNumeric(fakerCore): For generating the pin with greater control. + * + * @example + * pin(fakerCore) // '5067' + * pin(fakerCore, 6) // '213789' + * + * @since 11.0.0 + * + * @experimental + */ +export function pin(fakerCore: FakerCore, length?: number): string; +/** + * Generates a random PIN number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object. + * @param options.length The length of the PIN to generate. Defaults to `4`. + * + * @throws {FakerError} Will throw an error if length is less than 1. + * + * @see stringNumeric(fakerCore): For generating the pin with greater control. + * + * @example + * pin(fakerCore) // '5067' + * pin(fakerCore, { length: 6 }) // '213789' + * + * @since 11.0.0 + * + * @experimental + */ +export function pin( + fakerCore: FakerCore, + options?: { + /** + * The length of the PIN to generate. + * + * @default 4 + */ + length?: number; + } +): string; +/** + * Generates a random PIN number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object or the length of the PIN. + * @param options.length The length of the PIN to generate. Defaults to `4`. + * + * @throws {FakerError} Will throw an error if length is less than 1. + * + * @see stringNumeric(fakerCore): For generating the pin with greater control. + * + * @example + * pin(fakerCore) // '5067' + * pin(fakerCore, { length: 6 }) // '213789' + * pin(fakerCore, 6) // '213789' + * + * @since 11.0.0 + * + * @experimental + */ +export function pin( + fakerCore: FakerCore, + options?: + | number + | { + /** + * The length of the PIN to generate. + * + * @default 4 + */ + length?: number; + } +): string; +/** + * Generates a random PIN number. + * + * @param fakerCore The FakerCore to use. + * @param options An options object or the length of the PIN. + * @param options.length The length of the PIN to generate. Defaults to `4`. + * + * @throws {FakerError} Will throw an error if length is less than 1. + * + * @see stringNumeric(fakerCore): For generating the pin with greater control. + * + * @example + * pin(fakerCore) // '5067' + * pin(fakerCore, { length: 6 }) // '213789' + * pin(fakerCore, 6) // '213789' + * + * @since 11.0.0 + * + * @experimental + */ +export function pin( + fakerCore: FakerCore, + options: + | number + | { + /** + * The length of the PIN to generate. + * + * @default 4 + */ + length?: number; + } = {} +): string { + if (typeof options === 'number') { + options = { length: options }; + } + + const { length = 4 } = options; + + if (length < 1) { + throw new FakerError('minimum length is 1'); + } + + return numeric(fakerCore, { length, allowLeadingZeros: true }); +} diff --git a/src/modules/finance/routing-number.ts b/src/modules/finance/routing-number.ts new file mode 100644 index 00000000000..7928af0c88a --- /dev/null +++ b/src/modules/finance/routing-number.ts @@ -0,0 +1,40 @@ +import type { FakerCore } from '../../core'; +import { arrayElement } from '../helpers/array-element'; +import { numeric } from '../string/numeric'; + +/** + * Generates a random [ABA routing number](https://en.wikipedia.org/wiki/ABA_routing_transit_number). + * + * @param fakerCore The FakerCore to use. + * + * @example + * routingNumber(fakerCore) // '062197511' + * + * @since 11.0.0 + * + * @experimental + */ +export function routingNumber(fakerCore: FakerCore): string { + const federalReserveRoutingSymbol = arrayElement( + fakerCore, + fakerCore.locale.finance.federal_reserve_routing_symbol + ); + + const institutionIdentifier = numeric(fakerCore, { + length: 4, + allowLeadingZeros: true, + }); + + const routingNumber = federalReserveRoutingSymbol + institutionIdentifier; + + // Modules 10 straight summation. + let sum = 0; + + for (let i = 0; i < routingNumber.length; i += 3) { + sum += Number(routingNumber[i]) * 3; + sum += Number(routingNumber[i + 1]) * 7; + sum += Number(routingNumber[i + 2]) || 0; + } + + return `${routingNumber}${Math.ceil(sum / 10) * 10 - sum}`; +} diff --git a/src/modules/finance/transaction-description.ts b/src/modules/finance/transaction-description.ts new file mode 100644 index 00000000000..330641d4bbf --- /dev/null +++ b/src/modules/finance/transaction-description.ts @@ -0,0 +1,21 @@ +import type { FakerCore } from '../../core'; +import { Faker } from '../../faker'; + +/** + * Generates a random transaction description. + * + * @param fakerCore The FakerCore to use. + * + * @example + * transactionDescription(fakerCore) + * // 'payment transaction at Emard LLC using card ending with ****9187 for HNL 506.57 in account ***2584.' + * + * @since 11.0.0 + * + * @experimental + */ +export function transactionDescription(fakerCore: FakerCore): string { + return new Faker(fakerCore).helpers.fake( + fakerCore.locale.finance.transaction_description_pattern + ); +} diff --git a/src/modules/finance/transaction-type.ts b/src/modules/finance/transaction-type.ts new file mode 100644 index 00000000000..a3540f9b9a1 --- /dev/null +++ b/src/modules/finance/transaction-type.ts @@ -0,0 +1,18 @@ +import type { FakerCore } from '../../core'; +import { arrayElement } from '../helpers/array-element'; + +/** + * Returns a random transaction type. + * + * @param fakerCore The FakerCore to use. + * + * @example + * transactionType(fakerCore) // 'payment' + * + * @since 11.0.0 + * + * @experimental + */ +export function transactionType(fakerCore: FakerCore): string { + return arrayElement(fakerCore, fakerCore.locale.finance.transaction_type); +} diff --git a/src/modules/finance/_vat-number.ts b/src/modules/finance/vat-number.ts similarity index 65% rename from src/modules/finance/_vat-number.ts rename to src/modules/finance/vat-number.ts index c1c026986e3..be0164679da 100644 --- a/src/modules/finance/_vat-number.ts +++ b/src/modules/finance/vat-number.ts @@ -1,3 +1,8 @@ +import type { FakerCore } from '../../core'; +import { FakerError } from '../../errors/faker-error'; +import { arrayElement } from '../helpers/array-element'; +import { fromRegExp } from '../helpers/from-reg-exp'; + /** * The VAT identification number patterns of the EU member states, keyed by * ISO 3166-1 alpha-2 code, plus `EL`: the prefix Greek numbers carry in place @@ -5,6 +10,8 @@ * * Each pattern is written for `faker.helpers.fromRegExp()`. * Currently, all values are generated randomly, so parts with intent such as check digits will likely produce invalid values. + * + * @internal */ export const vatNumberFormats = { /** UID-Nummer. */ @@ -104,7 +111,11 @@ export const vatNumberFormats = { SK: 'SK[0-9]{10}', } as const satisfies Record>; -/** The codes to draw from, minus `GR`, which would give Greece double weight. */ +/** + * The codes to draw from, minus `GR`, which would give Greece double weight. + * + * @internal + */ export const vatNumberCountryCodes = Object.keys(vatNumberFormats).filter( (code) => code !== 'GR' ) as VatNumberCountryCode[]; @@ -113,3 +124,59 @@ export const vatNumberCountryCodes = Object.keys(vatNumberFormats).filter( * The country codes for which a VAT identification number can be generated. */ export type VatNumberCountryCode = keyof typeof vatNumberFormats; + +/** + * Generates a random VAT identification number for one of the EU member states. + * + * The supported country codes are the EU member states, using the two-letter code each + * country's numbers carry: + * `AT`, `BE`, `BG`, `CY`, `CZ`, `DE`, `DK`, `EE`, `EL` (or `GR`), `ES`, `FI`, `FR`, `HR`, `HU`, + * `IE`, `IT`, `LT`, `LU`, `LV`, `MT`, `NL`, `PL`, `PT`, `RO`, `SE`, `SI` and `SK`. + * + * @remark Please note that this currently only generates the structure of the respective country's VAT identification. + * But it will return random values for digits with intent such as check digits, so the result is likely to be invalid. + * + * @param fakerCore The FakerCore to use. + * @param options An options object. + * @param options.countryCode The two-letter code of the country you want a VAT number for. + * Greece may be given as either `GR` or `EL`. + * Defaults to a random supported country. + * + * @throws {FakerError} Will throw an error if the passed country code is not supported. + * + * @example + * vatNumber(fakerCore) // 'SK4318759382' + * vatNumber(fakerCore, { countryCode: 'DE' }) // 'DE644073457' + * vatNumber(fakerCore, { countryCode: 'NL' }) // 'NL840351580B96' + * vatNumber(fakerCore, { countryCode: 'GR' }) // 'EL892156043' + * + * @since 11.0.0 + * + * @experimental + */ +export function vatNumber( + fakerCore: FakerCore, + options: { + /** + * The two-letter code of the country you want a VAT number for. + * Greece may be given as either `GR` or `EL`. + * + * @default helpersArrayElement(fakerCore, vatNumberCountryCodes) + */ + countryCode?: VatNumberCountryCode; + } = {} +): string { + const { countryCode = arrayElement(fakerCore, vatNumberCountryCodes) } = + options; + + const pattern = vatNumberFormats[countryCode]; + + if (pattern == null) { + throw new FakerError(`Country code ${countryCode} not supported.`); + } + + return fromRegExp( + fakerCore, + typeof pattern === 'string' ? pattern : arrayElement(fakerCore, pattern) + ); +} diff --git a/test/modules/finance-iban.spec.ts b/test/modules/finance-iban.spec.ts index 58375d61ea5..4a830af9525 100644 --- a/test/modules/finance-iban.spec.ts +++ b/test/modules/finance-iban.spec.ts @@ -1,8 +1,8 @@ import { isIBAN } from 'validator'; import { describe, expect, it } from 'vitest'; import { faker } from '../../src'; -import { prettyPrintIban } from '../../src/modules/finance'; -import ibanLib from '../../src/modules/finance/_iban'; +import { ibanLib } from '../../src/modules/finance/_iban-lib'; +import { prettyPrintIban } from '../../src/modules/finance/iban'; import { times } from '../support/times'; const NON_SEEDED_BASED_RUN = 25; diff --git a/test/modules/finance.spec-d.ts b/test/modules/finance.spec-d.ts index b18f5da06ea..5a8c6777eb7 100644 --- a/test/modules/finance.spec-d.ts +++ b/test/modules/finance.spec-d.ts @@ -1,6 +1,6 @@ import type { VATCountryCode } from 'validator'; import { describe, expectTypeOf, it } from 'vitest'; -import type { VatNumberCountryCode } from '../../src/modules/finance/_vat-number'; +import type { VatNumberCountryCode } from '../../src/modules/finance/vat-number'; describe('finance', () => { describe('vatNumber', () => { diff --git a/test/modules/finance.spec.ts b/test/modules/finance.spec.ts index ba983209068..b28cc06ae50 100644 --- a/test/modules/finance.spec.ts +++ b/test/modules/finance.spec.ts @@ -8,13 +8,13 @@ import { FakerError } from '../../src/errors/faker-error'; import { BitcoinAddressFamily, BitcoinNetwork, -} from '../../src/modules/finance/_bitcoin'; -import ibanLib from '../../src/modules/finance/_iban'; -import type { VatNumberCountryCode } from '../../src/modules/finance/_vat-number'; +} from '../../src/modules/finance'; +import { ibanLib } from '../../src/modules/finance/_iban-lib'; import { vatNumberCountryCodes, vatNumberFormats, -} from '../../src/modules/finance/_vat-number'; +} from '../../src/modules/finance/vat-number'; +import type { VatNumberCountryCode } from '../../src/modules/finance/vat-number'; import { luhnCheck } from '../../src/modules/helpers/_luhn-check'; import { seededTests } from '../support/seeded-runs'; import { times } from '../support/times';