Skip to main content

Gig::Payment

Purpose

A Payment tracks the wage owed to a talent for a completed assignment and its disbursement status. It is created when a Gig::Assignment reaches verified status (billable times locked), and it tracks the money flow from Jod to the talent.

Payment represents the Jod → Talent money flow. The Employer → Jod money flow (gig credit consumption) is handled by the Billing domain and triggered at the same time as Payment creation.

Currently, wage disbursement happens via a bank instruction file (GIRO in Singapore) that finance uploads manually. Payment tracks whether this has happened. In the future, Payment serves as the extension point for third-party disbursement integrations.

Model Context

ContextDetails
EntityGig::Payment (child of Assignment, one-to-one)
LayerOperations — Settlement
Upstream dependenciesGig::Assignment (provides billable hours), Gig::Shift (provides hourly rate), Talent::Profile (who to pay)
Downstream dependentsBank instruction file generation (future), third-party disbursement integration (future)

State Machine

FromToTriggerNotes
(new)pendingUC-1: Assignment verifiedPayment created with calculated wage.
pendingprocessingUC-3: Included in bank instruction fileFinance has submitted the payment for disbursement.
processingpaidUC-4: Bank confirms disbursementTalent has received the money. Terminal state.

Use Cases

IDUse CaseTriggerActor
UC-1Payment created from verified AssignmentAssignment billable times lockedSystem
UC-2View payment detailEmployer or talent reviewing paymentEmployer / Talent
UC-3Payment included in bank instruction fileFinance generates batch disbursement fileSystem / Finance
UC-4Payment marked as paidBank confirms successful disbursementFinance
UC-5View pending payments for batch processingFinance preparing disbursement runFinance / Admin
UC-6Talent views payment historyTalent checking their earningsTalent

UC-1: Payment created from verified Assignment

FieldDetails
ActorSystem
TriggerGig::Assignment reaches verified status

Preconditions:

  • Assignment status: verified
  • billable_locked_at is set
  • billable_clock_in, billable_clock_out, and billable_break_minutes are all set

System Behavior:

  1. System calculates the wage:
    • total_hours = (billable_clock_out - billable_clock_in) - billable_break_minutes (converted to decimal hours)
    • hourly_rate copied from Gig::Shift.hourly_rate
    • gross_wage_cents = total_hours × hourly_rate (converted to cents)
    • deductions_cents = applicable deductions (e.g., insurance — see open questions)
    • net_wage_cents = gross_wage_cents - deductions_cents
  2. System creates Gig::Payment with status: pending
  3. System triggers Billing domain to consume gig credits from the employer's entitlement lots (consumed amount = gross wage)

Business Rules:

  • One Payment per Assignment — if a Payment already exists for this Assignment, creation is blocked (idempotency)
  • total_hours must be positive — if billable times result in zero or negative hours, the system flags an error for admin review
  • hourly_rate is copied from the Shift at verification time and stored on the Payment for audit (the Shift rate is the fact)
  • All monetary values are stored in cents to avoid floating-point precision issues
  • Payment creation and Billing credit consumption happen in the same database transaction

Postconditions:

  • Gig::Payment exists with status: pending
  • Billing credits consumed from employer's entitlement lots
  • Talent can see the pending payment in their earnings

UC-2: View payment detail

FieldDetails
ActorOrg::UserProfile (employer) or Talent::Profile
TriggerReviewing payment for a specific assignment

Preconditions:

  • Payment exists

System Behavior:

  1. System displays:
    • Talent name
    • Shift date and time
    • Billable hours breakdown: clock-in, clock-out, break, total hours
    • Rate: hourly rate
    • Wage: gross, deductions, net
    • Status: pending / processing / paid
    • Payment timestamps: created_at, processed_at, paid_at

Business Rules:

  • Employers see payments for their company's shifts
  • Talent see payments for their own assignments
  • The wage calculation is transparent — the talent can see exactly how their pay was computed

Postconditions:

  • Read-only operation

UC-3: Payment included in bank instruction file

FieldDetails
ActorSystem / Finance team
TriggerFinance generates a batch disbursement file

Preconditions:

  • One or more Payments with status: pending

System Behavior:

  1. System queries all pending Payments ready for disbursement
  2. System generates a bank instruction file (format: GIRO for Singapore) containing:
    • Talent bank account details (from Talent::Profile or related banking info)
    • Net wage amount per payment
    • Reference identifiers
  3. Each included Payment: statusprocessing, processed_at set
  4. Finance downloads and uploads the file to the bank

Business Rules:

  • Payments are batched — all pending payments are included in a single file (or grouped by criteria like company, date)
  • A Payment can only be included in one batch — once processing, it won't appear in subsequent batch runs
  • The bank instruction file format is country-specific (GIRO for Singapore, may differ for other markets)

Postconditions:

  • Included Payments are processing
  • Bank instruction file generated for finance to upload

UC-4: Payment marked as paid

FieldDetails
ActorFinance team / Admin
TriggerBank confirms successful disbursement

Preconditions:

  • Payment status: processing

System Behavior:

  1. Finance confirms the bank has disbursed the payment
  2. statuspaid, paid_at set
  3. Talent may be notified: "Your payment of $X.XX for [shift] has been processed"

Business Rules:

  • paid is a terminal state
  • Since we cannot connect to the bank, this is currently a manual confirmation by finance
  • Future: third-party integration could automate this step via webhook or API callback

Postconditions:

  • Payment status: paid
  • paid_at is set

UC-5: View pending payments for batch processing

FieldDetails
ActorFinance team / Identities::Admin
TriggerFinance preparing a disbursement run

Preconditions:

  • None

System Behavior:

  1. Admin navigates to the payments admin page
  2. System displays all pending Payments, grouped by:
    • Company (for reconciliation)
    • Date (for batch scheduling)
  3. Each Payment shows: talent name, shift date, net wage, assignment reference
  4. Admin can trigger batch file generation (UC-3)

Business Rules:

  • Filterable by company, date range, status
  • Shows aggregate totals for the batch (total payments, total amount)

Postconditions:

  • Read-only operation (until batch generation is triggered)

UC-6: Talent views payment history

FieldDetails
ActorTalent::Profile (talent)
TriggerTalent checking their earnings

Preconditions:

  • Talent has completed Assignments with Payments

System Behavior:

  1. Talent navigates to their earnings/payment history
  2. System displays all Payments for the talent, ordered by date descending:
    • Shift date and company name
    • Hours worked
    • Gross wage, deductions, net wage
    • Status (pending / processing / paid)
  3. Talent can tap a payment to see the full detail (UC-2)

Business Rules:

  • Talent can only see their own payments
  • Pending and processing payments are shown so the talent knows what to expect
  • Filterable by date range, status, company

Postconditions:

  • Read-only operation

Invariants

  1. One Payment per Assignment — enforced by unique index on gig_assignment_id
  2. Payment is only created when Assignment reaches verified status — no payment for cancelled or no-show Assignments
  3. All monetary values are stored in cents (integer) to avoid floating-point precision issues
  4. hourly_rate is copied from the Shift and stored on Payment — changes to the Shift rate after verification do not affect existing Payments
  5. total_hours must be positive — zero or negative hours are flagged for admin review
  6. Payment creation and Billing credit consumption are atomic (same database transaction)
  7. pendingprocessingpaid is the only valid transition path — no skipping states
  8. paid is a terminal state

Model Interactions

Related ModelRelationshipInteraction
Gig::AssignmentPayment belongs_to AssignmentOne-to-one. Payment is created when Assignment is verified. Billable hours come from the Assignment.
Gig::ShiftShift provides hourly ratehourly_rate is copied from the Shift at Payment creation time.
Talent::ProfilePayment belongs_to TalentProfileIdentifies who is owed the wage. Banking details come from the talent's profile.
BillingTriggers credit consumptionWhen Payment is created, the Billing domain consumes gig credits equal to the gross wage.

Schema Gaps

The gig_payments table is new — it does not exist in the current DBML for jodapp. The schema is defined in jodapp-api/docs/db/gig.dbml.

Open Questions:

  1. Deductions: What deductions apply? The legacy system has insurance deductions and "sponsored insurance." How should deductions be modelled — as a single deductions_cents column, or as a breakdown (e.g., insurance_cents, other_deductions_cents)?
  2. Partial payment for mid-shift cancellation: When an employer cancels a clocked_in Assignment, should a partial Payment be created for the hours already worked? If yes, the Payment would be created at cancellation time (not at verification time), which is a different trigger.
  3. Talent banking details: Where do bank account details live? On Talent::Profile directly, or in a separate Talent::BankAccount model? This is needed for bank instruction file generation.
  4. Multi-currency: The current system is Singapore only (SGD). When expanding to other markets, Payment will need a currency field. For now, SGD is assumed.
  5. Loyalty program integration: A future loyalty program (points per hour worked) would read from Payment to calculate points. Payment should be the source of truth for "hours worked and paid" — the loyalty system reads, not writes.