mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-04 01:17:30 -05:00 
			
		
		
		
	Add support for auto bill on due date
This commit is contained in:
		
							parent
							
								
									1f11d88d6b
								
							
						
					
					
						commit
						a5fdb88d79
					
				@ -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');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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');
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user