2020-07-07 14:33:11 +02:00
< ? php
/**
2020-09-06 11:38:10 +02:00
* Invoice Ninja ( https :// invoiceninja . com ) .
2020-07-07 14:33:11 +02:00
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
2022-04-27 05:20:41 +02:00
* @ copyright Copyright ( c ) 2022. Invoice Ninja LLC ( https :// invoiceninja . com )
2020-07-07 14:33:11 +02:00
*
2021-06-16 08:58:16 +02:00
* @ license https :// www . elastic . co / licensing / elastic - license
2020-07-07 14:33:11 +02:00
*/
namespace App\Services\Invoice ;
2020-07-08 04:20:44 +02:00
use App\DataMapper\InvoiceItem ;
2020-07-07 14:33:11 +02:00
use App\Events\Payment\PaymentWasCreated ;
use App\Factory\PaymentFactory ;
2021-09-03 14:59:48 +02:00
use App\Libraries\MultiDB ;
2020-10-12 06:10:34 +02:00
use App\Models\Credit ;
2020-07-07 14:33:11 +02:00
use App\Models\Invoice ;
use App\Models\Payment ;
2020-09-04 00:01:17 +02:00
use App\Models\PaymentHash ;
2020-10-26 05:06:58 +01:00
use App\Models\PaymentType ;
2020-07-07 14:33:11 +02:00
use App\Services\AbstractService ;
2020-10-08 05:31:02 +02:00
use App\Utils\Ninja ;
2020-09-04 00:01:17 +02:00
use Illuminate\Support\Str ;
2021-10-04 13:29:23 +02:00
use PDO ;
2020-07-07 14:33:11 +02:00
class AutoBillInvoice extends AbstractService
{
private $invoice ;
2020-09-06 11:38:10 +02:00
private $client ;
2020-07-07 14:33:11 +02:00
2020-10-08 02:28:23 +02:00
private $used_credit = [];
2020-10-07 13:03:53 +02:00
2021-09-03 14:59:48 +02:00
protected $db ;
public function __construct ( Invoice $invoice , $db )
2020-07-07 14:33:11 +02:00
{
$this -> invoice = $invoice ;
2022-05-17 01:33:52 +02:00
2021-09-03 14:59:48 +02:00
$this -> db = $db ;
2020-07-07 14:33:11 +02:00
}
public function run ()
{
2021-09-03 14:59:48 +02:00
MultiDB :: setDb ( $this -> db );
2022-03-04 23:21:17 +01:00
$this -> client = $this -> invoice -> client -> fresh ();
2021-09-03 14:59:48 +02:00
2021-05-26 08:04:38 +02:00
$is_partial = false ;
2020-10-07 13:03:53 +02:00
/* Is the invoice payable? */
2022-06-21 11:57:17 +02:00
if ( ! $this -> invoice -> isPayable ()) {
2020-07-08 04:20:44 +02:00
return $this -> invoice ;
2022-06-21 11:57:17 +02:00
}
2020-10-28 11:10:49 +01:00
2020-10-07 13:03:53 +02:00
/* Mark the invoice as sent */
2020-07-15 08:08:57 +02:00
$this -> invoice = $this -> invoice -> service () -> markSent () -> save ();
2020-10-07 13:03:53 +02:00
/* Mark the invoice as paid if there is no balance */
2022-06-21 11:57:17 +02:00
if (( int ) $this -> invoice -> balance == 0 ) {
2022-03-15 10:20:05 +01:00
return $this -> invoice -> service () -> markPaid () -> save ();
2022-06-21 11:57:17 +02:00
}
2021-01-29 12:00:30 +01:00
2020-10-09 03:59:59 +02:00
//if the credits cover the payments, we stop here, build the payment with credits and exit early
2022-06-21 11:57:17 +02:00
if ( $this -> client -> getSetting ( 'use_credits_payment' ) != 'off' ) {
2020-10-28 11:10:49 +01:00
$this -> applyCreditPayment ();
2022-06-21 11:57:17 +02:00
}
2020-07-07 14:33:11 +02:00
2021-09-03 07:38:20 +02:00
$amount = 0 ;
2020-10-07 13:03:53 +02:00
/* Determine $amount */
2020-11-25 15:19:52 +01:00
if ( $this -> invoice -> partial > 0 ) {
2021-05-26 08:04:38 +02:00
$is_partial = true ;
2021-09-22 03:57:03 +02:00
$invoice_total = $this -> invoice -> balance ;
2020-09-04 00:01:17 +02:00
$amount = $this -> invoice -> partial ;
2020-11-25 15:19:52 +01:00
} elseif ( $this -> invoice -> balance > 0 ) {
2020-09-04 00:01:17 +02:00
$amount = $this -> invoice -> balance ;
2020-11-25 15:19:52 +01:00
} else {
2020-10-23 06:18:16 +02:00
return $this -> invoice ;
2020-11-25 15:19:52 +01:00
}
2020-10-07 13:03:53 +02:00
2021-09-03 07:38:20 +02:00
info ( " Auto Bill - balance remains to be paid!! - { $amount } " );
2020-10-12 06:30:53 +02:00
2021-01-27 22:31:31 +01:00
/* Retrieve the Client Gateway Token */
2020-10-07 13:03:53 +02:00
$gateway_token = $this -> getGateway ( $amount );
/* Bail out if no payment methods available */
2022-06-21 11:57:17 +02:00
if ( ! $gateway_token || ! $gateway_token -> gateway || ! $gateway_token -> gateway -> driver ( $this -> client ) -> token_billing ) {
nlog ( 'Bailing out - no suitable gateway token found.' );
2020-10-07 13:03:53 +02:00
return $this -> invoice ;
2021-09-03 07:38:20 +02:00
}
2021-01-29 12:00:30 +01:00
2022-06-21 11:57:17 +02:00
nlog ( 'Gateway present - adding gateway fee' );
2021-09-04 07:51:31 +02:00
2020-10-07 13:03:53 +02:00
/* $gateway fee */
2021-01-27 22:31:31 +01:00
$this -> invoice = $this -> invoice -> service () -> addGatewayFee ( $gateway_token -> gateway , $gateway_token -> gateway_type_id , $amount ) -> save ();
2021-09-22 03:57:03 +02:00
//change from $this->invoice->amount to $this->invoice->balance
2022-06-21 11:57:17 +02:00
if ( $is_partial ) {
2021-09-22 03:57:03 +02:00
$fee = $this -> invoice -> balance - $invoice_total ;
2022-06-21 11:57:17 +02:00
} else {
2021-09-22 03:57:03 +02:00
$fee = $this -> invoice -> balance - $amount ;
2022-06-21 11:57:17 +02:00
}
2021-09-22 03:57:03 +02:00
2022-06-21 11:57:17 +02:00
if ( $fee > $amount ) {
2021-09-22 03:57:03 +02:00
$fee = 0 ;
2022-06-21 11:57:17 +02:00
}
2020-07-08 04:20:44 +02:00
2020-10-07 13:03:53 +02:00
/* Build payment hash */
2022-01-05 05:32:07 +01:00
2020-09-04 00:01:17 +02:00
$payment_hash = PaymentHash :: create ([
2021-09-23 07:36:31 +02:00
'hash' => Str :: random ( 64 ),
2021-10-17 12:40:40 +02:00
'data' => [ 'invoices' => [[ 'invoice_id' => $this -> invoice -> hashed_id , 'amount' => $amount , 'invoice_number' => $this -> invoice -> number ]]],
2020-09-04 00:01:17 +02:00
'fee_total' => $fee ,
'fee_invoice_id' => $this -> invoice -> id ,
]);
2020-07-08 04:20:44 +02:00
2021-09-04 07:51:31 +02:00
nlog ( " Payment hash created => { $payment_hash -> id } " );
2022-06-21 11:57:17 +02:00
2021-10-04 13:29:23 +02:00
$payment = false ;
2022-05-17 01:33:52 +02:00
try {
$payment = $gateway_token -> gateway
-> driver ( $this -> client )
-> setPaymentHash ( $payment_hash )
-> tokenBilling ( $gateway_token , $payment_hash );
} catch ( \Exception $e ) {
2022-05-18 06:37:11 +02:00
$this -> invoice -> auto_bill_tries += 1 ;
2022-05-17 06:46:03 +02:00
2022-05-17 01:33:52 +02:00
if ( $this -> invoice -> auto_bill_tries == 3 ) {
$this -> invoice -> auto_bill_enabled = false ;
2022-05-17 06:46:03 +02:00
$this -> invoice -> auto_bill_tries = 0 ; //reset the counter here in case auto billing is turned on again in the future.
2022-05-17 01:33:52 +02:00
$this -> invoice -> save ();
}
2022-06-21 11:57:17 +02:00
2022-06-18 09:10:15 +02:00
$this -> invoice -> save ();
2022-06-21 11:57:17 +02:00
nlog ( 'payment NOT captured for ' . $this -> invoice -> number . ' with error ' . $e -> getMessage ());
2022-05-17 01:33:52 +02:00
}
if ( $payment ) {
2022-06-21 11:57:17 +02:00
info ( 'Auto Bill payment captured for ' . $this -> invoice -> number );
2021-08-05 02:46:03 +02:00
}
2020-07-07 14:33:11 +02:00
}
2020-10-08 02:28:23 +02:00
/**
* If the credits on file cover the invoice amount
* the we create a matching payment using credits only
2020-10-28 11:10:49 +01:00
*
2020-10-08 02:28:23 +02:00
* @ return Invoice $invoice
*/
private function finalizePaymentUsingCredits ()
{
$amount = array_sum ( array_column ( $this -> used_credit , 'amount' ));
$payment = PaymentFactory :: create ( $this -> invoice -> company_id , $this -> invoice -> user_id );
$payment -> amount = $amount ;
2020-10-12 06:10:34 +02:00
$payment -> applied = $amount ;
2020-10-08 05:31:02 +02:00
$payment -> client_id = $this -> invoice -> client_id ;
$payment -> currency_id = $this -> invoice -> client -> getSetting ( 'currency_id' );
2020-10-08 02:28:23 +02:00
$payment -> date = now ();
2020-10-08 05:31:02 +02:00
$payment -> status_id = Payment :: STATUS_COMPLETED ;
2020-10-26 03:06:24 +01:00
$payment -> type_id = PaymentType :: CREDIT ;
2020-10-08 05:31:02 +02:00
$payment -> service () -> applyNumber () -> save ();
2020-10-08 02:28:23 +02:00
$payment -> invoices () -> attach ( $this -> invoice -> id , [ 'amount' => $amount ]);
2021-01-24 07:44:14 +01:00
$this -> invoice
-> service ()
-> setStatus ( Invoice :: STATUS_PAID )
-> save ();
2020-10-08 02:28:23 +02:00
2020-11-25 15:19:52 +01:00
foreach ( $this -> used_credit as $credit ) {
$current_credit = Credit :: find ( $credit [ 'credit_id' ]);
2021-01-24 07:44:14 +01:00
$payment -> credits ()
-> attach ( $current_credit -> id , [ 'amount' => $credit [ 'amount' ]]);
2020-10-28 11:10:49 +01:00
2022-06-21 11:57:17 +02:00
info ( " adjusting credit balance { $current_credit -> balance } by this amount " . $credit [ 'amount' ]);
2020-10-23 06:18:16 +02:00
2021-01-24 07:44:14 +01:00
$current_credit -> service ()
2022-06-21 11:57:17 +02:00
-> adjustBalance ( $credit [ 'amount' ] * - 1 )
2021-01-24 07:44:14 +01:00
-> updatePaidToDate ( $credit [ 'amount' ])
-> setCalculatedStatus ()
-> save ();
2020-11-25 15:19:52 +01:00
}
2020-10-08 02:28:23 +02:00
2020-11-25 15:19:52 +01:00
$payment -> ledger ()
2022-03-04 23:21:17 +01:00
-> updatePaymentBalance ( $amount * - 1 )
-> save ();
$client = $this -> invoice -> client -> fresh ();
2020-10-08 02:28:23 +02:00
2022-03-04 23:21:17 +01:00
$client -> service ()
-> updateBalance ( $amount * - 1 )
-> updatePaidToDate ( $amount )
-> adjustCreditBalance ( $amount * - 1 )
-> save ();
2020-10-08 02:28:23 +02:00
2022-03-09 02:31:54 +01:00
$this -> invoice -> ledger () //09-03-2022
// ->updateInvoiceBalance($amount * -1, "Invoice {$this->invoice->number} payment using Credit {$current_credit->number}")
2021-01-21 05:42:30 +01:00
-> updateCreditBalance ( $amount * - 1 , " Credit { $current_credit -> number } used to pay down Invoice { $this -> invoice -> number } " )
2020-10-08 02:28:23 +02:00
-> save ();
2021-12-17 12:11:36 +01:00
event ( 'eloquent.created: App\Models\Payment' , $payment );
2020-11-25 15:19:52 +01:00
event ( new PaymentWasCreated ( $payment , $payment -> company , Ninja :: eventVars ()));
2020-10-08 05:31:02 +02:00
2021-01-24 07:44:14 +01:00
return $this -> invoice
-> service ()
-> setCalculatedStatus ()
-> save ();
2020-10-08 02:28:23 +02:00
}
2020-10-07 13:03:53 +02:00
/**
* Applies credits to a payment prior to push
* to the payment gateway
2020-10-28 11:10:49 +01:00
*
2020-10-07 13:03:53 +02:00
* @ return $this
*/
2020-10-28 11:10:49 +01:00
private function applyCreditPayment ()
2020-10-07 13:03:53 +02:00
{
$available_credits = $this -> client
-> credits
-> where ( 'is_deleted' , false )
-> where ( 'balance' , '>' , 0 )
-> sortBy ( 'created_at' );
$available_credit_balance = $available_credits -> sum ( 'balance' );
2020-10-23 06:18:16 +02:00
info ( " available credit balance = { $available_credit_balance } " );
2022-06-21 11:57:17 +02:00
if (( int ) $available_credit_balance == 0 ) {
2020-10-07 13:03:53 +02:00
return ;
2020-11-25 15:19:52 +01:00
}
2020-10-07 13:03:53 +02:00
$is_partial_amount = false ;
if ( $this -> invoice -> partial > 0 ) {
$is_partial_amount = true ;
}
2020-10-08 05:31:02 +02:00
$this -> used_credit = [];
2020-10-07 13:03:53 +02:00
2020-11-25 15:19:52 +01:00
foreach ( $available_credits as $key => $credit ) {
if ( $is_partial_amount ) {
2020-10-08 01:36:06 +02:00
//more credit than needed
2021-01-24 10:28:18 +01:00
if ( $credit -> balance > $this -> invoice -> partial ) {
2020-10-08 02:28:23 +02:00
$this -> used_credit [ $key ][ 'credit_id' ] = $credit -> id ;
$this -> used_credit [ $key ][ 'amount' ] = $this -> invoice -> partial ;
$this -> invoice -> balance -= $this -> invoice -> partial ;
2021-01-24 07:44:14 +01:00
$this -> invoice -> paid_to_date += $this -> invoice -> partial ;
2020-10-08 01:36:06 +02:00
$this -> invoice -> partial = 0 ;
break ;
2020-11-25 15:19:52 +01:00
} else {
2020-10-08 02:28:23 +02:00
$this -> used_credit [ $key ][ 'credit_id' ] = $credit -> id ;
$this -> used_credit [ $key ][ 'amount' ] = $credit -> balance ;
2020-10-08 01:36:06 +02:00
$this -> invoice -> partial -= $credit -> balance ;
2020-10-08 02:28:23 +02:00
$this -> invoice -> balance -= $credit -> balance ;
2021-01-24 07:44:14 +01:00
$this -> invoice -> paid_to_date += $credit -> balance ;
2020-10-08 01:36:06 +02:00
}
2020-11-25 15:19:52 +01:00
} else {
2020-10-08 01:36:06 +02:00
//more credit than needed
2021-01-24 10:28:18 +01:00
if ( $credit -> balance > $this -> invoice -> balance ) {
2020-10-08 05:31:02 +02:00
$this -> used_credit [ $key ][ 'credit_id' ] = $credit -> id ;
$this -> used_credit [ $key ][ 'amount' ] = $this -> invoice -> balance ;
2021-01-24 07:44:14 +01:00
$this -> invoice -> paid_to_date += $this -> invoice -> balance ;
2020-10-08 01:36:06 +02:00
$this -> invoice -> balance = 0 ;
2021-01-24 07:44:14 +01:00
2020-10-08 01:36:06 +02:00
break ;
2020-11-25 15:19:52 +01:00
} else {
2020-10-08 05:31:02 +02:00
$this -> used_credit [ $key ][ 'credit_id' ] = $credit -> id ;
$this -> used_credit [ $key ][ 'amount' ] = $credit -> balance ;
2020-10-08 01:36:06 +02:00
$this -> invoice -> balance -= $credit -> balance ;
2021-01-24 07:44:14 +01:00
$this -> invoice -> paid_to_date += $credit -> balance ;
2020-10-08 01:36:06 +02:00
}
}
}
2020-10-07 13:03:53 +02:00
2020-10-23 06:18:16 +02:00
$this -> finalizePaymentUsingCredits ();
2020-10-07 13:03:53 +02:00
return $this ;
}
2020-10-12 06:10:34 +02:00
private function applyPaymentToCredit ( $credit , $amount ) : Credit
2020-10-07 13:03:53 +02:00
{
$credit_item = new InvoiceItem ;
$credit_item -> type_id = '1' ;
$credit_item -> product_key = ctrans ( 'texts.credit' );
$credit_item -> notes = ctrans ( 'texts.credit_payment' , [ 'invoice_number' => $this -> invoice -> number ]);
$credit_item -> quantity = 1 ;
$credit_item -> cost = $amount * - 1 ;
$credit_items = $credit -> line_items ;
$credit_items [] = $credit_item ;
$credit -> line_items = $credit_items ;
$credit = $credit -> calc () -> getCredit ();
2020-10-12 06:10:34 +02:00
$credit -> save ();
2020-10-07 13:03:53 +02:00
2020-10-12 06:10:34 +02:00
return $credit ;
2020-10-07 13:03:53 +02:00
}
2020-07-15 07:05:02 +02:00
/**
* Harvests a client gateway token which passes the
2020-09-06 11:38:10 +02:00
* necessary filters for an $amount .
*
2020-07-15 07:05:02 +02:00
* @ param float $amount The amount to charge
* @ return ClientGatewayToken The client gateway token
*/
2021-01-17 11:33:05 +01:00
public function getGateway ( $amount )
2020-07-07 14:33:11 +02:00
{
2020-07-13 00:28:19 +02:00
2021-01-17 11:33:05 +01:00
//get all client gateway tokens and set the is_default one to the first record
2022-02-18 00:52:17 +01:00
$gateway_tokens = $this -> client
-> gateway_tokens ()
-> whereHas ( 'gateway' , function ( $query ) {
2022-06-21 11:57:17 +02:00
$query -> where ( 'is_deleted' , 0 )
-> where ( 'deleted_at' , null );
}) -> orderBy ( 'is_default' , 'DESC' )
2022-02-18 00:52:17 +01:00
-> get ();
2022-06-21 11:57:17 +02:00
$filtered_gateways = $gateway_tokens -> filter ( function ( $gateway_token ) use ( $amount ) {
2021-01-17 11:33:05 +01:00
$company_gateway = $gateway_token -> gateway ;
//check if fees and limits are set
2022-06-21 11:57:17 +02:00
if ( isset ( $company_gateway -> fees_and_limits ) && ! is_array ( $company_gateway -> fees_and_limits ) && property_exists ( $company_gateway -> fees_and_limits , $gateway_token -> gateway_type_id )) {
2021-01-17 11:33:05 +01:00
//if valid we keep this gateway_token
2022-06-21 11:57:17 +02:00
if ( $this -> invoice -> client -> validGatewayForAmount ( $company_gateway -> fees_and_limits -> { $gateway_token -> gateway_type_id }, $amount )) {
2021-01-17 11:33:05 +01:00
return true ;
2022-06-21 11:57:17 +02:00
} else {
2021-01-17 11:33:05 +01:00
return false ;
2022-06-21 11:57:17 +02:00
}
2020-07-15 07:05:02 +02:00
}
2021-01-17 11:33:05 +01:00
2022-06-21 11:57:17 +02:00
return true ; //if no fees_and_limits set then we automatically must add this gateway
2021-01-17 11:33:05 +01:00
});
2022-06-21 11:57:17 +02:00
if ( $filtered_gateways -> count () >= 1 ) {
2021-01-17 11:33:05 +01:00
return $filtered_gateways -> first ();
2022-06-21 11:57:17 +02:00
}
2021-01-17 11:33:05 +01:00
return false ;
2020-07-08 02:18:13 +02:00
}
2020-07-07 16:50:51 +02:00
2020-07-08 05:07:07 +02:00
/**
2020-09-06 11:38:10 +02:00
* Adds a gateway fee to the invoice .
*
2020-07-08 05:07:07 +02:00
* @ param float $fee The fee amount .
2020-10-28 11:10:49 +01:00
* @ return AutoBillInvoice
2020-07-08 05:07:07 +02:00
*/
2020-07-08 04:20:44 +02:00
private function addFeeToInvoice ( float $fee )
{
2020-07-08 05:07:07 +02:00
//todo if we increase the invoice balance here, we will also need to adjust UP the client balance and ledger?
$starting_amount = $this -> invoice -> amount ;
2020-07-08 04:20:44 +02:00
$item = new InvoiceItem ;
$item -> quantity = 1 ;
$item -> cost = $fee ;
$item -> notes = ctrans ( 'texts.online_payment_surcharge' );
$item -> type_id = 3 ;
2020-09-06 11:38:10 +02:00
$items = ( array ) $this -> invoice -> line_items ;
2020-07-08 04:20:44 +02:00
$items [] = $item ;
$this -> invoice -> line_items = $items ;
2021-10-14 08:54:38 +02:00
$this -> invoice -> saveQuietly ();
2020-07-08 04:20:44 +02:00
2021-10-14 08:54:38 +02:00
$this -> invoice = $this -> invoice -> calc () -> getInvoice () -> saveQuietly ();
2020-07-08 04:20:44 +02:00
2020-09-06 11:38:10 +02:00
if ( $starting_amount != $this -> invoice -> amount && $this -> invoice -> status_id != Invoice :: STATUS_DRAFT ) {
2020-07-08 05:07:07 +02:00
$this -> invoice -> client -> service () -> updateBalance ( $this -> invoice -> amount - $starting_amount ) -> save ();
2021-01-21 05:42:30 +01:00
$this -> invoice -> ledger () -> updateInvoiceBalance ( $this -> invoice -> amount - $starting_amount , " Invoice { $this -> invoice -> number } balance updated after stale gateway fee removed " ) -> save ();
2020-07-08 05:07:07 +02:00
}
2020-07-08 08:54:16 +02:00
2020-07-08 05:07:07 +02:00
return $this ;
}
2020-07-07 14:33:11 +02:00
}