From a5fdb88d79899c311b59d2bee4eeedce05ec4f3f Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 24 May 2016 22:49:06 -0400 Subject: [PATCH] Add support for auto bill on due date --- .../Commands/SendRecurringInvoices.php | 38 +++++++++++++- app/Models/Invoice.php | 14 +++++ app/Models/Payment.php | 10 ---- app/Models/PaymentMethod.php | 10 +--- app/Ninja/Repositories/InvoiceRepository.php | 2 +- app/Services/PaymentService.php | 52 +++++++++++++++++-- .../2016_05_24_164847_wepay_ach.php | 19 ++++--- 7 files changed, 114 insertions(+), 31 deletions(-) diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index abf493d1ca54..6debedcd4001 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -8,6 +8,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Repositories\InvoiceRepository; +use App\Services\PaymentService; use App\Models\Invoice; use App\Models\InvoiceItem; use App\Models\Invitation; @@ -18,13 +19,15 @@ class SendRecurringInvoices extends Command protected $description = 'Send recurring invoices'; protected $mailer; protected $invoiceRepo; + protected $paymentService; - public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo) + public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, PaymentService $paymentService) { parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; + $this->paymentService = $paymentService; } public function fire() @@ -53,6 +56,39 @@ class SendRecurringInvoices extends Command } } + $delayedAutoBillInvoices = Invoice::with('account.timezone', 'recurring_invoice', 'invoice_items', 'client', 'user') + ->leftJoin('invoices as recurring_invoice', 'invoices.recurring_invoice_id', '=', 'recurring_invoice.id') + ->whereRaw('invoices.is_deleted IS FALSE AND invoices.deleted_at IS NULL AND invoices.is_recurring IS FALSE + AND invoices.balance > 0 AND invoices.due_date = ? + AND (recurring_invoice.auto_bill = ? OR (recurring_invoice.auto_bill != ? AND recurring_invoice.client_enable_auto_bill IS TRUE))', + array($today->format('Y-m-d'), AUTO_BILL_ALWAYS, AUTO_BILL_OFF)) + ->orderBy('invoices.id', 'asc') + ->get(); + $this->info(count($delayedAutoBillInvoices).' due recurring auto bill invoice instance(s) found'); + + foreach ($delayedAutoBillInvoices as $invoice) { + $autoBill = $invoice->getAutoBillEnabled(); + $billNow = false; + + if ($autoBill && !$invoice->isPaid()) { + $billNow = $invoice->account->auto_bill_on_due_date; + + if (!$billNow) { + $paymentMethod = $this->invoiceService->getClientDefaultPaymentMethod($invoice->client); + + if ($paymentMethod && $paymentMethod->requiresDelayedAutoBill()) { + $billNow = true; + } + } + } + + $this->info('Processing Invoice '.$invoice->id.' - Should bill '.($billNow ? 'YES' : 'NO')); + + if ($billNow) { + $this->paymentService->autoBillInvoice($invoice); + } + } + $this->info('Done'); } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 02fdc73f99bd..1c9a9baf62cc 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -934,6 +934,20 @@ class Invoice extends EntityModel implements BalanceAffecting } return false; } + + public function getAutoBillEnabled() { + if (!$this->is_recurring) { + $recurInvoice = $this->recurring_invoice; + } else { + $recurInvoice = $this; + } + + if (!$recurInvoice) { + return false; + } + + return $recurInvoice->auto_bill == AUTO_BILL_ALWAYS || ($recurInvoice->auto_bill != AUTO_BILL_OFF && $recurInvoice->client_enable_auto_bill); + } } Invoice::creating(function ($invoice) { diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 677c5c0ea0c1..288bc566a8f1 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -194,16 +194,6 @@ class Payment extends EntityModel { return $value ? str_pad($value, 4, '0', STR_PAD_LEFT) : null; } - - public function getIpAddressAttribute($value) - { - return !$value?$value:inet_ntop($value); - } - - public function setIpAddressAttribute($value) - { - $this->attributes['ip_address'] = inet_pton($value); - } } Payment::creating(function ($payment) { diff --git a/app/Models/PaymentMethod.php b/app/Models/PaymentMethod.php index 7110403ab828..c18d9d2b8bae 100644 --- a/app/Models/PaymentMethod.php +++ b/app/Models/PaymentMethod.php @@ -159,14 +159,8 @@ class PaymentMethod extends EntityModel } } - public function getIpAddressAttribute($value) - { - return !$value?$value:inet_ntop($value); - } - - public function setIpAddressAttribute($value) - { - $this->attributes['ip_address'] = inet_pton($value); + public function requiresDelayedAutoBill(){ + return $this->payment_type_id == PAYMENT_TYPE_DIRECT_DEBIT; } } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 979304ed01a0..6bbd4f9fa639 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -815,7 +815,7 @@ class InvoiceRepository extends BaseRepository $recurInvoice->last_sent_date = date('Y-m-d'); $recurInvoice->save(); - if ($recurInvoice->auto_bill == AUTO_BILL_ALWAYS || ($recurInvoice->auto_bill != AUTO_BILL_OFF && $recurInvoice->client_enable_auto_bill)) { + if ($recurInvoice->getAutoBillEnabled() && !$recurInvoice->account->auto_bill_on_due_date) { if ($this->paymentService->autoBillInvoice($invoice)) { // update the invoice reference to match its actual state // this is to ensure a 'payment received' email is sent diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index c80e193b82de..40feac81f157 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -89,7 +89,7 @@ class PaymentService extends BaseService ]; if ($input !== null) { - $data['ip_address'] = \Request::ip(); + $data['ip'] = \Request::ip(); } if ($accountGateway->isGateway(GATEWAY_PAYPAL_EXPRESS) || $accountGateway->isGateway(GATEWAY_PAYPAL_PRO)) { @@ -446,7 +446,7 @@ class PaymentService extends BaseService $accountGatewayToken->save(); $paymentMethod = $this->convertPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId); - $paymentMethod->ip_address = \Request::ip(); + $paymentMethod->ip = \Request::ip(); $paymentMethod->save(); } else { @@ -650,8 +650,8 @@ class PaymentService extends BaseService $payment->payment_type_id = $this->detectCardType($card->getNumber()); } - if (!empty($paymentDetails['ip_address'])) { - $payment->ip_address = $paymentDetails['ip_address']; + if (!empty($paymentDetails['ip'])) { + $payment->ip = $paymentDetails['ip']; } $savePaymentMethod = !empty($paymentMethod); @@ -839,6 +839,38 @@ class PaymentService extends BaseService return false; } + if ($defaultPaymentMethod->requiresDelayedAutoBill()) { + $invoiceDate = DateTime::createFromFormat('Y-m-d', $invoice_date); + $minDueDate = clone $invoiceDate; + $minDueDate->modify('+10 days'); + + if (DateTime::create() < $minDueDate) { + // Can't auto bill now + return false; + } + + $firstUpdate = \App\Models\Activities::where('invoice_id', '=', $invoice->id) + ->where('activity_type_id', '=', ACTIVITY_TYPE_UPDATE_INVOICE) + ->first(); + + if ($firstUpdate) { + $backup = json_decode($firstUpdate->json_backup); + + if ($backup->balance != $invoice->balance || $backup->due_date != $invoice->due_date) { + // It's changed since we sent the email can't bill now + return false; + } + } + + $invoicePayments = \App\Models\Activities::where('invoice_id', '=', $invoice->id) + ->where('activity_type_id', '=', ACTIVITY_TYPE_CREATE_PAYMENT); + + if ($invoicePayments->count()) { + // ACH requirements are strict; don't auto bill this + return false; + } + } + // setup the gateway/payment info $details = $this->getPaymentDetails($invitation, $accountGateway); $details['customerReference'] = $token; @@ -859,6 +891,18 @@ class PaymentService extends BaseService } } + public function getClientDefaultPaymentMethod($client) { + $this->getClientPaymentMethods($client); + + $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */); + + if (!$accountGatewayToken) { + return false; + } + + return $accountGatewayToken->default_payment_method; + } + public function getDatatable($clientPublicId, $search) { $datatable = new PaymentDatatable( ! $clientPublicId, $clientPublicId); diff --git a/database/migrations/2016_05_24_164847_wepay_ach.php b/database/migrations/2016_05_24_164847_wepay_ach.php index b8e6ca752a0e..cb1b79433321 100644 --- a/database/migrations/2016_05_24_164847_wepay_ach.php +++ b/database/migrations/2016_05_24_164847_wepay_ach.php @@ -13,26 +13,25 @@ class WePayAch extends Migration public function up() { Schema::table('contacts', function(Blueprint $table) { - $table->string('contact_key')->index()->default(null); + $table->string('contact_key')->nullable()->default(null)->index()->unique(); }); Schema::table('payment_methods', function($table) { $table->string('bank_name')->nullable(); + $table->string('ip')->nullable(); }); Schema::table('payments', function($table) { $table->string('bank_name')->nullable(); + $table->string('ip')->nullable(); }); Schema::table('accounts', function($table) { $table->boolean('auto_bill_on_due_date')->default(false); }); - - DB::statement('ALTER TABLE `payments` ADD `ip_address` VARBINARY(16)'); - DB::statement('ALTER TABLE `payment_methods` ADD `ip_address` VARBINARY(16)'); } /** @@ -43,15 +42,21 @@ class WePayAch extends Migration public function down() { Schema::table('contacts', function(Blueprint $table) { - $table->dropColumn('contact_key'); + $table->dropColumn('contact_key'); }); Schema::table('payments', function($table) { - $table->dropColumn('ip_address'); + $table->dropColumn('bank_name'); + $table->dropColumn('ip'); }); Schema::table('payment_methods', function($table) { - $table->dropColumn('ip_address'); + $table->dropColumn('bank_name'); + $table->dropColumn('ip'); + }); + + Schema::table('accounts', function($table) { + $table->dropColumn('auto_bill_on_due_date'); }); } }