laravel / cashier-stripe

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.
https://laravel.com/docs/billing
MIT License
2.39k stars 679 forks source link

Stripe Tax: isTaxExempt() isNotTaxExempt() reverseChargeApplies() methods returns incorrect state #1645

Closed sakalauskas closed 9 months ago

sakalauskas commented 9 months ago

Cashier Stripe Version

14.14.0

Laravel Version

10.41.0

PHP Version

8.2

Database Driver & Version

No response

Description

When using Laravel Cashier with Stripe Tax, the automatic tax is applied on the Subscription but not on the Customer object. As a side effect, Invoices generated on Stripe do not match invoices generated via Laravel.

The "tax_exempt" attribute when creating a new customer is always "none" and this is where is the problem. Generally, "tax_exempt" attribute seems that should not be used when Stripe Tax is used.

This issue mainly affects invoice generation. Where it should say "Tax is exempted" or "Tax to be paid on reverse charge basis", it states e.g. Sales Tax - Texas (8.25%) - $0.00. Since I am not registered to collect taxes in Texas, "automatic_tax" state is "not_collecting", which is basically "Tax is exempted".

I have reached out to Stripe Support and they confirmed that "tax_exempt" attribute isn't used with Stripe Tax:

When generating an invoice on your end, the tax status doesn't update because the tax calculation and status are handled by Stripe Tax. To confirm the tax status of an invoice, you can retrieve the upcoming invoice of a subscription and inspect the results of its tax calculation.

You can retrieve the tax amounts from the tax and total_tax_amounts fields on the upcoming invoice, and from the per-line-item tax_amounts fields.

A simple hotfix may be just manually updating "tax_exempt" attribute. But that just contradicts with Stripe Tax purpose - automatically calculate taxes.

The correct fix would be to use total_tax_amounts fields instead of tax_exempt attribute when Stripe Tax is used.

Steps To Reproduce

Cashier::calculateTaxes();

$user = User::find(...); $customer = $user->createOrGetStripeCustomer(['expand' => ['tax']]); $user->createTaxId('eu_vat', 'LT123456789123'); $user ->newSubscription('default', 'my_price_name')->create($token)

driesvints commented 9 months ago

Hey there,

Unfortunately we don't support this version anymore. Please check out our support policy on which versions we are currently supporting. Can you please try to upgrade to the latest version and see if your problem persists? If so, please open up a new issue and we'll help you out.

Thanks!

sakalauskas commented 6 months ago

@driesvints Unfortunately after upgrading to the latest version this remains the same.

Looks like Stripe Tax and Laravel Cashier are incompatible. At least when generating invoices/receipts.

It needs to rely on "taxability_reason" instead/and "tax_exempt" attribute: https://docs.stripe.com/tax/zero-tax

driesvints commented 6 months ago

@sakalauskas forgive me but I'm confused. Cashier Stripe doesn't uses tax_exempt at all anywhere. Only in its boolean checks reverseChargeApplies etc. I don't really understand what the issue is. Could you give some more concrete examples like the difference between a stripe and cashier invoice?

sakalauskas commented 6 months ago

@driesvints Basically, the issue is that invoices/receipts that are being generated are a bit incorrect due to isTaxExempt() isNotTaxExempt() reverseChargeApplies() methods which uses "tax_exempt" state. Stripe Tax doesn't use the "Tax Status" state to determine taxability. Laravel Cashier does. At the moment, I have "resolved" this by providing original Stripe invoices instead of Cashier-generated invoices but Stripe invoices lack customizability.

When a new customer is created, it is created with Tax Status: Taxable. Let's assume I want to use Stripe Tax and do not want to manually set Tax Status to Taxable, Reverse Charge or Exempt. If I leave it as Taxable, Stripe Tax automatically calculates whether it is taxable or not.

Setting Tax Status manually would still be needed for the Laravel Cashier invoice/receipt to be generated correctly but that kind of defeats the purpose of Stripe Tax slightly.

Let's take into account the following facts:

  1. Vendor Company is registered in Europe.
  2. Customer Tax Status is set to Taxable (default value)

Please see some of the examples where the invoice is generated:

  1. stripe.pdf cashier.pdf: Not OK: Vendor Company isn't registered to pay tax in the US so sales tax is exempted as it hasn't reached the threshold. Cashier invoice displays line "Sales Tax - California (10.75%) $0.00", but it should state tax is exempt (or nothing at all).

  2. stripe2.pdf cashier2.pdf: OK - Buyer is taxable and tax value is shown. (*To fully align with EU tax requirements, a VAT amount in EUR should be displayed but this definitely out of scope for Cashier library)

  3. stripe3.pdf cashier3.pdf: Not OK - Buyer has EU VAT code, therefore, it should be "reverse charged". Instead, a line "VAT - Italy (22%), $0.00" is displayed

Let me know if that makes sense.

I would love to open a PR but not exactly sure how to make the code lean. Potentially there could be multiple taxes and taxability reasons requiring refactoring the blade template. It might be that a simpler "solution" is to note that Stripe Tax are not compatible with Cashier-generated invoices.

driesvints commented 6 months ago

Gonna re-open this one. Will have a look when I find some time, thanks