← Back to QRID
Architecture overview  ·  April 2026

How QRID is built, in plain words.

This page is for general managers, presidents, controllers and partners who want enough of the architecture to ask the right questions of the people they trust on these things. It is not a specification. We have those, and we will share them under NDA.

The shape of the platform.

QRID is a single deployable platform organised into 29 modules — one per domain, like F&B, billing, governance, golf, marina. Each module owns its own data, its own rules, and its own corner of the API. They share a single member, a single ledger, and a single calendar.

We chose a modular monolith over microservices on purpose. At the scale we operate, microservices add operational complexity without buying anything we need. Each module behaves like an independent service inside one process; if any module ever needs to be split out for scale, the messaging library we use lets us do it as a configuration change — not a rewrite.

The application is hosted on .NET 10, talks to PostgreSQL 17 for primary data, and uses Redis 8 for cache and real-time state. The web admin is Next.js 16; the mobile app is React Native 0.84 on Expo. Everything sits behind Traefik for TLS, routing and rate limiting.

How tenants are resolved.

Each club is a tenant. A request arriving at QRID has to be matched to the correct tenant before any data is touched. This happens in three steps:

Tenant resolution diagram A request enters at Traefik, is routed by hostname, matched to a tenant, and then served with row-level data isolation. Requestartistclub.qrid.club/api/... TraefikTLS · routingrate limit Tenant resolverhost → tenant idJWT claim verified Module + DBEF row filtertenant_id = T 01 · arrives 02 · routed 03 · matched 04 · served Custom domain → CNAME → same path. Fallback: tenant.qrid.club. Platform admins are the only role allowed to override the tenant header.
Fig. 2 — Tenant resolution: hostname-first, JWT-verified, row-level enforced.

The resolver matches the incoming hostname to a tenant in Finbuckle.MultiTenant, validates that the JWT presented for the request belongs to a member of that tenant, and then attaches the tenant to the request scope. From that point, every database query is automatically filtered by tenant id at the EF Core level — not just by application logic. Cross-tenant access is impossible without explicitly being a platform admin.

The reciprocal flow, end-to-end.

When a member of Artist Club visits Dhanmondi Club — or, in the future, Gulshan Club, or a club abroad — QRID has to do four things at once: verify the visitor, enforce the day-limit policy of both clubs, allow the visitor to charge to their home account, and settle between the two clubs at month-end.

Reciprocal flow diagram A four-step flow: member requests a digital letter of introduction, GM approves, host club checks the member in, and a monthly settlement runs between the two clubs. i Member request Visiting Dhaka next week ii Home GM approves Letter signed digitally iii QR issued to host Day limit, scope encoded iv Check-in Host club, scanned v Charges at host F&B, spa, locker vi Posted to home ledger Member's house account vii Monthly settlement Clubs net, fee retained No paper. No phone tag. Both GMs see the same record.
Fig. 3 — Reciprocal flow from request through monthly settlement.

What used to be a phone call between two general managers and a stamped letter is now a record both clubs can audit. Day-limit enforcement, billing, and dispute handling all happen against the same source of truth.

Payments, as plugins.

Clubs in different countries use different payment processors. A club in Bangladesh might use SSLCommerz or bKash; a club in Singapore might use Stripe; an institution might handle everything through bank transfer. We do not pretend a single processor fits everyone.

Payment plugin architecture Each club configures one or more payment gateway plugins; the registry routes a charge to the active plugin which calls out to the underlying processor. Charge command PostChargeToLedger amount, member, dept Gateway registry per-club config encrypted credentials Stripe plugin api.stripe.com SSLCommerz plugin sslcommerz.com bKash plugin bkash.com Manual methods (cash, cheque, bank transfer) are always available without plugin configuration.
Fig. 4 — Payment plugin architecture. Each club picks the plugins it needs.

A gateway plugin is a small piece of code that knows how to talk to one processor. The club's general manager enables the plugins their bank already accepts, and pastes the credentials into the admin console. The credentials are encrypted at rest using ASP.NET Core Data Protection — a key the club's database alone cannot decrypt without the application's keyring.

Clubs always have the option to record a charge as cash, cheque, or bank transfer with no integration at all. We do not take a position on which mix is right for a given club; we provide the rails.

The stack, briefly.

  • Identity — Wenme. OAuth 2.1 with PKCE. We do not roll our own auth.
  • Authorisation — Darwan. Granular permissions per role per tenant. Cached for five minutes, fail-closed in production.
  • Communications — BitsPath. Email, SMS, WhatsApp and push delivered through a single API.
  • Application — .NET 10 with ASP.NET Core minimal APIs. Wolverine 5 for in-process messaging and the transactional outbox.
  • Database — PostgreSQL 17. Schema-per-module isolation. Daily encrypted backups; weekly restore drills.
  • Cache — Redis 8. Sessions, authorisation cache, real-time pub/sub.
  • Real-time — SignalR over WebSocket. Bookings, check-ins, statement updates push live.
  • Web admin — Next.js 16 with React 19 and Tailwind 4.
  • Mobile — Expo SDK 54, React Native 0.84. Passkeys; QR + NFC; Apple/Google Wallet passes.
  • Edge — Traefik v3 with automatic Let's Encrypt for the club's custom domain.
  • Hosting — Docker Swarm on KaritKarma-operated infrastructure in Dhaka, with an option to deploy in-region for tenants that require it.

How we operate it.

The same team that builds QRID also runs it. There is no third-party operations partner between the engineers and the production cluster. We deploy by SSH, not by webhook. Backups are encrypted. Restore drills are weekly. For incident updates, write to contact@karitkarma.com. The security policy lives at /security, and the data processing addendum is available on request.

When something is wrong, you reach a person at KaritKarma. Not a tier-one queue.

qrid.club April 2026  ·  v1 Talk to our team →