41 lines
4.3 KiB
PHP
41 lines
4.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace App\Services;
|
|
use App\Core\Database;
|
|
|
|
/** Integer-fils ledger. Credits are never inferred from UI, courses, or watch time. */
|
|
final class TeacherLedgerService {
|
|
public static function balance(int $teacherId): array {
|
|
$account=self::account($teacherId);
|
|
$row=Database::selectOne('SELECT COALESCE(SUM(CASE WHEN credit_account_id=? THEN amount_fils WHEN debit_account_id=? THEN -amount_fils ELSE 0 END),0) AS balance FROM ledger_entries WHERE credit_account_id=? OR debit_account_id=?',[$account,$account,$account,$account]);
|
|
$holds=Database::selectOne("SELECT COALESCE(SUM(amount_fils),0) AS held FROM teacher_withdrawal_holds WHERE teacher_id=? AND status='held'",[$teacherId]);
|
|
$gross=(int)($row['balance']??0);$held=(int)($holds['held']??0);
|
|
return ['account_id'=>$account,'gross_fils'=>$gross,'held_fils'=>$held,'available_fils'=>max(0,$gross-$held)];
|
|
}
|
|
public static function placeWithdrawalHold(int $teacherId,int $amountFils,string $key):array {
|
|
if($amountFils<=0||$amountFils>100000000||!preg_match('/^[A-Za-z0-9._:-]{16,128}$/',$key))return ['http_status'=>400,'status'=>'error','message'=>'قيمة الحجز أو مفتاح الإعادة غير صالح.'];
|
|
$pdo=Database::getConnection();$pdo->beginTransaction();try {
|
|
$existing=Database::selectOne('SELECT uuid,amount_fils,status FROM teacher_withdrawal_holds WHERE teacher_id=? AND idempotency_key=? FOR UPDATE',[$teacherId,$key]);
|
|
if($existing){if((int)$existing['amount_fils']!==$amountFils)return self::finish($pdo,['http_status'=>409,'status'=>'idempotency_conflict','message'=>'استعمل المفتاح ذاته بمبلغ مختلف.']);return self::finish($pdo,['http_status'=>200,'status'=>$existing['status'],'withdrawal_hold_id'=>$existing['uuid'],'replayed'=>true]);}
|
|
$balance=self::balance($teacherId);
|
|
if($amountFils>$balance['available_fils'])return self::finish($pdo,['http_status'=>409,'status'=>'insufficient_available_balance','message'=>'الرصيد المتاح لا يغطي الحجز.','available_fils'=>$balance['available_fils']]);
|
|
$uuid=self::uuid();Database::insert("INSERT INTO teacher_withdrawal_holds (uuid,teacher_id,amount_fils,status,idempotency_key) VALUES (?,?,?,'held',?)",[$uuid,$teacherId,$amountFils,$key]);
|
|
return self::finish($pdo,['http_status'=>201,'status'=>'held','withdrawal_hold_id'=>$uuid,'amount_fils'=>$amountFils]);
|
|
}catch(\Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
|
|
}
|
|
public static function releaseHold(int $teacherId,string $holdUuid,string $reason):array {
|
|
if(!self::isUuid($holdUuid)||trim($reason)==='')return ['http_status'=>400,'status'=>'error','message'=>'بيانات إلغاء الحجز غير صالحة.'];
|
|
$changed=Database::execute("UPDATE teacher_withdrawal_holds SET status='released',released_at=NOW() WHERE uuid=? AND teacher_id=? AND status='held'",[$holdUuid,$teacherId]);
|
|
return $changed===1?['http_status'=>200,'status'=>'released','reason'=>$reason]:['http_status'=>409,'status'=>'hold_not_releasable','message'=>'الحجز غير موجود أو تمت تسويته.'];
|
|
}
|
|
private static function account(int $teacherId):int {
|
|
$existing=Database::selectOne("SELECT id FROM ledger_accounts WHERE code='teacher_available' AND owner_type='teacher' AND owner_id=? AND currency='JOD' LIMIT 1",[$teacherId]);
|
|
if($existing)return(int)$existing['id'];
|
|
Database::insert("INSERT INTO ledger_accounts (code,owner_type,owner_id,currency) VALUES ('teacher_available','teacher',?,'JOD')",[$teacherId]);
|
|
return(int)(Database::selectOne("SELECT id FROM ledger_accounts WHERE code='teacher_available' AND owner_type='teacher' AND owner_id=? AND currency='JOD' LIMIT 1",[$teacherId])['id']??0);
|
|
}
|
|
private static function isUuid(string $v):bool{return(bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',$v);}
|
|
private static function uuid():string{$b=random_bytes(16);$b[6]=chr((ord($b[6])&15)|64);$b[8]=chr((ord($b[8])&63)|128);return vsprintf('%s%s-%s-%s-%s-%s%s%s',str_split(bin2hex($b),4));}
|
|
private static function finish(\PDO $pdo,array $result):array{$pdo->commit();return $result;}
|
|
}
|