/home/edulekha/crm.edulekha.com/application/libraries/Stripe_subscriptions_synchronizer.php
<?php

use Stripe\Subscription;

defined('BASEPATH') or exit('No direct script access allowed');

class Stripe_subscriptions_synchronizer extends Stripe_core
{
    protected $dbSubscription;
    protected $subscription;

    public function sync()
    {
        $this->ci->load->library('stripe_core');
        $this->ci->load->model('subscriptions_model');
        $this->ci->load->model('invoices_model');
        $this->ci->load->model('payments_model');

        foreach ($this->get_all_subscriptions() as $subscription) {
            $this->subscription = $subscription;

            if (! isset($subscription->metadata['pcrm-subscription-hash'])) {
                echo 'Skipping sync for subscription' . $subscription->id . ', not created from application<br /><br /><br />';

                continue;
            }

            $dbSubscription = $this->ci->subscriptions_model->get_by_hash($subscription->metadata['pcrm-subscription-hash']);

            if (! $dbSubscription) {
                echo 'Skipping sync for subscription' . $subscription->id . ', not in database<br /><br /><br />';

                continue;
            }

            $this->dbSubscription = $dbSubscription;

            if (! is_null($dbSubscription->in_test_environment)) {
                if ($dbSubscription->in_test_environment && ! $this->ci->stripe_gateway->is_test()
                || $this->ci->stripe_gateway->is_test() && ! $dbSubscription->in_test_environment
                ) {
                    echo 'Skipping sync for subscription' . $subscription->id . ', environment not match<br /><br /><br />';

                    continue;
                }
            } else {
                $this->ci->subscriptions_model->update(
                    $dbSubscription->id,
                    ['in_test_environment' => $this->ci->stripe_gateway->is_test()]
                );
            }

            echo 'Syncing - <b>DB ID: [' . $dbSubscription->id . '], Stripe ID: ' . $subscription->id . '</b><br />';

            if ($this->check_stripe_client_id() === false) {
                continue;
            }

            $invoices = $this->get_subscription_invoices($subscription->id);

            foreach ($invoices->data as $invoice) {
                $invoiceId = $this->ci->invoices_model->add(create_subscription_invoice_data($dbSubscription, $invoice));

                foreach ($invoice->payments->data as $invoicePayment) {
                    if ($invoicePayment->status === 'paid' && ! $this->ci->payments_model->transaction_exists($invoicePayment->payment->payment_intent)) {
                        if (! defined('STRIPE_SUBSCRIPTION_INVOICE')) {
                            define('STRIPE_SUBSCRIPTION_INVOICE', true);
                        }

                        if ($invoiceId) {
                            $this->ci->db->where('id', $invoiceId)->update('invoices', [
                                'addedfrom' => $dbSubscription->created_from,
                            ]);

                            $this->ci->payments_model->add([
                                'paymentmode'   => 'stripe',
                                'amount'        => strcasecmp($dbSubscription->currency_name, 'JPY') == 0 ? $invoicePayment->amount_paid : $invoicePayment->amount_paid / 100,
                                'invoiceid'     => $invoiceId,
                                'transactionid' => $invoicePayment->payment->payment_intent,
                            ], $dbSubscription->id);
                        }
                    }
                }
            }

            $update = [
                'next_billing_cycle'     => $subscription->items->data[0]->current_period_end,
                'stripe_subscription_id' => $subscription->id,
                'status'                 => $subscription->status,
                'date_subscribed'        => date('Y-m-d H:i:s', $subscription->start_date),
                'date'                   => date('Y-m-d', $subscription->start_date),
                'quantity'               => $subscription->items->data[0]->quantity,
                'ends_at'                => $subscription->cancel_at_period_end ? $subscription->cancel_at : null,
            ];

            if ($subscription->status == 'canceled') {
                $update['next_billing_cycle'] = null;
                // Was future and now canceled, use the same date
                if (is_null($subscription->latest_invoice)) {
                    $update['date'] = date('Y-m-d', $subscription->items->data[0]->current_period_end);
                }
            } elseif (is_null($subscription->latest_invoice) && $subscription->status == 'active') {
                // If canceled before first payment is made?
                if (! $subscription->cancel_at_period_end) {
                    // is future
                    $update['status'] = 'future';
                    // This is the anchor period, start end
                    $update['date'] = date('Y-m-d', $subscription->items->data[0]->current_period_end);
                }
            }
            // elseif (in_array($subscription->status, ['incomplete', 'incomplete_expired'])) {
            // } elseif (in_array($subscription->status, ['active', 'past_due', 'unpaid'])) {
            // }

            $this->ci->subscriptions_model->update($dbSubscription->id, $update);
        }
    }

    protected function get_subscription_invoices($id)
    {
        return Stripe\Invoice::all(['subscription' => $id, 'limit' => 100, 'expand' => ['data.payments']]);
    }

    protected function check_stripe_client_id()
    {
        if ($this->ci->stripe_gateway->is_test()) {
            return;
        }

        $this->ci->db->where('userid', $this->dbSubscription->clientid);
        $dbClient = $this->ci->db->get('clients')->row();

        // This should not happen ever?
        if ($this->dbSubscription->stripe_subscription_id != $this->subscription->id) {
            echo '<b>Aborting, incosistent subscription ID, DB:' . $this->dbSubscription->stripe_subscription_id . ', Stripe: ' . $this->subscription->id . '</b><br /><br /><br />';

            return false;
        }

        if (empty($dbClient->stripe_id)) {
            echo 'Updating stripe_id for client "' . $dbClient->userid . '" [' . $this->subscription->customer . ']<br />';
            $this->ci->db->where('userid', $this->dbSubscription->clientid);
            $this->ci->db->update('clients', ['stripe_id' => $this->subscription->customer]);
        } else {
            echo 'Skip update client stripe_id, current: ' . $dbClient->stripe_id . ', Stripe subscription customer id: ' . $this->subscription->customer . '<br />';

            // This should not happen ever?
            if ($this->subscription->customer != $dbClient->stripe_id) {
                echo '<b>Abort, incosistent stripe id found</b><br /><br /><br />';
            }
        }
    }

    protected function get_all_subscriptions()
    {
        $hasMore       = true;
        $subscriptions = null;
        $startingAfter = null;

        do {
            $response = Subscription::all(
                array_merge(['limit' => 100, 'status' => 'all', 'created' => ['gt' => strtotime('-30 days')]], $startingAfter ? ['starting_after' => $startingAfter] : [])
            );

            if (is_null($subscriptions)) {
                $subscriptions = $response;
            } else {
                $subscriptions->data = array_merge($subscriptions->data, $response->data);
            }

            $startingAfter             = $subscriptions->data[count($subscriptions->data) - 1]->id ?? null;
            $hasMore                   = $response['has_more'];
            $subscriptions['has_more'] = $hasMore;
        } while ($hasMore);

        return $subscriptions->data ?? [];
    }
}