request()->get('/customers/'.$customerID); if (! $response->successful()) { return null; } $data = $response->json('data'); return is_array($data) ? $data : null; } public function subscription(string $subscriptionID): array { $this->requireWritableConfiguration(); $response = $this->request() ->get('/subscriptions/'.$subscriptionID); return $this->subscriptionData( $response, 'Paddle could not retrieve the subscription.' ); } public function updateSubscriptionPrice( string $subscriptionID, string $priceID ): array { $this->requireWritableConfiguration(); /* * Paddle restricts subscription item changes while a subscription * is trialing. Existing item quantities may only be updated using * do_not_bill during the trial. * * Once the subscription is outside its trial we can use normal * immediate proration. */ $subscription = $this->subscription($subscriptionID); $status = (string) ($subscription['status'] ?? ''); $prorationBillingMode = $status === 'trialing' ? 'do_not_bill' : 'prorated_immediately'; /* * Preserve the existing subscription item and replace its price. * * Paddle treats the submitted items array as the desired * subscription items, so quantity is retained where possible. */ $existingItems = is_array($subscription['items'] ?? null) ? $subscription['items'] : []; $existingItem = $existingItems[0] ?? []; $quantity = max( 1, (int) ($existingItem['quantity'] ?? 1) ); $response = $this->request()->patch( '/subscriptions/'.$subscriptionID, [ 'proration_billing_mode' => $prorationBillingMode, 'items' => [ [ 'price_id' => $priceID, 'quantity' => $quantity, ], ], ] ); return $this->subscriptionData( $response, 'Paddle could not update the subscription.' ); } public function cancelSubscriptionImmediately( string $subscriptionID ): array { $this->requireWritableConfiguration(); $response = $this->request()->post( '/subscriptions/'.$subscriptionID.'/cancel', [ 'effective_from' => 'immediately', ] ); return $this->subscriptionData( $response, 'Paddle could not cancel the subscription.' ); } private function request(): PendingRequest { return Http::baseUrl( (string) config('paddle.api_url') ) ->withToken( (string) config('paddle.api_key') ) ->acceptJson() ->timeout(15); } private function requireWritableConfiguration(): void { if ( trim( (string) config('paddle.api_key') ) === '' ) { throw new RuntimeException( 'Paddle subscription management is not configured.' ); } } private function subscriptionData( Response $response, string $fallback ): array { if (! $response->successful()) { $detail = trim( (string) ( $response->json('error.detail') ?? '' ) ); throw new RuntimeException( $detail !== '' ? $detail : $fallback ); } $data = $response->json('data'); if (! is_array($data)) { throw new RuntimeException($fallback); } return $data; } }