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.

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
{
"status": "success",
"statusCode": 200,
"message": "Commodity financings fetched successfully",
"data": {
"limit": 10,
"count": 100,
"currentPage": 1,
"totalPages": 10,
"commodityFinancings": [
{ "id": "123e4567-e89b-12d3-a456-426614174000", "product_name": "Laptop Dell Inspiron", "product_price": "15000000.0000", "status": "active", "...": "CommodityFinancingDto shape" }
]
}
}
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): MOBILE gets member_contributions via the with-summary variant; WEB gets the formatted variant.

bearer read-commodity-financing
ParamTypeNotes
financing_idUUIDFinancing ID
{
"status": "success",
"statusCode": 200,
"message": "commodity finance fetched successfully",
"data": {
"commodityFinance": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"product_name": "Laptop Dell Inspiron",
"product_price": "15000000.0000",
"reselling_price": "16500000.0000",
"approved_installment_amount": "1500000.00",
"paid_amount": "4500000.00",
"remaining_amount": "12000000.00",
"approved_installment_period": 12,
"status": "active",
"legal_agreement_signed": true,
"deduction_date": "2026-01-15",
"deduction_failed_attempts": 0,
"deduction_next_retry_date": null
}
}
}
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