È un buon modo per tenere traccia dei cicli di abbonamento e capire se è necessario caricare l'abbonato?

2

Lavoro in PHP e costruisco un sistema di gestione delle sottoscrizioni da zero.

Sto cercando di capire le funzioni richieste per l'interfaccia di sottoscrizione (OOP) che devono essere implementate da classi che manterranno traccia del tipo di abbonamento, del prezzo, delle date di inizio / fine della fatturazione cicli, giorno di fatturazione del mese, data di inizio della sottoscrizione, numero di cicli a pagamento, ecc.

Requisiti e amp; Scopo: l'intero sistema verrà interrogato ogni giorno per verificare se ogni abbonamento deve essere fatturato o fatturato nuovamente se non è stato superato in precedenza.

Qual è un buon modo per farlo? È quello che ho finora ok?

Concettualmente, ho pensato a quanto segue, ma non sono sicuro se sto andando nella giusta direzione:

/*** Run this daily ***/

// Retrieve from DB
$subscription = new Subscription_Magazine('SUBSCRIPTION_ID');

// Call a utils function
if (SubscriptionUtils::isPaymentNeeded($subscription))
{
    // Bill one cycle
    SubscriptionUtils::charge($subscription, $subscription->getPaymentMethod(), 1);
}


/*** Utils to handle logic ***/

class SubscriptionUtils
{
    const RETRY_ONLY_AFTER = "+1 day";

    public static function isPaymentNeeded($subscription)
    {
        // Check if neither active nor past due
        if (!$subscription->isActive() && !$subscription->isPastDue())
        {
            // Expired or canceled
            return false;
        }

        // Check for recurring
        if (!$subscription->isRecurring())
        {
            return false;
        }

        /* ? ? ?   |   |   |   |   |   |   |   ? ? ? */
        /* ? ? ?  \|/ \|/ \|/ \|/ \|/ \|/ \|/  ? ? ? */
        /* ? ? ?   V   V   V   V   V   V   V   ? ? ? */

        // Get subscribe date, cycles paid count, and length of cycle
        $regDate = $subscription->getSubscriptionStartDate(); //unix timestamp
        $cyclesPaid = $subscription->getPaidCyclesCount(); //int
        $cycleLength = $subscription->getCycleLength(); //"+1 month" / "+6 months" / "+1 year"

        // Check if paid through date is more than one month from now
        $paidThroughDate = self::modifyTimestamp($regDate, $cycleLength, $cyclesPaid);
        $oneCycleFromNow = self::modifyTimestamp(time(), $cycleLength);
        if ($paidThroughDate > $oneCycleFromNow)
        {
            return false;
        }

        // Check if we are on or passed the billing date
        $currentDay = date("j"); //int 1-31
        $billingDay = $subscription->getBillingDayOfMonth(); //int 1-31
        $daysInMonth = date("t"); //int 28-31
        // Use valid day
        $thisBillingDay = $billingDay > $daysInMonth ? $daysInMonth : $billingDay;
        if ($thisBillingDay < $billingDay)
        {
            return false;
        }

        // Check if we already tried recently
        $transaction = $subscriptions->getLastTransaction();
        if ($transaction instanceof Transaction && time() < modifyTimestamp($transaction->date(), self::RETRY_ONLY_AFTER))
        {
            return false;
        }

        //yes, return true that we need to charge now
        return true;

        /* ? ? ?   ^   ^   ^   ^   ^   ^   ^   ? ? ? */
        /* ? ? ?  /|\ /|\ /|\ /|\ /|\ /|\ /|\  ? ? ? */
        /* ? ? ?   |   |   |   |   |   |   |   ? ? ? */

    }

    public static function charge(Subscription $subscription, PaymentMethod $paymentMethod, $cyclesToCharge)
    {
        $transaction = new Transaction();
        $transaction->add("One Month Magazine", $subscription->getPrice(), $cyclesToCharge);
        $transaction->pay($paymentMethod);

        $subscription->addTransaction($transaction);

        if (!$transaction->success())
        {
            return false;
        }

        $subscription->paidCycleIncrement($cyclesToCharge);
        return true;
    }

    public static function modifyTimestamp($timestamp, $modifyText, $repeatNum = 1)
    {
        //Trivia code
        //Note: dates don't rollover.
        //For example: if "+1 month", then January 31st will result in February 28th (or 29th)
    }

    //...other stuff too
}
    
posta Joseph Shih 01.08.2016 - 11:13
fonte

1 risposta

1

Dovresti ripensare a questo. È necessario suddividere la logica di fatturazione / pagamento dalla logica di rinnovo dell'abbonamento. Per la logica del rinnovo, sarei propenso a memorizzare una "prossima data di rinnovo" che calcolo e aggiorno ogni volta che rinnovo l'abbonato (e al primo ingresso di un abbonato). In questo modo è sufficiente interrogare per i record che devono essere rinnovati. La tua logica sopra visiterà ogni record nel database per verificare se è idoneo per il rinnovo e che non è un approccio molto scalabile.

Nel ciclo di rinnovo, creerai quindi un nuovo articolo di fatturazione per il cliente. Un'ulteriore logica potrebbe elaborare questi elementi di fatturazione: ogni articolo di fatturazione attiva un articolo di pagamento e si tiene traccia del successo / fallimento degli articoli di pagamento man mano che il pagamento viene elaborato. Poiché gli aggiornamenti sullo stato dei pagamenti possono essere utilizzati anche per aggiornare l'abbonamento (ad esempio un pagamento potrebbe non riuscire, è quindi possibile sospendere l'abbonamento fino al ricevimento del pagamento).

Gli abbonamenti e i pagamenti sono piuttosto complessi con un giusto numero di if e but in loro, quindi qualsiasi progetto di soluzione sarà abbastanza complesso.

    
risposta data 02.08.2016 - 10:15
fonte