From 6949cdd5028c8f43d31ec48c5a203e66bea892d9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 25 Oct 2021 14:32:22 +1100 Subject: [PATCH 1/8] minor fixes for paypal express on failure --- app/PaymentDrivers/PayPalExpressPaymentDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/PaymentDrivers/PayPalExpressPaymentDriver.php b/app/PaymentDrivers/PayPalExpressPaymentDriver.php index 2ee33668b8d7..43b2a6a1ad4a 100644 --- a/app/PaymentDrivers/PayPalExpressPaymentDriver.php +++ b/app/PaymentDrivers/PayPalExpressPaymentDriver.php @@ -93,7 +93,7 @@ class PayPalExpressPaymentDriver extends BaseDriver return $response->redirect(); } - $this->sendFailureMail($response->getData()); + $this->sendFailureMail($response->getMessage()); $message = [ 'server_response' => $response->getMessage(), From b8e81df0587a989f291ef12a283f3b0f3493d3b6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 26 Oct 2021 08:00:30 +1100 Subject: [PATCH 2/8] Add some exceptions to the ignore list --- app/Exceptions/Handler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 951767d0ecda..afe61dd9e7f1 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -53,6 +53,7 @@ class Handler extends ExceptionHandler CommandNotFoundException::class, ValidationException::class, ModelNotFoundException::class, + NotFoundHttpException::class, ]; /** From 70e0a1db52c19866888772d59cf6b50cfc1da51c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 26 Oct 2021 14:44:34 +1100 Subject: [PATCH 3/8] Client payment failure emails --- app/Jobs/Mail/PaymentFailedMailer.php | 19 +++++++++++++++++++ app/Mail/Admin/ClientPaymentFailureObject.php | 5 ++--- resources/lang/en/texts.php | 5 +++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/Jobs/Mail/PaymentFailedMailer.php b/app/Jobs/Mail/PaymentFailedMailer.php index 5512c4b1a986..9d84a7459502 100644 --- a/app/Jobs/Mail/PaymentFailedMailer.php +++ b/app/Jobs/Mail/PaymentFailedMailer.php @@ -15,6 +15,7 @@ use App\Jobs\Mail\NinjaMailer; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Libraries\MultiDB; +use App\Mail\Admin\ClientPaymentFailureObject; use App\Mail\Admin\EntityNotificationMailer; use App\Mail\Admin\PaymentFailureObject; use App\Models\Client; @@ -102,6 +103,24 @@ class PaymentFailedMailer implements ShouldQueue }); //add client payment failures here. + nlog("pre client failure email"); + + if($contact = $this->client->primary_contact()->first()) + { + + nlog("inside failure"); + + $mail_obj = (new ClientPaymentFailureObject($this->client, $this->error, $this->company, $this->payment_hash))->build(); + + $nmo = new NinjaMailerObject; + $nmo->mailable = new NinjaMailer($mail_obj); + $nmo->company = $this->company; + $nmo->to_user = $contact; + $nmo->settings = $settings; + + NinjaMailerJob::dispatch($nmo); + } + } diff --git a/app/Mail/Admin/ClientPaymentFailureObject.php b/app/Mail/Admin/ClientPaymentFailureObject.php index c18101b2edae..df13ad8a5049 100644 --- a/app/Mail/Admin/ClientPaymentFailureObject.php +++ b/app/Mail/Admin/ClientPaymentFailureObject.php @@ -14,7 +14,6 @@ namespace App\Mail\Admin; use App\Models\Invoice; use App\Utils\HtmlEngine; use App\Utils\Ninja; -use App\Utils\Number; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\App; use stdClass; @@ -91,7 +90,7 @@ class ClientPaymentFailureObject return ctrans( 'texts.notification_invoice_payment_failed_subject', - ['invoice' => $this->client->present()->name()] + ['invoice' => implode(",", $this->invoices->pluck('number')->toArray())] ); } @@ -110,7 +109,7 @@ class ClientPaymentFailureObject ] ), 'greeting' => ctrans('texts.email_salutation', ['name' => $this->client->present()->name]), - 'message' => $this->error, + 'message' => ctrans('texts.client_payment_failure_body', ['invoice' => implode(",", $this->invoices->pluck('number')->toArray()), 'amount' => $this->getAmount()]), 'signature' => $signature, 'logo' => $this->company->present()->logo(), 'settings' => $this->client->getMergedSettings(), diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 441e7382691c..101c949af91d 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1400,7 +1400,7 @@ $LANG = array( 'more_options' => 'More options', 'credit_card' => 'Credit Card', 'bank_transfer' => 'Bank Transfer', - 'no_transaction_reference' => 'We did not recieve a payment transaction reference from the gateway.', + 'no_transaction_reference' => 'We did not receive a payment transaction reference from the gateway.', 'use_bank_on_file' => 'Use Bank on File', 'auto_bill_email_message' => 'This invoice will automatically be billed to the payment method on file on the due date.', 'bitcoin' => 'Bitcoin', @@ -4334,7 +4334,8 @@ $LANG = array( 'clone_to_expense' => 'Clone to expense', 'checkout' => 'Checkout', 'acss' => 'Pre-authorized debit payments', - 'invalid_amount' => 'Invalid amount. Number/Decimal values only.' + 'invalid_amount' => 'Invalid amount. Number/Decimal values only.', + 'client_payment_failure_body' => 'Payment for Invoice :invoice for amount :amount failed.', ); return $LANG; From 9c21e096a165a611d7b97e161759e0f65f244a59 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 26 Oct 2021 16:25:16 +1100 Subject: [PATCH 4/8] Update validation rules for update task status --- app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php b/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php index 817e48d6a3e4..e7e82f5a1187 100644 --- a/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php +++ b/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php @@ -33,9 +33,10 @@ class UpdateTaskStatusRequest extends Request { $rules = []; - if ($this->input('name')) { - $rules['name'] = Rule::unique('task_statuses')->where('company_id', auth()->user()->company()->id)->ignore($this->task_status->id); - } + // 26/10/2021 we disable this as it prevent updating existing task status meta data where the same name already exists + // if ($this->input('name')) { + // $rules['name'] = Rule::unique('task_statuses')->where('company_id', auth()->user()->company()->id)->ignore($this->task_status->id); + // } return $rules; From f34d02a3713974092625769d357ece81c9c963a3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 26 Oct 2021 20:39:33 +1100 Subject: [PATCH 5/8] Minor fixes for create company --- app/Http/Controllers/CompanyController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 5ac5ed157a78..b83d472977f0 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -240,7 +240,7 @@ class CompanyController extends BaseController /* * Create token */ - $user_agent = request()->input('token_name') ?: request()->server('HTTP_USER_AGENT'); + $user_agent = request()->has('token_name') ? request()->input('token_name') : request()->server('HTTP_USER_AGENT'); $company_token = CreateCompanyToken::dispatchNow($company, auth()->user(), $user_agent); From faf77d8bd960097710f832411930005bf77d5e8a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 26 Oct 2021 21:47:28 +1100 Subject: [PATCH 6/8] Fixes for deleting an unapplied payment --- app/Services/Payment/DeletePayment.php | 13 ++- .../Payments/UnappliedPaymentDeleteTest.php | 101 ++++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 tests/Feature/Payments/UnappliedPaymentDeleteTest.php diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index b486180550ca..9e531e19b371 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -115,15 +115,20 @@ class DeletePayment ->updatePaidToDate($net_deletable * -1) ->save(); - // $paymentable_invoice->client - // ->service() - // ->updatePaidToDate($net_deletable * -1) - // ->save(); + } }); } + else { + $this->payment + ->client + ->service() + ->updatePaidToDate(($this->payment->amount - $this->payment->applied)*-1) + ->save(); + + } return $this; } diff --git a/tests/Feature/Payments/UnappliedPaymentDeleteTest.php b/tests/Feature/Payments/UnappliedPaymentDeleteTest.php new file mode 100644 index 000000000000..91794770cd63 --- /dev/null +++ b/tests/Feature/Payments/UnappliedPaymentDeleteTest.php @@ -0,0 +1,101 @@ +faker = \Faker\Factory::create(); + + $this->makeTestData(); + $this->withoutExceptionHandling(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + public function testUnappliedPaymentDelete() + { + + + $data = [ + 'amount' => 1000, + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + ], + 'date' => '2020/12/12', + + ]; + + $response = null; + + 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); + $this->assertNotNull($message); + } + + if ($response){ + $arr = $response->json(); + $response->assertStatus(200); + + $this->assertEquals(0, $this->client->paid_to_date); + + $payment_id = $arr['data']['id']; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->delete('/api/v1/payments/'. $payment_id); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + $this->assertNotNull($message); + } + + $response->assertStatus(200); + + $this->assertEquals(0, $this->client->fresh()->paid_to_date); + + } + + + + } + +} \ No newline at end of file From 7e23de8e5a0731f0618f3a3ed0755096c92a60b8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 27 Oct 2021 05:01:10 +1100 Subject: [PATCH 7/8] fixes for refunding unapplied payments --- app/Http/Controllers/PaymentController.php | 2 - .../PaymentAppliedValidAmount.php | 2 +- app/Services/Payment/DeletePayment.php | 2 + app/Services/Payment/RefundPayment.php | 10 +- .../Payments/UnappliedPaymentDeleteTest.php | 6 +- .../Payments/UnappliedPaymentRefundTest.php | 110 ++++++++++++++++++ 6 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 tests/Feature/Payments/UnappliedPaymentRefundTest.php diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index e0ab42bdc038..b612c6388aa5 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -683,8 +683,6 @@ class PaymentController extends BaseController { $payment = $request->payment(); -// nlog($request->all()); - $payment = $payment->refund($request->all()); return $this->itemResponse($payment); diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php index e3a6e3c7dfa5..292712a1d738 100644 --- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -51,7 +51,7 @@ class PaymentAppliedValidAmount implements Rule $payment_amounts = 0; $invoice_amounts = 0; - $payment_amounts = $payment->amount - $payment->applied; + $payment_amounts = $payment->amount - $payment->refunded - $payment->applied; if (request()->input('credits') && is_array(request()->input('credits'))) { foreach (request()->input('credits') as $credit) { diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 9e531e19b371..626327f803a7 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -122,6 +122,8 @@ class DeletePayment } else { + /* If there are no invoices - then we need to still adjust the total client->paid_to_date amount*/ + $this->payment ->client ->service() diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 2b44eae951e8..0dfa6fa355eb 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -267,9 +267,17 @@ class RefundPayment // $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string); $client = $this->payment->client->fresh(); - //$client->service()->updatePaidToDate(-1 * $this->total_refund)->save(); + $client->service()->updatePaidToDate(-1 * $refunded_invoice['amount'])->save(); } + else{ + //if we are refunding and no payments have been tagged, then we need to decrement the client->paid_to_date by the total refund amount. + + $client = $this->payment->client->fresh(); + + $client->service()->updatePaidToDate(-1 * $this->total_refund)->save(); + + } return $this; } diff --git a/tests/Feature/Payments/UnappliedPaymentDeleteTest.php b/tests/Feature/Payments/UnappliedPaymentDeleteTest.php index 91794770cd63..bc93dcfa51d5 100644 --- a/tests/Feature/Payments/UnappliedPaymentDeleteTest.php +++ b/tests/Feature/Payments/UnappliedPaymentDeleteTest.php @@ -48,7 +48,6 @@ class UnappliedPaymentDeleteTest extends TestCase public function testUnappliedPaymentDelete() { - $data = [ 'amount' => 1000, 'client_id' => $this->client->hashed_id, @@ -74,9 +73,12 @@ class UnappliedPaymentDeleteTest extends TestCase $arr = $response->json(); $response->assertStatus(200); - $this->assertEquals(0, $this->client->paid_to_date); $payment_id = $arr['data']['id']; + $payment = Payment::with('client')->find($this->decodePrimaryKey($payment_id)); + + $this->assertEquals(1000, $payment->amount); + $this->assertEquals(1000, $payment->client->paid_to_date); try { $response = $this->withHeaders([ diff --git a/tests/Feature/Payments/UnappliedPaymentRefundTest.php b/tests/Feature/Payments/UnappliedPaymentRefundTest.php new file mode 100644 index 000000000000..bd4cceb96e5b --- /dev/null +++ b/tests/Feature/Payments/UnappliedPaymentRefundTest.php @@ -0,0 +1,110 @@ +faker = \Faker\Factory::create(); + + $this->makeTestData(); + $this->withoutExceptionHandling(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + public function testUnappliedPaymentRefund() + { + + $data = [ + 'amount' => 1000, + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + ], + 'date' => '2020/12/12', + + ]; + + $response = null; + + 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); + $this->assertNotNull($message); + } + + if ($response){ + $arr = $response->json(); + $response->assertStatus(200); + + $this->assertEquals(1000, $this->client->fresh()->paid_to_date); + + $payment_id = $arr['data']['id']; + + $this->assertEquals(1000, $arr['data']['amount']); + + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); + + $data = [ + 'id' => $this->encodePrimaryKey($payment->id), + 'amount' => 500, + 'date' => '2020/12/12', + ]; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/refund', $data); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + $this->assertNotNull($message); + } + + $response->assertStatus(200); + + $this->assertEquals(500, $this->client->fresh()->paid_to_date); + + } + + + + } + +} \ No newline at end of file From dad9836fa21b9ed1996172b27f98aea5851932b7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 27 Oct 2021 14:17:56 +1100 Subject: [PATCH 8/8] MInor fixes for client numbers --- app/Repositories/ClientRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 8dda9f5c692e..4765256cde03 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -65,7 +65,7 @@ class ClientRepository extends BaseRepository $client->fill($data); $client->save(); - if (!isset($client->number) || empty($client->number)) { + if (!isset($client->number) || empty($client->number) || strlen($client->number) == 0) { $client->number = $this->getNextClientNumber($client); }