Skip to content

Cards

The Cards module manages two card types: Issued Cards (cards issued by Moria/BaaS partner for an account, both virtual and physical) and Linked Cards (external user-owned cards linked for payments). Both controllers share the same CardService + DTOs, but are split by path so FE can differentiate listing/issuance UX from linking UX. Sensitive fields (full PAN, CVV, expiry) are stored encrypted on the server, and only the last 4 digits appear in responses.

PropertyValue
Base URL{HOST}/v1
AuthBearer JWT (header Authorization) or cookie access_token
Content-Typeapplication/json
Error envelope{ "message": string | string[], "statusCode": number, "error": string }
ValidationGlobal ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown field → 400
Related modulesaccounts, payments, payment-gateway, users
Document versionv1 · 2026-05-20
AudienceInternal FE devs (mobile + web)

Two parallel controllers — CardController at /issued-cards for Moria-issued cards, and LinkedCardsController at /linked-cards for external cards linked by users. All endpoints require a Bearer JWT plus ACL permissions (create-card, read-card, update-card, delete-card).

MethodPathAuthSummary
POST/v1/issued-cardsbearerIssue a new card (virtual / physical)
GET/v1/issued-cardsbearerList all issued cards
GET/v1/issued-cards/:idbearerDetail of a single issued card
PATCH/v1/issued-cards/:idbearerUpdate issued card (status, image)
DELETE/v1/issued-cards/:idbearerDelete/deactivate issued card
POST/v1/linked-cardsbearerLink a user’s external card
GET/v1/linked-cardsbearerList all linked cards
GET/v1/linked-cards/:idbearerDetail of a single linked card
PATCH/v1/linked-cards/:idbearerUpdate linked card
DELETE/v1/linked-cards/:idbearerUnlink linked card

Endpoints for cards issued by Moria / BaaS partner (physical or virtual) bound to a single account_id. Path: /v1/issued-cards.


Issue a new card (virtual or physical) for an account. The server handles all sensitive fields (PAN, CVV, expiry, network, BaaS provider) via the BaaS integration — FE may only send the optional card_image.

bearer create-card
FieldTypeRequiredNotes
card_imagestringoptionalCard image URL (see file-manager module). All sensitive fields (PAN, CVV, expiry, network, BaaS provider, account_id) are set by the server.
{
"card_image": "https://cdn.moriafund.com/cards/template-visa.png"
}
{
"status": "success",
"statusCode": 201,
"message": "Card created successfully",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"card_holder_name": "John Doe",
"last_digits": "XXXX",
"card_type": "debit",
"status": "pending",
"form": "virtual",
"network": "visa",
"baas_provider": "cimb",
"card_number": "****-****-****-XXXX",
"account_id": "660e8400-e29b-41d4-a716-446655440111",
"card_image": "https://cdn.moriafund.com/cards/template-visa.png",
"issued_at": "2026-05-20T08:30:00.000Z",
"expires_at": "2030-05-20T08:30:00.000Z",
"created_at": "2026-05-20T08:30:00.000Z"
}
}

The card_number field is always masked (last 4 only). Full PAN, CVV, and raw expiry are never sent to the client — the server stores them encrypted.

StatusWhen it occurs
400 Bad RequestInvalid card data (unknown field, bad URL format)
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing create-card permission

Retrieve all issued cards. The service determines scope (per account / per organization) based on the logged-in user.

bearer read-card
{
"status": "success",
"statusCode": 200,
"message": "Cards retrieved successfully",
"data": {
"limit": 10,
"count": 24,
"currentPage": 1,
"totalPages": 3,
"cards": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"card_holder_name": "John Doe",
"last_digits": "XXXX",
"card_type": "debit",
"status": "active",
"form": "virtual",
"network": "visa",
"baas_provider": "cimb",
"card_number": "****-****-****-XXXX",
"account_id": "660e8400-e29b-41d4-a716-446655440111",
"card_image": "https://cdn.moriafund.com/cards/template-visa.png",
"issued_at": "2026-01-01T00:00:00.000Z",
"expires_at": "2030-01-01T00:00:00.000Z"
}
]
}
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing read-card permission

GET /v1/issued-cards/:id bearer

Section titled “GET /v1/issued-cards/:id ”

Detail of a single issued card. The response only contains masked data + metadata; PAN/CVV is still not sent.

bearer read-card
ParamTypeNotes
idstringIssued card ID
{
"status": "success",
"statusCode": 200,
"message": "Card retrieved successfully",
"data": {
"card": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"card_holder_name": "John Doe",
"last_digits": "XXXX",
"card_type": "debit",
"status": "active",
"form": "virtual",
"network": "visa",
"baas_provider": "cimb",
"card_number": "****-****-****-XXXX",
"account_id": "660e8400-e29b-41d4-a716-446655440111",
"card_image": "https://cdn.moriafund.com/cards/template-visa.png",
"issued_at": "2026-01-01T00:00:00.000Z",
"expires_at": "2030-01-01T00:00:00.000Z"
}
}
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing read-card permission
404 Not FoundCard not found

PATCH /v1/issued-cards/:id bearer

Section titled “PATCH /v1/issued-cards/:id ”

Update an issued card. Only non-sensitive fields (e.g. card_image) can be changed — PAN, CVV, and expiry are not in the DTO.

bearer update-card
ParamTypeNotes
idstringIssued card ID
FieldTypeRequiredNotes
card_imagestringoptionalNew image URL. PAN/CVV/expiry fields cannot be updated via this endpoint.
{
"card_image": "https://cdn.moriafund.com/cards/template-gold.png"
}
{
"status": "success",
"statusCode": 200,
"message": "Card updated successfully",
"data": { "...": "same shape as CardDto" }
}
StatusWhen it occurs
400 Bad RequestInvalid update data
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing update-card permission
404 Not FoundCard not found

DELETE /v1/issued-cards/:id bearer

Section titled “DELETE /v1/issued-cards/:id ”

Delete / deactivate an issued card. Soft-delete via deleted_at from AuditableEntity — the record remains in DB for audit purposes.

bearer delete-card
ParamTypeNotes
idstringIssued card ID
{
"status": "success",
"statusCode": 200,
"message": "Card deleted successfully"
}
StatusWhen it occurs
400 Bad RequestCannot delete an active card (e.g. balance remaining / pending transactions)
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing delete-card permission
404 Not FoundCard not found

Endpoints for external cards (user-owned debit/credit from other banks) linked for payments. Path: /v1/linked-cards. The DTO shape is identical to issued cards — the separation is only by UX context and audit log.


Link an external card (debit / credit) to the user’s account for use as a source of funds for payments. Card tokenization is actually performed by the BaaS / payment gateway — FE only sends a minimal payload.

bearer create-card
FieldTypeRequiredNotes
card_imagestringoptionalCard logo / image URL. PAN and CVV are tokenized by the BaaS — not sent directly in this DTO.
{
"card_image": "https://cdn.moriafund.com/cards/visa-debit.png"
}
{
"status": "success",
"statusCode": 201,
"message": "Card created successfully",
"data": {
"id": "770e8400-e29b-41d4-a716-446655440222",
"card_holder_name": "John Doe",
"last_digits": "XXXX",
"card_type": "debit",
"status": "active",
"form": "physical",
"network": "mastercard",
"baas_provider": "berrypay",
"card_number": "****-****-****-XXXX",
"account_id": "660e8400-e29b-41d4-a716-446655440111",
"card_image": "https://cdn.moriafund.com/cards/visa-debit.png",
"issued_at": "2024-06-01T00:00:00.000Z",
"expires_at": "2028-06-01T00:00:00.000Z"
}
}
StatusWhen it occurs
400 Bad RequestInvalid card data / tokenization failed
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing create-card permission

List all linked cards belonging to the user. Scope is determined by the service.

bearer read-card
{
"status": "success",
"statusCode": 200,
"message": "Linked cards retrieved successfully",
"data": {
"limit": 10,
"count": 3,
"currentPage": 1,
"totalPages": 1,
"cards": [
{
"id": "770e8400-e29b-41d4-a716-446655440222",
"card_holder_name": "John Doe",
"last_digits": "XXXX",
"card_type": "debit",
"status": "active",
"form": "physical",
"network": "mastercard",
"baas_provider": "berrypay",
"card_number": "****-****-****-XXXX",
"account_id": "660e8400-e29b-41d4-a716-446655440111",
"card_image": "https://cdn.moriafund.com/cards/visa-debit.png",
"issued_at": "2024-06-01T00:00:00.000Z",
"expires_at": "2028-06-01T00:00:00.000Z"
}
]
}
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing read-card permission

GET /v1/linked-cards/:id bearer

Section titled “GET /v1/linked-cards/:id ”

Detail of a single linked card.

bearer read-card
ParamTypeNotes
idstringLinked card ID
{
"status": "success",
"statusCode": 200,
"message": "Linked card retrieved successfully",
"data": {
"card": { "...": "same shape as CardDto · sensitive fields remain masked" }
}
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing read-card permission
404 Not FoundLinked card not found

PATCH /v1/linked-cards/:id bearer

Section titled “PATCH /v1/linked-cards/:id ”

Update a linked card (e.g. card_image or billing metadata). Raw PAN/CVV/expiry cannot be changed via this endpoint.

bearer update-card
ParamTypeNotes
idstringLinked card ID
FieldTypeRequiredNotes
card_imagestringoptionalNew image URL
{
"card_image": "https://cdn.moriafund.com/cards/mc-debit-v2.png"
}
{
"status": "success",
"statusCode": 200,
"message": "Card updated successfully",
"data": { "...": "same shape as CardDto" }
}
StatusWhen it occurs
400 Bad RequestInvalid update data
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing update-card permission
404 Not FoundLinked card not found

DELETE /v1/linked-cards/:id bearer

Section titled “DELETE /v1/linked-cards/:id ”

Unlink an external card from the user’s account. Soft-delete; FE must refresh the card list afterwards.

bearer delete-card
ParamTypeNotes
idstringLinked card ID
{
"status": "success",
"statusCode": 200,
"message": "Card deleted successfully"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing delete-card permission
404 Not FoundLinked card not found

  • debit · credit · dual
  • pending — awaiting BaaS activation
  • active — ready to use
  • suspended — frozen by admin/user
  • expired — past expires_at
  • lost · stolen — reported lost/stolen
  • virtual · physical
  • visa · mastercard
  • cimb · berrypay · amarbank
  • card_number (PAN) — stored via @EncryptedColumn; response sends masked form only
  • cvvnever sent to FE
  • card_holder_name, last_digits — encrypted in DB, decrypted server-side on response
  • expires_at — sent as ISO timestamp, not physical card format (MM/YY)
{
"message": "Card not found",
"statusCode": 404,
"error": "Not Found"
}
  • 400 invalid body/param validation
  • 401 token expired / missing
  • 403 permission mismatch
  • 404 card not found
  • 500 internal — show a generic toast