# Kanject.Wallet

A double-entry ledger and accounting engine that deploys into *your* PCI-scoped AWS — chart-of-accounts, attribute-driven posting profiles, balances, holds, and multi-currency. It records the movements your own payment stack makes; **Kanject keeps the books — it never touches the money.** *Money moves. We do the accounting.*

> **Wallet is a ledger, not a payment processor:** Wallet records, balances, and reconciles every movement — it is **not** a Merchant of Record and never holds or routes funds. The actual charge and payout run through *your* existing payment stack; you post the resulting events to Wallet, and it keeps the books.

**You'll learn**

- Provision the ledger into your PCI-scoped AWS account
- Declare a **chart of accounts** with attribute-driven account roles
- Define a **posting profile** whose debits and credits balance by construction
- Post and commit balanced transactions at runtime with the manager services

## Provision

Wallet is a CloudFormation stack in *your* account — the ledger tables, posting engine, and reconciliation jobs. Deploy it once per stage; the template is idempotent.

```bash
# Deploys the ledger into your PCI-scoped AWS. Idempotent.
kanject baas deploy wallet --env dev
```

## Register it in your service

Register with `AddKanjectWallet<TWalletDbContext>(...)`, passing a typed options delegate. `TWalletDbContext` is your own class deriving from `WalletDbContext`; the options carry the AWS region, schema, and per-stage namespace.

```csharp
using Kanject.Wallet.Extensions;

// Your ledger's DbContext derives from WalletDbContext (a single-table
// data layer, like the other Kanject modules).
public partial class AppWalletDbContext : WalletDbContext;

// Register with a typed options delegate — the generic is the DbContext.
builder.Services.AddKanjectWallet<AppWalletDbContext>(options =>
{
    options.Region            = appSettings.AwsRegion;
    options.SchemaName        = "wallet";
    options.DatabaseNamespace = appSettings.Stage;   // per-stage isolation
});
```

## Declare your chart of accounts

The ledger is **attribute-driven**: you declare accounts and their roles in code, and the numbering policy keeps each category in its own range. Account roles (`SettlementClearing`, `WalletLiability`, `EscrowLiability`, …) are what posting rules target, so a profile never hard-codes an account number.

```csharp
using Kanject.Wallet.Abstractions.Attributes;
using Kanject.Wallet.Abstractions.Attributes.Accounts;

// Declare your chart of accounts once — an attribute-driven ledger.
[ChartOfAccounts(version: 1, legalEntity: "KANJECT-LTD")]
[NumberingPolicy(category: Asset,     min: 1000, max: 1999)]
[NumberingPolicy(category: Liability, min: 2000, max: 2999)]
public partial class KanjectCoA
{
    [AssetAccount    (number: 1100, name: "Stripe Clearing", role: SettlementClearing)]
    [LiabilityAccount(number: 2001, name: "Customer Wallet", role: WalletLiability,
                       requiresCounterparty: true, requiresBalanceValidation: true)]
    [LiabilityAccount(number: 2002, name: "Escrow",          role: EscrowLiability)]
    public partial void Accounts();
}
```

## Define a posting profile

Every money flow is a **posting profile** — a set of rules that translate an event into balanced journal lines. Debits and credits **must** balance, and the binding expressions (`@event.Amount`, `@wallet:CustomerWallet(...)`) resolve against the event at post time.

```csharp
using Kanject.Wallet.Abstractions.Posting.Contracts;

// Each money flow is a posting profile. Debits and credits MUST balance —
// the ledger refuses to compile an unbalanced rule.
[PostingProfile(version: 1, groupType: ProfileGroupType.Settlement)]
[UseChartOfAccount<KanjectCoA>]
public partial class CustomerPaymentPosting
{
    [PostingRule(name: "Customer Card Payment", priority: 100)]
    [PostingEvent<PostingEvent>(topic: "CustomerPayment", version: 1)]
    [DebitAsset      (Role = ControlRole.SettlementClearing, Amount = "@event.Amount")]
    [CreditUserWallet(User = "@wallet:CustomerWallet(@event.SourceParty.Id)",
                      Amount = "@event.Amount")]
    public partial Task RulesAsync(IPostingEvent @event);
}
```

> **Pitfall:** A posting profile that doesn't balance — total debits ≠ total credits — is a **build error, not a production surprise**. The ledger validates every profile at construction, so an unbalanced rule never ships.

## Post transactions at runtime

The manager services are the runtime surface. `IWalletManager` opens and queries wallets; `ITransactionManager` logs, posts, commits, and reverses transactions built from your posting profiles:

```csharp
// At runtime, inject the manager services. Wallets are created through
// IWalletManager; balanced transactions are posted through ITransactionManager
// from a posting event, then committed.
public class PaymentService(
    IWalletManager wallets,
    ITransactionManager transactions)
{
    public Task<CreateWalletResponse?> OpenWalletAsync(CreateWalletRequest request, Guid userId)
        => wallets.CreateWalletAsync(request, userId);

    public async Task RecordCustomerPaymentAsync(PostingEvent paymentEvent)
    {
        // Posts the journal lines your CustomerPaymentPosting profile defines.
        var posted = await transactions.PostTransactionAsync(paymentEvent);
        await transactions.CommitTransactionAsync(
            new CommitTransactionRequest { /* txn reference from the posted result */ });
    }
}
```

## Holds, balances & multi-currency

- **Double-entry by construction** — every movement is two balanced journal lines.
- **Multi-currency** — accounts, conversions, and FX rates per tenant.
- **Holds & releases** — place and release holds against wallet balances (escrow, liens).
- **Transaction lifecycle** — log, post, commit, reverse, and cancel through `ITransactionManager`.
- **KYC & sanctions states** — wallet parties carry KYC and sanctions-screening status.

**Recap**

- Wallet is a double-entry **ledger**, not a payment processor — it keeps the books; your existing payment stack moves the money.
- The chart of accounts is attribute-driven; posting rules target account **roles**, not numbers.
- Every posting profile balances by construction — unbalanced rules fail the build.
- At runtime, `IWalletManager` and `ITransactionManager` open wallets and post/commit balanced transactions.

## Related modules

- **[Identity](https://www.kanject.com/docs/baas-identity/)** — KYC context — tie ledger parties to authenticated users.
- **[EventHub](https://www.kanject.com/docs/baas-eventhub/)** — Publish transaction events that drive posting profiles.
- **[NotificationHub](https://www.kanject.com/docs/baas-notifications/)** — Send receipts and balance alerts off ledger events.

> **Next step:** See the [Wallet product page](https://www.kanject.com/baas/wallet/) for the feature tour, or read the [BaaS deployment guide](https://www.kanject.com/docs/baas/) for how Wallet composes with the other modules in one service.

---
_Source: https://www.kanject.com/docs/baas-wallet/ · Kanject Docs_
