From 53747d76cd4e44b41df78b0eee40c002f5cad505 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Aug 2021 17:36:39 +1000 Subject: [PATCH 1/8] Fixes for postal code import --- app/Import/Transformers/Csv/ClientTransformer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Import/Transformers/Csv/ClientTransformer.php b/app/Import/Transformers/Csv/ClientTransformer.php index f9fb21212c..eabcf10b2b 100644 --- a/app/Import/Transformers/Csv/ClientTransformer.php +++ b/app/Import/Transformers/Csv/ClientTransformer.php @@ -39,6 +39,7 @@ class ClientTransformer extends BaseTransformer 'work_phone' => $this->getString( $data, 'client.phone' ), 'address1' => $this->getString( $data, 'client.address1' ), 'address2' => $this->getString( $data, 'client.address2' ), + 'postal_code' => $this->getString( $data, 'client.postal_code'), 'city' => $this->getString( $data, 'client.city' ), 'state' => $this->getString( $data, 'client.state' ), 'shipping_address1' => $this->getString( $data, 'client.shipping_address1' ), From 760da5c86461d541c8265aa9f1355b44c62c07d8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 07:15:03 +1000 Subject: [PATCH 2/8] Fixes for support messages --- app/Mail/SupportMessageSent.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php index 709c9058ee..ff50840209 100644 --- a/app/Mail/SupportMessageSent.php +++ b/app/Mail/SupportMessageSent.php @@ -54,6 +54,7 @@ class SupportMessageSent extends Mailable $priority = ''; $plan = $account->plan ?: ''; + $plan = ucfirst($plan); if(strlen($plan) >1) $priority = '[PRIORITY] '; @@ -63,9 +64,9 @@ class SupportMessageSent extends Mailable $db = str_replace("db-ninja-", "", $company->db); if(Ninja::isHosted()) - $subject = "{$priority}Hosted-{$db} :: {ucfirst($plan)} :: ".date('M jS, g:ia'); + $subject = "{$priority}Hosted-{$db} :: {$plan} :: ".date('M jS, g:ia'); else - $subject = "{$priority}Self Hosted :: {ucfirst($plan)} :: ".date('M jS, g:ia'); + $subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia'); return $this->from(config('mail.from.address'), $user->present()->name()) ->replyTo($user->email, $user->present()->name()) From 6669b81c4d4734558f00726f2f989405a369624f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 07:43:26 +1000 Subject: [PATCH 3/8] Clean up on Import --- app/Jobs/Util/Import.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index 26b961df7f..79a072f8b7 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -1506,12 +1506,6 @@ class Import implements ShouldQueue 'new' => $expense_category->id, ]; - // $this->ids['expense_categories'] = [ - // "expense_categories_{$old_user_key}" => [ - // 'old' => $resource['id'], - // 'new' => $expense_category->id, - // ], - // ]; } ExpenseCategory::reguard(); From cde5f527e1e644bce4691460111275d1695ca86a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 08:54:11 +1000 Subject: [PATCH 4/8] Working on ApplePay --- app/PaymentDrivers/Stripe/ApplePay.php | 91 ++++++++++++++ app/PaymentDrivers/Stripe/CreditCard.php | 4 +- app/PaymentDrivers/StripePaymentDriver.php | 3 +- .../gateways/stripe/applepay/pay.blade.php | 112 ++++++++++++++++++ 4 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 app/PaymentDrivers/Stripe/ApplePay.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php diff --git a/app/PaymentDrivers/Stripe/ApplePay.php b/app/PaymentDrivers/Stripe/ApplePay.php new file mode 100644 index 0000000000..3b32ede65b --- /dev/null +++ b/app/PaymentDrivers/Stripe/ApplePay.php @@ -0,0 +1,91 @@ +stripe_driver = $stripe_driver; + } + + public function paymentView(array $data) + { + $data['gateway'] = $this->stripe_driver; + $data['payment_hash'] = $this->stripe_driver->payment_hash->hash; + $data['payment_method_id'] = GatewayType::APPLE_PAY; + $data['country'] = $this->stripe_driver->client->country; + $data['currency'] = $this->stripe_driver->client->currency()->code; + $data['stripe_amount'] = $this->stripe_driver->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe_driver->client->currency()->precision, $this->stripe_driver->client->currency()); + $data['invoices'] = $this->stripe_driver->payment_hash->invoices(); + + $data['intent'] = \Stripe\PaymentIntent::create([ + 'amount' => $data['stripe_amount'], + 'currency' => $this->stripe_driver->client->getCurrencyCode();, + ], $this->stripe_driver->stripe_connect_auth); + + $this->stripe_driver->payment_hash->data = array_merge((array) $this->stripe_driver->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]); + $this->stripe_driver->payment_hash->save(); + + return render('gateways.stripe.applepay.pay', $data); + } + + + public function paymentResponse(PaymentResponseRequest $request) + { + + $this->stripe_driver->init(); + + $state = [ + 'server_response' => json_decode($request->gateway_response), + 'payment_hash' => $request->payment_hash, + ]; + + $state['payment_intent'] = \Stripe\PaymentIntent::retrieve($state['server_response']->id, $this->stripe_driver->stripe_connect_auth); + + $state['customer'] = $state['payment_intent']->customer; + + $this->stripe_driver->payment_hash->data = array_merge((array) $this->stripe_driver->payment_hash->data, $state); + $this->stripe_driver->payment_hash->save(); + + $server_response = $this->stripe_driver->payment_hash->data->server_response; + + $response_handler = new CreditCard($this->stripe_driver); + + if ($server_response->status == 'succeeded') { + + $this->stripe_driver->logSuccessfulGatewayResponse(['response' => json_decode($request->gateway_response), 'data' => $this->stripe_driver->payment_hash], SystemLog::TYPE_STRIPE); + + return $response_handler->processSuccessfulPayment(); + } + + return $response_handler->processUnsuccessfulPayment($server_response); + + + } + +} diff --git a/app/PaymentDrivers/Stripe/CreditCard.php b/app/PaymentDrivers/Stripe/CreditCard.php index 72981fea30..cdb36d8d35 100644 --- a/app/PaymentDrivers/Stripe/CreditCard.php +++ b/app/PaymentDrivers/Stripe/CreditCard.php @@ -108,7 +108,7 @@ class CreditCard return $this->processUnsuccessfulPayment($server_response); } - private function processSuccessfulPayment() + public function processSuccessfulPayment() { $stripe_method = $this->stripe->getStripePaymentMethod($this->stripe->payment_hash->data->server_response->payment_method); @@ -148,7 +148,7 @@ class CreditCard return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); } - private function processUnsuccessfulPayment($server_response) + public function processUnsuccessfulPayment($server_response) { PaymentFailureMailer::dispatch($this->stripe->client, $server_response->cancellation_reason, $this->stripe->client->company, $server_response->amount); diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index f792ce4954..7ad6f87062 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -25,6 +25,7 @@ use App\Models\PaymentHash; use App\Models\SystemLog; use App\PaymentDrivers\Stripe\ACH; use App\PaymentDrivers\Stripe\Alipay; +use App\PaymentDrivers\Stripe\ApplePay; use App\PaymentDrivers\Stripe\Charge; use App\PaymentDrivers\Stripe\Connect\Verify; use App\PaymentDrivers\Stripe\CreditCard; @@ -72,7 +73,7 @@ class StripePaymentDriver extends BaseDriver GatewayType::BANK_TRANSFER => ACH::class, GatewayType::ALIPAY => Alipay::class, GatewayType::SOFORT => SOFORT::class, - GatewayType::APPLE_PAY => 1, // TODO + GatewayType::APPLE_PAY => ApplePay::class, GatewayType::SEPA => 1, // TODO ]; diff --git a/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php new file mode 100644 index 0000000000..3ef1568f6d --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php @@ -0,0 +1,112 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Alipay', 'card_title' => 'Alipay']) + +@section('gateway_head') + +@endsection + +@section('gateway_content') +
+ @csrf + + + + +
+ + + + @include('portal.ninja2020.gateways.includes.payment_details') + +
+ +
+ +@endsection + +@push('footer') + + + +@endpush \ No newline at end of file From 7092d66ecd41c0eeef8302de6c6adabc5d8e0058 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 10:58:57 +1000 Subject: [PATCH 5/8] Apple Pay --- app/PaymentDrivers/Stripe/ApplePay.php | 28 ++++++++++++++++++- .../gateways/stripe/applepay/pay.blade.php | 12 +++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/PaymentDrivers/Stripe/ApplePay.php b/app/PaymentDrivers/Stripe/ApplePay.php index 3b32ede65b..7b03510bd7 100644 --- a/app/PaymentDrivers/Stripe/ApplePay.php +++ b/app/PaymentDrivers/Stripe/ApplePay.php @@ -22,6 +22,7 @@ use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\StripePaymentDriver; use App\PaymentDrivers\Stripe\CreditCard; +use App\Utils\Ninja; class ApplePay { @@ -35,6 +36,8 @@ class ApplePay public function paymentView(array $data) { + $this->registerDomain(); + $data['gateway'] = $this->stripe_driver; $data['payment_hash'] = $this->stripe_driver->payment_hash->hash; $data['payment_method_id'] = GatewayType::APPLE_PAY; @@ -75,7 +78,7 @@ class ApplePay $server_response = $this->stripe_driver->payment_hash->data->server_response; $response_handler = new CreditCard($this->stripe_driver); - + if ($server_response->status == 'succeeded') { $this->stripe_driver->logSuccessfulGatewayResponse(['response' => json_decode($request->gateway_response), 'data' => $this->stripe_driver->payment_hash], SystemLog::TYPE_STRIPE); @@ -88,4 +91,27 @@ class ApplePay } + + private function registerDomain() + { + if(Ninja::isHosted()) + { + + $domain = isset($this->stripe_driver->company_gateway->company->portal_domain) ? $this->stripe_driver->company_gateway->company->portal_domain : $this->stripe_driver->company_gateway->company->domain(); + + \Stripe\ApplePayDomain::create([ + 'domain_name' => $domain, + ], $this->stripe_driver->stripe_connect_auth); + + } + else { + + \Stripe\ApplePayDomain::create([ + 'domain_name' => config('ninja.app_url'), + ]); + + } + + } } + diff --git a/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php index 3ef1568f6d..76e72631df 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php @@ -1,4 +1,4 @@ -@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Alipay', 'card_title' => 'Alipay']) +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Apple Pay', 'card_title' => 'Apple Pay']) @section('gateway_head') @@ -88,6 +88,7 @@ stripe.confirmCardPayment(clientSecret).then(function(result) { if (result.error) { // The payment failed -- ask your customer for a new payment method. + handleFailure(result.error) } else { // The payment has succeeded. handleSuccess(result); @@ -108,5 +109,14 @@ document.getElementById('server-response').submit(); } + handleFailure(message) { + let errors = document.getElementById('errors'); + + errors.textContent = ''; + errors.textContent = message; + errors.hidden = false; + + } + @endpush \ No newline at end of file From 92d91904d2489dde70656b6e354daaa513b7f851 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 14:05:45 +1000 Subject: [PATCH 6/8] Sepa --- app/PaymentDrivers/Stripe/SEPA.php | 109 +++++++++++++++++++++++++++++ composer.json | 1 + composer.lock | 59 +++++++++++++++- 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 app/PaymentDrivers/Stripe/SEPA.php diff --git a/app/PaymentDrivers/Stripe/SEPA.php b/app/PaymentDrivers/Stripe/SEPA.php new file mode 100644 index 0000000000..d78d0c065b --- /dev/null +++ b/app/PaymentDrivers/Stripe/SEPA.php @@ -0,0 +1,109 @@ +stripe_driver = $stripe_driver; + } + + public function authorizeView(array $data) + { + $customer = $this->stripe_driver->findOrCreateCustomer(); + + $setup_intent = \Stripe\SetupIntent::create([ + 'payment_method_types' => ['sepa_debit'], + 'customer' => $customer->id, + ], $this->stripe_driver->stripe_connect_auth); + + $client_secret = $setup_intent->client_secret + // Pass the client secret to the client + + + $data['gateway'] = $this->stripe; + + return render('gateways.stripe.sepa.authorize', array_merge($data)); + } + + + public function paymentResponse(PaymentResponseRequest $request) + { + + // $this->stripe_driver->init(); + + // $state = [ + // 'server_response' => json_decode($request->gateway_response), + // 'payment_hash' => $request->payment_hash, + // ]; + + // $state['payment_intent'] = \Stripe\PaymentIntent::retrieve($state['server_response']->id, $this->stripe_driver->stripe_connect_auth); + + // $state['customer'] = $state['payment_intent']->customer; + + // $this->stripe_driver->payment_hash->data = array_merge((array) $this->stripe_driver->payment_hash->data, $state); + // $this->stripe_driver->payment_hash->save(); + + // $server_response = $this->stripe_driver->payment_hash->data->server_response; + + // $response_handler = new CreditCard($this->stripe_driver); + + // if ($server_response->status == 'succeeded') { + + // $this->stripe_driver->logSuccessfulGatewayResponse(['response' => json_decode($request->gateway_response), 'data' => $this->stripe_driver->payment_hash], SystemLog::TYPE_STRIPE); + + // return $response_handler->processSuccessfulPayment(); + // } + + // return $response_handler->processUnsuccessfulPayment($server_response); + + + } + + /* Searches for a stripe customer by email + otherwise searches by gateway tokens in StripePaymentdriver + finally creates a new customer if none found + */ + private function getCustomer() + { + $searchResults = \Stripe\Customer::all([ + "email" => $this->stripe_driver->client->present()->email(), + "limit" => 1, + "starting_after" => null + ], $this->stripe_driver->stripe_connect_auth); + + + if(count($searchResults) >= 1) + return $searchResults[0]; + + return $this->stripe_driver->findOrCreateCustomer(); + + } +} + diff --git a/composer.json b/composer.json index 8595adacbb..1a43c7a250 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "fakerphp/faker": "^1.14", "fideloper/proxy": "^4.2", "fruitcake/laravel-cors": "^2.0", + "gocardless/gocardless-pro": "^4.12", "google/apiclient": "^2.7", "guzzlehttp/guzzle": "^7.0.1", "hashids/hashids": "^4.0", diff --git a/composer.lock b/composer.lock index 89eea29d9e..a031a9c51a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "275a9dd3910b6ec79607b098406dc6c7", + "content-hash": "bcd9405b1978cef268d732794883e91d", "packages": [ { "name": "asm/php-ansible", @@ -2221,6 +2221,61 @@ ], "time": "2021-04-26T11:24:25+00:00" }, + { + "name": "gocardless/gocardless-pro", + "version": "4.12.0", + "source": { + "type": "git", + "url": "https://github.com/gocardless/gocardless-pro-php.git", + "reference": "e63b97b215c27179023dd2e911133ee75e543fbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gocardless/gocardless-pro-php/zipball/e63b97b215c27179023dd2e911133ee75e543fbd", + "reference": "e63b97b215c27179023dd2e911133ee75e543fbd", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/guzzle": "^6.0 | ^7.0", + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "GoCardlessPro\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "GoCardless and contributors", + "homepage": "https://github.com/gocardless/gocardless-pro-php/contributors" + } + ], + "description": "GoCardless Pro PHP Client Library", + "homepage": "https://gocardless.com/", + "keywords": [ + "api", + "direct debit", + "gocardless" + ], + "support": { + "issues": "https://github.com/gocardless/gocardless-pro-php/issues", + "source": "https://github.com/gocardless/gocardless-pro-php/tree/v4.12.0" + }, + "time": "2021-08-12T15:41:16+00:00" + }, { "name": "google/apiclient", "version": "v2.10.1", @@ -14972,5 +15027,5 @@ "platform-dev": { "php": "^7.3|^7.4|^8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } From 74427d6860879283e59b7e203daded354625ba7f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 14:34:51 +1000 Subject: [PATCH 7/8] Support messages --- app/Mail/SupportMessageSent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php index ff50840209..42016f7ca7 100644 --- a/app/Mail/SupportMessageSent.php +++ b/app/Mail/SupportMessageSent.php @@ -53,7 +53,7 @@ class SupportMessageSent extends Mailable $account = auth()->user()->account; $priority = ''; - $plan = $account->plan ?: ''; + $plan = $account->plan ?: 'customer support'; $plan = ucfirst($plan); if(strlen($plan) >1) From 950e0eca203349005b2d70e44891da67b4a53dc8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 15:27:01 +1000 Subject: [PATCH 8/8] SEPA --- app/Helpers/Invoice/InvoiceItemSum.php | 1 + .../gateways/stripe/sepa/authorize.blade.php | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 resources/views/portal/ninja2020/gateways/stripe/sepa/authorize.blade.php diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index a5a3a73a3e..fc2a28d97b 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -92,6 +92,7 @@ class InvoiceItemSum private function sumLineItem() { //todo need to support quantities less than the precision amount // $this->setLineTotal($this->formatValue($this->item->cost, $this->currency->precision) * $this->formatValue($this->item->quantity, $this->currency->precision)); + $this->setLineTotal($this->item->cost * $this->item->quantity); return $this; diff --git a/resources/views/portal/ninja2020/gateways/stripe/sepa/authorize.blade.php b/resources/views/portal/ninja2020/gateways/stripe/sepa/authorize.blade.php new file mode 100644 index 0000000000..def2150fb3 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/sepa/authorize.blade.php @@ -0,0 +1,82 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'SEPA', 'card_title' => 'SEPA']) + +@section('gateway_head') + @if($gateway->company_gateway->getConfigField('account_id')) + + + @else + + @endif +@endsection + +@section('gateway_content') + @if(session()->has('ach_error')) +
+

{{ session('ach_error') }}

+
+ @endif + +
+ @csrf + + + + + +
+ + + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_type')]) + + + {{ __('texts.individual_account') }} + + + + {{ __('texts.company_account') }} + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.country')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.currency')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.routing_number')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_number')]) + + @endcomponent + + @component('portal.ninja2020.components.general.card-element-single') + + + @endcomponent + + @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'save-button']) + {{ ctrans('texts.add_payment_method') }} + @endcomponent +@endsection + +@section('gateway_footer') + + +@endsection