80 lines
2.7 KiB
PHP
80 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Actions;
|
|
|
|
use App\Models\Customer;
|
|
use App\Models\Order;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\Jobs\AdvanceRunJob;
|
|
use Illuminate\Database\UniqueConstraintViolationException;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* Turns a paid Stripe event into a customer + order + provisioning run. The
|
|
* order's unique stripe_event_id is the idempotency key: a duplicate webhook
|
|
* never starts a second run.
|
|
*/
|
|
class StartCustomerProvisioning
|
|
{
|
|
/**
|
|
* @param array{id:string,email:string,name:?string,stripe_customer_id:?string,plan:string,datacenter:string,amount_cents:int,currency:string} $event
|
|
*/
|
|
public function fromStripeEvent(array $event): ?Order
|
|
{
|
|
if (Order::query()->where('stripe_event_id', $event['id'])->exists()) {
|
|
return null; // already processed
|
|
}
|
|
|
|
$customer = $this->resolveCustomer($event);
|
|
|
|
try {
|
|
[$order, $run] = DB::transaction(function () use ($event, $customer) {
|
|
$order = Order::create([
|
|
'customer_id' => $customer->id,
|
|
'plan' => $event['plan'],
|
|
'amount_cents' => $event['amount_cents'],
|
|
'currency' => $event['currency'],
|
|
'datacenter' => $event['datacenter'],
|
|
'stripe_event_id' => $event['id'],
|
|
'status' => 'paid',
|
|
]);
|
|
|
|
$run = ProvisioningRun::create([
|
|
'subject_type' => Order::class,
|
|
'subject_id' => $order->id,
|
|
'pipeline' => 'customer',
|
|
'status' => ProvisioningRun::STATUS_PENDING,
|
|
'current_step' => 0,
|
|
'context' => [],
|
|
]);
|
|
|
|
return [$order, $run];
|
|
});
|
|
} catch (UniqueConstraintViolationException) {
|
|
return null; // concurrent duplicate webhook
|
|
}
|
|
|
|
AdvanceRunJob::dispatch($run->uuid); // after commit
|
|
|
|
return $order;
|
|
}
|
|
|
|
/**
|
|
* Find or create the customer, race-safe against the unique email index so a
|
|
* concurrent first-time purchase can't create two customers for one email.
|
|
*
|
|
* @param array{email:string,name:?string,stripe_customer_id:?string} $event
|
|
*/
|
|
private function resolveCustomer(array $event): Customer
|
|
{
|
|
try {
|
|
return Customer::query()->firstOrCreate(
|
|
['email' => $event['email']],
|
|
['name' => $event['name'] ?? $event['email'], 'stripe_customer_id' => $event['stripe_customer_id'] ?? null],
|
|
);
|
|
} catch (UniqueConstraintViolationException) {
|
|
return Customer::query()->where('email', $event['email'])->firstOrFail();
|
|
}
|
|
}
|
|
}
|