node.jspaypalpaypal-subscriptions

How to handle PayPal subscription cancellation with remaining time left using Node.js?


I am building a subscription-based web application using Node.js and the PayPal API. When a user cancels their subscription, I want to handle the scenario where they still have time left on their current subscription period.

Similar to Stripe's cancel_at_period_end feature, I want to set the user's isPremium status to false only after the remaining subscription time has ended. However, I'm unsure how to implement this logic with the PayPal API.

Currently, my application sends a cancellation request to PayPal's API when the user cancels their subscription. However, this immediately revokes their premium access, even if they have time left on their subscription.

I've included the relevant code snippets below. Could someone please guide me on how to modify my code to achieve the desired behavior of keeping the user's isPremium status true until their remaining subscription time has elapsed?

Code:

// paypal.js (relevant portions)

paypalRouter.post("/cancel-subscription/webhook", async (req, res) => {
  // ...
  if (webhookEvent.event_type === "BILLING.SUBSCRIPTION.CANCELLED") {
    const userId = webhookEvent.resource.custom_id;
    const subscriptionId = webhookEvent.resource.id;
    const nextBillingTime = webhookEvent.resource.billing_info.next_billing_time;
    const startTimeSubscriptionPayPal = webhookEvent.resource.start_time;

    try {
      const updatedUserCancelSubscription = await User.findOneAndUpdate(
        { _id: userId, subscriptionId: subscriptionId },
        {
          $set: {
            suscriptionStatusCancelled: true,
            subscriptionStatusPending: false,
            subscriptionStatusConfirmed: false,
            nextBillingTime: nextBillingTime,
            // isPremium: false, // This immediately revokes premium access
          },
        },
        { new: true }
      );

      // ...
    } catch (error) {
      // ...
    }
  }
});

paypalRouter.delete("/cancel-subscription", auth, async (req, res) => {
  // ...
  const cancelResponse = await axios.post(
    `https://api-m.sandbox.paypal.com/v1/billing/subscriptions/${user.subscriptionId}/cancel`,
    {
      // cancel_at: user.nextBillingTime, // I've tried setting this parameter but it doesn't work
    },
    // ...
  );
  // ...
});

I've tried setting the cancel_at parameter to nextBillingTime when sending the cancellation request to PayPal, but it doesn't seem to work. I'm not sure if I'm doing it correctly or if there's a better way to achieve the desired behavior.

Furthermore, I would appreciate any guidance or suggestions on how to handle this scenario properly.


Solution

  • With PayPal subscriptions, the best solution is to ignore profile cancellations completely. The event does not serve a useful purpose.

    Instead, implement your logic based only on the PAYMENT.SALE.COMPLETED event. Every time you receive a payment, update the profile's good-through date in your records. Whether or not it is "cancelled" before the next payment or "active" in PayPal will not affect this, all that matters is whether the new PAYMENT.SALE.COMPLETED event is received by the expected day.