Skip to content

Conversation

Diddyy
Copy link

@Diddyy Diddyy commented Jun 6, 2025

This PR targets the 16.x branch in line with Laravel’s support policy. It finalizes Cashier’s compatibility with Stripe’s Basil API (API version 2025-03-31.basil), as highlighted in the Cashier source code:

// src/Cashier.php
const STRIPE_VERSION = '2025-03-31.basil';

Summary of Changes

Invoice Line Items: Added helpers to extract pricing data using Stripe’s new price_details and inline_price_data structures introduced in Basil:

public function priceId()
{
    // Handle the new pricing structure (Basil release)
    if (isset($this->item->pricing) && $this->item->pricing->type === 'price_details') {
        return $this->item->pricing->price_details->price ?? null;
    }

    return null;
}

Unit Amount Handling: Retrieve unit amounts and support inline price data:

public function unitAmount()
{
    // Handle the new pricing structure (Basil release)
    if (isset($this->item->pricing)) {
        if (isset($this->item->pricing->unit_amount_decimal)) {
            return (int) $this->item->pricing->unit_amount_decimal;
        }
        if ($this->item->pricing->type === 'inline_price_data' &&
            isset($this->item->pricing->inline_price_data->unit_amount)) {
            return (int) $this->item->pricing->inline_price_data->unit_amount;
        }
    }

    return null;
}

Tax Behavior Retrieval: Access the tax_behavior on invoice line items:

public function taxBehavior()
{
    $price = $this->price();
    return $price ? ($price->tax_behavior ?? null) : null;
}

Benefits to Users

Keeps Cashier aligned with Stripe’s latest Basil API, as requested by Taylor April tweet inviting contributions.

Testing

Added PHPUnit tests covering the new invoice line item helpers:

priceId and price retrieval
unitAmount handling for both price_details and inline_price_data
taxBehavior method

All test suites pass locally. Please refer to the CI checks for confirmation.

Motivation

Taylor mentioned that the team “would love some assistance making Cashier compatible with Stripe's latest API version (Basil)” — this PR answers that call. It updates core invoice item logic and tests to ensure developers can rely on Cashier when using the latest Stripe features.

This is my first significant open source contribution attempt so apologies if it isn't quite up to scratch, I welcome any criticism or commits to improve it!

@driesvints driesvints self-assigned this Jun 9, 2025
@driesvints driesvints marked this pull request as draft June 9, 2025 08:02
@driesvints
Copy link
Member

Thank you. I'll review this one when I find some time.

@driesvints driesvints changed the base branch from 15.x to 16.x June 9, 2025 08:02
@driesvints driesvints changed the base branch from 16.x to 15.x June 9, 2025 08:02
@driesvints driesvints changed the base branch from 15.x to 16.x June 9, 2025 08:04
@kyranb
Copy link
Contributor

kyranb commented Jul 7, 2025

@driesvints

Is there a rough target release date for Cashier 16/Basil support?

Diddyy added 8 commits July 9, 2025 23:27
Add meter_event_name storage for metered items
- Add new expansion paths so invoice responses include full tax_rate objects
- Simplify tax rate lookup to return the expanded object and eliminate extra Stripe API calls
- Introduce nullable `meter_id` column in `subscription_items` (lines 12–17 in migration)
- Cast `meter_id` and `meter_event_name` as strings on the model for direct access
- Cache meter ID and event name on subscription swaps and price additions
- Enhance usage reporting to read from cache or fetch-and-store meter details on first call
- Add tests to ensure `meter_id` and `meter_event_name` persist after creation
- uniqid can collide in high‑load situations. Laravel’s Str::uuid() provides truly unique identifiers.
@Diddyy Diddyy marked this pull request as ready for review July 9, 2025 23:35
@Diddyy Diddyy requested a review from kyranb July 9, 2025 23:35
@Diddyy
Copy link
Author

Diddyy commented Jul 9, 2025

I feel like its been draft for too long. I have done all I personally can on it so any further suggestions I will implement but apart from that I feel like it would be ace to get the ball rolling on this being merged! @driesvints

- Add guard in `SubscriptionBuilder::quantity` to throw `InvalidArgumentException` if no prices are provided
- Introduce unit test to verify `quantity()` without any prices raises the expected exception
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
@taylorotwell taylorotwell marked this pull request as draft July 18, 2025 16:59
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
Signed-off-by: Mior Muhammad Zaki <[email protected]>
@crynobone crynobone marked this pull request as ready for review July 22, 2025 22:32
@taylorotwell
Copy link
Member

@crynobone could you do a bullet point list upgrade guide of what users the steps users will need to take to upgrade?

@Diddyy
Copy link
Author

Diddyy commented Aug 12, 2025

@crynobone @taylorotwell @driesvints Whats currently holding this back? Keen to see it merged :)

isset($parameters['subscription_details']) ||
isset($parameters['schedule']) ||
isset($parameters['schedule_details']) ||
isset($parameters['invoice_items']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be &&?

@taylorotwell taylorotwell merged commit 53fb951 into laravel:16.x Aug 14, 2025
6 checks passed
@taylorotwell
Copy link
Member

@Diddyy merged, but I have no idea how to update the docs for this, and also need an upgrade guide.

@kyranb
Copy link
Contributor

kyranb commented Aug 15, 2025

Thanks @taylorotwell for merging.

One other thing to flag perhaps before this gets a tagged release. It seems that as part of the basil release, Stripe are moving towards a new "flexible" billing mode.

https://docs.stripe.com/billing/subscriptions/billing-mode
https://docs.stripe.com/changelog/basil/2025-06-30/billing-mode
https://docs.stripe.com/billing/subscriptions/billing-mode?dashboard-or-api=api&lang=php
https://docs.stripe.com/api/subscriptions/create#create_subscription-billing_mode

While it's possible to pass in the 'billing_mode' when creating a new subscription/checkout like so

$user->newSubscription('default', $priceId)
      ->create(null, [], [
          'billing_mode' => ['type' => 'flexible']
      ]);

I'm wondering if it's worth having a config option and/or a helper method on the SubscriptionBuilder

i.e

'cashier' => [
          'default_billing_mode' => env('CASHIER_DEFAULT_BILLING_MODE', 'classic'),
      ],

or

  class SubscriptionBuilder
  {
      /**
       * The billing mode for the subscription.
       *
       * @var array|null
       */
      protected $billingMode = null;

      /**
       * Set the billing mode for the subscription.
       *
       * @param  string  $type
       * @return $this
       */
      public function withBillingMode(string $type = 'flexible')
      {
          $this->billingMode = ['type' => $type];
          return $this;
      }

      /**
       * Get the default billing mode from config.
       *
       * @return string
       */
      protected function getDefaultBillingMode(): string
      {
          return config('cashier.default_billing_mode', 'classic');
      }

      /**
       * Get the effective billing mode for this subscription.
       *
       * @return string
       */
      protected function getEffectiveBillingMode(): string
      {
          if ($this->billingMode) {
              return $this->billingMode['type'];
          }

          return $this->getDefaultBillingMode();
      }

      /**
       * Get the billing mode for the Stripe payload.
       *
       * @return array|null
       */
      protected function getBillingModeForPayload()
      {
          $mode = $this->getEffectiveBillingMode();

          // Only include billing_mode if it's not classic (Stripe's default)
          if ($mode !== 'classic') {
              return ['type' => $mode];
          }

          return null;
      }
      

What do you think? @taylorotwell @driesvints @crynobone

@Diddyy
Copy link
Author

Diddyy commented Aug 18, 2025

@Diddyy merged, but I have no idea how to update the docs for this, and also need an upgrade guide.

@crynobone is this something you're able to help with? :)

@crynobone
Copy link
Member

@Diddyy I'm going through the upgrading guide, and we did face some issues, especially dealing with v2 specific API on an old Stripe account (old API keys and API versions).

image image

https://docs.stripe.com/api-v2-overview?api-version=2025-07-30.preview&rds=1#limitations

We need to be able to list all the required steps to upgrade to the new version.

@crynobone
Copy link
Member

Furthermore, new metered billings is not on the PR description.

@Diddyy
Copy link
Author

Diddyy commented Aug 18, 2025

@Diddyy I'm going through the upgrading guide, and we did face some issues, especially dealing with v2 specific API on an old Stripe account (old API keys and API versions).

image image
https://docs.stripe.com/api-v2-overview?api-version=2025-07-30.preview&rds=1#limitations

We need to be able to list all the required steps to upgrade to the new version.

Thanks for checking this @crynobone 🙌

I won’t be able to put together the full upgrade guide myself, but I agree it’s needed to document the steps (API version upgrade, sandbox key requirements, metered billing, etc.). Maybe it makes sense to open a follow-up issue in the relevant repo dedicated to the upgrade guide so we can track that separately?

@Diddyy Diddyy mentioned this pull request Aug 19, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants