Skip to content

Commodity Financing

The Commodity Financing module enables commodity financing (cars, laptops, etc.) for employees of an organization under a Shariah-compliant murabahah scheme. Two controllers are involved: CommodityFinancingController at /commodity-financings (employee CRUD + sign agreement) and AdminCommodityFinancingController (summary, approve/reject, admin withdraw). The status lifecycle follows a state machine: pending → under_review → legal_agreement → signed → active → completed (with branches for rejected/cancelled). Architecture B: a per-financing holding account is created automatically by the service and owned by the organization — the borrower has no direct claim on the holding account.

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, users, organizations, payments (payback), withdrawal, document
Document versionv1 · 2026-05-20
AudienceInternal FE devs (mobile + web)

An employee (INDIVIDUAL) submits an application via POST /commodity-financings; organization/MORIA admins can also create one on behalf of the employee (the body uses AdminCreateCommodityFinancingDto). The admin then approves/rejects via PATCH /commodity-financings/:id/approve; after approval, the employee signs the legal agreement via POST /commodity-financings/:id/sign. Auto-deduction then runs from the employee’s source pocket. Admins can withdraw funds from the holding account to forward them to the destination org account.

MethodPathAuthSummary
POST/v1/commodity-financingsbearerCreate a commodity financing (employee or admin-for-employee)
POST/v1/commodity-financings/:financing_id/signbearerEmployee signs the legal agreement
GET/v1/commodity-financingsbearerPaginated list with status/user/account filters
GET/v1/commodity-financings/:financing_idbearerDetail of a single financing (response differs for mobile vs web)
PATCH/v1/commodity-financings/:financing_idbearerUpdate financing parameters (except those already completed)
DELETE/v1/commodity-financings/:financing_idbearerSoft-delete a financing
GET/v1/summary/commodity-financingsbearerOrganization statistics summary (admin only)
PATCH/v1/commodity-financings/:financing_id/approvebearerAdmin approves/rejects an application
POST/v1/commodity-financings/:financing_id/withdrawbearerAdmin withdraws from the holding account to the destination org account

POST /v1/commodity-financings bearer

Section titled “POST /v1/commodity-financings ”

Create a commodity financing application. If the caller is INDIVIDUAL, the body uses CreateCommodityFinancingDto. If MORIA/ORGANIZATION admin, the body uses AdminCreateCommodityFinancingDto (with a user_id field). The service decides the path based on user.user_type.

bearer create-commodity-financing RESOURCE_CREATED

Request body — CreateCommodityFinancingDto (employee)

Section titled “Request body — CreateCommodityFinancingDto (employee)”
FieldTypeRequiredNotes
product_namestringyesProduct name (e.g. Tesla Car)
product_pricestringyesProduct price as a string (rupiah)
product_linkstringyesProduct reference URL
requested_installment_amountstringoptionalRequested installment amount
requested_installment_periodnumberoptionalNumber of installment months (Min(1))
statusenum CommodityFinancingStatusoptionalDefault pending
legal_agreement_signedbooleanoptionalDefault false
deduction_datestring (ISO 8601)optionalAuto-deduction date

Request body — AdminCreateCommodityFinancingDto (admin-for-employee)

Section titled “Request body — AdminCreateCommodityFinancingDto (admin-for-employee)”
FieldTypeRequiredNotes
user_idUUIDyesBorrower the financing is created for
product_name, product_price, product_linkstringyesSame as the employee DTO
requested_installment_amountstringoptional
approved_installment_amountstringoptionalAdmin can set the approved amount directly
requested_installment_periodnumberoptionalMin(1)
statusenum CommodityFinancingStatusoptional
legal_agreement_signedbooleanoptional
completedbooleanoptional
deduction_datestring (ISO 8601)optional
{
"product_name": "Tesla Car",
"product_price": "240000000",
"product_link": "https://teslacar123.com",
"requested_installment_amount": "1000000",
"requested_installment_period": 24
}
{
"status": "success",
"statusCode": 201,
"message": "Commodity financing created successfully",
"data": {
"commodityFinance": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"account_id": "123e4567-e89b-12d3-a456-426614174001",
"organization_id": "123e4567-e89b-12d3-a456-426614174002",
"user_id": "123e4567-e89b-12d3-a456-426614174003",
"product_name": "Tesla Car",
"slug_product_name": "tesla_car",
"product_price": "240000000.0000",
"reselling_price": "0.0000",
"product_link": "https://teslacar123.com",
"approved_installment_amount": "0.00",
"requested_installment_amount": "1000000.00",
"paid_amount": "0.00",
"remaining_amount": "240000000.00",
"requested_installment_period": 24,
"approved_installment_period": 0,
"status": "pending",
"legal_agreement_signed": false,
"deduction_date": null,
"deduction_failed_attempts": 0,
"deduction_next_retry_date": null,
"created_at": "2026-05-20T08:30:00.000Z"
}
}
}
StatusWhen it occurs
400 Bad RequestValidation failed / account not linked to user
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing create-commodity-financing permission
404 Not FoundAccount or user not found
  • Creates a commodity_financings row with initial status pending.
  • Creates a per-financing holding accounts row (org-owned, account_type=COMMODITY_FINANCING) atomically.
  • Emits BusinessEvent RESOURCE_CREATED (impact MEDIUM).

POST /v1/commodity-financings/:financing_id/sign bearer

Section titled “POST /v1/commodity-financings/:financing_id/sign ”

Employee signs the legal agreement for a financing already in status legal_agreement. Moves the status to signed / active per the service logic.

bearer sign-commodity-financing RESOURCE_UPDATED
ParamTypeNotes
financing_idUUIDFinancing ID — validated by ParseUUIDPipe
FieldTypeRequiredNotes
legal_agreement_signedbooleanyesSet to true to sign
{ "legal_agreement_signed": true }
{
"status": "success",
"statusCode": 200,
"message": "Legal agreement signed successfully",
"data": { "commodityFinance": { "...": "CommodityFinancingDto shape, status=signed/active" } }
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot the owner / status invalid for signing
404 Not FoundFinancing not found

GET /v1/commodity-financings bearer

Section titled “GET /v1/commodity-financings ”

Paginated list of financings. INDIVIDUAL only sees their own; ORGANIZATION admin sees all within the org; MORIA is unrestricted. The user_id/account_id filters are validated against the caller’s scope — sending an ID outside the scope → 403.

Web / mobile — honors x-client-type (default mobile). See Client types (web vs mobile) for the full rules. Concretely: web nests audit fields as created/updated/deleted { at, by } objects and replaces relations with formatted account/user/organization objects; mobile returns the raw entity with flat created_at/updated_at + created_by/updated_by/deleted_by and foreign-key ids (account_id, organization_id, user_id). Both clients include product_image_document and legal_agreementweb as a formatted document object ({ id, document_type, document: <url>, … }), mobile as the raw Documents row (or null).

bearer read-commodity-financing
ParamTypeDefaultNotes
pagenumber1Page number
limitnumber10Records per page
statusenum CommodityFinancingStatusoptionalpending, under_review, rejected, legal_agreement, cancelled, signed, active, completed
order'asc' | 'desc'optionalSort by created_at
user_idstring (UUID)optionalFilter by user; INDIVIDUAL may only use their own user.id; ORGANIZATION admin only users from their org
account_idstring (UUID)optionalFilter by account; INDIVIDUAL only their own account; ORGANIZATION admin only accounts from their org

The list collection is keyed commodityFinances. Each item is the raw entity with flat audit columns and foreign-key ids.

{
"status": "success",
"statusCode": 200,
"message": "Organization commodity financings fetched successfully",
"data": {
"limit": 10,
"count": 69,
"currentPage": 1,
"totalPages": 7,
"commodityFinances": [
{
"id": "49018062-bdb2-417f-a9a5-46cbe6119fe8",
"created_at": "2026-05-26T06:31:13.815Z",
"updated_at": "2026-05-26T06:31:13.815Z",
"created_by": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"updated_by": "00000000-0000-0000-0000-000000000000",
"deleted_by": "00000000-0000-0000-0000-000000000000",
"account_id": "3d56d2f0-5842-4e1e-8f76-fa3578be063a",
"organization_id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"product_name": "Tesla x 465",
"slug_product_name": "tesla_x_465",
"user_id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"product_price": "3500000000.0000",
"reselling_price": "0.0000",
"product_link": "https://tesla.com/type/x/465/indonesia-45-pesanan-ongkir",
"approved_installment_amount": "0.00",
"requested_installment_amount": "583333333.00",
"paid_amount": "0.00",
"remaining_amount": "0.00",
"requested_installment_period": 6,
"approved_installment_period": 0,
"status": "pending",
"legal_agreement_signed": false,
"deduction_date": null,
"deduction_failed_attempts": 0,
"deduction_next_retry_date": null,
"product_image_document": [],
"legal_agreement": null
}
]
},
"lang": "en"
}

product_image_document is an array of raw Documents rows (product images; [] when none, currently capped at one): e.g. product_image_document: [{ "id": "…", "url": "https://s3-dev.moriafund.com/moria-bucket-dev/image/….jpg", "document_type": "image", "mime_type": "image/jpeg", "size_bytes": 12345, "filename": "…", "visibility": "private", "status": "uploaded", … }]. legal_agreement is a single raw Documents row (or null). In all cases the url is the absolute, ready-to-use public URL — no need to prepend a base. The web shape formats each (product_image_document stays an array of formatted docs).

Audit fields are nested as created/updated/deleted { at, by }; relations are formatted into account/user/organization objects, and product_image_document/legal_agreement are returned as formatted document objects.

{
"status": "success",
"statusCode": 200,
"message": "Organization commodity financings fetched successfully",
"data": {
"limit": 10,
"count": 69,
"currentPage": 1,
"totalPages": 7,
"commodityFinances": [
{
"id": "49018062-bdb2-417f-a9a5-46cbe6119fe8",
"created": {
"at": "2026-05-26T06:31:13.815Z",
"by": {}
},
"updated": {
"at": "2026-05-26T06:31:13.815Z",
"by": {}
},
"deleted": {
"at": null,
"by": {}
},
"product_name": "Tesla x 465",
"product_price": "3500000000.0000",
"reselling_price": "0.0000",
"product_link": "https://tesla.com/type/x/465/indonesia-45-pesanan-ongkir",
"product_image_document": [],
"approved_installment_amount": "0.00",
"requested_installment_amount": "583333333.00",
"requested_installment_period": 6,
"approved_installment_period": 0,
"paid_amount": "0.00",
"remaining_amount": "0.00",
"deduction_date": null,
"status": "pending",
"legal_agreement": null,
"account": {
"id": "3d56d2f0-5842-4e1e-8f76-fa3578be063a",
"account_number": "CF-FQDRNLG6B3",
"name": "Tesla x 465",
"account_type": "commodity_financing"
},
"user": {
"id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"email": "[email protected]",
"first_name": "Admin",
"last_name": "Primary",
"middle_name": null,
"phone_number": "+6288560649118",
"gender": "male",
"user_type": "individual",
"user_status": "active",
"profile_image": null,
"organization": {
"id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"name": "moria fund",
"email": "[email protected]",
"phone_number": "+1234567890",
"logo_id": null
}
},
"organization": {
"id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"name": "moria fund",
"email": "[email protected]",
"phone_number": "+1234567890",
"logo_id": null
}
}
]
},
"lang": "en"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenINDIVIDUAL sends another user’s user_id/account_id · ORGANIZATION admin sends a user/account from another org

GET /v1/commodity-financings/:financing_id bearer

Section titled “GET /v1/commodity-financings/:financing_id ”

Detail of a single financing. Response differs based on the client (header x-client-type): both variants attach a member_contributions summary; web additionally formats the audit fields and relations.

Web / mobile — honors x-client-type (default mobile). See Client types (web vs mobile) for the full rules. Concretely: web nests audit fields as created/updated/deleted { at, by } objects and replaces relations with formatted account/user/organization objects; mobile returns the raw entity with flat created_at/updated_at + created_by/updated_by/deleted_by and foreign-key ids (account_id, organization_id, user_id). Both clients include product_image_document and legal_agreementweb as a formatted document object ({ id, document_type, document: <url>, … }), mobile as the raw Documents row (or null). Both attach member_contributions.

bearer read-commodity-financing
ParamTypeNotes
financing_idUUIDFinancing ID

Raw entity with flat audit columns and foreign-key ids, plus the member_contributions summary.

{
"status": "success",
"statusCode": 200,
"message": "Commodity financing fetched successfully",
"data": {
"commodityFinance": {
"id": "49018062-bdb2-417f-a9a5-46cbe6119fe8",
"created_at": "2026-05-26T06:31:13.815Z",
"updated_at": "2026-05-26T06:31:13.815Z",
"created_by": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"updated_by": "00000000-0000-0000-0000-000000000000",
"deleted_by": "00000000-0000-0000-0000-000000000000",
"account_id": "3d56d2f0-5842-4e1e-8f76-fa3578be063a",
"organization_id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"product_name": "Tesla x 465",
"slug_product_name": "tesla_x_465",
"user_id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"product_price": "3500000000.0000",
"reselling_price": "0.0000",
"product_link": "https://tesla.com/type/x/465/indonesia-45-pesanan-ongkir",
"approved_installment_amount": "0.00",
"requested_installment_amount": "583333333.00",
"paid_amount": "0.00",
"remaining_amount": "0.00",
"requested_installment_period": 6,
"approved_installment_period": 0,
"status": "pending",
"legal_agreement_signed": false,
"deduction_date": null,
"deduction_failed_attempts": 0,
"deduction_next_retry_date": null,
"product_image_document": [],
"legal_agreement": null,
"member_contributions": [
{
"user_id": "",
"total_contribution": "0.00",
"contribution_count": 0
}
]
}
},
"lang": "en"
}

Audit fields nested as created/updated/deleted { at, by }; relations formatted into account/user/organization objects; product_image_document/legal_agreement surfaced; member_contributions still attached.

{
"status": "success",
"statusCode": 200,
"message": "Commodity financing fetched successfully",
"data": {
"commodityFinance": {
"id": "49018062-bdb2-417f-a9a5-46cbe6119fe8",
"created": {
"at": "2026-05-26T06:31:13.815Z",
"by": {}
},
"updated": {
"at": "2026-05-26T06:31:13.815Z",
"by": {}
},
"deleted": {
"at": null,
"by": {}
},
"product_name": "Tesla x 465",
"product_price": "3500000000.0000",
"reselling_price": "0.0000",
"product_link": "https://tesla.com/type/x/465/indonesia-45-pesanan-ongkir",
"product_image_document": [],
"approved_installment_amount": "0.00",
"requested_installment_amount": "583333333.00",
"requested_installment_period": 6,
"approved_installment_period": 0,
"paid_amount": "0.00",
"remaining_amount": "0.00",
"deduction_date": null,
"status": "pending",
"legal_agreement": null,
"account": {
"id": "3d56d2f0-5842-4e1e-8f76-fa3578be063a",
"account_number": "CF-FQDRNLG6B3",
"name": "Tesla x 465",
"account_type": "commodity_financing"
},
"user": {
"id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"email": "[email protected]",
"first_name": "Admin",
"last_name": "Primary",
"middle_name": null,
"phone_number": "+6288560649118",
"gender": "male",
"user_type": "individual",
"user_status": "active",
"profile_image": null,
"organization": {
"id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"name": "moria fund",
"email": "[email protected]",
"phone_number": "+1234567890",
"logo_id": null
}
},
"organization": {
"id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"name": "moria fund",
"email": "[email protected]",
"phone_number": "+1234567890",
"logo_id": null
},
"member_contributions": [
{
"user_id": "",
"total_contribution": "0.00",
"contribution_count": 0
}
]
}
},
"lang": "en"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenINDIVIDUAL viewing another user’s financing
404 Not FoundFinancing not found

PATCH /v1/commodity-financings/:financing_id bearer

Section titled “PATCH /v1/commodity-financings/:financing_id ”

Update financing parameters. Cannot update a financing already completed. All fields are optional — send only what changed.

bearer update-commodity-financing RESOURCE_UPDATED
ParamTypeNotes
financing_idUUIDFinancing ID

Request body — UpdateCommodityFinancingDto

Section titled “Request body — UpdateCommodityFinancingDto”
FieldTypeNotes
product_namestringNew product name
product_pricestringNew product price
reselling_pricestringReselling price (murabahah markup)
product_linkstringNew product URL
approved_installment_amountstringApproved installment amount
approved_installment_periodnumberApproved installment period (Min(1))
deduction_datestring (ISO 8601)New auto-deduction date
statusenum CommodityFinancingStatusNew status
{
"approved_installment_amount": "1200000",
"approved_installment_period": 18
}
{
"status": "success",
"statusCode": 200,
"message": "Commodity financing updated successfully",
"data": { "commodityFinance": { "...": "CommodityFinancingDto shape" } }
}
StatusWhen it occurs
400 Bad RequestValidation failed
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenFinancing already completed
404 Not FoundFinancing not found

DELETE /v1/commodity-financings/:financing_id bearer

Section titled “DELETE /v1/commodity-financings/:financing_id ”

Soft-delete a financing (set deleted_at). Cannot delete a financing already completed.

bearer delete-commodity-financing RESOURCE_DELETED
ParamTypeNotes
financing_idUUIDFinancing ID
{
"status": "success",
"statusCode": 200,
"message": "Commodity financing deleted successfully"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenFinancing already completed
404 Not FoundFinancing not found
  • Emits BusinessEvent RESOURCE_DELETED (impact HIGH).
  • Holding account balance is handled per service logic (not permanently deleted).

GET /v1/summary/commodity-financings bearer

Section titled “GET /v1/summary/commodity-financings ”

Aggregate commodity financing statistics for the caller’s organization. Only admins (MORIA/ORGANIZATION) are allowed. Useful for analytics dashboards (totals, status breakdowns, averages).

bearer MORIA, ORGANIZATION read-commodity-financing
{
"status": "success",
"statusCode": 200,
"message": "commodity financing summary fetched successfully",
"data": {
"organization_id": "123e4567-e89b-12d3-a456-426614174000",
"organization_name": "Acme Corporation",
"total_financings": "50",
"total_product_value": "750000000.00",
"total_approved_installment_amount": "75000000.00",
"pending_financings": "5",
"under_review_financings": "8",
"rejected_financings": "3",
"legal_agreement_financings": "6",
"cancelled_financings": "2",
"signed_financings": "10",
"active_financings": "15",
"completed_financings": "20",
"signed_agreements": "30",
"agreements_with_id": "28",
"total_installment_periods": "600",
"avg_installment_period": "12",
"avg_product_price": "15000000.00",
"avg_approved_installment_amount": "1500000.00",
"avg_installment_percentage": "10.00"
}
}
StatusWhen it occurs
401 UnauthorizedMORIA attempts to view an organization other than their own
403 ForbiddenRole is not MORIA/ORGANIZATION
404 Not FoundOrganization not found

PATCH /v1/commodity-financings/:financing_id/approve bearer

Section titled “PATCH /v1/commodity-financings/:financing_id/approve ”

Admin approves or rejects an application. Sets a new status + (optional) approved_installment_amount + requested_installment_period. Valid statuses: legal_agreement for approve, rejected/cancelled for reject.

bearer MORIA, ORGANIZATION update-commodity-financing RESOURCE_UPDATED
ParamTypeNotes
financing_idUUIDFinancing ID

Request body — ApproveCommodityFinancingDto

Section titled “Request body — ApproveCommodityFinancingDto”
FieldTypeRequiredNotes
statusenum CommodityFinancingStatusyesNew status
approved_installment_amountstringoptionalApproved installment amount
requested_installment_periodnumberoptionalPeriod (IsInt)
{
"status": "legal_agreement",
"approved_installment_amount": "1500000",
"requested_installment_period": 12
}
{
"status": "success",
"statusCode": 200,
"message": "commodity finance updated successfully"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenRole is not MORIA/ORGANIZATION
404 Not FoundFinancing not found

POST /v1/commodity-financings/:financing_id/withdraw bearer

Section titled “POST /v1/commodity-financings/:financing_id/withdraw ”

Admin withdraws funds from the per-financing holding account to the destination org account. Architecture B: only org admins have access to the holding account; the borrower has no claim. Used to forward funds to a vendor / supplier.

bearer MORIA, ORGANIZATION update-commodity-financing RESOURCE_UPDATED
ParamTypeNotes
financing_idUUIDFinancing ID, source of the holding account

Request body — AdminWithdrawCommodityFinancingDto

Section titled “Request body — AdminWithdrawCommodityFinancingDto”
FieldTypeRequiredNotes
amountstringyesWithdrawal nominal (rupiah), must be > 0 and <= holding balance
destination_account_idUUIDyesDestination account, must be owned by the same organization
{
"amount": "500000",
"destination_account_id": "be2cb7da-e217-401c-8d07-b01e64adfb34"
}
{
"status": "success",
"statusCode": 200,
"message": "commodity financing withdrawal processed successfully"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenRole is not MORIA/ORGANIZATION · destination account belongs to a different organization
404 Not FoundFinancing / destination account not found
  • Moves the balance from the holding account to destination_account_id via the transactions service.
  • Emits BusinessEvent RESOURCE_UPDATED (impact HIGH).

  • pending — new application, not yet reviewed
  • under_review — admin currently reviewing
  • rejected — admin rejected the application
  • legal_agreement — approved, awaiting agreement signing
  • cancelled — cancelled
  • signed — agreement signed
  • active — auto-deduction running
  • completed — financing paid off

pending → under_review → legal_agreement → signed → active → completed

Rejection branch: from pending/under_reviewrejected or cancelled.

{
"message": "you can't view another user commodity finance",
"statusCode": 403,
"error": "Forbidden"
}

message can be a string or an array of strings (multi-field validation errors).

  • 400 body/query/param validation or account not linked to user
  • 401 token expired / missing
  • 403 not the owner / wrong scope / status already completed
  • 404 financing/account/user not found
  • 500 internal — show a generic toast