diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index a5a3a73a3e48..fc2a28d97ba6 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/app/Import/Transformers/Csv/ClientTransformer.php b/app/Import/Transformers/Csv/ClientTransformer.php index f9fb21212c1f..eabcf10b2b9a 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' ), diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index 26b961df7f4f..79a072f8b7cd 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(); diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php index 709c9058ee26..42016f7ca75e 100644 --- a/app/Mail/SupportMessageSent.php +++ b/app/Mail/SupportMessageSent.php @@ -53,7 +53,8 @@ class SupportMessageSent extends Mailable $account = auth()->user()->account; $priority = ''; - $plan = $account->plan ?: ''; + $plan = $account->plan ?: 'customer support'; + $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()) diff --git a/app/PaymentDrivers/Stripe/ApplePay.php b/app/PaymentDrivers/Stripe/ApplePay.php new file mode 100644 index 000000000000..7b03510bd766 --- /dev/null +++ b/app/PaymentDrivers/Stripe/ApplePay.php @@ -0,0 +1,117 @@ +stripe_driver = $stripe_driver; + } + + 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; + $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); + + + } + + + 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/app/PaymentDrivers/Stripe/CreditCard.php b/app/PaymentDrivers/Stripe/CreditCard.php index 72981fea3013..cdb36d8d3557 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/Stripe/SEPA.php b/app/PaymentDrivers/Stripe/SEPA.php new file mode 100644 index 000000000000..d78d0c065b88 --- /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/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 1edb8f44254f..12cd5a30e1a9 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/composer.json b/composer.json index 8595adacbbc3..1a43c7a250be 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 89eea29d9e7b..a031a9c51a07 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" } 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 000000000000..76e72631df7c --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/applepay/pay.blade.php @@ -0,0 +1,122 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Apple Pay', 'card_title' => 'Apple Pay']) + +@section('gateway_head') + +@endsection + +@section('gateway_content') +
+ + + + @include('portal.ninja2020.gateways.includes.payment_details') + + + +@endsection + +@push('footer') + + + +@endpush \ No newline at end of file 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 000000000000..def2150fb30d --- /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') }}
+