find($addonKey, $amount, $currency, $interval); if ($existing !== null) { return $existing; } $productId = $this->product($addonKey); $priceId = $this->stripe->createPrice( productId: $productId, amountCents: $amount, currency: $currency, interval: $interval, metadata: [ // Read back when a Stripe invoice line has to be turned into // wording a customer can read — see StripeInvoiceLines. 'addon' => $addonKey, ], // Keyed on what the Price IS, so a crash between Stripe creating it // and us storing its id gives back the same Price on the next // attempt rather than a second one at the same money. idempotencyKey: "clupilot-addon-price-{$addonKey}-{$interval}-{$amount}-{$currency}", ); $this->remember($addonKey, $amount, $currency, $interval, $productId, $priceId); return $priceId; } /** The Price we already have for this combination, or null. */ public function find(string $addonKey, int $amountCents, string $currency, string $interval): ?string { $id = StripeAddonPrice::query() ->where('addon_key', $addonKey) ->where('amount_cents', $amountCents) ->where('currency', strtoupper($currency)) ->where('interval', $interval) ->value('stripe_price_id'); return is_string($id) && $id !== '' ? $id : null; } /** * The Stripe Product every Price for this module hangs off. * * One per module, however many Prices it accumulates — that is what a * Product is for, and it is what makes the module readable in Stripe's own * dashboard rather than a list of anonymous amounts. */ private function product(string $addonKey): string { $existing = StripeAddonPrice::query() ->where('addon_key', $addonKey) ->value('stripe_product_id'); if (is_string($existing) && $existing !== '') { return $existing; } return $this->stripe->createProduct( app(AddonCatalogue::class)->name($addonKey), ['addon' => $addonKey], idempotencyKey: "clupilot-addon-product-{$addonKey}", ); } private function remember( string $addonKey, int $amountCents, string $currency, string $interval, string $productId, string $priceId, ): void { try { StripeAddonPrice::create([ 'addon_key' => $addonKey, 'amount_cents' => $amountCents, 'currency' => $currency, 'interval' => $interval, 'stripe_product_id' => $productId, 'stripe_price_id' => $priceId, ]); } catch (UniqueConstraintViolationException) { // Two bookings of the same module landed together. Stripe replayed // one Price for both — the idempotency key saw to that — so there is // nothing to correct here beyond letting the first row stand. } } }