npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sharia-compliant-finance

v2.0.0

Published

Islamic finance toolkit: Sharia-compliant BNPL and instalment financing (murabaha, ijara, tawarruq, diminishing musharakah), a zakat engine with nisab and hawl, a configurable Shariah screening rule engine, and riba/gharar analysis. Integer-precision mone

Readme

sharia-compliant-finance

Islamic finance primitives you can put in a real product: Sharia-compliant BNPL and instalment financing, a zakat engine that actually knows about nisab and hawl, a configurable Shariah screening rule engine, and riba/gharar analysis.

Zero runtime dependencies. TypeScript source, ships ESM + CJS + type declarations. Money is integer minor units, so schedules always reconcile.

npm install sharia-compliant-finance

Why v2 exists

v1 was eight functions built on hard-coded arrays. It answered questions with bare booleans, which meant you were told no without being told why, and it got several answers outright wrong:

| v1 behaviour | What v2 does | | --- | --- | | calculateZakat(20, 'cash') returned 0.50 | Nisab is tested first. Below the threshold, zakat is zero. | | No holding-period check | Hawl is tracked as a lunar year, with the due date returned. | | Every asset taxed at 2.5% of face value | Fourteen asset classes, each with its own rule. Livestock and crops have their own nisab and rates entirely. | | Liabilities ignored | Deducted, with the rule varying by madhhab. | | industry: "Liquor Distribution" passed as halal | Resolved by alias, regex, and NAICS/SIC/GICS code. Unknown input reports insufficient-data, never a silent pass. | | A supermarket with 1% alcohol revenue was banned outright | Revenue-share tolerance, exactly as index screens work, plus a purification amount. | | Riba meant interestRate > 0 | Also catches tenor-linked fees, late charges the lender keeps, compounding, repricing on default, floating benchmarks, and riba al-fadl / al-nasiah in commodity and currency exchange. | | isGhararPresent needed you to have done the analysis already | Fourteen structural checks on subject matter, price, delivery, and contingencies, graded yasir vs fahish. | | Loss shared by profit ratio | Profit by agreed ratio, loss strictly by capital — and in a mudarabah, entirely by the capital provider. | | Float arithmetic | Integer minor units with largest-remainder allocation. |

Every v1 function still works. See MIGRATION.md.


Islamic BNPL in twenty lines

Murabaha is the structure behind almost every Islamic BNPL product: the financier buys the goods, takes ownership risk, then resells at a disclosed cost plus a fixed markup, payable in instalments.

import { createMurabaha, toDecimalString } from 'sharia-compliant-finance';

const plan = createMurabaha({
  currency: 'AED',
  assetCost: 3600,                              // what you paid for the item
  markup: { type: 'percentage', value: 8 },     // fixed at contract, forever
  installments: 4,
  frequency: 'monthly',
  contractDate: '2026-03-31',
  possession: {                                 // you must own it before you sell it
    acquiredAt: '2026-03-28',
    type: 'physical',
    description: 'Smartphone, 256GB',
  },
  latePayment: { type: 'charity-penalty', ratePerAnnum: 5, gracePeriodDays: 7 },
  rebatePolicy: { type: 'full-unearned-profit' },
});

toDecimalString(plan.sellingPrice);   // '3888.00'
plan.installments.map(i => i.dueDate);
// ['2026-04-30', '2026-05-31', '2026-06-30', '2026-07-31']  ← month-end respected
plan.compliance.verdict;              // 'compliant'
plan.disclosure.effectiveAnnualRate;  // for regulatory disclosure only; nothing accrues

The instalments always sum to the selling price exactly. Rounding lands on the earliest payments, never as a mystery balance at the end.

What the schedule enforces

createMurabaha({ ...plan, possession: { acquiredAt: '2026-04-05' } });
// finding: murabaha.sold-before-ownership — violation
// "The financier acquired the asset after selling it. That is a sale of what
//  one does not own."

Early settlement (ibra')

The debt is a fixed price, so the customer owes the whole remaining balance whether they pay today or on the last due date. A discount is the financier waiving something, and the quote says so explicitly:

const quote = quoteEarlySettlement({ schedule: plan, installmentsPaid: 2 });
quote.grossOutstanding;    // 1944.00 — what is actually owed
quote.unearnedProfit;      //  144.00
quote.rebate;              //  144.00 — granted, not deducted by right
quote.settlementAmount;    // 1800.00

Policies: none, full-unearned-profit, percentage-of-unearned, fixed, or your own function. Setting contractuallyGuaranteed: true raises a finding, because AAOIFI requires ibra' to stay discretionary while Bank Negara Malaysia requires the opposite — the library records which regime you are following rather than picking for you.

Late payment — the part conventional BNPL gets wrong

const charge = calculateLatePaymentCharge({
  currency: 'AED',
  policy: { type: 'charity-penalty', ratePerAnnum: 5, gracePeriodDays: 7 },
  overdueAmount: 972,
  dueDate: '2026-04-30',
  asOf: '2026-05-30',
});

charge.amount;                   // the deterrent charge
charge.payableTo;                // 'charity'
charge.recogniseAsIncome;        // false — you may not book a cent of it
charge.outstandingDebtUnchanged; // true — the price cannot grow. Ever.

The charge is simple, never compounded: a charge for sixty days is exactly twice one for thirty. Use { type: 'actual-cost' } to recover documented collection expense instead, which you may retain.

Rescheduling

rescheduleFinancing({ schedule: plan, installmentsPaid: 2, newInstallmentCount: 8 });
// Spreads the same total over more payments. previousTotalPayable === newTotalPayable.

rescheduleFinancing({ ...same, additionalMarkup: 50 });
// throws ShariaConstraintError — "an increase in exchange for time is riba al-jahiliyyah"

Other structures

| Function | Use for | | --- | --- | | createMurabaha | Goods BNPL, asset finance, trade finance | | createIjara | Leasing, including ijara muntahia bittamleek | | createDiminishingMusharakah | Home finance, high-value co-ownership | | createTawarruq | Cash financing — always warns; see below |

All four return the same FinancingSchedule shape, so one checkout component renders any of them.

createIjara refuses to let you push major maintenance, asset takaful, or total-loss risk onto the lessee — a lessor that carries no ownership risk is a lender, and the rent is then interest.

createDiminishingMusharakah rejects a buy-back at original nominal value, which would guarantee the financier's capital and make the whole thing a secured loan.

createTawarruq always emits a warning and flags the organised form as a violation, citing OIC Fiqh Academy resolution 179 (2009). It exists because real products use it, not because it is recommended.


Zakat

Nisab is a weight of gold or silver, so it needs a live metal price. This package makes no network calls and ships no market data — you supply the prices, which also means the number is auditable against a quote you can point to.

import { calculateZakat } from 'sharia-compliant-finance';

const result = calculateZakat({
  currency: 'PKR',
  assets: [
    { type: 'cash', value: 500_000 },
    { type: 'gold', grams: 50, purityKarat: 22, personalUse: true },
    { type: 'receivable', value: 80_000, recoverability: 'doubtful' },
    { type: 'shares-passive', value: 300_000, zakatableAssetRatio: 0.42 },
    { type: 'rental-property', value: 9_000_000, incomeHeld: 120_000 },
    { type: 'personal-use', value: 2_500_000 },     // home — exempt
  ],
  liabilities: [
    { label: 'Credit card', amount: 40_000, type: 'immediate' },
    { label: 'Home finance', amount: 6_000_000, type: 'long-term', monthlyInstalment: 45_000 },
  ],
  metalPrices: { currency: 'PKR', goldPricePerGram: 21_000, silverPricePerGram: 260 },
  hawlStartDate: '2025-02-01',
  valuationDate: '2026-02-05',
  madhhab: 'hanafi',
});

result.zakatDue;              // Money
result.aboveNisab;            // boolean
result.hawl;                  // { satisfied, dueDate, daysElapsed, daysRemaining }
result.assets;                // every asset with included/excluded and the reason
result.findings;              // warnings about assumptions the engine had to make

Asset classes

cash · bank-balance · receivable (good / doubtful / bad) · gold · silver · trade-goods · shares-active · shares-passive · crypto · pension · rental-property · fixed-asset · personal-use · other

Each carries its own rule. A rental building is exempt while the rent retained at year end is not; a passively held share is assessed on the issuer's zakatable assets, not the whole share price; plant and machinery are tools of trade and exempt.

Madhhab

The four schools genuinely differ on questions that change the number. Each profile is explicit and overridable:

calculateZakat({ ...input, madhhab: 'hanafi' });  // worn jewellery IS zakatable
calculateZakat({ ...input, madhhab: 'shafii' });  // worn jewellery is exempt

| | Nisab | Worn jewellery | Debt deduction | | --- | --- | --- | --- | | Hanafi | silver | zakatable | all debts | | Shafi'i | gold | exempt | due only | | Maliki | gold | exempt | due only | | Hanbali | gold | exempt | all debts | | contemporary (default) | lower of the two | exempt | next 12 months |

Livestock and crops

Not everyone's wealth is a bank balance.

calculateLivestockZakat({ kind: 'camel', count: 50, freelyGrazing: true });
// due: [{ count: 1, grade: 'hiqqah', description: 'A three-year-old female camel.' }]

calculateAgricultureZakat({ harvestKg: 4000, irrigation: 'natural' });
// { rate: 0.10, zakatDueKg: 400 }   ← 5% if artificially irrigated, 7.5% if mixed

Screening

The rule engine that replaces v1's six-string array.

import { screen, createScreeningEngine, AAOIFI_POLICY } from 'sharia-compliant-finance';

screen({ industry: 'Liquor Distribution' }).verdict;   // 'non-compliant'
screen({ industryCodes: [{ scheme: 'NAICS', code: '713210' }] }).verdict; // 'non-compliant'
screen({ industry: 'artisanal widget refurbishment' }).verdict; // 'insufficient-data'

That last one is the point. v1 passed anything it did not recognise. This reports that it could not tell, and tells you to classify it.

Revenue tolerance, not blanket exclusion

screen({
  name: 'Supermarket',
  revenueBreakdown: [
    { activity: 'grocery', revenue: money(980_000, 'USD') },
    { activity: 'alcohol', revenue: money(20_000, 'USD') },
  ],
});
// verdict: 'requires-review'
// nonCompliantRevenueShare: 0.02, within the 5% tolerance
// → purificationRate tells you how much income to give away

Financial ratios

Built-in policy packs: aaoifi, djim, sp-shariah, msci-islamic, ftse-shariah, activity-only. They differ in both thresholds and denominators, which is why they must be selectable rather than baked in.

const report = screen({ name: 'Example Plc', industry: 'software', financials }, { policy: AAOIFI_POLICY });
report.ratios;
// [{ ruleId: 'aaoifi.debt-to-market-cap', value: 0.5, threshold: 0.3, passed: false, ... }]

Making it yours

const engine = createScreeningEngine({
  policy: definePolicy({ id: 'house', label: 'Our board', activity: { revenueTolerance: 0 } }, AAOIFI_POLICY),

  // Override a ruling, or add a category the taxonomy has never heard of
  activities: [
    { id: 'tobacco', label: 'Tobacco', ruling: 'doubtful', aliases: ['cigarettes'] },
    { id: 'nft-marketplace', label: 'NFT marketplace', ruling: 'doubtful', patterns: ['\\bnfts?\\b'] },
  ],

  // Bolt on rules that have nothing to do with fiqh
  customRules: [{
    id: 'internal.sanctions',
    evaluate: (subject) => subject.metadata?.sanctioned
      ? { code: 'sanctions.listed', severity: 'violation', message: 'On the internal sanctions list.' }
      : null,
  }],
});

Purification

calculatePurification({ income: money(1000, 'USD'), nonCompliantShare: 0.023 });
// { amountToPurify: 23.00, retained: 977.00,
//   note: 'Give this away without expecting reward. It is not sadaqah and
//          should not be claimed as a charitable deduction.' }

Rounded up, never down.


Riba and gharar

checkRiba({ latePayment: { charged: true, accruesToLender: true } });
// violation: a late charge kept by the financier is an increase on a debt for time

checkRiba({ fees: [{ name: 'Facility fee', basis: 'time-based' }] });
// violation: a charge for the passage of time is interest whatever it is labelled

checkRiba({ exchange: { give: { asset: 'gold', amount: 10 },
                        receive: { asset: 'silver', amount: 800 },
                        settlement: 'deferred' } });
// violation: riba al-nasiah — same ribawi category, so settlement must be spot
analyseContract({
  subjectMatter: { description: 'Wheat, 10t, grade A', forwardContractType: 'salam',
                   existsAtContract: false, ownedBySeller: true,
                   quantitySpecified: true, qualitySpecified: true, deliverable: true },
  price: { specified: true, paidInFullAtContract: true },
  delivery: { dateSpecified: true, placeSpecified: true },
});
// ghararLevel: 'none' — a valid salam, even though the goods do not yet exist

Profit and loss sharing

shareProfitAndLoss({
  currency: 'USD',
  structure: 'musharakah',
  partners: [
    { name: 'Bank', capital: 70_000, profitShare: 0.4 },
    { name: 'Entrepreneur', capital: 30_000, profitShare: 0.6 },
  ],
  result: -10_000,          // a loss
});
// Loss: 7,000 / 3,000 — by CAPITAL, not by the 40/60 profit ratio.
// That is not configurable, because a partnership that allocates loss any
// other way has stopped being a partnership.

A guaranteedReturn on any partner produces a violation. A mudarib bears no financial loss unless negligent: true is recorded.


Money

import { money, allocate, split, sum } from 'sharia-compliant-finance';

money(1.005, 'USD').minor;   // 101 — the half cent binary floats lose
money(1.234, 'KWD').minor;   // 1234 — three decimals, correctly
split(money(100, 'USD'), 3); // 33.34 / 33.33 / 33.33, summing to exactly 100.00

Every amount is an integer count of minor units. Currency exponents follow ISO 4217 (JPY 0, KWD/BHD/OMR 3, everything else 2); registerCurrency adds your own. allocate uses largest-remainder, which is why every schedule in this package reconciles.


Reading results

Nothing returns a bare boolean. Every check produces findings:

{
  code: 'murabaha.sold-before-ownership',
  severity: 'violation',            // 'info' | 'warning' | 'violation'
  category: 'financing',
  message: '...',
  evidence: { acquiredAt: '2026-04-05', contractDate: '2026-03-31' },
  reference: 'AAOIFI SS 8 cl. 3/1/1',
  remediation: 'Complete the purchase leg before executing the sale.',
}

Verdicts: compliant · requires-review (a warning needs a human) · insufficient-data (inputs were missing — not a pass) · non-compliant.

Compose several checks into one report:

generateComplianceReport({
  reference: 'ORDER-4471',
  screening: { industry: 'electronics retail' },
  riba: { kind: 'sale', principal: money(3600, 'AED'), repayment: money(3888, 'AED') },
  financing: plan,
});

API surface

Financing — createMurabaha createIjara createTawarruq createDiminishingMusharakah quoteEarlySettlement calculateLatePaymentCharge rescheduleFinancing outstandingBalance overdueInstallments effectiveAnnualRate

Zakat — calculateZakat calculateNisab valueMetal calculateLivestockZakat calculateAgricultureZakat calculateSadaqah MADHHAB_PROFILES

Screening — screen createScreeningEngine definePolicy calculatePurification ActivityTaxonomy AAOIFI_POLICY DJIM_POLICY SP_SHARIAH_POLICY MSCI_ISLAMIC_POLICY FTSE_SHARIAH_POLICY

Contracts — checkRiba analyseContract classifyRibawi RIBAWI_ASSETS

Partnership — shareProfitAndLoss

Money — money fromMinor add subtract multiply divide allocate split sum format toDecimalString registerCurrency

Reports — generateComplianceReport summariseVerdict

Everything is fully typed. Import types with import type { ... }.


Scope and limits

This is a calculation and screening tool. It does not issue fatwa and it does not replace your Shariah supervisory board.

Where scholars genuinely differ, the library says so rather than picking a side — that is what doubtful and requires-review are for. Where a figure depends on data the library cannot see (a metal price, an issuer's zakatable asset ratio, whether a lessee was negligent), it asks you for it and warns when you leave it out. Nothing passes by omission.

References cited in findings point to AAOIFI Shari'ah Standards, OIC Fiqh Academy resolutions, and the published index methodologies. They are pointers for your reviewers, not a claim of certification.


Development

npm install
npm run typecheck
npm test          # 105 tests
npm run build

License

ISC