112 lines
4.2 KiB
PHP
112 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace Modules\Core\Payment\Drivers;
|
|
|
|
use Lunar\Exceptions\FingerprintMismatchException;
|
|
use Lunar\Exceptions\Carts\CartException;
|
|
use Lunar\Exceptions\DisallowMultipleCartOrdersException;
|
|
use Lunar\Models\Cart;
|
|
use Lunar\Models\Order;
|
|
use Lunar\Stripe\Actions\UpdateOrderFromIntent;
|
|
use Lunar\Stripe\Facades\Stripe;
|
|
use Lunar\Stripe\Models\StripePaymentIntent;
|
|
use Modules\Core\Checkout\Contracts\PaymentDriver;
|
|
use Modules\Core\Checkout\Services\CheckoutService;
|
|
use Modules\Core\Payment\Exceptions\PaymentNotConfirmedException;
|
|
use Stripe\PaymentIntent;
|
|
|
|
/**
|
|
* Wraps Lunar\Stripe\StripePaymentType::authorize() to satisfy
|
|
* Modules\Core\Checkout\Contracts\PaymentDriver — calls
|
|
* CheckoutService::placeOrder($fingerprint) at the moment Stripe confirms
|
|
* payment, instead of the vendor's own Cart::createOrder() call.
|
|
*
|
|
* This is a fork, not a decoration: StripePaymentType::authorize() is
|
|
* `final` and calls Cart::createOrder() directly with no seam to redirect
|
|
* that one call — so this class reimplements authorize()'s logic (intent
|
|
* retrieval, capture-on-policy, status mapping via UpdateOrderFromIntent)
|
|
* rather than wrapping the vendor method. Kept deliberately close to the
|
|
* original so a lunarphp/stripe upgrade is easy to diff against. See
|
|
* docs/payments.md.
|
|
*/
|
|
class StripePaymentDriver implements PaymentDriver
|
|
{
|
|
public function __construct(
|
|
private readonly CheckoutService $checkout,
|
|
) {}
|
|
|
|
/**
|
|
* Same key lunarphp/stripe's own StripeManager reads its API key from
|
|
* (Stripe::setApiKey(config('services.stripe.key')) in
|
|
* StripeManager::__construct()) — no key, no usable driver.
|
|
*/
|
|
public function isConfigured(): bool
|
|
{
|
|
return filled(config('services.stripe.key'));
|
|
}
|
|
|
|
/**
|
|
* @throws PaymentNotConfirmedException if Stripe hasn't confirmed the
|
|
* payment intent (wrong intent id, already processed, order already
|
|
* placed, or the gateway call itself fails) — nothing here should be
|
|
* treated as "place the order anyway."
|
|
* @throws FingerprintMismatchException
|
|
* @throws CartException
|
|
*/
|
|
public function confirm(Cart $cart, string $type, string $fingerprint, array $data): Order
|
|
{
|
|
$paymentIntentId = $data['payment_intent'];
|
|
|
|
$paymentIntentModel = StripePaymentIntent::where('intent_id', $paymentIntentId)->first();
|
|
|
|
if ($paymentIntentModel && ! $paymentIntentModel->isActive()) {
|
|
throw new PaymentNotConfirmedException('Payment intent already processed.');
|
|
}
|
|
|
|
if (! $paymentIntentModel) {
|
|
$paymentIntentModel = StripePaymentIntent::create([
|
|
'intent_id' => $paymentIntentId,
|
|
'cart_id' => $cart->id,
|
|
]);
|
|
}
|
|
|
|
$paymentIntentModel->update(['processing_at' => now()]);
|
|
|
|
$stripe = Stripe::getClient();
|
|
$paymentIntent = $stripe->paymentIntents->retrieve($paymentIntentId);
|
|
|
|
if (! $paymentIntent) {
|
|
throw new PaymentNotConfirmedException('Unable to locate payment intent.');
|
|
}
|
|
|
|
$policy = config('lunar.stripe.policy', 'automatic');
|
|
|
|
if ($paymentIntent->status === PaymentIntent::STATUS_REQUIRES_CAPTURE && $policy === 'automatic') {
|
|
$paymentIntent = $stripe->paymentIntents->capture($paymentIntentId);
|
|
}
|
|
|
|
if ($paymentIntent->status !== PaymentIntent::STATUS_SUCCEEDED) {
|
|
$paymentIntentModel->update(['status' => $paymentIntent->status]);
|
|
|
|
throw new PaymentNotConfirmedException(
|
|
$paymentIntent->last_payment_error->message ?? "Payment intent status: {$paymentIntent->status}."
|
|
);
|
|
}
|
|
|
|
try {
|
|
$order = $this->checkout->placeOrder($fingerprint);
|
|
} catch (DisallowMultipleCartOrdersException|CartException $e) {
|
|
throw new PaymentNotConfirmedException($e->getMessage(), previous: $e);
|
|
}
|
|
|
|
$paymentIntentModel->order_id = $order->id;
|
|
$paymentIntentModel->status = $paymentIntent->status;
|
|
$paymentIntentModel->processed_at = now();
|
|
$paymentIntentModel->save();
|
|
|
|
UpdateOrderFromIntent::execute($order, $paymentIntent);
|
|
|
|
return $order->refresh();
|
|
}
|
|
}
|