*/ public const VALID_METHODS = [ self::METHOD_CARD, self::METHOD_ETRANSFER, self::METHOD_COMP ]; public const STATUS_PENDING = 'pending'; public const STATUS_PAID = 'paid'; public const STATUS_FAILED = 'failed'; public const STATUS_REFUNDED = 'refunded'; /** * All valid payment statuses. * * @var list */ public const VALID_STATUSES = [ self::STATUS_PENDING, self::STATUS_PAID, self::STATUS_FAILED, self::STATUS_REFUNDED ]; public const REG_LESSON = 'lesson'; public const REG_ENROLLMENT = 'enrollment'; public function __construct( public readonly int $studentId, public readonly int $instructorId, public readonly string $registrationType, public readonly int $registrationId, public readonly float $amount, public readonly string $currency = 'CAD', public readonly string $method = self::METHOD_ETRANSFER, public readonly string $status = self::STATUS_PENDING, public readonly float $taxRate = 0.0, public readonly float $taxAmount = 0.0, public readonly float $creditApplied = 0.0, public readonly ?string $dueDate = null, public readonly ?string $periodKey = null, public readonly ?string $noticeBatch = null, public readonly ?string $etransferEmail = null, public readonly ?string $stripePaymentIntentId = null, public readonly ?string $receiptNumber = null, public readonly ?string $receiptSentAt = null, public readonly ?string $paidAt = null, public readonly ?string $createdAt = null, public readonly ?int $id = null, ) {} public static function fromRow( \stdClass $row ): self { return new self( studentId: Val::int( $row->student_id ), instructorId: Val::int( $row->instructor_id ), registrationType: Val::string( $row->registration_type ), registrationId: Val::int( $row->registration_id ), amount: Val::float( $row->amount ), currency: Val::string( $row->currency ), method: Val::string( $row->method ), status: Val::string( $row->status ), taxRate: Val::float( $row->tax_rate ), taxAmount: Val::float( $row->tax_amount ), creditApplied: Val::float( $row->credit_applied ?? 0 ), dueDate: Val::stringOrNull( $row->due_date ?? null ), periodKey: Val::stringOrNull( $row->period_key ?? null ), noticeBatch: Val::stringOrNull( $row->notice_batch ?? null ), etransferEmail: Val::stringOrNull( $row->etransfer_email ), stripePaymentIntentId: Val::stringOrNull( $row->stripe_payment_intent_id ), receiptNumber: Val::stringOrNull( $row->receipt_number ), receiptSentAt: Val::stringOrNull( $row->receipt_sent_at ), paidAt: Val::stringOrNull( $row->paid_at ), createdAt: Val::stringOrNull( $row->created_at ), id: Val::int( $row->id ), ); } public function isPaid(): bool { return self::STATUS_PAID === $this->status; } /** * Whether this payment was generated by the daily billing scan (weekly / * monthly) rather than taken at registration. Scheduled payments carry a due * date, can cover several lessons, and are never auto-voided on cancellation. */ public function isScheduled(): bool { return null !== $this->dueDate; } /** * Amount billed including tax. */ public function total(): float { return round( $this->amount + $this->taxAmount, 2 ); } /** * What the student still owes after any account credit applied to this payment. * The full `total()` less `creditApplied`, floored at zero. */ public function netDue(): float { return round( max( 0.0, $this->total() - $this->creditApplied ), 2 ); } /** * Minimal payment info embedded in registration-creation responses: enough * for the front end to decide whether (and how) to run the payment step. * * @return array */ public function toSummaryArray(): array { return [ 'id' => $this->id, 'method' => $this->method, 'status' => $this->status, ]; } /** * Returns a plain array representation of the payment. * * @return array */ public function toArray(): array { return [ 'id' => $this->id, 'student_id' => $this->studentId, 'instructor_id' => $this->instructorId, 'registration_type' => $this->registrationType, 'etransfer_email' => $this->etransferEmail, 'registration_id' => $this->registrationId, 'amount' => $this->amount, 'tax_rate' => $this->taxRate, 'tax_amount' => $this->taxAmount, 'total' => $this->total(), 'credit_applied' => $this->creditApplied, 'net_due' => $this->netDue(), 'currency' => $this->currency, 'method' => $this->method, 'status' => $this->status, 'due_date' => $this->dueDate, 'period_key' => $this->periodKey, 'notice_batch' => $this->noticeBatch, 'receipt_number' => $this->receiptNumber, 'paid_at' => $this->paidAt, 'created_at' => $this->createdAt, ]; } }