GuestDeclarationDraftStatus::all()], [['ORDER_SN'], 'string', 'max' => 32], [['ACCESS_TOKEN_HASH'], 'string', 'max' => 64], [['CURRENCY'], 'string', 'max' => 8], [['PAYMENT_REFERENCE'], 'string', 'max' => 128], [['USER_ID'], 'string', 'max' => 32], [['ORDER_SN'], 'unique'], [['ACCESS_TOKEN_HASH'], 'unique'], [['PAYMENT_REFERENCE'], 'unique'], ]; } public function transitionTo(string $newStatus): bool { if (!GuestDeclarationDraftStatus::canTransition($this->STATUS, $newStatus)) { $this->addError('STATUS', 'Invalid guest declaration draft status transition.'); return false; } $this->STATUS = $newStatus; return $this->save(false, ['STATUS', 'UPDATED_AT']); } public static function claimForProcessing(string $orderSn): bool { return self::updateAll( ['STATUS' => GuestDeclarationDraftStatus::PROCESSING, 'UPDATED_AT' => time()], [ 'AND', ['ORDER_SN' => $orderSn], ['STATUS' => GuestDeclarationDraftStatus::PENDING_PAYMENT], ['>', 'EXPIRES_AT', time()], ] ) === 1; } public function accessTokenMatches(string $accessToken): bool { return $accessToken !== '' && hash_equals($this->ACCESS_TOKEN_HASH, hash('sha256', $accessToken)); } public function isExpired(?int $now = null): bool { return GuestDeclarationDraftStatus::isExpired((int) $this->EXPIRES_AT, $now); } public function getPayload(): array { $payload = json_decode($this->PAYLOAD, true); return is_array($payload) ? $payload : []; } public function getOrderSnapshot(): array { $snapshot = json_decode($this->ORDER_SNAPSHOT, true); return is_array($snapshot) ? $snapshot : []; } }