From a329af2ddd099e7888075953d269a5d78e1258cb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 26 Jan 2024 10:04:50 +1100 Subject: [PATCH 1/7] Add partially unapplied payment status --- app/Models/Payment.php | 8 ++++++-- .../components/livewire/payments-table.blade.php | 2 +- resources/views/portal/ninja2020/payments/show.blade.php | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 8302c53430bb..d43cacf6f043 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -314,9 +314,9 @@ class Payment extends BaseModel return $this->createClientDate($this->date, $this->client->timezone()->name)->format($date_format->format); } - public static function badgeForStatus(int $status): string + public function badgeForStatus(): string { - switch ($status) { + switch ($this->status_id) { case self::STATUS_PENDING: return '
'.ctrans('texts.payment_status_1').'
'; case self::STATUS_CANCELLED: @@ -324,6 +324,10 @@ class Payment extends BaseModel case self::STATUS_FAILED: return '
'.ctrans('texts.payment_status_3').'
'; case self::STATUS_COMPLETED: + + if($this->amount > $this->applied) + return '
' . ctrans('texts.partially_unapplied') . '
'; + return '
'.ctrans('texts.payment_status_4').'
'; case self::STATUS_PARTIALLY_REFUNDED: return '
'.ctrans('texts.payment_status_5').'
'; diff --git a/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php b/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php index 7d3f0a60a15d..c4ae5672ebb8 100644 --- a/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php @@ -66,7 +66,7 @@ {{ \Illuminate\Support\Str::limit($payment->transaction_reference, 35) }} - {!! \App\Models\Payment::badgeForStatus($payment->status_id) !!} + {!! $payment->badgeForStatus() !!} diff --git a/resources/views/portal/ninja2020/payments/show.blade.php b/resources/views/portal/ninja2020/payments/show.blade.php index a0604b92efe8..9c9e51cbd6ab 100644 --- a/resources/views/portal/ninja2020/payments/show.blade.php +++ b/resources/views/portal/ninja2020/payments/show.blade.php @@ -21,7 +21,7 @@ {{ ctrans('texts.status') }}
- {!! \App\Models\Payment::badgeForStatus($payment->status_id) !!} + {!! $payment->badgeForStatus() !!}
@endif From 2aa26a52274eb5a0a24a1d58bf6e51680c16fa3b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 26 Jan 2024 10:20:28 +1100 Subject: [PATCH 2/7] Updates for partially applied payments --- app/Services/Template/TemplateService.php | 2 +- .../ninja2020/components/livewire/subscriptions-table.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 6cab89f1b4bb..da5c7bf0e3b9 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -651,7 +651,7 @@ class TemplateService return [ 'status' => $payment->stringStatus($payment->status_id), - 'badge' => $payment->badgeForStatus($payment->status_id), + 'badge' => $payment->badgeForStatus(), 'amount' => Number::formatMoney($payment->amount, $payment->client), 'applied' => Number::formatMoney($payment->applied, $payment->client), 'balance' => Number::formatMoney(($payment->amount - $payment->refunded - $payment->applied), $payment->client), diff --git a/resources/views/portal/ninja2020/components/livewire/subscriptions-table.blade.php b/resources/views/portal/ninja2020/components/livewire/subscriptions-table.blade.php index 86762dd5b622..fc4a5b80d733 100644 --- a/resources/views/portal/ninja2020/components/livewire/subscriptions-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/subscriptions-table.blade.php @@ -53,7 +53,7 @@ @forelse($recurring_invoices as $recurring_invoice) - {!! $recurring_invoice->badgeForStatus($recurring_invoice->status_id) !!} + {!! $recurring_invoice::badgeForStatus($recurring_invoice->status_id) !!} {{ $recurring_invoice->subscription->name }} From 849a9dc113adf0abec0ce6e86d4ff10a073c08d1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 27 Jan 2024 15:43:37 +1100 Subject: [PATCH 3/7] Updates for mailers on hosted platform --- .../Controllers/ClientPortal/NinjaPlanController.php | 1 + app/Jobs/Mail/NinjaMailerJob.php | 12 ++++++++++-- app/Jobs/Subscription/CleanStaleInvoiceOrder.php | 2 +- app/Models/Account.php | 7 ++++--- app/PaymentDrivers/CheckoutComPaymentDriver.php | 11 +++++------ app/PaymentDrivers/PayPalPPCPPaymentDriver.php | 2 +- app/Services/Email/Email.php | 8 ++++++++ 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/NinjaPlanController.php b/app/Http/Controllers/ClientPortal/NinjaPlanController.php index 89afe88a8cf5..5a1bca2ea7c3 100644 --- a/app/Http/Controllers/ClientPortal/NinjaPlanController.php +++ b/app/Http/Controllers/ClientPortal/NinjaPlanController.php @@ -154,6 +154,7 @@ class NinjaPlanController extends Controller $account->is_trial = true; $account->hosted_company_count = 10; $account->trial_started = now(); + $account->trial_plan = 'pro'; $account->save(); } diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index b51264af116e..9570e7736283 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -259,15 +259,23 @@ class NinjaMailerJob implements ShouldQueue $t = app('translator'); $t->replace(Ninja::transformTranslations($this->nmo->settings)); + /** Force free/trials onto specific mail driver */ + if(Ninja::isHosted() && !$this->company->account->isPaid()) + { + $this->mailer = 'mailgun'; + $this->setHostedMailgunMailer(); + return $this; + } + switch ($this->nmo->settings->email_sending_method) { case 'default': $this->mailer = config('mail.default'); // $this->setHostedMailgunMailer(); //should only be activated if hosted platform needs to fall back to mailgun - break; + return $this; case 'mailgun': $this->mailer = 'mailgun'; $this->setHostedMailgunMailer(); - break; + return $this; case 'gmail': $this->mailer = 'gmail'; $this->setGmailMailer(); diff --git a/app/Jobs/Subscription/CleanStaleInvoiceOrder.php b/app/Jobs/Subscription/CleanStaleInvoiceOrder.php index 74d2e259da63..aacd7b32d03a 100644 --- a/app/Jobs/Subscription/CleanStaleInvoiceOrder.php +++ b/app/Jobs/Subscription/CleanStaleInvoiceOrder.php @@ -59,7 +59,7 @@ class CleanStaleInvoiceOrder implements ShouldQueue Invoice::query() ->withTrashed() ->where('status_id', Invoice::STATUS_SENT) - ->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(10)]) + ->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(30)]) ->where('balance', '>', 0) ->cursor() ->each(function ($invoice) { diff --git a/app/Models/Account.php b/app/Models/Account.php index 18d09f68eb65..7249f4308085 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -363,9 +363,10 @@ class Account extends BaseModel return false; } - $plan_details = $this->getPlanDetails(); - - return $plan_details && $plan_details['trial']; + //@27-01-2024 - updates for logic around trials + return !$this->plan_paid && $this->trial_started && Carbon::parse($this->trial_started)->addDays(14)->gte(now()->subHours(12)); + // $plan_details = $this->getPlanDetails(); + // return $plan_details && $plan_details['trial']; } public function startTrial($plan): void diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 39bfe23b7473..ce91f337a6bc 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -44,9 +44,6 @@ use Checkout\Payments\Request\Source\RequestIdSource; use Exception; use Illuminate\Support\Facades\Auth; -//use Checkout\Customers\Four\CustomerRequest as FourCustomerRequest; -//use Checkout\Payments\Four\Request\Source\RequestIdSource as SourceRequestIdSource; - class CheckoutComPaymentDriver extends BaseDriver { use SystemLogTrait; @@ -334,8 +331,10 @@ class CheckoutComPaymentDriver extends BaseDriver public function updateCustomer() { + nlog("merp"); + try { - + $request = new CustomerRequest(); $phone = new Phone(); @@ -346,9 +345,9 @@ class CheckoutComPaymentDriver extends BaseDriver $response = $this->gateway->getCustomersClient()->update("customer_id", $request); } catch (CheckoutApiException $e) { - + nlog($e->getMessage()); } catch (CheckoutAuthorizationException $e) { - + nlog($e->getMessage()); } } diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index e24e6a2d470d..9a6f1acf86ef 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -445,7 +445,7 @@ class PayPalPPCPPaymentDriver extends BaseDriver "items" => [ [ "name" => ctrans('texts.invoice_number').'# '.$invoice->number, - "description" => substr($description, 0, 127), + "description" => mb_substr($description, 0, 127), "quantity" => "1", "unit_amount" => [ "currency_code" => $this->client->currency()->code, diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index e1cbb99ef3b2..9f71781b1993 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -484,6 +484,14 @@ class Email implements ShouldQueue */ private function setMailDriver(): self { + + /** Force free/trials onto specific mail driver */ + if(Ninja::isHosted() && !$this->company->account->isPaid()) { + $this->mailer = 'mailgun'; + $this->setHostedMailgunMailer(); + return $this; + } + switch ($this->email_object->settings->email_sending_method) { case 'default': $this->mailer = config('mail.default'); From 753c9c42395a63174141757cc894478eb40051ca Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 27 Jan 2024 16:08:54 +1100 Subject: [PATCH 4/7] Update Checkout.com customer on successful payment --- app/PaymentDrivers/CheckoutCom/CreditCard.php | 9 +++++---- app/PaymentDrivers/CheckoutComPaymentDriver.php | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index 38f592b201d1..ec5bfeec1731 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -173,10 +173,6 @@ class CreditCard implements MethodInterface if ($request->has('token') && ! is_null($request->token) && ! empty($request->token)) { return $this->attemptPaymentUsingToken($request); } - - if($this->checkout->company_gateway->update_details) { - $this->checkout->updateCustomer(); - } return $this->attemptPaymentUsingCreditCard($request); } @@ -235,8 +231,13 @@ class CreditCard implements MethodInterface try { $response = $this->checkout->gateway->getPaymentsClient()->requestPayment($paymentRequest); + + if($this->checkout->company_gateway->update_details && isset($response['customer'])) { + $this->checkout->updateCustomer($response['customer']['id'] ?? ''); + } if ($response['status'] == 'Authorized') { + return $this->processSuccessfulPayment($response); } diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index ce91f337a6bc..469986059b33 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -329,9 +329,11 @@ class CheckoutComPaymentDriver extends BaseDriver } } - public function updateCustomer() + public function updateCustomer($customer_id = null) { - nlog("merp"); + + if(!$customer_id) + return; try { @@ -343,7 +345,9 @@ class CheckoutComPaymentDriver extends BaseDriver $request->name = $this->client->present()->name(); $request->phone = $phone; - $response = $this->gateway->getCustomersClient()->update("customer_id", $request); + $response = $this->gateway->getCustomersClient()->update($customer_id, $request); + + } catch (CheckoutApiException $e) { nlog($e->getMessage()); } catch (CheckoutAuthorizationException $e) { @@ -384,10 +388,6 @@ class CheckoutComPaymentDriver extends BaseDriver $this->init(); - if($this->company_gateway->update_details) { - $this->updateCustomer(); - } - $paymentRequest = $this->bootTokenRequest($cgt->token); $paymentRequest->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode()); $paymentRequest->reference = '#'.$invoice->number.' - '.now(); From d05a58cebe86ec54350c0b55b3166a31170a054b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 27 Jan 2024 18:59:56 +1100 Subject: [PATCH 5/7] Livewire fixes for subscription purchasds --- app/Livewire/BillingPortalPurchase.php | 1 + app/Services/ClientPortal/InstantPayment.php | 1 + .../views/billing-portal/purchase.blade.php | 17 +++++++++-------- .../views/billing-portal/purchasev2.blade.php | 8 +------- .../livewire/billing-portal-purchase.blade.php | 2 +- .../ninja2020/subscriptions/switch.blade.php | 7 ++++++- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/Livewire/BillingPortalPurchase.php b/app/Livewire/BillingPortalPurchase.php index efb665d511e0..0889c2fc88a1 100644 --- a/app/Livewire/BillingPortalPurchase.php +++ b/app/Livewire/BillingPortalPurchase.php @@ -353,6 +353,7 @@ class BillingPortalPurchase extends Component $this->payment_method_id = $gateway_type_id; $this->handleBeforePaymentEvents(); + } /** diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php index 3f329c0d8032..7ce624d5f270 100644 --- a/app/Services/ClientPortal/InstantPayment.php +++ b/app/Services/ClientPortal/InstantPayment.php @@ -44,6 +44,7 @@ class InstantPayment public function run() { + nlog($this->request->all()); $is_credit_payment = false; $tokens = []; diff --git a/resources/views/billing-portal/purchase.blade.php b/resources/views/billing-portal/purchase.blade.php index 312a33682200..64724977c3e2 100644 --- a/resources/views/billing-portal/purchase.blade.php +++ b/resources/views/billing-portal/purchase.blade.php @@ -6,18 +6,19 @@ @stop @push('footer') + + @endpush diff --git a/resources/views/billing-portal/purchasev2.blade.php b/resources/views/billing-portal/purchasev2.blade.php index 33883489393f..ff94394bfe2c 100644 --- a/resources/views/billing-portal/purchasev2.blade.php +++ b/resources/views/billing-portal/purchasev2.blade.php @@ -7,14 +7,8 @@ @push('footer') + @endpush From 9f5cfd7440d512384f6f7c8ceb0e3e02717c5170 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 28 Jan 2024 09:21:45 +1100 Subject: [PATCH 6/7] Adjustments for PayPal Rest implementation --- .../PayPalPPCPPaymentDriver.php | 3 - .../PayPalRestPaymentDriver.php | 323 ++++++++++++++---- .../ninja2020/gateways/paypal/pay.blade.php | 79 +++-- 3 files changed, 306 insertions(+), 99 deletions(-) diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index 9a6f1acf86ef..5fcb5ab1c183 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -519,9 +519,6 @@ class PayPalPPCPPaymentDriver extends BaseDriver ->withHeaders($this->getHeaders($headers)) ->{$verb}("{$this->api_endpoint_url}{$uri}", $data); - // nlog($r); - // nlog($r->json()); - if($r->successful()) { return $r; } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index 43c7d8397a3a..18ae5ce8113a 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -12,15 +12,16 @@ namespace App\PaymentDrivers; -use App\Exceptions\PaymentFailed; -use App\Jobs\Util\SystemLogger; -use App\Models\GatewayType; -use App\Models\Invoice; -use App\Models\PaymentType; -use App\Models\SystemLog; -use App\Utils\Traits\MakesHash; -use Illuminate\Support\Facades\Http; +use Carbon\Carbon; use Omnipay\Omnipay; +use App\Models\Invoice; +use App\Models\SystemLog; +use App\Models\GatewayType; +use App\Models\PaymentType; +use App\Jobs\Util\SystemLogger; +use App\Utils\Traits\MakesHash; +use App\Exceptions\PaymentFailed; +use Illuminate\Support\Facades\Http; class PayPalRestPaymentDriver extends BaseDriver { @@ -40,6 +41,12 @@ class PayPalRestPaymentDriver extends BaseDriver private string $paypal_payment_method = ''; + private ?int $gateway_type_id = null; + + protected mixed $access_token = null; + + protected ?Carbon $token_expiry = null; + private array $funding_options = [ 3 => 'paypal', 1 => 'card', @@ -51,7 +58,7 @@ class PayPalRestPaymentDriver extends BaseDriver // 13 => 'ideal', // 26 => 'mercadopago', // 27 => 'mybank', - // 28 => 'paylater', + 28 => 'paylater', // 16 => 'p24', // 7 => 'sofort' ]; @@ -74,25 +81,61 @@ class PayPalRestPaymentDriver extends BaseDriver public function init() { - $this->omnipay_gateway = Omnipay::create( - $this->company_gateway->gateway->provider - ); - - $this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig()); + // $this->omnipay_gateway = Omnipay::create( + // $this->company_gateway->gateway->provider + // ); + // $this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig()); $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; + $secret = $this->company_gateway->getConfigField('secret'); + $client_id = $this->company_gateway->getConfigField('clientId'); + + if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) { + return $this; + } + + $response = Http::withBasicAuth($client_id, $secret) + ->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded']) + ->withQueryParameters(['grant_type' => 'client_credentials']) + ->post("{$this->api_endpoint_url}/v1/oauth2/token"); + + if($response->successful()) { + $this->access_token = $response->json()['access_token']; + $this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60); + } else { + throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401); + } + return $this; + } - public function setPaymentMethod($payment_method_id) + private function getPaymentMethod($gateway_type_id): int + { + $method = PaymentType::PAYPAL; + + match($gateway_type_id) { + "1" => $method = PaymentType::CREDIT_CARD_OTHER, + "3" => $method = PaymentType::PAYPAL, + "25" => $method = PaymentType::VENMO, + "28" => $method = PaymentType::PAY_LATER, + }; + + return $method; + } + + public function setPaymentMethod($payment_method_id): self { if(!$payment_method_id) { return $this; } + $this->gateway_type_id = $payment_method_id; + $this->paypal_payment_method = $this->funding_options[$payment_method_id]; + return $this; } @@ -122,7 +165,9 @@ class PayPalRestPaymentDriver extends BaseDriver $data['client_id'] = $this->company_gateway->getConfigField('clientId'); $data['token'] = $this->getClientToken(); $data['order_id'] = $this->createOrder($data); - $data['funding_options'] = $this->paypal_payment_method; + $data['funding_source'] = $this->paypal_payment_method; + $data['gateway_type_id'] = $this->gateway_type_id; + $data['currency'] = $this->client->currency()->code; return render('gateways.paypal.pay', $data); @@ -166,13 +211,43 @@ class PayPalRestPaymentDriver extends BaseDriver public function processPaymentResponse($request) { + $this->init(); + + $request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']); $response = json_decode($request['gateway_response'], true); - if($response['status'] == 'COMPLETED' && isset($response['purchase_units'])) { + //capture + $orderID = $response['orderID']; + + if($this->company_gateway->require_shipping_address) { + + $shipping_data = + [[ + "op" => "replace", + "path" => "/purchase_units/@reference_id=='default'/shipping/address", + "value" => [ + "address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1, + "address_line_2" => $this->client->shipping_address2, + "admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city, + "admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state, + "postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code, + "country_code" => $this->client->present()->shipping_country_code(), + ], + ]]; + + $r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $shipping_data); + + } + + $r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']); + + $response = $r; + + if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) { $data = [ - 'payment_type' => PaymentType::PAYPAL, - 'amount' => $response['purchase_units'][0]['amount']['value'], + 'payment_type' => $this->getPaymentMethod($request->gateway_type_id), + 'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'], 'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'], 'gateway_type_id' => GatewayType::PAYPAL, ]; @@ -192,6 +267,10 @@ class PayPalRestPaymentDriver extends BaseDriver } else { + if(isset($response['headers']) ?? false) { + unset($response['headers']); + } + SystemLogger::dispatch( ['response' => $response], SystemLog::CATEGORY_GATEWAY_RESPONSE, @@ -201,9 +280,11 @@ class PayPalRestPaymentDriver extends BaseDriver $this->client->company, ); + $message = $response['body']['details'][0]['description'] ?? 'Payment failed. Please try again.'; - throw new PaymentFailed('Payment failed. Please try again.', 401); + throw new PaymentFailed($message, 400); } + } private function getClientToken(): string @@ -226,60 +307,169 @@ class PayPalRestPaymentDriver extends BaseDriver $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); + $description = collect($invoice->line_items)->map(function ($item) { + return $item->notes; + })->implode("\n"); + $order = [ - "intent" => "CAPTURE", - "payer" => [ - "name" => [ - "given_name" => $this->client->present()->first_name(), - "surname" => $this->client->present()->last_name(), - ], - "email_address" => $this->client->present()->email(), - "address" => [ - "address_line_1" => $this->client->address1, - "address_line_2" => $this->client->address2, - "admin_area_1" => $this->client->city, - "admin_area_2" => $this->client->state, - "postal_code" => $this->client->postal_code, - "country_code" => $this->client->country->iso_3166_2, - ] - ], - "purchase_units" => [ - [ - "description" => ctrans('texts.invoice_number').'# '.$invoice->number, - "invoice_id" => $invoice->number, - "amount" => [ - "value" => (string)$data['amount_with_fee'], - "currency_code" => $this->client->currency()->code, - "breakdown" => [ - "item_total" => [ - "currency_code" => $this->client->currency()->code, - "value" => (string)$data['amount_with_fee'] - ] - ] - ], - "items" => [ - [ - "name" => ctrans('texts.invoice_number').'# '.$invoice->number, - "quantity" => "1", - "unit_amount" => [ - "currency_code" => $this->client->currency()->code, - "value" => (string)$data['amount_with_fee'] + + "intent" => "CAPTURE", + "payment_source" => [ + "paypal" => [ + + "name" => [ + "given_name" => $this->client->present()->first_name(), + "surname" => $this->client->present()->last_name(), + ], + "email_address" => $this->client->present()->email(), + "address" => [ + "address_line_1" => $this->client->address1, + "address_line_2" => $this->client->address2, + "admin_area_2" => $this->client->city, + "admin_area_1" => $this->client->state, + "postal_code" => $this->client->postal_code, + "country_code" => $this->client->country->iso_3166_2, + ], + "experience_context" => [ + "user_action" => "PAY_NOW" + ], ], ], - ], - ] - ] - ]; + "purchase_units" => [ + [ + "custom_id" => $this->payment_hash->hash, + "description" => ctrans('texts.invoice_number') . '# ' . $invoice->number, + "invoice_id" => $invoice->number, + "payment_instruction" => [ + "disbursement_mode" => "INSTANT", + ], + $this->getShippingAddress(), + "amount" => [ + "value" => (string) $data['amount_with_fee'], + "currency_code" => $this->client->currency()->code, + "breakdown" => [ + "item_total" => [ + "currency_code" => $this->client->currency()->code, + "value" => (string) $data['amount_with_fee'] + ] + ] + ], + "items" => [ + [ + "name" => ctrans('texts.invoice_number') . '# ' . $invoice->number, + "description" => mb_substr($description, 0, 127), + "quantity" => "1", + "unit_amount" => [ + "currency_code" => $this->client->currency()->code, + "value" => (string) $data['amount_with_fee'] + ], + ], + ], + ], + ] + ]; + + + if($shipping = $this->getShippingAddress()) { + $order['purchase_units'][0]["shipping"] = $shipping; + } $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); + // nlog($r->json()); + return $r->json()['id']; + + + // $_invoice = collect($this->payment_hash->data->invoices)->first(); + + // $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); + + // $order = [ + // "intent" => "CAPTURE", + // "payer" => [ + // "name" => [ + // "given_name" => $this->client->present()->first_name(), + // "surname" => $this->client->present()->last_name(), + // ], + // "email_address" => $this->client->present()->email(), + // "address" => [ + // "address_line_1" => $this->client->address1, + // "address_line_2" => $this->client->address2, + // "admin_area_1" => $this->client->city, + // "admin_area_2" => $this->client->state, + // "postal_code" => $this->client->postal_code, + // "country_code" => $this->client->country->iso_3166_2, + // ] + // ], + // "purchase_units" => [ + // [ + // "description" => ctrans('texts.invoice_number').'# '.$invoice->number, + // "invoice_id" => $invoice->number, + // "amount" => [ + // "value" => (string)$data['amount_with_fee'], + // "currency_code" => $this->client->currency()->code, + // "breakdown" => [ + // "item_total" => [ + // "currency_code" => $this->client->currency()->code, + // "value" => (string)$data['amount_with_fee'] + // ] + // ] + // ], + // "items" => [ + // [ + // "name" => ctrans('texts.invoice_number').'# '.$invoice->number, + // "quantity" => "1", + // "unit_amount" => [ + // "currency_code" => $this->client->currency()->code, + // "value" => (string)$data['amount_with_fee'] + // ], + // ], + // ], + // ] + // ] + // ]; + + // $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); + + // return $r->json()['id']; + } + private function getShippingAddress(): ?array + { + return $this->company_gateway->require_shipping_address ? + [ + "address" => + [ + "address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1, + "address_line_2" => $this->client->shipping_address2, + "admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city, + "admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state, + "postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code, + "country_code" => $this->client->present()->shipping_country_code(), + ], + ] + + : null; + + } + + /** + * Generates the gateway request + * + * @param string $uri + * @param string $verb + * @param array $data + * @param ?array $headers + * @return \Illuminate\Http\Client\Response + */ public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = []) { - $r = Http::withToken($this->omnipay_gateway->getToken()) + $this->init(); + + $r = Http::withToken($this->access_token) ->withHeaders($this->getHeaders($headers)) ->{$verb}("{$this->api_endpoint_url}{$uri}", $data); @@ -287,6 +477,15 @@ class PayPalRestPaymentDriver extends BaseDriver return $r; } + SystemLogger::dispatch( + ['response' => $r->body()], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_PAYPAL, + $this->client, + $this->client->company, + ); + throw new PaymentFailed("Gateway failure - {$r->body()}", 401); } diff --git a/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php b/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php index 14ce66ed2549..db7b9d563d64 100644 --- a/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php @@ -10,6 +10,7 @@ + @@ -23,52 +24,62 @@ @endsection @push('footer') - + +
@endpush \ No newline at end of file From 5b2428c2a4fa30a761d2fab1b3c3ad0c5b74a0f7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 28 Jan 2024 10:24:31 +1100 Subject: [PATCH 7/7] Allow Gocardless ACSS payments for CAD customers --- .../CreatePaymentMethodRequest.php | 2 +- app/Models/Client.php | 5 ++++ app/Models/CompanyGateway.php | 2 +- .../GoCardlessPaymentDriver.php | 23 +++++++++---------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/Http/Requests/ClientPortal/PaymentMethod/CreatePaymentMethodRequest.php b/app/Http/Requests/ClientPortal/PaymentMethod/CreatePaymentMethodRequest.php index 76ffa929dfd7..5ba9c2fa104a 100644 --- a/app/Http/Requests/ClientPortal/PaymentMethod/CreatePaymentMethodRequest.php +++ b/app/Http/Requests/ClientPortal/PaymentMethod/CreatePaymentMethodRequest.php @@ -27,7 +27,7 @@ class CreatePaymentMethodRequest extends FormRequest ->filter(function ($method) use (&$available_methods) { $available_methods[] = $method['gateway_type_id']; }); - + if (in_array($this->query('method'), $available_methods)) { return true; } diff --git a/app/Models/Client.php b/app/Models/Client.php index 8bfa386404a1..61bda7230946 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -688,6 +688,11 @@ class Client extends BaseModel implements HasLocalePreference return GatewayType::SEPA; } + //Special handler for GoCardless + if($this->currency()->code == 'CAD' && ($this->getBankTransferGateway()->gateway_key == 'b9886f9257f0c6ee7c302f1c74475f6c') ?? false) { + return GatewayType::DIRECT_DEBIT; + } + if (in_array($this->currency()->code, ['EUR', 'GBP','DKK','SEK','AUD','NZD','USD'])) { return GatewayType::DIRECT_DEBIT; } diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index d71e4ebd3fbe..253af5585fe6 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -148,7 +148,7 @@ class CompanyGateway extends BaseModel '944c20175bbe6b9972c05bcfe294c2c7' => 313, 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs' => 314, '65faab2ab6e3223dbe848b1686490baz' => 320, - 'b9886f9257f0c6ee7c302f1c74475f6c' => 321, + 'b9886f9257f0c6ee7c302f1c74475f6c' => 321, //GoCardless 'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9' => 322, '80af24a6a691230bbec33e930ab40666' => 323, ]; diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 270bac07267e..3b1fb23e9589 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -11,23 +11,23 @@ namespace App\PaymentDrivers; -use App\Factory\ClientContactFactory; -use App\Factory\ClientFactory; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Jobs\Mail\PaymentFailedMailer; -use App\Jobs\Util\SystemLogger; use App\Models\Client; -use App\Models\ClientGatewayToken; use App\Models\Country; -use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; +use App\Models\SystemLog; +use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; -use App\Utils\Traits\GeneratesCounter; +use App\Factory\ClientFactory; +use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; +use App\Models\ClientGatewayToken; +use App\Factory\ClientContactFactory; +use App\Jobs\Mail\PaymentFailedMailer; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\QueryException; +use App\Http\Requests\Payments\PaymentWebhookRequest; class GoCardlessPaymentDriver extends BaseDriver { @@ -79,8 +79,7 @@ class GoCardlessPaymentDriver extends BaseDriver if ( $this->client && isset($this->client->country) - // && in_array($this->client->country->iso_3166_3, ['GBR']) - && in_array($this->client->currency()->code, ['EUR', 'GBP','DKK','SEK','AUD','NZD']) + && in_array($this->client->currency()->code, ['EUR', 'GBP','DKK','SEK','AUD','NZD','CAD']) ) { $types[] = GatewayType::DIRECT_DEBIT; } @@ -92,7 +91,7 @@ class GoCardlessPaymentDriver extends BaseDriver if ($this->client->currency()->code === 'GBP') { $types[] = GatewayType::INSTANT_BANK_PAY; } - + return $types; }