From 6978af0b8df815b5d910addac7a3d3c8ba57101f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 25 Aug 2026 09:42:54 +0200 Subject: [PATCH] feat(reviews): add review service and Stripe payment gateway --- src/Services/ReviewService.php | 65 ++++++++++++++++++++++++++++++++++ src/Services/StripeGateway.php | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/Services/ReviewService.php create mode 100644 src/Services/StripeGateway.php diff --git a/src/Services/ReviewService.php b/src/Services/ReviewService.php new file mode 100644 index 0000000..684ea58 --- /dev/null +++ b/src/Services/ReviewService.php @@ -0,0 +1,65 @@ +db->scalar( + "SELECT COUNT(*) + FROM orders o + JOIN orderItems oi ON oi.orderId = o.id + WHERE o.userId = ? AND oi.productId = ? AND o.status IN ('paid', 'fulfilled')", + [$userId, $productId] + ) > 0; + } + + public function eligibility(int $userId, int $productId): string + { + if (!$this->hasPurchased($userId, $productId)) { + return self::notPurchased; + } + if ($this->reviews->existsForUser($productId, $userId)) { + return self::alreadyReviewed; + } + return self::eligible; + } + + public function canReview(int $userId, int $productId): bool + { + return $this->eligibility($userId, $productId) === self::eligible; + } + + public function submit(int $userId, int $productId, int $rating, string $title, string $body): int + { + if (!$this->canReview($userId, $productId)) { + throw new \RuntimeException('This account is not eligible to review this product.'); + } + $rating = max(1, min(5, $rating)); + return $this->reviews->create($productId, $userId, $rating, trim($title), trim($body)); + } + + public function approve(int $reviewId): void + { + $this->reviews->setStatus($reviewId, 'approved'); + } + + public function reject(int $reviewId): void + { + $this->reviews->setStatus($reviewId, 'rejected'); + } +} diff --git a/src/Services/StripeGateway.php b/src/Services/StripeGateway.php new file mode 100644 index 0000000..1f380ef --- /dev/null +++ b/src/Services/StripeGateway.php @@ -0,0 +1,65 @@ +client = new StripeClient($this->secretKey); + } + + public function createCheckoutSession(int $orderId, array $lines, string $successUrl, string $cancelUrl): array + { + $lineItems = []; + foreach ($lines as $line) { + $lineItems[] = [ + 'quantity' => $line['quantity'], + 'price_data' => [ + 'currency' => $this->currency, + 'unit_amount' => (int) $line['product']['priceCents'], + 'product_data' => [ + 'name' => $line['product']['name'], + ], + ], + ]; + } + + $session = $this->client->checkout->sessions->create([ + 'mode' => 'payment', + 'line_items' => $lineItems, + 'success_url' => $successUrl, + 'cancel_url' => $cancelUrl, + 'client_reference_id' => (string) $orderId, + 'metadata' => ['orderId' => (string) $orderId], + ]); + + return ['id' => $session->id, 'url' => $session->url]; + } + + public function retrieveSession(string $sessionId): array + { + $session = $this->client->checkout->sessions->retrieve($sessionId); + return [ + 'paymentStatus' => $session->payment_status, + 'orderId' => isset($session->metadata->orderId) ? (int) $session->metadata->orderId : null, + ]; + } + + public function parseWebhook(string $payload, string $signature): array + { + $event = Webhook::constructEvent($payload, $signature, $this->webhookSecret); + return [ + 'type' => $event->type, + 'session' => $event->data->object, + ]; + } +}