diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 6e0ca307cdf2..36df31e76e8c 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -90,6 +90,11 @@ class Payment extends BaseModel return $this->belongsTo(Client::class)->withTrashed(); } + public function company_gateway() + { + return $this->belongsTo(CompanyGateway::class)->withTrashed(); + } + public function company() { return $this->belongsTo(Company::class); diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 1c84507ce082..32c0dc472999 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -44,8 +44,9 @@ class RefundPayment return $this->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment) ->setStatus() //sets status of payment - ->buildCreditNote() //generate the credit note - ->buildCreditLineItems() //generate the credit note items + //->reversePayment() + //->buildCreditNote() //generate the credit note + //->buildCreditLineItems() //generate the credit note items ->updateCreditables() //return the credits first ->updatePaymentables() //update the paymentable items ->adjustInvoices() @@ -53,19 +54,23 @@ class RefundPayment ->save(); } + /** + * Process the refund through the gateway + * + * @return $this + */ private function processGatewayRefund() { if ($this->refund_data['gateway_refund'] !== false && $this->total_refund > 0) { - $gateway = CompanyGateway::first(); - if ($gateway) { + if ($this->payment->company_gateway) { $response = $gateway->driver($this->payment->client)->refund($this->payment, $this->total_refund); if ($response['success']) { throw new PaymentRefundFailed(); } - $this->payment->refunded = $this->total_refund; + $this->payment->refunded += $this->total_refund; $this ->createActivity($gateway) @@ -78,20 +83,12 @@ class RefundPayment return $this; } - public function updateCreditNoteBalance() - { - $this->credit_note->balance -= $this->total_refund; - $this->credit_note->status_id = Credit::STATUS_APPLIED; - - $this->credit_note->balance === 0 - ? $this->credit_note->status_id = Credit::STATUS_APPLIED - : $this->credit_note->status_id = Credit::STATUS_PARTIAL; - - $this->credit_note->save(); - - return $this; - } - + /** + * Create the payment activity + * + * @param json $notes gateway_transaction + * @return $this + */ private function createActivity($notes) { $fields = new \stdClass; @@ -116,90 +113,43 @@ class RefundPayment return $this; } + /** + * Determine the amount of refund + * + * @return $this + */ private function calculateTotalRefund() { - if (array_key_exists('invoices', $this->refund_data) && count($this->refund_data['invoices']) > 0){ - info("array of invoice to refund"); + if (array_key_exists('invoices', $this->refund_data) && count($this->refund_data['invoices']) > 0) $this->total_refund = collect($this->refund_data['invoices'])->sum('amount'); - } - else{ - info("no invoices found - refunding total."); + else $this->total_refund = $this->refund_data['amount']; - } return $this; + } + /** + * Set the payment status + */ private function setStatus() { - if ($this->refund_data['amount'] == $this->payment->amount) { + + if ($this->refund_data['amount'] == $this->payment->amount) $this->payment->status_id = Payment::STATUS_REFUNDED; - } else { + else $this->payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED; - } - - return $this; - } - - private function buildCreditNote() - { - $this->credit_note = CreditFactory::create($this->payment->company_id, $this->payment->user_id); - $this->credit_note->assigned_user_id = isset($this->payment->assigned_user_id) ?: null; - $this->credit_note->date = $this->refund_data['date']; - $this->credit_note->status_id = Credit::STATUS_SENT; - $this->credit_note->client_id = $this->payment->client->id; - $this->credit_note->amount = $this->total_refund; - $this->credit_note->balance = $this->total_refund; - - $this->credit_note->save(); - $this->credit_note->number = $this->payment->client->getNextCreditNumber($this->payment->client); - $this->credit_note->save(); - - return $this; - } - - private function buildCreditLineItems() - { - $ledger_string = ''; - - if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { - foreach ($this->refund_data['invoices'] as $invoice) { - - $inv = Invoice::find($invoice['invoice_id']); - - $credit_line_item = InvoiceItemFactory::create(); - $credit_line_item->quantity = 1; - $credit_line_item->cost = $invoice['amount']; - $credit_line_item->product_key = ctrans('texts.invoice'); - $credit_line_item->notes = ctrans('texts.refund_body', ['amount' => $invoice['amount'], 'invoice_number' => $inv->number]); - $credit_line_item->line_total = $invoice['amount']; - $credit_line_item->date = $this->refund_data['date']; - - $ledger_string .= $credit_line_item->notes . ' '; - - $line_items[] = $credit_line_item; - } - } else { - - $credit_line_item = InvoiceItemFactory::create(); - $credit_line_item->quantity = 1; - $credit_line_item->cost = $this->refund_data['amount']; - $credit_line_item->product_key = ctrans('texts.credit'); - $credit_line_item->notes = ctrans('texts.credit_created_by', ['transaction_reference' => $this->payment->number]); - $credit_line_item->line_total = $this->refund_data['amount']; - $credit_line_item->date = $this->refund_data['date']; - - $line_items = []; - $line_items[] = $credit_line_item; - } - - $this->credit_note->line_items = $line_items; - $this->credit_note->save(); return $this; + } + /** + * Update the paymentable records + * + * @return $this + */ private function updatePaymentables() { if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { @@ -218,6 +168,12 @@ class RefundPayment return $this; } + /** + * If credits have been bundled in this payment, we + * need to reverse these + * + * @return $this + */ private function updateCreditables() { @@ -253,50 +209,134 @@ class RefundPayment return $this; } - + /** + * Reverse the payments made on invoices + * + * @return $this + */ private function adjustInvoices() { $adjustment_amount = 0; if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { - foreach ($this->refund_data['invoices'] as $refunded_invoice) { + + foreach ($this->refund_data['invoices'] as $refunded_invoice) + { + $invoice = Invoice::find($refunded_invoice['invoice_id']); $invoice->service()->updateBalance($refunded_invoice['amount'])->save(); - if ($invoice->amount == $invoice->balance) { + if ($invoice->amount == $invoice->balance) $invoice->service()->setStatus(Invoice::STATUS_SENT); - } else { + else $invoice->service()->setStatus(Invoice::STATUS_PARTIAL); - } - + $invoice->save(); $client = $invoice->client; $adjustment_amount += $refunded_invoice['amount']; $client->balance += $refunded_invoice['amount']; - $client->save(); //todo adjust ledger balance here? or after and reference the credit and its total } - $ledger_string = ''; //todo + $ledger_string = "Refund for Invoice {$invoice->number} for amount " . $refunded_invoice['amount']; //todo $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string); - $this->payment->client->paid_to_date -= $this->refund_data['amount']; - $this->payment->client->save(); + $client = $this->payment->client->fresh(); + $client->paid_to_date -= $this->total_refund; + $client->save(); } return $this; } + /** + * Saves the payment + * + * @return Payment $payment + */ private function save() { $this->payment->save(); return $this->payment; } + + // public function updateCreditNoteBalance() + // { + // $this->credit_note->balance -= $this->total_refund; + // $this->credit_note->status_id = Credit::STATUS_APPLIED; + + // $this->credit_note->balance === 0 + // ? $this->credit_note->status_id = Credit::STATUS_APPLIED + // : $this->credit_note->status_id = Credit::STATUS_PARTIAL; + + // $this->credit_note->save(); + + // return $this; + // } + + // private function buildCreditNote() + // { + // $this->credit_note = CreditFactory::create($this->payment->company_id, $this->payment->user_id); + // $this->credit_note->assigned_user_id = isset($this->payment->assigned_user_id) ?: null; + // $this->credit_note->date = $this->refund_data['date']; + // $this->credit_note->status_id = Credit::STATUS_SENT; + // $this->credit_note->client_id = $this->payment->client->id; + // $this->credit_note->amount = $this->total_refund; + // $this->credit_note->balance = $this->total_refund; + + // $this->credit_note->save(); + // $this->credit_note->number = $this->payment->client->getNextCreditNumber($this->payment->client); + // $this->credit_note->save(); + + // return $this; + // } + + // private function buildCreditLineItems() + // { + // $ledger_string = ''; + + // if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { + // foreach ($this->refund_data['invoices'] as $invoice) { + + // $inv = Invoice::find($invoice['invoice_id']); + + // $credit_line_item = InvoiceItemFactory::create(); + // $credit_line_item->quantity = 1; + // $credit_line_item->cost = $invoice['amount']; + // $credit_line_item->product_key = ctrans('texts.invoice'); + // $credit_line_item->notes = ctrans('texts.refund_body', ['amount' => $invoice['amount'], 'invoice_number' => $inv->number]); + // $credit_line_item->line_total = $invoice['amount']; + // $credit_line_item->date = $this->refund_data['date']; + + // $ledger_string .= $credit_line_item->notes . ' '; + + // $line_items[] = $credit_line_item; + // } + // } else { + + // $credit_line_item = InvoiceItemFactory::create(); + // $credit_line_item->quantity = 1; + // $credit_line_item->cost = $this->refund_data['amount']; + // $credit_line_item->product_key = ctrans('texts.credit'); + // $credit_line_item->notes = ctrans('texts.credit_created_by', ['transaction_reference' => $this->payment->number]); + // $credit_line_item->line_total = $this->refund_data['amount']; + // $credit_line_item->date = $this->refund_data['date']; + + // $line_items = []; + // $line_items[] = $credit_line_item; + // } + + // $this->credit_note->line_items = $line_items; + // $this->credit_note->save(); + + // return $this; + // } + } diff --git a/app/Services/Payment/RefundPaymentOld.php b/app/Services/Payment/RefundPaymentOld.php new file mode 100644 index 000000000000..1c84507ce082 --- /dev/null +++ b/app/Services/Payment/RefundPaymentOld.php @@ -0,0 +1,302 @@ +payment = $payment; + + $this->refund_data = $refund_data; + + $this->total_refund = 0; + + $this->gateway_refund_status = false; + + $this->activity_repository = new ActivityRepository(); + } + + public function run() + { + + return $this->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment) + ->setStatus() //sets status of payment + ->buildCreditNote() //generate the credit note + ->buildCreditLineItems() //generate the credit note items + ->updateCreditables() //return the credits first + ->updatePaymentables() //update the paymentable items + ->adjustInvoices() + ->processGatewayRefund() //process the gateway refund if needed + ->save(); + } + + private function processGatewayRefund() + { + if ($this->refund_data['gateway_refund'] !== false && $this->total_refund > 0) { + $gateway = CompanyGateway::first(); + + if ($gateway) { + $response = $gateway->driver($this->payment->client)->refund($this->payment, $this->total_refund); + + if ($response['success']) { + throw new PaymentRefundFailed(); + } + + $this->payment->refunded = $this->total_refund; + + $this + ->createActivity($gateway) + ->updateCreditNoteBalance(); + } + } else { + $this->payment->refunded += $this->total_refund; + } + + return $this; + } + + public function updateCreditNoteBalance() + { + $this->credit_note->balance -= $this->total_refund; + $this->credit_note->status_id = Credit::STATUS_APPLIED; + + $this->credit_note->balance === 0 + ? $this->credit_note->status_id = Credit::STATUS_APPLIED + : $this->credit_note->status_id = Credit::STATUS_PARTIAL; + + $this->credit_note->save(); + + return $this; + } + + private function createActivity($notes) + { + $fields = new \stdClass; + $activity_repo = new ActivityRepository(); + + $fields->payment_id = $this->payment->id; + $fields->user_id = $this->payment->user_id; + $fields->company_id = $this->payment->company_id; + $fields->activity_type_id = Activity::REFUNDED_PAYMENT; + $fields->credit_id = $this->credit_note->id; + $fields->notes = json_encode($notes); + + if (isset($this->refund_data['invoices'])) { + foreach ($this->refund_data['invoices'] as $invoice) { + $fields->invoice_id = $invoice['invoice_id']; + $activity_repo->save($fields, $this->payment); + } + } else { + $activity_repo->save($fields, $this->payment); + } + + return $this; + } + + private function calculateTotalRefund() + { + + if (array_key_exists('invoices', $this->refund_data) && count($this->refund_data['invoices']) > 0){ + info("array of invoice to refund"); + $this->total_refund = collect($this->refund_data['invoices'])->sum('amount'); + } + else{ + info("no invoices found - refunding total."); + $this->total_refund = $this->refund_data['amount']; + } + + return $this; + } + + private function setStatus() + { + if ($this->refund_data['amount'] == $this->payment->amount) { + $this->payment->status_id = Payment::STATUS_REFUNDED; + } else { + $this->payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED; + } + + return $this; + } + + private function buildCreditNote() + { + $this->credit_note = CreditFactory::create($this->payment->company_id, $this->payment->user_id); + $this->credit_note->assigned_user_id = isset($this->payment->assigned_user_id) ?: null; + $this->credit_note->date = $this->refund_data['date']; + $this->credit_note->status_id = Credit::STATUS_SENT; + $this->credit_note->client_id = $this->payment->client->id; + $this->credit_note->amount = $this->total_refund; + $this->credit_note->balance = $this->total_refund; + + $this->credit_note->save(); + $this->credit_note->number = $this->payment->client->getNextCreditNumber($this->payment->client); + $this->credit_note->save(); + + return $this; + } + + private function buildCreditLineItems() + { + $ledger_string = ''; + + if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { + foreach ($this->refund_data['invoices'] as $invoice) { + + $inv = Invoice::find($invoice['invoice_id']); + + $credit_line_item = InvoiceItemFactory::create(); + $credit_line_item->quantity = 1; + $credit_line_item->cost = $invoice['amount']; + $credit_line_item->product_key = ctrans('texts.invoice'); + $credit_line_item->notes = ctrans('texts.refund_body', ['amount' => $invoice['amount'], 'invoice_number' => $inv->number]); + $credit_line_item->line_total = $invoice['amount']; + $credit_line_item->date = $this->refund_data['date']; + + $ledger_string .= $credit_line_item->notes . ' '; + + $line_items[] = $credit_line_item; + } + } else { + + $credit_line_item = InvoiceItemFactory::create(); + $credit_line_item->quantity = 1; + $credit_line_item->cost = $this->refund_data['amount']; + $credit_line_item->product_key = ctrans('texts.credit'); + $credit_line_item->notes = ctrans('texts.credit_created_by', ['transaction_reference' => $this->payment->number]); + $credit_line_item->line_total = $this->refund_data['amount']; + $credit_line_item->date = $this->refund_data['date']; + + $line_items = []; + $line_items[] = $credit_line_item; + } + + $this->credit_note->line_items = $line_items; + $this->credit_note->save(); + + return $this; + } + + private function updatePaymentables() + { + if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { + $this->payment->invoices->each(function ($paymentable_invoice) { + + collect($this->refund_data['invoices'])->each(function ($refunded_invoice) use ($paymentable_invoice) { + + if ($refunded_invoice['invoice_id'] == $paymentable_invoice->id) { + $paymentable_invoice->pivot->refunded += $refunded_invoice['amount']; + $paymentable_invoice->pivot->save(); + } + }); + }); + } + + return $this; + } + + private function updateCreditables() + { + + if ($this->payment->credits()->exists()) { + //Adjust credits first!!! + foreach ($this->payment->credits as $paymentable_credit) { + $available_credit = $paymentable_credit->pivot->amount - $paymentable_credit->pivot->refunded; + + if ($available_credit > $this->total_refund) { + $paymentable_credit->pivot->refunded += $this->total_refund; + $paymentable_credit->pivot->save(); + + $paymentable_credit->balance += $this->total_refund; + $paymentable_credit->save(); + + $this->total_refund = 0; + } else { + $paymentable_credit->pivot->refunded += $available_credit; + $paymentable_credit->pivot->save(); + + $paymentable_credit->balance += $available_credit; + $paymentable_credit->save(); + + $this->total_refund -= $available_credit; + } + + if ($this->total_refund == 0) { + break; + } + } + } + + return $this; + } + + + private function adjustInvoices() + { + $adjustment_amount = 0; + + if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { + foreach ($this->refund_data['invoices'] as $refunded_invoice) { + $invoice = Invoice::find($refunded_invoice['invoice_id']); + + $invoice->service()->updateBalance($refunded_invoice['amount'])->save(); + + if ($invoice->amount == $invoice->balance) { + $invoice->service()->setStatus(Invoice::STATUS_SENT); + } else { + $invoice->service()->setStatus(Invoice::STATUS_PARTIAL); + } + + $invoice->save(); + + $client = $invoice->client; + + $adjustment_amount += $refunded_invoice['amount']; + $client->balance += $refunded_invoice['amount']; + + $client->save(); + + //todo adjust ledger balance here? or after and reference the credit and its total + } + + $ledger_string = ''; //todo + + $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string); + + $this->payment->client->paid_to_date -= $this->refund_data['amount']; + $this->payment->client->save(); + } + + return $this; + } + + private function save() + { + $this->payment->save(); + + return $this->payment; + } +} diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index 8baaaf600022..5109954e312f 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -954,6 +954,7 @@ class CreateUsersTable extends Migration $t->unsignedInteger('client_contact_id')->nullable(); $t->unsignedInteger('invitation_id')->nullable(); $t->unsignedInteger('company_gateway_id')->nullable(); + $t->unsignedInteger('gateway_type_id')->nullable(); $t->unsignedInteger('type_id')->nullable(); $t->unsignedInteger('status_id')->index(); $t->decimal('amount', 16, 4)->default(0); diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index 167b8a468d4d..5d173a2d30ff 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -47,9 +47,6 @@ class UserTest extends TestCase $this->makeTestData(); - $this->withoutMiddleware( - ThrottleRequests::class - ); } public function testUserList() @@ -79,7 +76,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', ])->post('/api/v1/users?include=company_user', $data); $response->assertStatus(200);