Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsubscribe field ends_at is null #1632

Closed
GuhLucena opened this issue Jan 10, 2024 · 6 comments · Fixed by #1633
Closed

Unsubscribe field ends_at is null #1632

GuhLucena opened this issue Jan 10, 2024 · 6 comments · Fixed by #1633
Labels

Comments

@GuhLucena
Copy link

Cashier Stripe Version

15.1

Laravel Version

10.10

PHP Version

8.2

Database Driver & Version

MariaDB 10.4.32

Description

When canceling a subscription that is configured to be canceled immediately and responds to the collection reasson, the 'ends_at' field in the subscriptions table is set to null.

And $request->user()?->subscribed() does not identify that the subscription has ended

Steps To Reproduce

The subscription is set to be canceled immediately and with the Cancellation reason activated on the Customer portal.
https://dashboard.stripe.com/test/settings/billing/portal

image

when the customer cancels the subscription via the portal and replies to "Cancellation reason", the 'ends_at' field is set to 'null'

image

The problem is in the webhook it is deleting the 'ends_at' value because the update event is executed after the delete event.
image

@driesvints
Copy link
Member

Hi @GuhLucena. Could you share the payloads of these two events?

@GuhLucena
Copy link
Author

GuhLucena commented Jan 11, 2024

customer.subscription.updated

{
  "id": ["evt_1OX6sPAjJBizF0r1YxfNjZgL"](https://dashboard.stripe.com/test/events/evt_1OX6sPAjJBizF0r1YxfNjZgL),
  "object": "event",
  "api_version": "2023-10-16",
  "created": 1704912705,
  "data": {
    "object": {
      "id": ["sub_1OX6qWAjJBizF0r1Qiuceeyl"](https://dashboard.stripe.com/test/subscriptions/sub_1OX6qWAjJBizF0r1Qiuceeyl),
      "object": "subscription",
      "application": null,
      "application_fee_percent": null,
      "automatic_tax": {
        "enabled": false
      },
      "billing_cycle_anchor": 1704912588,
      "billing_thresholds": null,
      "cancel_at": null,
      "cancel_at_period_end": false,
      "canceled_at": 1704912692,
      "cancellation_details": {
        "comment": "CARAO DIMAIIIIIS",
        "feedback": "too_expensive",
        "reason": "cancellation_requested"
      },
      "collection_method": "charge_automatically",
      "created": 1704912588,
      "currency": "brl",
      "current_period_end": 1707590988,
      "current_period_start": 1704912588,
      "customer": ["cus_PLoRuG6bEwluyH"](https://dashboard.stripe.com/test/customers/cus_PLoRuG6bEwluyH),
      "days_until_due": null,
      "default_payment_method": "pm_1OX6qVAjJBizF0r1MNGj7UsB",
      "default_source": null,
      "default_tax_rates": [
      ],
      "description": null,
      "discount": null,
      "ended_at": 1704912692,
      "items": {
        "object": "list",
        "data": [
          {
            "id": ["si_PLoT6tl9dX7zVl"](https://dashboard.stripe.com/test),
            "object": "subscription_item",
            "billing_thresholds": null,
            "created": 1704912589,
            "metadata": {
            },
            "plan": {
              "id": ["price_1OVNDmAjJBizF0r1zMsNhxKW"](https://dashboard.stripe.com/test/prices/price_1OVNDmAjJBizF0r1zMsNhxKW),
              "object": "plan",
              "active": true,
              "aggregate_usage": null,
              "amount": 500,
              "amount_decimal": "500",
              "billing_scheme": "per_unit",
              "created": 1704498878,
              "currency": "brl",
              "interval": "month",
              "interval_count": 1,
              "livemode": false,
              "metadata": {
              },
              "nickname": null,
              "product": ["prod_PK1GhuGcYvoaKV"](https://dashboard.stripe.com/test/products/prod_PK1GhuGcYvoaKV),
              "tiers_mode": null,
              "transform_usage": null,
              "trial_period_days": null,
              "usage_type": "licensed"
            },
            "price": {
              "id": ["price_1OVNDmAjJBizF0r1zMsNhxKW"](https://dashboard.stripe.com/test/prices/price_1OVNDmAjJBizF0r1zMsNhxKW),
              "object": "price",
              "active": true,
              "billing_scheme": "per_unit",
              "created": 1704498878,
              "currency": "brl",
              "custom_unit_amount": null,
              "livemode": false,
              "lookup_key": null,
              "metadata": {
              },
              "nickname": null,
              "product": ["prod_PK1GhuGcYvoaKV"](https://dashboard.stripe.com/test/products/prod_PK1GhuGcYvoaKV),
              "recurring": {
                "aggregate_usage": null,
                "interval": "month",
                "interval_count": 1,
                "trial_period_days": null,
                "usage_type": "licensed"
              },
              "tax_behavior": "unspecified",
              "tiers_mode": null,
              "transform_quantity": null,
              "type": "recurring",
              "unit_amount": 500,
              "unit_amount_decimal": "500"
            },
            "quantity": 1,
            "subscription": ["sub_1OX6qWAjJBizF0r1Qiuceeyl"](https://dashboard.stripe.com/test/subscriptions/sub_1OX6qWAjJBizF0r1Qiuceeyl),
            "tax_rates": [
            ]
          }
        ],
        "has_more": false,
        "total_count": 1,
        "url": "/v1/subscription_items?subscription=sub_1OX6qWAjJBizF0r1Qiuceeyl"
      },
      "latest_invoice": "in_1OX6qWAjJBizF0r1PWWs9MAO",
      "livemode": false,
      "metadata": {
        "is_on_session_checkout": "true",
        "type": "default",
        "name": "default"
      },
      "next_pending_invoice_item_invoice": null,
      "on_behalf_of": null,
      "pause_collection": null,
      "payment_settings": {
        "payment_method_options": null,
        "payment_method_types": null,
        "save_default_payment_method": "off"
      },
      "pending_invoice_item_interval": null,
      "pending_setup_intent": null,
      "pending_update": null,
      "plan": {
        "id": ["price_1OVNDmAjJBizF0r1zMsNhxKW"](https://dashboard.stripe.com/test/prices/price_1OVNDmAjJBizF0r1zMsNhxKW),
        "object": "plan",
        "active": true,
        "aggregate_usage": null,
        "amount": 500,
        "amount_decimal": "500",
        "billing_scheme": "per_unit",
        "created": 1704498878,
        "currency": "brl",
        "interval": "month",
        "interval_count": 1,
        "livemode": false,
        "metadata": {
        },
        "nickname": null,
        "product": ["prod_PK1GhuGcYvoaKV"](https://dashboard.stripe.com/test/products/prod_PK1GhuGcYvoaKV),
        "tiers_mode": null,
        "transform_usage": null,
        "trial_period_days": null,
        "usage_type": "licensed"
      },
      "quantity": 1,
      "schedule": null,
      "start_date": 1704912588,
      "status": "canceled",
      "test_clock": null,
      "transfer_data": null,
      "trial_end": null,
      "trial_settings": {
        "end_behavior": {
          "missing_payment_method": "create_invoice"
        }
      },
      "trial_start": null
    },
    "previous_attributes": {
      "cancellation_details": {
        "comment": null,
        "feedback": null
      }
    }
  },
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": "req_AOeIlKkjAZkCLU",
    "idempotency_key": "7b20dd47-c44c-4eb0-8111-bed1109dee47"
  },
  "type": "customer.subscription.updated"
}

customer.subscription.deleted

{
  "id": ["evt_1OX6sDAjJBizF0r1lecNHf8e"](https://dashboard.stripe.com/test/events/evt_1OX6sDAjJBizF0r1lecNHf8e),
  "object": "event",
  "api_version": "2023-10-16",
  "created": 1704912693,
  "data": {
    "object": {
      "id": ["sub_1OX6qWAjJBizF0r1Qiuceeyl"](https://dashboard.stripe.com/test/subscriptions/sub_1OX6qWAjJBizF0r1Qiuceeyl),
      "object": "subscription",
      "application": null,
      "application_fee_percent": null,
      "automatic_tax": {
        "enabled": false
      },
      "billing_cycle_anchor": 1704912588,
      "billing_thresholds": null,
      "cancel_at": null,
      "cancel_at_period_end": false,
      "canceled_at": 1704912692,
      "cancellation_details": {
        "comment": null,
        "feedback": null,
        "reason": "cancellation_requested"
      },
      "collection_method": "charge_automatically",
      "created": 1704912588,
      "currency": "brl",
      "current_period_end": 1707590988,
      "current_period_start": 1704912588,
      "customer": ["cus_PLoRuG6bEwluyH"](https://dashboard.stripe.com/test/customers/cus_PLoRuG6bEwluyH),
      "days_until_due": null,
      "default_payment_method": "pm_1OX6qVAjJBizF0r1MNGj7UsB",
      "default_source": null,
      "default_tax_rates": [
      ],
      "description": null,
      "discount": null,
      "ended_at": 1704912692,
      "items": {
        "object": "list",
        "data": [
          {
            "id": ["si_PLoT6tl9dX7zVl"](https://dashboard.stripe.com/test),
            "object": "subscription_item",
            "billing_thresholds": null,
            "created": 1704912589,
            "metadata": {
            },
            "plan": {
              "id": ["price_1OVNDmAjJBizF0r1zMsNhxKW"](https://dashboard.stripe.com/test/prices/price_1OVNDmAjJBizF0r1zMsNhxKW),
              "object": "plan",
              "active": true,
              "aggregate_usage": null,
              "amount": 500,
              "amount_decimal": "500",
              "billing_scheme": "per_unit",
              "created": 1704498878,
              "currency": "brl",
              "interval": "month",
              "interval_count": 1,
              "livemode": false,
              "metadata": {
              },
              "nickname": null,
              "product": ["prod_PK1GhuGcYvoaKV"](https://dashboard.stripe.com/test/products/prod_PK1GhuGcYvoaKV),
              "tiers_mode": null,
              "transform_usage": null,
              "trial_period_days": null,
              "usage_type": "licensed"
            },
            "price": {
              "id": ["price_1OVNDmAjJBizF0r1zMsNhxKW"](https://dashboard.stripe.com/test/prices/price_1OVNDmAjJBizF0r1zMsNhxKW),
              "object": "price",
              "active": true,
              "billing_scheme": "per_unit",
              "created": 1704498878,
              "currency": "brl",
              "custom_unit_amount": null,
              "livemode": false,
              "lookup_key": null,
              "metadata": {
              },
              "nickname": null,
              "product": ["prod_PK1GhuGcYvoaKV"](https://dashboard.stripe.com/test/products/prod_PK1GhuGcYvoaKV),
              "recurring": {
                "aggregate_usage": null,
                "interval": "month",
                "interval_count": 1,
                "trial_period_days": null,
                "usage_type": "licensed"
              },
              "tax_behavior": "unspecified",
              "tiers_mode": null,
              "transform_quantity": null,
              "type": "recurring",
              "unit_amount": 500,
              "unit_amount_decimal": "500"
            },
            "quantity": 1,
            "subscription": ["sub_1OX6qWAjJBizF0r1Qiuceeyl"](https://dashboard.stripe.com/test/subscriptions/sub_1OX6qWAjJBizF0r1Qiuceeyl),
            "tax_rates": [
            ]
          }
        ],
        "has_more": false,
        "total_count": 1,
        "url": "/v1/subscription_items?subscription=sub_1OX6qWAjJBizF0r1Qiuceeyl"
      },
      "latest_invoice": "in_1OX6qWAjJBizF0r1PWWs9MAO",
      "livemode": false,
      "metadata": {
        "is_on_session_checkout": "true",
        "type": "default",
        "name": "default"
      },
      "next_pending_invoice_item_invoice": null,
      "on_behalf_of": null,
      "pause_collection": null,
      "payment_settings": {
        "payment_method_options": null,
        "payment_method_types": null,
        "save_default_payment_method": "off"
      },
      "pending_invoice_item_interval": null,
      "pending_setup_intent": null,
      "pending_update": null,
      "plan": {
        "id": ["price_1OVNDmAjJBizF0r1zMsNhxKW"](https://dashboard.stripe.com/test/prices/price_1OVNDmAjJBizF0r1zMsNhxKW),
        "object": "plan",
        "active": true,
        "aggregate_usage": null,
        "amount": 500,
        "amount_decimal": "500",
        "billing_scheme": "per_unit",
        "created": 1704498878,
        "currency": "brl",
        "interval": "month",
        "interval_count": 1,
        "livemode": false,
        "metadata": {
        },
        "nickname": null,
        "product": ["prod_PK1GhuGcYvoaKV"](https://dashboard.stripe.com/test/products/prod_PK1GhuGcYvoaKV),
        "tiers_mode": null,
        "transform_usage": null,
        "trial_period_days": null,
        "usage_type": "licensed"
      },
      "quantity": 1,
      "schedule": null,
      "start_date": 1704912588,
      "status": "canceled",
      "test_clock": null,
      "transfer_data": null,
      "trial_end": null,
      "trial_settings": {
        "end_behavior": {
          "missing_payment_method": "create_invoice"
        }
      },
      "trial_start": null
    }
  },
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": "c15079ab-8dd4-4c9f-accc-ffb8e65d9828"
  },
  "type": "customer.subscription.deleted"
}

@GuhLucena
Copy link
Author

Maybe this will give you more information.

when you cancel without replying and commenting, the order of the events in the webhook looks like the one circled in blue.

and when you cancel, reply and comment, it looks like the one circled in orange.

image

@GuhLucena
Copy link
Author

image

I think I've found it!

instead of "cancel_at" the right one is "canceled_at"

@GuhLucena
Copy link
Author

If you look deeper, there is:
'cancel_at' => date on which it will be canceled when this is configured as "cancel at end of billing period"
image

'canceled_at' => the date it was canceled. If it is set to 'cancel immediately' it will have the timestamp and 'cancel_at' will be null.
image

In this case, you need to check whether the 'cancel_at_period_end' field is true or false.
true -> use cancel_at
false -> use cancel_at if different from null

@driesvints
Copy link
Member

Thank you. I sent in #1633 for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants