get( $target_id ) ) { DomainError::raise( ErrorCode::INVALID, array( 'reason' => 'target' ) ); } if ( array() !== $scope_ids ) { DomainError::raise( ErrorCode::INVALID, array( 'reason' => 'scope' ) ); } } public function authorize( int $target_id, Actor $actor ): void { $course = Services::course_repo()->get( $target_id ); if ( null === $course ) { DomainError::raise( ErrorCode::NOT_FOUND ); } Authorize::edit_post( $actor, $course->home_post_id ); } public function on_sale( Offer $offer, \DateTimeImmutable $now ): bool { $course = Services::course_repo()->get( $offer->target_id ); return null !== $course && $course->is_active() && \RoosterLMS\CourseInfo\ComingSoon::enrolment_open( $course, $now->setTimezone( new \DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' ) ); } public function grant( Offer $offer, SaleLine $line, Actor $actor ): SaleGrant { $flags = Services::capacity()->has_place_locked( $offer->target_id, $line->user_id ) ? 0 : Sale::FLAG_OVERSOLD; $result = Services::grants()->grant( 0, $line->user_id, $offer->target_id, GrantSource::PURCHASE, $actor ); Services::grant_scope()->clear( $result->grant_id, $actor ); $until = $offer->access_until( '' !== $line->paid_at ? $line->paid_at : \RoosterLMS\Data\Transaction::now() ); Services::grants()->apply_expiry( $result, $until ); return new SaleGrant( array( $result->grant_id ), 0, $until, $flags ); } public function end( Offer $offer, Sale $sale, Actor $actor ): void { Services::sales()->reconcile_course_access( $sale->user_id, $offer->target_id, $actor ); } }