From 0878decf1803bb91026f0b92d156eaa841c3d39f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jan 2020 07:15:10 +1100 Subject: [PATCH] Implement payment rules at application edge (FormRequest) (#3202) * Ensure payments, invoice and credit amount balance in the validator prior to saving * additional payment validation rules and tests for processing payments * Factories for credits * Tests for payments * Working on updating a payment * Working on updating a payment * fixes for updating a payment * Working on Payment Tests * More tests for payments, formrequests * remove product_key as required from products --- app/Factory/InvoiceItemFactory.php | 33 ++ app/Http/Controllers/PaymentController.php | 2 +- .../Requests/Payment/StorePaymentRequest.php | 16 +- .../Requests/Payment/UpdatePaymentRequest.php | 37 +- .../Requests/Product/StoreProductRequest.php | 2 +- .../PaymentAmountsBalanceRule.php | 72 +++ .../PaymentAppliedValidAmount.php | 79 +++ .../ValidCreditsPresentRule.php | 65 +++ app/Jobs/Credit/ApplyCreditPayment.php | 63 +-- app/Jobs/Invoice/ApplyInvoicePayment.php | 6 +- app/Models/Credit.php | 34 ++ app/Repositories/PaymentRepository.php | 54 +- database/factories/CreditFactory.php | 32 ++ tests/Feature/ClientApiTest.php | 6 +- tests/Feature/PaymentTest.php | 477 +++++++++++++++++- tests/Feature/UserTest.php | 2 +- tests/Integration/MultiDBUserTest.php | 2 +- tests/MockAccountData.php | 3 +- 18 files changed, 916 insertions(+), 69 deletions(-) create mode 100644 app/Http/ValidationRules/PaymentAmountsBalanceRule.php create mode 100644 app/Http/ValidationRules/PaymentAppliedValidAmount.php create mode 100644 app/Http/ValidationRules/ValidCreditsPresentRule.php create mode 100644 database/factories/CreditFactory.php diff --git a/app/Factory/InvoiceItemFactory.php b/app/Factory/InvoiceItemFactory.php index bce8eba36f..5c528f27ed 100644 --- a/app/Factory/InvoiceItemFactory.php +++ b/app/Factory/InvoiceItemFactory.php @@ -75,4 +75,37 @@ class InvoiceItemFactory return $data; } + + /** + * Generates an array of dummy data for invoice items + * @param int $items Number of line items to create + * @return array array of objects + */ + public static function generateCredit(int $items = 1) :array + { + $faker = \Faker\Factory::create(); + + $data = []; + + for ($x=0; $x<$items; $x++) { + $item = self::create(); + $item->quantity = $faker->numberBetween(-1, -10); + $item->cost = $faker->randomFloat(2, -1, -1000); + $item->line_total = $item->quantity * $item->cost; + $item->is_amount_discount = true; + $item->discount = $faker->numberBetween(1, 10); + $item->notes = $faker->realText(20); + $item->product_key = $faker->word(); + $item->custom_value1 = $faker->realText(10); + $item->custom_value2 = $faker->realText(10); + $item->custom_value3 = $faker->realText(10); + $item->custom_value4 = $faker->realText(10); + $item->tax_name1 = 'GST'; + $item->tax_rate1 = 10.00; + + $data[] = $item; + } + + return $data; + } } diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 83920a59f9..33da7c3117 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -412,7 +412,7 @@ class PaymentController extends BaseController if($request->entityIsDeleted($payment)) return $request->disallowUpdate(); - $payment = $this->payment_repo->save(request(), $payment); + $payment = $this->payment_repo->save($request, $payment); return $this->itemResponse($payment); } diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index efd242ae8b..0be53a30c2 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -13,6 +13,8 @@ namespace App\Http\Requests\Payment; use App\Http\Requests\Request; use App\Http\ValidationRules\ValidPayableInvoicesRule; +use App\Http\ValidationRules\PaymentAmountsBalanceRule; +use App\Http\ValidationRules\ValidCreditsPresentRule; use App\Models\Payment; use App\Utils\Traits\MakesHash; @@ -46,11 +48,20 @@ class StorePaymentRequest extends Request } } - if (isset($input['invoices']) && is_array($input['invoices']) === false) { $input['invoices'] = null; } + if (isset($input['credits']) && is_array($input['credits']) !== false) { + foreach ($input['credits'] as $key => $value) { + $input['credits'][$key]['id'] = $this->decodePrimaryKey($value['id']); + } + } + + if (isset($input['credits']) && is_array($input['credits']) === false) { + $input['credits'] = null; + } + $this->replace($input); } @@ -58,10 +69,11 @@ class StorePaymentRequest extends Request { $rules = [ 'amount' => 'numeric|required', + 'amount' => [new PaymentAmountsBalanceRule(),new ValidCreditsPresentRule()], 'date' => 'required', 'client_id' => 'required', 'invoices' => new ValidPayableInvoicesRule(), - 'number' => 'nullable|unique', + 'number' => 'nullable', ]; return $rules; diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 2ac08e5145..22b78e0f0f 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -12,13 +12,16 @@ namespace App\Http\Requests\Payment; use App\Http\Requests\Request; +use App\Http\ValidationRules\PaymentAppliedValidAmount; +use App\Http\ValidationRules\ValidCreditsPresentRule; use App\Utils\Traits\ChecksEntityStatus; +use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; class UpdatePaymentRequest extends Request { use ChecksEntityStatus; - + use MakesHash; /** * Determine if the user is authorized to make this request. * @@ -34,8 +37,38 @@ class UpdatePaymentRequest extends Request public function rules() { return [ + 'invoices' => ['required','array','min:1',new PaymentAppliedValidAmount,new ValidCreditsPresentRule], 'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', - 'client_id' => 'required', ]; } + + protected function prepareForValidation() + { + $input = $this->all(); + + if(isset($input['client_id'])) + unset($input['client_id']); + + if(isset($input['amount'])) + unset($input['amount']); + + if(isset($input['type_id'])) + unset($input['type_id']); + + if(isset($input['date'])) + unset($input['date']); + + if(isset($input['transaction_reference'])) + unset($input['transaction_reference']); + + if(isset($input['number'])) + unset($input['number']); + + if (isset($input['invoices']) && is_array($input['invoices']) !== false) { + foreach ($input['invoices'] as $key => $value) { + $input['invoices'][$key]['id'] = $this->decodePrimaryKey($value['id']); + } + } + $this->replace($input); + } } diff --git a/app/Http/Requests/Product/StoreProductRequest.php b/app/Http/Requests/Product/StoreProductRequest.php index a50b76513e..b146070664 100644 --- a/app/Http/Requests/Product/StoreProductRequest.php +++ b/app/Http/Requests/Product/StoreProductRequest.php @@ -30,7 +30,7 @@ class StoreProductRequest extends Request public function rules() { return [ - 'product_key' => 'required|unique:products,product_key,null,null,company_id,'.auth()->user()->companyId(), + //'product_key' => 'required|unique:products,product_key,null,null,company_id,'.auth()->user()->companyId(), 'cost' => 'numeric', 'price' => 'numeric', 'quantity' => 'numeric', diff --git a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php new file mode 100644 index 0000000000..e437c9a044 --- /dev/null +++ b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php @@ -0,0 +1,72 @@ +calculateAmounts(); + } + + /** + * @return string + */ + public function message() + { + return 'Amounts do not balance correctly.'; + } + + private function calculateAmounts() :bool + { + $data = []; + $payment_amounts = 0; + $invoice_amounts = 0; + + $payment_amounts += request()->input('amount'); + + if(request()->input('credits') && is_array(request()->input('credits'))) + { + foreach(request()->input('credits') as $credit) + { + $payment_amounts += $credit['amount']; + } + } + + if(request()->input('invoices') && is_array(request()->input('invoices'))) + { + foreach(request()->input('invoices') as $invoice) + { + $invoice_amounts =+ $invoice['amount']; + } + } + else + return true; // if no invoices are present, then this is an unapplied payment, let this pass validation! + + return $payment_amounts >= $invoice_amounts; + + } +} diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php new file mode 100644 index 0000000000..3711f08322 --- /dev/null +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -0,0 +1,79 @@ +calculateAmounts(); + } + + /** + * @return string + */ + public function message() + { + return 'Insufficient applied amount remaining to cover payment.'; + } + + private function calculateAmounts() :bool + { + + $payment = Payment::whereId($this->decodePrimaryKey(request()->segment(4)))->company()->first(); + + if(!$payment) + return false; + + $data = []; + $payment_amounts = 0; + $invoice_amounts = 0; + + $payment_amounts = $payment->amount - $payment->applied; + + if(request()->input('credits') && is_array(request()->input('credits'))) + { + foreach(request()->input('credits') as $credit) + { + $payment_amounts += $credit['amount']; + } + } + + if(request()->input('invoices') && is_array(request()->input('invoices'))) + { + foreach(request()->input('invoices') as $invoice) + { + $invoice_amounts =+ $invoice['amount']; + } + } + + return $payment_amounts >= $invoice_amounts; + + } +} diff --git a/app/Http/ValidationRules/ValidCreditsPresentRule.php b/app/Http/ValidationRules/ValidCreditsPresentRule.php new file mode 100644 index 0000000000..1471cb7043 --- /dev/null +++ b/app/Http/ValidationRules/ValidCreditsPresentRule.php @@ -0,0 +1,65 @@ +validCreditsPresent(); + } + + /** + * @return string + */ + public function message() + { + return 'Attempting to use one or more invalid credits'; + } + + + + private function validCreditsPresent() :bool + { + $data = []; +//todo need to ensure the clients credits are here not random ones! + + if(request()->input('credits') && is_array(request()->input('credits'))) + { + foreach(request()->input('credits') as $credit) + { + $cred = Credit::find($credit['id']); + + if($cred->balance >= $credit['amount']) + return false; + } + } + + return true; + + } +} diff --git a/app/Jobs/Credit/ApplyCreditPayment.php b/app/Jobs/Credit/ApplyCreditPayment.php index 2ebdf56988..d37bf12a26 100644 --- a/app/Jobs/Credit/ApplyCreditPayment.php +++ b/app/Jobs/Credit/ApplyCreditPayment.php @@ -1,27 +1,27 @@ invoice = $invoice; + $this->credit = $credit; $this->payment = $payment; $this->amount = $amount; $this->company = $company; @@ -63,46 +63,25 @@ class ApplyCreditPayment implements ShouldQueue { MultiDB::setDB($this->company->db); - UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($this->amount*-1), $this->company); - UpdateClientBalance::dispatchNow($this->payment->client, $this->amount*-1, $this->company); - UpdateClientPaidToDate::dispatchNow($this->payment->client, $this->amount, $this->company); - /* Update Pivot Record amount */ - $this->payment->invoices->each(function ($inv) { - if ($inv->id == $this->invoice->id) { - $inv->pivot->amount = $this->amount; - $inv->pivot->save(); + $this->payment->credits->each(function ($cred) { + if ($cred->id == $this->credit->id) { + $cred->pivot->amount = $this->amount; + $cred->pivot->save(); } }); - if ($this->invoice->hasPartial()) { - //is partial and amount is exactly the partial amount - if ($this->invoice->partial == $this->amount) { - $this->invoice->clearPartial(); - $this->invoice->setDueDate(); - $this->invoice->setStatus(Invoice::STATUS_PARTIAL); - $this->invoice->updateBalance($this->amount*-1); - } elseif ($this->invoice->partial > 0 && $this->invoice->partial > $this->amount) { //partial amount exists, but the amount is less than the partial amount - $this->invoice->partial -= $this->amount; - $this->invoice->updateBalance($this->amount*-1); - } elseif ($this->invoice->partial > 0 && $this->invoice->partial < $this->amount) { //partial exists and the amount paid is GREATER than the partial amount - $this->invoice->clearPartial(); - $this->invoice->setDueDate(); - $this->invoice->setStatus(Invoice::STATUS_PARTIAL); - $this->invoice->updateBalance($this->amount*-1); - } - } elseif ($this->amount == $this->invoice->balance) { //total invoice paid. - $this->invoice->clearPartial(); - //$this->invoice->setDueDate(); - $this->invoice->setStatus(Invoice::STATUS_PAID); - $this->invoice->updateBalance($this->amount*-1); - } elseif($this->amount < $this->invoice->balance) { //partial invoice payment made - $this->invoice->clearPartial(); - $this->invoice->updateBalance($this->amount*-1); + $credit_balance = $this->credit->balance; + + if ($this->amount == $credit_balance) { //total credit applied. + $this->credit->setStatus(Credit::STATUS_APPLIED); + $this->credit->updateBalance($this->amount*-1); + } elseif($this->amount < $credit_balance) { //compare number appropriately + $this->credit->setStatus(Credit::PARTIAL); + $this->credit->updateBalance($this->amount*-1); } /* Update Payment Applied Amount*/ - $this->payment->applied += $this->amount; $this->payment->save(); } diff --git a/app/Jobs/Invoice/ApplyInvoicePayment.php b/app/Jobs/Invoice/ApplyInvoicePayment.php index 95819576c6..bee6178327 100644 --- a/app/Jobs/Invoice/ApplyInvoicePayment.php +++ b/app/Jobs/Invoice/ApplyInvoicePayment.php @@ -65,7 +65,7 @@ class ApplyInvoicePayment implements ShouldQueue UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($this->amount*-1), $this->company); UpdateClientBalance::dispatchNow($this->payment->client, $this->amount*-1, $this->company); - UpdateClientPaidToDate::dispatchNow($this->payment->client, $this->amount, $this->company); + //UpdateClientPaidToDate::dispatchNow($this->payment->client, $this->amount, $this->company); /* Update Pivot Record amount */ $this->payment->invoices->each(function ($inv) { @@ -102,8 +102,8 @@ class ApplyInvoicePayment implements ShouldQueue } /* Update Payment Applied Amount*/ - $this->payment->applied += $this->amount; - $this->payment->save(); + // $this->payment->applied += $this->amount; + // $this->payment->save(); } diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 7a8c3d5137..9fb6673368 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -130,4 +130,38 @@ class Credit extends BaseModel return $credit_calc->build(); } + + + + + /** + * @param float $balance_adjustment + */ + public function updateBalance($balance_adjustment) + { + if ($this->is_deleted) { + return; + } + + $balance_adjustment = floatval($balance_adjustment); + + $this->balance = $this->balance + $balance_adjustment; + + if ($this->balance == 0) { + $this->status_id = self::STATUS_APPLIED; + $this->save(); + //event(new InvoiceWasPaid($this, $this->company));//todo + + return; + } + + $this->save(); + } + + public function setStatus($status) + { + $this->status_id = $status; + $this->save(); + } + } diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 7a5f03ce5b..00347159ed 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -13,6 +13,7 @@ namespace App\Repositories; use App\Events\Payment\PaymentWasCreated; use App\Factory\CreditFactory; +use App\Jobs\Client\UpdateClientPaidToDate; use App\Jobs\Company\UpdateCompanyLedgerWithPayment; use App\Jobs\Invoice\ApplyClientPayment; use App\Jobs\Invoice\ApplyInvoicePayment; @@ -45,7 +46,7 @@ class PaymentRepository extends BaseRepository } /** - * Saves a payment. + * Saves and updates a payment. //todo refactor to handle refunds and payments. * * * @param Request $request the request object @@ -54,22 +55,32 @@ class PaymentRepository extends BaseRepository */ public function save(Request $request, Payment $payment) : ?Payment { - //todo this save() only works for new payments... will fail if a Payment is updated and saved through here. - $payment->fill($request->input()); + //todo this save() only works for new payments... will fail if a Payment is updated and saved through here. + $payment->fill($request->all()); $payment->status_id = Payment::STATUS_COMPLETED; - $payment->number = $payment->client->getNextPaymentNumber($payment->client); $payment->save(); + + if(!$payment->number) + $payment->number = $payment->client->getNextPaymentNumber($payment->client); + //we only ever update the ACTUAL amount of money transferred + UpdateClientPaidToDate::dispatchNow($payment->client, $payment->amount, $payment->company); + + $invoice_totals = 0; + $credit_totals = 0; + if ($request->input('invoices') && is_array($request->input('invoices'))) { - + + $invoice_totals = array_sum(array_column($request->input('invoices'),'amount')); + $invoices = Invoice::whereIn('id', array_column($request->input('invoices'), 'id'))->company()->get(); $payment->invoices()->saveMany($invoices); - + foreach ($request->input('invoices') as $paid_invoice) { - $invoice = Invoice::whereId($this->decodePrimaryKey($paid_invoice['id'])->company()->first(); + $invoice = Invoice::whereId($paid_invoice['id'])->company()->first(); if ($invoice) { ApplyInvoicePayment::dispatchNow($invoice, $payment, $paid_invoice['amount'], $invoice->company); @@ -82,13 +93,16 @@ class PaymentRepository extends BaseRepository if($request->input('credits') && is_array($request->input('credits'))) { + + $credit_totals = array_sum(array_column($request->input('credits'),'amount')); + $credits = Credit::whereIn('id', array_column($request->input('credits'), 'id'))->company()->get(); $payment->credits()->saveMany($credits); foreach ($request->input('credits') as $paid_credit) { - $credit = Credit::whereId($this->decodePrimaryKey($paid_credit['id'])->company()->first(); + $credit = Credit::whereId($paid_credit['id'])->company()->first(); if($credit) ApplyCreditPayment::dispatchNow($paid_credit, $payment, $paid_credit['amount'], $credit->company); @@ -98,7 +112,15 @@ class PaymentRepository extends BaseRepository event(new PaymentWasCreated($payment, $payment->company)); + $invoice_totals -= $credit_totals; + + if($invoice_totals == $payment->amount) + $payment->applied = $payment->amount; + elseif($invoice_totals < $payment->amount) + $payment->applied = $invoice_totals; + //UpdateInvoicePayment::dispatchNow($payment); + $payment->save(); return $payment->fresh(); } @@ -124,8 +146,22 @@ class PaymentRepository extends BaseRepository } + private function applyPayment(Request $request, Payment $payment) :?Payment { + $invoice_totals = array_sum(array_column($request->input('invoices'),'amount')); + + $invoices = Invoice::whereIn('id', array_column($request->input('invoices'), 'id'))->company()->get(); + + $payment->invoices()->saveMany($invoices); + + foreach ($request->input('invoices') as $paid_invoice) { + $invoice = Invoice::whereId($paid_invoice['id'])->company()->first(); + + if ($invoice) { + ApplyInvoicePayment::dispatchNow($invoice, $payment, $paid_invoice['amount'], $invoice->company); + } + } } @@ -170,6 +206,8 @@ class PaymentRepository extends BaseRepository $client = $payment->client; $client->paid_to_date += $invoice_total_adjustment; + $payment->save(); + $client->save(); } diff --git a/database/factories/CreditFactory.php b/database/factories/CreditFactory.php new file mode 100644 index 0000000000..b1741d74aa --- /dev/null +++ b/database/factories/CreditFactory.php @@ -0,0 +1,32 @@ +define(App\Models\Credit::class, function (Faker $faker) { + return [ + 'status_id' => App\Models\Credit::STATUS_DRAFT, + 'number' => $faker->ean13(), + 'discount' => $faker->numberBetween(1,10), + 'is_amount_discount' => (bool)random_int(0,1), + 'tax_name1' => 'GST', + 'tax_rate1' => 10, + 'tax_name2' => 'VAT', + 'tax_rate2' => 17.5, + //'tax_name3' => 'THIRDTAX', + //'tax_rate3' => 5, + // 'custom_value1' => $faker->numberBetween(1,4), + // 'custom_value2' => $faker->numberBetween(1,4), + // 'custom_value3' => $faker->numberBetween(1,4), + // 'custom_value4' => $faker->numberBetween(1,4), + 'is_deleted' => false, + 'po_number' => $faker->text(10), + 'date' => $faker->date(), + 'due_date' => $faker->date(), + 'line_items' => InvoiceItemFactory::generateCredit(5), + 'backup' => '', + 'terms' => $faker->text(500), + ]; +}); \ No newline at end of file diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 576401809e..2b610f595c 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -100,7 +100,7 @@ class ClientApiTest extends TestCase $arr = $response->json(); - $this->assertNull($arr['data']['deleted_at']); + $this->assertNull($arr['data']['archived_at']); } public function testClientArchived() @@ -117,7 +117,7 @@ class ClientApiTest extends TestCase $arr = $response->json(); \Log::error($arr); - $this->assertNotNull($arr['data'][0]['deleted_at']); + $this->assertNotNull($arr['data'][0]['archived_at']); } public function testClientRestored() @@ -134,7 +134,7 @@ class ClientApiTest extends TestCase $arr = $response->json(); - $this->assertNull($arr['data'][0]['deleted_at']); + $this->assertNull($arr['data'][0]['archived_at']); } public function testClientDeleted() diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index a22ea7a064..df35009d21 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -18,7 +18,6 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; use Illuminate\Validation\ValidationException; use Tests\MockAccountData; @@ -35,6 +34,7 @@ class PaymentTest extends TestCase use MakesHash; use DatabaseTransactions; use MockAccountData; + public function setUp() :void { @@ -74,8 +74,9 @@ class PaymentTest extends TestCase $client = Client::all()->first(); - factory(\App\Models\Payment::class, 1)->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $client->id]); - + factory(\App\Models\Payment::class, 1)->create( + ['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $client->id] + ); $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), @@ -319,7 +320,7 @@ class PaymentTest extends TestCase catch(ValidationException $e) { $message = json_decode($e->validator->getMessageBag(),1); $this->assertNotNull($message); - \Log::error($message); + //\Log::error($message); } if($response) { @@ -466,4 +467,472 @@ class PaymentTest extends TestCase $this->assertEquals($invoice->balance, 8); } + + public function testPaymentValidationAmount() + { + + $this->invoice = null; + + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->partial = 5.0; + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + $this->invoice->save(); + + + $data = [ + 'amount' => 1.0, + 'client_id' => $client->hashed_id, + 'invoices' => [ + [ + 'id' => $this->invoice->hashed_id, + 'amount' => 2.0 + ], + ], + 'date' => '2019/12/12', + ]; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments?include=invoices', $data); + + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + + $this->assertTrue(array_key_exists('amount', $message)); + + } + + // if($response) + // $response->assertStatus(200); + } + + + public function testPaymentChangesBalancesCorrectly() + { + + $this->invoice = null; + + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + $this->invoice->save(); + + + $data = [ + 'amount' => 2.0, + 'client_id' => $client->hashed_id, + 'invoices' => [ + [ + 'id' => $this->invoice->hashed_id, + 'amount' => 2.0 + ], + ], + 'date' => '2019/12/12', + ]; + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments?include=invoices', $data); + + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + + $this->assertTrue(array_key_exists('amount', $message)); + + } + + if($response){ + $response->assertStatus(200); + + $invoice = Invoice::find($this->decodePrimaryKey($this->invoice->hashed_id)); + + $this->assertEquals($invoice->balance, 8); + + $payment = $invoice->payments()->first(); + + $this->assertEquals($payment->applied, 2); + } + } + + public function testUpdatePaymentValidationWorks() + { + + $this->invoice = null; + + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + $this->invoice->save(); + + $payment = PaymentFactory::create($this->company->id, $this->user->id); + $payment->amount = 10; + $payment->client_id = $client->id; + $payment->date = now(); + $payment->save(); + + + $data = [ + 'amount' => 2.0, + 'client_id' => $client->hashed_id, + 'invoices' => [], + 'date' => '2019/12/12', + ]; + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/payments/'.$this->encodePrimaryKey($payment->id), $data); + + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + + $this->assertTrue(array_key_exists('invoices', $message)); + + } + + if($response) + $response->assertStatus(302); + } + + + public function testUpdatePaymentValidationPasses() + { + + $this->invoice = null; + + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + $this->invoice->save(); + + $payment = PaymentFactory::create($this->company->id, $this->user->id); + $payment->amount = 10; + $payment->client_id = $client->id; + $payment->date = now(); + $payment->number = $client->getNextPaymentNumber($client); + $payment->save(); + + + $data = [ + 'amount' => 10.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'id' => $this->encodePrimaryKey($this->invoice->id), + 'amount' => 10, + ] + ], + 'date' => '2019/12/12', + ]; + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/payments/'.$this->encodePrimaryKey($payment->id), $data); + + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + \Log::error(print_r($e->validator->getMessageBag(),1)); + + $this->assertTrue(array_key_exists('invoices', $message)); + + } + + if($response) + $response->assertStatus(200); + } + + + public function testDoublePaymentTestWithInvalidAmounts() + { + + $this->invoice = null; + + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + + $data = [ + 'amount' => 15.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'id' => $this->encodePrimaryKey($this->invoice->id), + 'amount' => 10, + ] + ], + 'date' => '2019/12/12', + ]; + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/', $data); + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + \Log::error(print_r($e->validator->getMessageBag(),1)); + } + + $response->assertStatus(200); + + $arr = $response->json(); + + $payment_id = $arr['data']['id']; + + $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + + $this->assertEquals($payment->amount, 15); + $this->assertEquals($payment->applied, 10); + + $this->invoice = null; + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + + + $data = [ + 'amount' => 15.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'id' => $this->encodePrimaryKey($this->invoice->id), + 'amount' => 10, + ] + ], + 'date' => '2019/12/12', + ]; + + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/payments/'.$this->encodePrimaryKey($payment->id), $data); + + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + \Log::error(print_r($e->validator->getMessageBag(),1)); + + $this->assertTrue(array_key_exists('invoices', $message)); + \Log::error('hit error'); + } + + //$response->assertStatus(302); + + } + + + public function testDoublePaymentTestWithValidAmounts() + { + + $this->invoice = null; + + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + + $data = [ + 'amount' => 20.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'id' => $this->encodePrimaryKey($this->invoice->id), + 'amount' => 10, + ] + ], + 'date' => '2019/12/12', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $payment_id = $arr['data']['id']; + + $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + + $this->assertEquals($payment->amount, 20); + $this->assertEquals($payment->applied, 10); + + $this->invoice = null; + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + $this->invoice->markSent(); + + + $data = [ + 'amount' => 20.0, + 'client_id' => $this->encodePrimaryKey($client->id), + 'invoices' => [ + [ + 'id' => $this->encodePrimaryKey($this->invoice->id), + 'amount' => 10, + ] + ], + 'date' => '2019/12/12', + ]; + + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/payments/'.$this->encodePrimaryKey($payment->id), $data); + + } + catch(ValidationException $e) { + + $message = json_decode($e->validator->getMessageBag(),1); + \Log::error(print_r($e->validator->getMessageBag(),1)); + + $this->assertTrue(array_key_exists('invoices', $message)); + \Log::error('hit error'); + } + + $response->assertStatus(200); + + } } diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index 93c00e969d..622f588e45 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -56,7 +56,7 @@ class UserTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', ])->get('/api/v1/users'); $response->assertStatus(200); diff --git a/tests/Integration/MultiDBUserTest.php b/tests/Integration/MultiDBUserTest.php index 05c8016ed1..5208dd73a1 100644 --- a/tests/Integration/MultiDBUserTest.php +++ b/tests/Integration/MultiDBUserTest.php @@ -62,7 +62,7 @@ class MultiDBUserTest extends TestCase 'last_name' => 'user_db_1-s', 'phone' => '55555', 'email_verified_at' => now(), - 'password' => 'ALongAndBriliantPassword', // secret + 'password' => Hash::make('ALongAndBriliantPassword'), // secret 'remember_token' => \Illuminate\Support\Str::random(10), 'email' => 'db1@example.com', 'oauth_user_id' => '123', diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index a2e4bafcc8..c511b990b0 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -37,6 +37,7 @@ use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesHash; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Schema; /** @@ -101,7 +102,7 @@ trait MockAccountData if(!$this->user){ $this->user = factory(\App\Models\User::class)->create([ - 'password' => 'ALongAndBriliantPassword', + 'password' => Hash::make('ALongAndBriliantPassword'), 'confirmation_code' => $this->createDbHash(config('database.default')) ]); }