feat(reviews): add review service and Stripe payment gateway
This commit is contained in:
1 parent
4c8d38e972
commit
6978af0b8d
2 files changed
+130
No files matched your search
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bazaar\Services;
|
||||||
|
|
||||||
|
use Bazaar\Core\Database;
|
||||||
|
use Bazaar\Repositories\ReviewRepository;
|
||||||
|
|
||||||
|
final class ReviewService
|
||||||
|
{
|
||||||
|
public const eligible = 'eligible';
|
||||||
|
public const notPurchased = 'notPurchased';
|
||||||
|
public const alreadyReviewed = 'alreadyReviewed';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Database $db,
|
||||||
|
private readonly ReviewRepository $reviews
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPurchased(int $userId, int $productId): bool
|
||||||
|
{
|
||||||
|
return (int) $this->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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bazaar\Services;
|
||||||
|
|
||||||
|
use Stripe\StripeClient;
|
||||||
|
use Stripe\Webhook;
|
||||||
|
|
||||||
|
final class StripeGateway
|
||||||
|
{
|
||||||
|
private StripeClient $client;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $secretKey,
|
||||||
|
private readonly string $webhookSecret,
|
||||||
|
private readonly string $currency
|
||||||
|
) {
|
||||||
|
$this->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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user