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
| Context | Details |
|---|---|
| Entity | Gig::Payment (child of Assignment, one-to-one) |
| Layer | Operations — Settlement |
| Upstream dependencies | Gig::Assignment (provides billable hours), Gig::Shift (provides hourly rate), Talent::Profile (who to pay) |
| Downstream dependents | Bank instruction file generation (future), third-party disbursement integration (future) |
State Machine
| From | To | Trigger | Notes |
|---|---|---|---|
| (new) | pending | UC-1: Assignment verified | Payment created with calculated wage. |
pending | processing | UC-3: Included in bank instruction file | Finance has submitted the payment for disbursement. |
processing | paid | UC-4: Bank confirms disbursement | Talent has received the money. Terminal state. |
Use Cases
| ID | Use Case | Trigger | Actor |
|---|---|---|---|
| UC-1 | Payment created from verified Assignment | Assignment billable times locked | System |
| UC-2 | View payment detail | Employer or talent reviewing payment | Employer / Talent |
| UC-3 | Payment included in bank instruction file | Finance generates batch disbursement file | System / Finance |
| UC-4 | Payment marked as paid | Bank confirms successful disbursement | Finance |
| UC-5 | View pending payments for batch processing | Finance preparing disbursement run | Finance / Admin |
| UC-6 | Talent views payment history | Talent checking their earnings | Talent |
UC-1: Payment created from verified Assignment
| Field | Details |
|---|---|
| Actor | System |
| Trigger | Gig::Assignment reaches verified status |
Preconditions:
- Assignment
status: verified billable_locked_atis setbillable_clock_in,billable_clock_out, andbillable_break_minutesare all set
System Behavior:
- System calculates the wage:
total_hours = (billable_clock_out - billable_clock_in) - billable_break_minutes(converted to decimal hours)hourly_ratecopied fromGig::Shift.hourly_rategross_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
- System creates
Gig::Paymentwithstatus: pending - 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_hoursmust be positive — if billable times result in zero or negative hours, the system flags an error for admin reviewhourly_rateis 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::Paymentexists withstatus: pending- Billing credits consumed from employer's entitlement lots
- Talent can see the pending payment in their earnings
UC-2: View payment detail
| Field | Details |
|---|---|
| Actor | Org::UserProfile (employer) or Talent::Profile |
| Trigger | Reviewing payment for a specific assignment |
Preconditions:
- Payment exists
System Behavior:
- 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
| Field | Details |
|---|---|
| Actor | System / Finance team |
| Trigger | Finance generates a batch disbursement file |
Preconditions:
- One or more Payments with
status: pending
System Behavior:
- System queries all
pendingPayments ready for disbursement - System generates a bank instruction file (format: GIRO for Singapore) containing:
- Talent bank account details (from
Talent::Profileor related banking info) - Net wage amount per payment
- Reference identifiers
- Talent bank account details (from
- Each included Payment:
status→processing,processed_atset - 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
| Field | Details |
|---|---|
| Actor | Finance team / Admin |
| Trigger | Bank confirms successful disbursement |
Preconditions:
- Payment
status: processing
System Behavior:
- Finance confirms the bank has disbursed the payment
status→paid,paid_atset- Talent may be notified: "Your payment of $X.XX for [shift] has been processed"
Business Rules:
paidis 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_atis set
UC-5: View pending payments for batch processing
| Field | Details |
|---|---|
| Actor | Finance team / Identities::Admin |
| Trigger | Finance preparing a disbursement run |
Preconditions:
- None
System Behavior:
- Admin navigates to the payments admin page
- System displays all
pendingPayments, grouped by:- Company (for reconciliation)
- Date (for batch scheduling)
- Each Payment shows: talent name, shift date, net wage, assignment reference
- 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
| Field | Details |
|---|---|
| Actor | Talent::Profile (talent) |
| Trigger | Talent checking their earnings |
Preconditions:
- Talent has completed Assignments with Payments
System Behavior:
- Talent navigates to their earnings/payment history
- 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)
- 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
- One Payment per Assignment — enforced by unique index on
gig_assignment_id - Payment is only created when Assignment reaches
verifiedstatus — no payment for cancelled or no-show Assignments - All monetary values are stored in cents (integer) to avoid floating-point precision issues
hourly_rateis copied from the Shift and stored on Payment — changes to the Shift rate after verification do not affect existing Paymentstotal_hoursmust be positive — zero or negative hours are flagged for admin review- Payment creation and Billing credit consumption are atomic (same database transaction)
pending→processing→paidis the only valid transition path — no skipping statespaidis a terminal state
Model Interactions
| Related Model | Relationship | Interaction |
|---|---|---|
Gig::Assignment | Payment belongs_to Assignment | One-to-one. Payment is created when Assignment is verified. Billable hours come from the Assignment. |
Gig::Shift | Shift provides hourly rate | hourly_rate is copied from the Shift at Payment creation time. |
Talent::Profile | Payment belongs_to TalentProfile | Identifies who is owed the wage. Banking details come from the talent's profile. |
Billing | Triggers credit consumption | When 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:
- Deductions: What deductions apply? The legacy system has insurance deductions and "sponsored insurance." How should deductions be modelled — as a single
deductions_centscolumn, or as a breakdown (e.g.,insurance_cents,other_deductions_cents)? - Partial payment for mid-shift cancellation: When an employer cancels a
clocked_inAssignment, 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. - Talent banking details: Where do bank account details live? On
Talent::Profiledirectly, or in a separateTalent::BankAccountmodel? This is needed for bank instruction file generation. - Multi-currency: The current system is Singapore only (SGD). When expanding to other markets, Payment will need a
currencyfield. For now, SGD is assumed. - 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.