Stripe API refund after subscription cancelled

Stripe will handle this now. It can immediately cancel the subscription and create an invoice refunding them the cost of the unused part of the month.

https://stripe.com/docs/api/subscriptions/cancel

Add the "prorate" tag to refund the remaining monthly cost.

In PHP:

$subscription = \Stripe\Subscription::retrieve(
  'SUBSCRIPTION_ID'
);

$subscription->delete([
    'prorate' => true
]);

EDIT:

@Dobes Vandermeer commented below and pointed out my mistake. The above method will not actually refund the customer the prorated amount but only create an account credit which will be applied to the next invoice.

The code below will actually refund the prorated amount to the card. It generates a sample prorated invoice as if the customer switched their subscription quantity to zero. It then refunds that prorated amount from the last invoice on their active subscription.

The "subscription_proration_date" tag is optional but useful for if you need to prorate the subscription from a certain point in time.

// get active subscription
$subscription = \Stripe\Subscription::retrieve(' SUBSCRIPTION_ID ');

// sample prorated invoice for a subscription with quantity of 0
$sample_subscription_item = array(
    "id"       => $subscription->items->data[0]->id,
    "plan"     => $subscription->items->data[0]->plan->id,
    "quantity" => 0,
);

$upcoming_prorated_invoice = \Stripe\Invoice::upcoming([
    "customer"                    => $subscription->customer,
    "subscription"                => $subscription->id,
    "subscription_items"          => array($sample_subscription_item),
    "subscription_proration_date" => ' PRORATED_DATE ', // optional
]);

// find prorated amount
$prorated_amount = 0;
foreach($upcoming_prorated_invoice->lines->data as $invoice) {
    if ($invoice->type == "invoiceitem") {

        $prorated_amount = ($invoice->amount < 0) ? abs($invoice->amount) : 0;

        break;

    }
}

// find charge id on the active subscription's last invoice
$latest_invoice = \Stripe\Invoice::retrieve($subscription->latest_invoice);
$latest_charge_id = $latest_invoice->charge;

// refund amount from last invoice charge
if ($prorated_amount > 0) {

    $refund = \Stripe\Refund::create([
        'charge' => $latest_charge_id,
        'amount' => $prorated_amount,
    ]);
  
}

// delete subscription
$subscription->delete();

You will need to calculate refund amount, and then make a refund API call to Stripe. After refund you will have to make another API call for Subscription cancellation


After researching for a while, I came to this flow written in JavaScript for Node.js:

refundAndUnsubscribe = async function () {
    try {

        // Set proration date to this moment:
        const proration_date = Math.floor(Date.now() / 1000);

        let sub = await stripe.subscriptions.retrieve("sub_CILnalN9HpvADj");

        // See what the next invoice would look like with a plan switch
        // and proration set:
        let items = [{
            quantity: 0,
            id: sub.items.data[0].id,
            plan: "your_plan" // Switch to new plan
        }];


        let invoices = await stripe.invoices.retrieveUpcoming('cus_CIP9dtlq143gq7', 'sub_CILnalN9HpvADj', {
            subscription_items: items,
            subscription_proration_date: proration_date
        });

        //List all invoices
        let payedInvoicesList = await stripe.invoices.list({
            customer: 'cus_CIP9dtlq143gq7'
        });

        // Calculate the proration cost:
        let current_prorations = [];
        let cost = 0;
        for (var i = 0; i < invoices.lines.data.length; i++) {
            let invoice_item = invoices.lines.data[i];
            if (invoice_item.period.start == proration_date) {
                current_prorations.push(invoice_item);
                cost += invoice_item.amount;
            }
        }

        //create a refund
        if (cost !== 0) {
            cost = (cost < 0) ? cost * -1 : cost //A positive integer in cents

            let refund = await stripe.refunds.create({
                charge: payedInvoicesList.data[0].charge,
                amount: cost
            });
        }

        // delete subscription
        return stripe.subscriptions.del('sub_CILnalN9HpvADj');

    } catch (e) {
        console.log(e);
    }
}