diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index efca1de3573a..36635429a6d7 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -363,7 +363,15 @@ class BaseRule implements RuleInterface public function override($item): self { + $this->tax_rate1 = $item->tax_rate1; + $this->tax_name1 = $item->tax_name1; + $this->tax_rate2 = $item->tax_rate2; + $this->tax_name2 = $item->tax_name2; + $this->tax_rate3 = $item->tax_rate3; + $this->tax_name3 = $item->tax_name3; + return $this; + } public function calculateRates(): self diff --git a/app/DataMapper/Tax/US/Rule.php b/app/DataMapper/Tax/US/Rule.php index 469d16c74a6f..e880027201ea 100644 --- a/app/DataMapper/Tax/US/Rule.php +++ b/app/DataMapper/Tax/US/Rule.php @@ -49,6 +49,10 @@ class Rule extends BaseRule implements RuleInterface $this->tax_rate1 = $item->tax_rate1; $this->tax_name1 = $item->tax_name1; + $this->tax_rate2 = $item->tax_rate2; + $this->tax_name2 = $item->tax_name2; + $this->tax_rate3 = $item->tax_rate3; + $this->tax_name3 = $item->tax_name3; return $this; diff --git a/app/Http/Controllers/ClientPortal/SwitchCompanyController.php b/app/Http/Controllers/ClientPortal/SwitchCompanyController.php index 8f6dc09366dd..ec0f15541f7a 100644 --- a/app/Http/Controllers/ClientPortal/SwitchCompanyController.php +++ b/app/Http/Controllers/ClientPortal/SwitchCompanyController.php @@ -22,9 +22,10 @@ class SwitchCompanyController extends Controller public function __invoke(string $contact) { - $client_contact = ClientContact::where('email', auth()->user()->email) - ->where('id', $this->transformKeys($contact)) - ->first(); + $client_contact = ClientContact::query() + ->where('email', auth()->user()->email) + ->where('id', $this->transformKeys($contact)) + ->firstOrFail(); auth()->guard('contact')->loginUsingId($client_contact->id, true); diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index ac923ebf1ccc..301307372ae6 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -557,7 +557,7 @@ class QuoteController extends BaseController } }); - return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); + return $this->listResponse(Quote::query()->withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); } if ($action == 'bulk_print' && $user->can('view', $quotes->first())) { @@ -587,7 +587,7 @@ class QuoteController extends BaseController } }); - return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); + return $this->listResponse(Quote::query()->withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); } /* diff --git a/app/PaymentDrivers/Authorize/RefundTransaction.php b/app/PaymentDrivers/Authorize/RefundTransaction.php index 462d4f4236cc..998ffbad33a9 100644 --- a/app/PaymentDrivers/Authorize/RefundTransaction.php +++ b/app/PaymentDrivers/Authorize/RefundTransaction.php @@ -12,14 +12,16 @@ namespace App\PaymentDrivers\Authorize; -use App\Jobs\Util\SystemLogger; use App\Models\Payment; use App\Models\SystemLog; +use App\Jobs\Util\SystemLogger; use App\PaymentDrivers\AuthorizePaymentDriver; -use net\authorize\api\contract\v1\CreateTransactionRequest; -use net\authorize\api\contract\v1\CustomerProfilePaymentType; +use net\authorize\api\contract\v1\PaymentType; +use net\authorize\api\contract\v1\CreditCardType; use net\authorize\api\contract\v1\PaymentProfileType; use net\authorize\api\contract\v1\TransactionRequestType; +use net\authorize\api\contract\v1\CreateTransactionRequest; +use net\authorize\api\contract\v1\CustomerProfilePaymentType; use net\authorize\api\controller\CreateTransactionController; /** @@ -43,24 +45,42 @@ class RefundTransaction $transaction_details = $this->authorize_transaction->getTransactionDetails($payment->transaction_reference); + $creditCard = $transaction_details->getTransaction()->getPayment()->getCreditCard(); + $creditCardNumber = $creditCard->getCardNumber(); + $creditCardExpiry = $creditCard->getExpirationDate(); + $transaction_status = $transaction_details->getTransaction()->getTransactionStatus(); + + $transaction_type = $transaction_status == 'capturedPendingSettlement' ? 'voidTransaction' : 'refundTransaction'; + + if($transaction_type == 'voidTransaction') { + $amount = $transaction_details->getTransaction()->getAuthAmount(); + } + $this->authorize->init(); // Set the transaction's refId $refId = 'ref'.time(); - $paymentProfile = new PaymentProfileType(); - $paymentProfile->setPaymentProfileId($transaction_details->getTransaction()->getProfile()->getCustomerPaymentProfileId()); + // $paymentProfile = new PaymentProfileType(); + // $paymentProfile->setPaymentProfileId($transaction_details->getTransaction()->getProfile()->getCustomerPaymentProfileId()); - // set customer profile - $customerProfile = new CustomerProfilePaymentType(); - $customerProfile->setCustomerProfileId($transaction_details->getTransaction()->getProfile()->getCustomerProfileId()); - $customerProfile->setPaymentProfile($paymentProfile); + // // // set customer profile + // $customerProfile = new CustomerProfilePaymentType(); + // $customerProfile->setCustomerProfileId($transaction_details->getTransaction()->getProfile()->getCustomerProfileId()); + // $customerProfile->setPaymentProfile($paymentProfile); + + $creditCard = new CreditCardType(); + $creditCard->setCardNumber($creditCardNumber); + $creditCard->setExpirationDate($creditCardExpiry); + $paymentOne = new PaymentType(); + $paymentOne->setCreditCard($creditCard); //create a transaction $transactionRequest = new TransactionRequestType(); - $transactionRequest->setTransactionType('refundTransaction'); + $transactionRequest->setTransactionType($transaction_type); $transactionRequest->setAmount($amount); - $transactionRequest->setProfile($customerProfile); + // $transactionRequest->setProfile($customerProfile); + $transactionRequest->setPayment($paymentOne); $transactionRequest->setRefTransId($payment->transaction_reference); $request = new CreateTransactionRequest(); @@ -83,6 +103,7 @@ class RefundTransaction 'transaction_response' => $tresponse->getResponseCode(), 'payment_id' => $payment->id, 'amount' => $amount, + 'voided' => $transaction_status == 'capturedPendingSettlement' ? true : false, ]; SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company); @@ -166,4 +187,5 @@ class RefundTransaction SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company); } + } diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index fe56ef089145..007f1df6efbf 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -35,6 +35,10 @@ class RefundPayment private $activity_repository; + private bool $refund_failed = false; + + private string $refund_failed_message = ''; + public function __construct($payment, $refund_data) { $this->payment = $payment; @@ -50,12 +54,14 @@ class RefundPayment public function run() { - $this->payment = $this->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment) + $this->payment = $this + ->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment) + ->processGatewayRefund() //process the gateway refund if needed ->setStatus() //sets status of payment ->updateCreditables() //return the credits first ->updatePaymentables() //update the paymentable items ->adjustInvoices() - ->processGatewayRefund() //process the gateway refund if needed + ->finalize() ->save(); if (array_key_exists('email_receipt', $this->refund_data) && $this->refund_data['email_receipt'] == 'true') { @@ -71,9 +77,28 @@ class RefundPayment return $this->payment; } + private function finalize(): self + { + if($this->refund_failed) + throw new PaymentRefundFailed($this->refund_failed_message); + + return $this; + } + /** * Process the refund through the gateway. * + * @var array $response + * [ + * 'transaction_reference' => (string), + * 'transaction_response' => (string), + * 'success' => (bool), + * 'description' => (string), + * 'code' => (string), + * 'payment_id' => (int), + * 'amount' => (float), + * ]; + * * @return $this * @throws PaymentRefundFailed */ @@ -83,16 +108,27 @@ class RefundPayment if ($this->payment->company_gateway) { $response = $this->payment->company_gateway->driver($this->payment->client)->refund($this->payment, $this->total_refund); + if($response['amount'] ?? false) + $this->total_refund = $response['amount']; + + if($response['voided'] ?? false) + { + //When a transaction is voided - all invoices attached to the payment need to be reversed, this + //block prevents the edge case where a partial refund was attempted. + $this->refund_data['invoices'] = $this->payment->invoices->map(function ($invoice){ + return [ + 'invoice_id' => $invoice->id, + 'amount' => $invoice->pivot->amount, + ]; + })->toArray(); + } + $this->payment->refunded += $this->total_refund; if ($response['success'] == false) { $this->payment->save(); - - if (array_key_exists('description', $response)) { - throw new PaymentRefundFailed($response['description']); - } else { - throw new PaymentRefundFailed(); - } + $this->refund_failed = true; + $this->refund_failed_message = $response['description'] ?? ''; } } } else {