From 9a08eb0940a749a6a4fb2297f40eb6f50f825fd7 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 5 Dec 2022 08:12:05 +0100 Subject: [PATCH 1/4] Add support for Klarna --- app/PaymentDrivers/StripePaymentDriver.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index a46d17ace9c8..73500a5a70e8 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -37,6 +37,7 @@ use App\PaymentDrivers\Stripe\CreditCard; use App\PaymentDrivers\Stripe\EPS; use App\PaymentDrivers\Stripe\FPX; use App\PaymentDrivers\Stripe\GIROPAY; +use App\PaymentDrivers\Stripe\Klarna; use App\PaymentDrivers\Stripe\iDeal; use App\PaymentDrivers\Stripe\ImportCustomers; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook; @@ -97,6 +98,7 @@ class StripePaymentDriver extends BaseDriver GatewayType::BECS => BECS::class, GatewayType::ACSS => ACSS::class, GatewayType::FPX => FPX::class, + GatewayType::KLARNA => KLARNA::class, ]; const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; @@ -233,6 +235,13 @@ class StripePaymentDriver extends BaseDriver && in_array($this->client->country->iso_3166_3, ['CAN', 'USA'])) { $types[] = GatewayType::ACSS; } + if ($this->client + && $this->client->currency() + && in_array($this->client->currency()->code, ['EUR', 'USD', 'GBP', 'DKK', 'SEK', 'NOK']) + && isset($this->client->country) + && in_array($this->client->country->iso_3166_3, ['DE', 'AT', 'BE'])) { + $types[] = GatewayType::KLARNA; + } if ( $this->client @@ -271,6 +280,9 @@ class StripePaymentDriver extends BaseDriver case GatewayType::GIROPAY: return 'gateways.stripe.giropay'; break; + case GatewayType::KLARNA: + return 'gateways.stripe.klarna'; + break; case GatewayType::IDEAL: return 'gateways.stripe.ideal'; case GatewayType::EPS: From 701344947f45fc3265b8f87e47df9d5bc1c88eae Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 5 Dec 2022 08:42:28 +0100 Subject: [PATCH 2/4] Add Support for Klarna --- app/Models/Gateway.php | 5 +- app/Models/GatewayType.php | 4 + app/Models/PaymentType.php | 1 + app/PaymentDrivers/Stripe/Klarna.php | 154 ++++++++++++++++++ app/PaymentDrivers/StripePaymentDriver.php | 26 ++- .../2022_05_12_56879_add_stripe_klarna.php | 34 ++++ lang/en/texts.php | 98 +++++++++-- .../js/clients/payments/stripe-klarna.js | 68 ++++++++ .../stripe/klarna/authorize.blade.php | 7 + .../gateways/stripe/klarna/pay.blade.php | 31 ++++ webpack.mix.js | 4 + 11 files changed, 409 insertions(+), 23 deletions(-) create mode 100644 app/PaymentDrivers/Stripe/Klarna.php create mode 100644 database/migrations/2022_05_12_56879_add_stripe_klarna.php create mode 100644 resources/js/clients/payments/stripe-klarna.js create mode 100644 resources/views/portal/ninja2020/gateways/stripe/klarna/authorize.blade.php create mode 100644 resources/views/portal/ninja2020/gateways/stripe/klarna/pay.blade.php diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 1fc248c0b61e..47f35f9c9863 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -134,13 +134,14 @@ class Gateway extends StaticModel GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']], ]; break; - case 56: + case 56: //Stripe return [ GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded']], GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false], GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false], - GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], //Stripe + GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], + GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']], diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index a3e1bc4fd2e6..9802edae51fa 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -61,6 +61,8 @@ class GatewayType extends StaticModel const FPX = 22; + const KLARNA = 23; + public function gateway() { return $this->belongsTo(Gateway::class); @@ -116,6 +118,8 @@ class GatewayType extends StaticModel return ctrans('texts.payment_type_instant_bank_pay'); case self::FPX: return ctrans('texts.fpx'); + case self::KLARNA: + return ctrans('texts.klarna'); default: return ' '; break; diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index 22eb885b787d..21b165014c65 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -55,6 +55,7 @@ class PaymentType extends StaticModel const ACSS = 44; const INSTANT_BANK_PAY = 45; const FPX = 46; + const KLARNA = 47; public static function parseCardType($cardName) { diff --git a/app/PaymentDrivers/Stripe/Klarna.php b/app/PaymentDrivers/Stripe/Klarna.php new file mode 100644 index 000000000000..dfafdef7a2a2 --- /dev/null +++ b/app/PaymentDrivers/Stripe/Klarna.php @@ -0,0 +1,154 @@ +stripe = $stripe; + } + + public function authorizeView($data) + { + return render('gateways.stripe.klarna.authorize', $data); + } + + public function paymentView(array $data) + { + $this->stripe->init(); + + $data['gateway'] = $this->stripe; + $data['return_url'] = $this->buildReturnUrl(); + $data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()); + $data['client'] = $this->stripe->client; + $data['customer'] = $this->stripe->findOrCreateCustomer()->id; + $data['country'] = $this->stripe->client->country->iso_3166_2; + + $amount = $data['total']['amount_with_fee']; + + $invoice_numbers = collect($data['invoices'])->pluck('invoice_number'); + + if ($invoice_numbers.length > 0) { + $description = ctrans('texts.payment_provider_paymenttext', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]); + } else { + $description = ctrans('texts.payment_prvoder_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]); + } + + $intent = \Stripe\PaymentIntent::create([ + 'amount' => $data['stripe_amount'], + 'currency' => 'eur', + 'payment_method_types' => ['klarna'], + 'customer' => $this->stripe->findOrCreateCustomer(), + 'description' => $description, + 'metadata' => [ + 'payment_hash' => $this->stripe->payment_hash->hash, + 'gateway_type_id' => GatewayType::GIROPAY, + ], + ], $this->stripe->stripe_connect_auth); + + $data['pi_client_secret'] = $intent->client_secret; + + $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]); + $this->stripe->payment_hash->save(); + + return render('gateways.stripe.klarna.pay', $data); + } + + private function buildReturnUrl(): string + { + return route('client.payments.response', [ + 'company_gateway_id' => $this->stripe->company_gateway->id, + 'payment_hash' => $this->stripe->payment_hash->hash, + 'payment_method_id' => GatewayType::KLARNA, + ]); + } + + public function paymentResponse($request) + { + $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all()); + $this->stripe->payment_hash->save(); + + if (in_array($request->redirect_status, ['succeeded','pending'])) { + return $this->processSuccessfulPayment($request->payment_intent); + } + + return $this->processUnsuccessfulPayment(); + } + + public function processSuccessfulPayment(string $payment_intent) + { + + $this->stripe->init(); + + //catch duplicate submissions. + if (Payment::where('transaction_reference', $payment_intent)->exists()) { + return redirect()->route('client.payments.index'); + } + + $data = [ + 'payment_method' => $payment_intent, + 'payment_type' => PaymentType::GIROPAY, + 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), + 'transaction_reference' => $payment_intent, + 'gateway_type_id' => GatewayType::KLARNA, + ]; + + $this->stripe->createPayment($data, Payment::STATUS_PENDING); + + SystemLogger::dispatch( + ['response' => $this->stripe->payment_hash->data, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + return redirect()->route('client.payments.index'); + } + + public function processUnsuccessfulPayment() + { + $server_response = $this->stripe->payment_hash->data; + + $this->stripe->sendFailureMail($server_response); + + $message = [ + 'server_response' => $server_response, + 'data' => $this->stripe->payment_hash->data, + ]; + + SystemLogger::dispatch( + $message, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + throw new PaymentFailed(ctrans('texts.payment_provider_failed_process_payment'), 500); + } +} diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 73500a5a70e8..09926793567d 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -410,7 +410,7 @@ class StripePaymentDriver extends BaseDriver { $this->init(); - $params = []; + $params = ['usage' => 'off_session']; $meta = $this->stripe_connect_auth; return SetupIntent::create($params, $meta); @@ -669,14 +669,22 @@ class StripePaymentDriver extends BaseDriver ], $this->stripe_connect_auth); if ($charge->captured) { - $payment = Payment::query() - ->where('transaction_reference', $transaction['payment_intent']) - ->where('company_id', $request->getCompany()->id) - ->where(function ($query) use ($transaction) { - $query->where('transaction_reference', $transaction['payment_intent']) - ->orWhere('transaction_reference', $transaction['id']); - }) - ->first(); + + $payment = false; + + if(isset($transaction['payment_intent'])) + { + $payment = Payment::query() + ->where('transaction_reference', $transaction['payment_intent']) + ->where('company_id', $request->getCompany()->id) + ->first(); + } + elseif(isset($transaction['id'])) { + $payment = Payment::query() + ->where('transaction_reference', $transaction['id']) + ->where('company_id', $request->getCompany()->id) + ->first(); + } if ($payment) { $payment->status_id = Payment::STATUS_COMPLETED; diff --git a/database/migrations/2022_05_12_56879_add_stripe_klarna.php b/database/migrations/2022_05_12_56879_add_stripe_klarna.php new file mode 100644 index 000000000000..969553634d52 --- /dev/null +++ b/database/migrations/2022_05_12_56879_add_stripe_klarna.php @@ -0,0 +1,34 @@ +id = 47; + $type->name = 'Klarna'; + $type->gateway_type_id = GatewayType::KLARNA; + + $type->save(); + }); + $type = new GatewayType(); + + $type->id = 23; + $type->alias = 'klarna'; + $type->name = 'Klarna'; + + $type->save(); + } +}; diff --git a/lang/en/texts.php b/lang/en/texts.php index f829dee616e0..fe90fc06bef5 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2822,11 +2822,11 @@ $LANG = array( 'invalid_url' => 'Invalid URL', 'workflow_settings' => 'Workflow Settings', 'auto_email_invoice' => 'Auto Email', - 'auto_email_invoice_help' => 'Automatically email recurring invoices when they are created.', + 'auto_email_invoice_help' => 'Automatically email recurring invoices when created.', 'auto_archive_invoice' => 'Auto Archive', - 'auto_archive_invoice_help' => 'Automatically archive invoices when they are paid.', + 'auto_archive_invoice_help' => 'Automatically archive invoices when paid.', 'auto_archive_quote' => 'Auto Archive', - 'auto_archive_quote_help' => 'Automatically archive quotes when they are converted.', + 'auto_archive_quote_help' => 'Automatically archive quotes when converted to invoice.', 'require_approve_quote' => 'Require approve quote', 'require_approve_quote_help' => 'Require clients to approve quotes.', 'allow_approve_expired_quote' => 'Allow approve expired quote', @@ -3414,7 +3414,7 @@ $LANG = array( 'credit_number_counter' => 'Credit Number Counter', 'reset_counter_date' => 'Reset Counter Date', 'counter_padding' => 'Counter Padding', - 'shared_invoice_quote_counter' => 'Shared Invoice Quote Counter', + 'shared_invoice_quote_counter' => 'Share Invoice Quote Counter', 'default_tax_name_1' => 'Default Tax Name 1', 'default_tax_rate_1' => 'Default Tax Rate 1', 'default_tax_name_2' => 'Default Tax Name 2', @@ -3688,7 +3688,7 @@ $LANG = array( 'force_update_help' => 'You are running the latest version but there may be pending fixes available.', 'mark_paid_help' => 'Track the expense has been paid', 'mark_invoiceable_help' => 'Enable the expense to be invoiced', - 'add_documents_to_invoice_help' => 'Make the documents visible', + 'add_documents_to_invoice_help' => 'Make the documents visible to client', 'convert_currency_help' => 'Set an exchange rate', 'expense_settings' => 'Expense Settings', 'clone_to_recurring' => 'Clone to Recurring', @@ -4061,7 +4061,7 @@ $LANG = array( 'save_payment_method_details' => 'Save payment method details', 'new_card' => 'New card', 'new_bank_account' => 'New bank account', - 'company_limit_reached' => 'Limit of 10 companies per account.', + 'company_limit_reached' => 'Limit of :limit companies per account.', 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', 'credit_number_taken' => 'Credit number already taken', 'credit_not_found' => 'Credit not found', @@ -4199,7 +4199,7 @@ $LANG = array( 'client_id_number' => 'Client ID Number', 'count_minutes' => ':count Minutes', 'password_timeout' => 'Password Timeout', - 'shared_invoice_credit_counter' => 'Shared Invoice/Credit Counter', + 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', 'activity_80' => ':user created subscription :subscription', 'activity_81' => ':user updated subscription :subscription', @@ -4219,7 +4219,7 @@ $LANG = array( 'max_companies_desc' => 'You have reached your maximum number of companies. Delete existing companies to migrate new ones.', 'migration_already_completed' => 'Company already migrated', 'migration_already_completed_desc' => 'Looks like you already migrated :company_name to the V5 version of the Invoice Ninja. In case you want to start over, you can force migrate to wipe existing data.', - 'payment_method_cannot_be_authorized_first' => 'This payment method can be can saved for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', + 'payment_method_cannot_be_authorized_first' => 'This payment method can be can saved for future use, once you complete your first transaction. Don\'t forget to check "Store details" during payment process.', 'new_account' => 'New account', 'activity_100' => ':user created recurring invoice :recurring_invoice', 'activity_101' => ':user updated recurring invoice :recurring_invoice', @@ -4254,7 +4254,7 @@ $LANG = array( 'auto_bill_disabled' => 'Auto Bill Disabled', 'select_payment_method' => 'Select a payment method:', 'login_without_password' => 'Log in without password', - 'email_sent' => 'E-mail sent, please check your inbox.', + 'email_sent' => 'Email me when an invoice is sent', 'one_time_purchases' => 'One time purchases', 'recurring_purchases' => 'Recurring purchases', 'you_might_be_interested_in_following' => 'You might be interested in the following', @@ -4540,7 +4540,7 @@ $LANG = array( 'reminder_message' => 'Reminder for invoice :number for :balance', 'gmail_credentials_invalid_subject' => 'Send with GMail invalid credentials', 'gmail_credentials_invalid_body' => 'Your GMail credentials are not correct, please log into the administrator portal and navigate to Settings > User Details and disconnect and reconnect your GMail account. We will send you this notification daily until this issue is resolved', - 'notification_invoice_sent' => 'Invoice Sent', + 'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.', 'total_columns' => 'Total Fields', 'view_task' => 'View Task', 'cancel_invoice' => 'Cancel', @@ -4573,13 +4573,13 @@ $LANG = array( 'tax_amount3' => 'Tax Amount 3', 'update_project' => 'Update Project', 'auto_archive_invoice_cancelled' => 'Auto Archive Cancelled Invoice', - 'auto_archive_invoice_cancelled_help' => 'Automatically archive invoices when they are cancelled', + 'auto_archive_invoice_cancelled_help' => 'Automatically archive invoices when cancelled', 'no_invoices_found' => 'No invoices found', 'created_record' => 'Successfully created record', 'auto_archive_paid_invoices' => 'Auto Archive Paid', 'auto_archive_paid_invoices_help' => 'Automatically archive invoices when they are paid.', 'auto_archive_cancelled_invoices' => 'Auto Archive Cancelled', - 'auto_archive_cancelled_invoices_help' => 'Automatically archive invoices when they are cancelled.', + 'auto_archive_cancelled_invoices_help' => 'Automatically archive invoices when cancelled.', 'alternate_pdf_viewer' => 'Alternate PDF Viewer', 'alternate_pdf_viewer_help' => 'Improve scrolling over the PDF preview [BETA]', 'currency_cayman_island_dollar' => 'Cayman Island Dollar', @@ -4777,6 +4777,80 @@ $LANG = array( 'invoice_task_project' => 'Invoice Task Project', 'invoice_task_project_help' => 'Add the project to the invoice line items', + 'bulk_action' => 'Bulk Action', + 'phone_validation_error' => 'This mobile/cell phone number is not valid, please enter in E.164 format', + 'transaction' => 'Transaction', + 'disable_2fa' => 'Disable 2FA', + 'change_number' => 'Change Number', + 'resend_code' => 'Resend Code', + 'base_type' => 'Base Type', + 'category_type' => 'Category Type', + 'bank_transaction' => 'Transaction', + 'bulk_print' => 'Print PDF', + 'vendor_postal_code' => 'Vendor Postal Code', + 'preview_location' => 'Preview Location', + 'bottom' => 'Bottom', + 'side' => 'Side', + 'pdf_preview' => 'PDF Preview', + 'long_press_to_select' => 'Long Press to Select', + 'purchase_order_item' => 'Purchase Order Item', + 'would_you_rate_the_app' => 'Would you like to rate the app?', + 'include_deleted' => 'Include Deleted', + 'include_deleted_help' => 'Include deleted records in reports', + 'due_on' => 'Due On', + 'browser_pdf_viewer' => 'Use Browser PDF Viewer', + 'browser_pdf_viewer_help' => 'Warning: Prevents interacting with app over the PDF', + 'converted_transactions' => 'Successfully converted transactions', + 'default_category' => 'Default Category', + 'connect_accounts' => 'Connect Accounts', + 'manage_rules' => 'Manage Rules', + 'search_category' => 'Search 1 Category', + 'search_categories' => 'Search :count Categories', + 'min_amount' => 'Min Amount', + 'max_amount' => 'Max Amount', + 'converted_transaction' => 'Successfully converted transaction', + 'convert_to_payment' => 'Convert to Payment', + 'deposit' => 'Deposit', + 'withdrawal' => 'Withdrawal', + 'deposits' => 'Deposits', + 'withdrawals' => 'Withdrawals', + 'matched' => 'Matched', + 'unmatched' => 'Unmatched', + 'create_credit' => 'Create Credit', + 'transactions' => 'Transactions', + 'new_transaction' => 'New Transaction', + 'edit_transaction' => 'Edit Transaction', + 'created_transaction' => 'Successfully created transaction', + 'updated_transaction' => 'Successfully updated transaction', + 'archived_transaction' => 'Successfully archived transaction', + 'deleted_transaction' => 'Successfully deleted transaction', + 'removed_transaction' => 'Successfully removed transaction', + 'restored_transaction' => 'Successfully restored transaction', + 'search_transaction' => 'Search Transaction', + 'search_transactions' => 'Search :count Transactions', + 'deleted_bank_account' => 'Successfully deleted bank account', + 'removed_bank_account' => 'Successfully removed bank account', + 'restored_bank_account' => 'Successfully restored bank account', + 'search_bank_account' => 'Search Bank Account', + 'search_bank_accounts' => 'Search :count Bank Accounts', + 'code_was_sent_to' => 'A code has been sent via SMS to :number', + 'verify_phone_number_2fa_help' => 'Please verify your phone number for 2FA backup', + 'enable_applying_payments_later' => 'Enable Applying Payments Later', + 'line_item_tax_rates' => 'Line Item Tax Rates', + 'show_tasks_in_client_portal' => 'Show Tasks in Client Portal', + 'notification_quote_expired_subject' => 'Quote :invoice has expired for :client', + 'notification_quote_expired' => 'The following Quote :invoice for client :client and :amount has now expired.', + 'auto_sync' => 'Auto Sync', + 'refresh_accounts' => 'Refresh Accounts', + 'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account', + 'click_here_to_connect_bank_account' => 'Click here to connect your bank account', + 'include_tax' => 'Include tax', + 'email_template_change' => 'E-mail template body can be changed on', + 'task_update_authorization_error' => 'Insufficient permissions, or task may be locked', + 'cash_vs_accrual' => 'Accrual accounting', + 'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.', + 'expense_paid_report' => 'Expensed reporting', + 'expense_paid_report_help' => 'Turn on for reporting all expenses, turn off for reporting only paid expenses', ); return $LANG; diff --git a/resources/js/clients/payments/stripe-klarna.js b/resources/js/clients/payments/stripe-klarna.js new file mode 100644 index 000000000000..dfaa63092919 --- /dev/null +++ b/resources/js/clients/payments/stripe-klarna.js @@ -0,0 +1,68 @@ +/** + * Invoice Ninja (https://invoiceninja.com) + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +class ProcessKlarna { + constructor(key, stripeConnect) { + this.key = key; + this.errors = document.getElementById('errors'); + this.stripeConnect = stripeConnect; + } + + setupStripe = () => { + + if (this.stripeConnect){ + // this.stripe.stripeAccount = this.stripeConnect; + + this.stripe = Stripe(this.key, { + stripeAccount: this.stripeConnect, + }); + + } + else { + this.stripe = Stripe(this.key); + } + + + return this; + }; + + handle = () => { + document.getElementById('pay-now').addEventListener('click', (e) => { + let errors = document.getElementById('errors'); + + document.getElementById('pay-now').disabled = true; + document.querySelector('#pay-now > svg').classList.remove('hidden'); + document.querySelector('#pay-now > span').classList.add('hidden'); + + this.stripe.confirmKlarnaPayment( + document.querySelector('meta[name=pi-client-secret').content, + { + payment_method: { + billing_details: { + name: document.getElementById("giropay-name").value, + }, + }, + return_url: document.querySelector( + 'meta[name="return-url"]' + ).content, + } + ); + }); + }; +} + +const publishableKey = document.querySelector( + 'meta[name="stripe-publishable-key"]' +)?.content ?? ''; + +const stripeConnect = + document.querySelector('meta[name="stripe-account-id"]')?.content ?? ''; + +new ProcessKlarna(publishableKey, stripeConnect).setupStripe().handle(); diff --git a/resources/views/portal/ninja2020/gateways/stripe/klarna/authorize.blade.php b/resources/views/portal/ninja2020/gateways/stripe/klarna/authorize.blade.php new file mode 100644 index 000000000000..ceb2d28000d5 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/klarna/authorize.blade.php @@ -0,0 +1,7 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.bank_account'), 'card_title' => ctrans('texts.bank_account')]) + +@section('gateway_content') + @component('portal.ninja2020.components.general.card-element-single', ['title' => ctrans('texts.bank_account'), 'show_title' => false]) + {{ __('texts.sofort_authorize_label') }} + @endcomponent +@endsection diff --git a/resources/views/portal/ninja2020/gateways/stripe/klarna/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/klarna/pay.blade.php new file mode 100644 index 000000000000..54989216165f --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/stripe/klarna/pay.blade.php @@ -0,0 +1,31 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Klarna', 'card_title' => 'Klarna']) + +@section('gateway_head') + @if($gateway->company_gateway->getConfigField('account_id')) + + + @else + + @endif + + + + + +@endsection + +@section('gateway_content') + + + @include('portal.ninja2020.gateways.includes.payment_details') + + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')]) + {{ ctrans('texts.klarna') }} ({{ ctrans('texts.bank_transfer') }}) + @endcomponent + @include('portal.ninja2020.gateways.includes.pay_now') +@endsection + +@push('footer') + + +@endpush diff --git a/webpack.mix.js b/webpack.mix.js index 2ed78e49227c..df0604426709 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -22,6 +22,10 @@ mix.js("resources/js/app.js", "public/js") "resources/js/clients/payments/stripe-ach.js", "public/js/clients/payments/stripe-ach.js" ) + .js( + "resources/js/clients/payments/stripe-klarna.js", + "public/js/clients/payments/stripe-klarna.js" + ) .js( "resources/js/clients/invoices/action-selectors.js", "public/js/clients/invoices/action-selectors.js" From f57c7425b4abde10045007389e9749146a3f1a5f Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Mon, 5 Dec 2022 09:07:11 +0100 Subject: [PATCH 3/4] Added text --- lang/en/texts.php | 608 +++++++++++++++++++++++----------------------- 1 file changed, 305 insertions(+), 303 deletions(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index fe90fc06bef5..35057fac9c66 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -254,6 +254,8 @@ $LANG = array( 'notification_invoice_paid' => 'A payment of :amount was made by client :client towards Invoice :invoice.', 'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.', 'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.', + 'stripe_paymenttext' => 'Invoice :invoicenumber for :amount for client :client', + 'stripe_paymenttext_without_invoice' => 'Payment with no invoice for amount :amount for client :client', 'reset_password' => 'You can reset your account password by clicking the following button:', 'secure_payment' => 'Secure Payment', 'card_number' => 'Card Number', @@ -1004,7 +1006,7 @@ $LANG = array( 'status_approved' => 'Approved', 'quote_settings' => 'Quote Settings', 'auto_convert_quote' => 'Auto Convert', - 'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.', + 'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved.', 'validate' => 'Validate', 'info' => 'Info', 'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)', @@ -3899,307 +3901,307 @@ $LANG = array( 'registration_url' => 'Registration URL', 'show_product_cost' => 'Show Product Cost', 'complete' => 'Complete', - 'next' => 'Next', - 'next_step' => 'Next step', - 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', - 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', - 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', - 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', - 'reset_password_text' => 'Enter your email to reset your password.', - 'password_reset' => 'Password reset', - 'account_login_text' => 'Welcome back! Glad to see you.', - 'request_cancellation' => 'Request cancellation', - 'delete_payment_method' => 'Delete Payment Method', - 'about_to_delete_payment_method' => 'You are about to delete the payment method.', - 'action_cant_be_reversed' => 'Action can\'t be reversed', - 'profile_updated_successfully' => 'The profile has been updated successfully.', - 'currency_ethiopian_birr' => 'Ethiopian Birr', - 'client_information_text' => 'Use a permanent address where you can receive mail.', - 'status_id' => 'Invoice Status', - 'email_already_register' => 'This email is already linked to an account', - 'locations' => 'Locations', - 'freq_indefinitely' => 'Indefinitely', - 'cycles_remaining' => 'Cycles remaining', - 'i_understand_delete' => 'I understand, delete', - 'download_files' => 'Download Files', - 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', - 'new_signup' => 'New Signup', - 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', - 'notification_payment_paid_subject' => 'Payment was made by :client', - 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', - 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', - 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', - 'notification_bot' => 'Notification Bot', - 'invoice_number_placeholder' => 'Invoice # :invoice', - 'entity_number_placeholder' => ':entity # :entity_number', - 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', - 'display_log' => 'Display Log', - 'send_fail_logs_to_our_server' => 'Report errors in realtime', - 'setup' => 'Setup', - 'quick_overview_statistics' => 'Quick overview & statistics', - 'update_your_personal_info' => 'Update your personal information', - 'name_website_logo' => 'Name, website & logo', - 'make_sure_use_full_link' => 'Make sure you use full link to your site', - 'personal_address' => 'Personal address', - 'enter_your_personal_address' => 'Enter your personal address', - 'enter_your_shipping_address' => 'Enter your shipping address', - 'list_of_invoices' => 'List of invoices', - 'with_selected' => 'With selected', - 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', - 'list_of_recurring_invoices' => 'List of recurring invoices', - 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', - 'cancellation' => 'Cancellation', - 'about_cancellation' => 'In case you want to stop the recurring invoice, please click to request the cancellation.', - 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.', - 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', - 'list_of_payments' => 'List of payments', - 'payment_details' => 'Details of the payment', - 'list_of_payment_invoices' => 'List of invoices affected by the payment', - 'list_of_payment_methods' => 'List of payment methods', - 'payment_method_details' => 'Details of payment method', - 'permanently_remove_payment_method' => 'Permanently remove this payment method.', - 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', - 'confirmation' => 'Confirmation', - 'list_of_quotes' => 'Quotes', - 'waiting_for_approval' => 'Waiting for approval', - 'quote_still_not_approved' => 'This quote is still not approved', - 'list_of_credits' => 'Credits', - 'required_extensions' => 'Required extensions', - 'php_version' => 'PHP version', - 'writable_env_file' => 'Writable .env file', - 'env_not_writable' => '.env file is not writable by the current user.', - 'minumum_php_version' => 'Minimum PHP version', - 'satisfy_requirements' => 'Make sure all requirements are satisfied.', - 'oops_issues' => 'Oops, something does not look right!', - 'open_in_new_tab' => 'Open in new tab', - 'complete_your_payment' => 'Complete payment', - 'authorize_for_future_use' => 'Authorize payment method for future use', - 'page' => 'Page', - 'per_page' => 'Per page', - 'of' => 'Of', - 'view_credit' => 'View Credit', - 'to_view_entity_password' => 'To view the :entity you need to enter password.', - 'showing_x_of' => 'Showing :first to :last out of :total results', - 'no_results' => 'No results found.', - 'payment_failed_subject' => 'Payment failed for Client :client', - 'payment_failed_body' => 'A payment made by client :client failed with message :message', - 'register' => 'Register', - 'register_label' => 'Create your account in seconds', - 'password_confirmation' => 'Confirm your password', - 'verification' => 'Verification', - 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', - 'checkout_com' => 'Checkout.com', - 'footer_label' => 'Copyright © :year :company.', - 'credit_card_invalid' => 'Provided credit card number is not valid.', - 'month_invalid' => 'Provided month is not valid.', - 'year_invalid' => 'Provided year is not valid.', - 'https_required' => 'HTTPS is required, form will fail', - 'if_you_need_help' => 'If you need help you can post to our', - 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', - 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', - 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', - 'recommended_in_production' => 'Highly recommended in production', - 'enable_only_for_development' => 'Enable only for development', - 'test_pdf' => 'Test PDF', - 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', - 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', - 'node_status' => 'Node status', - 'npm_status' => 'NPM status', - 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', - 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', - 'locked_invoice' => 'This invoice is locked and unable to be modified', - 'downloads' => 'Downloads', - 'resource' => 'Resource', - 'document_details' => 'Details about the document', - 'hash' => 'Hash', - 'resources' => 'Resources', - 'allowed_file_types' => 'Allowed file types:', - 'common_codes' => 'Common codes and their meanings', - 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', - 'download_selected' => 'Download selected', - 'to_pay_invoices' => 'To pay invoices, you have to', - 'add_payment_method_first' => 'add payment method', - 'no_items_selected' => 'No items selected.', - 'payment_due' => 'Payment due', - 'account_balance' => 'Account balance', - 'thanks' => 'Thanks', - 'minimum_required_payment' => 'Minimum required payment is :amount', - 'under_payments_disabled' => 'Company doesn\'t support under payments.', - 'over_payments_disabled' => 'Company doesn\'t support over payments.', - 'saved_at' => 'Saved at :time', - 'credit_payment' => 'Credit applied to Invoice :invoice_number', - 'credit_subject' => 'New credit :number from :account', - 'credit_message' => 'To view your credit for :amount, click the link below.', - 'payment_type_Crypto' => 'Cryptocurrency', - 'payment_type_Credit' => 'Credit', - 'store_for_future_use' => 'Store for future use', - 'pay_with_credit' => 'Pay with credit', - 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', - 'pay_with' => 'Pay with', - 'n/a' => 'N/A', - 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', - 'not_specified' => 'Not specified', - 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', - 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', - 'pay' => 'Pay', - 'instructions' => 'Instructions', - 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', - 'assigned_user' => 'Assigned User', - 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', - 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', - 'minimum_payment' => 'Minimum Payment', - 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', - 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', - 'required_payment_information' => 'Required payment details', - 'required_payment_information_more' => 'To complete a payment we need more details about you.', - 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', - 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', - 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', - 'save_payment_method_details' => 'Save payment method details', - 'new_card' => 'New card', - 'new_bank_account' => 'New bank account', - 'company_limit_reached' => 'Limit of :limit companies per account.', - 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', - 'credit_number_taken' => 'Credit number already taken', - 'credit_not_found' => 'Credit not found', - 'invoices_dont_match_client' => 'Selected invoices are not from a single client', - 'duplicate_credits_submitted' => 'Duplicate credits submitted.', - 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', - 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', - 'client_id_required' => 'Client id is required', - 'expense_number_taken' => 'Expense number already taken', - 'invoice_number_taken' => 'Invoice number already taken', - 'payment_id_required' => 'Payment `id` required.', - 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', - 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', - 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', - 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', - 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', - 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', - 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', - 'project_client_do_not_match' => 'Project client does not match entity client', - 'quote_number_taken' => 'Quote number already taken', - 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', - 'user_not_associated_with_account' => 'User not associated with this account', - 'amounts_do_not_balance' => 'Amounts do not balance correctly.', - 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', - 'insufficient_credit_balance' => 'Insufficient balance on credit.', - 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', - 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', - 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', - 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', - 'migration_completed' => 'Migration completed', - 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', - 'api_404' => '404 | Nothing to see here!', - 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', - 'no_backup_exists' => 'No backup exists for this activity', - 'company_user_not_found' => 'Company User record not found', - 'no_credits_found' => 'No credits found.', - 'action_unavailable' => 'The requested action :action is not available.', - 'no_documents_found' => 'No Documents Found', - 'no_group_settings_found' => 'No group settings found', - 'access_denied' => 'Insufficient privileges to access/modify this resource', - 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', - 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', - 'route_not_available' => 'Route not available', - 'invalid_design_object' => 'Invalid custom design object', - 'quote_not_found' => 'Quote/s not found', - 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', - 'scheduler_has_run' => 'Scheduler has run', - 'scheduler_has_never_run' => 'Scheduler has never run', - 'self_update_not_available' => 'Self update not available on this system.', - 'user_detached' => 'User detached from company', - 'create_webhook_failure' => 'Failed to create Webhook', - 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', - 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', - 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', - 'vendor_address1' => 'Vendor Street', - 'vendor_address2' => 'Vendor Apt/Suite', - 'partially_unapplied' => 'Partially Unapplied', - 'select_a_gmail_user' => 'Please select a user authenticated with Gmail', - 'list_long_press' => 'List Long Press', - 'show_actions' => 'Show Actions', - 'start_multiselect' => 'Start Multiselect', - 'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address', - 'converted_paid_to_date' => 'Converted Paid to Date', - 'converted_credit_balance' => 'Converted Credit Balance', - 'converted_total' => 'Converted Total', - 'reply_to_name' => 'Reply-To Name', - 'payment_status_-2' => 'Partially Unapplied', - 'color_theme' => 'Color Theme', - 'start_migration' => 'Start Migration', - 'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact', - 'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice', - 'hello' => 'Hello', - 'group_documents' => 'Group documents', - 'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?', - 'migration_select_company_label' => 'Select companies to migrate', - 'force_migration' => 'Force migration', - 'require_password_with_social_login' => 'Require Password with Social Login', - 'stay_logged_in' => 'Stay Logged In', - 'session_about_to_expire' => 'Warning: Your session is about to expire', - 'count_hours' => ':count Hours', - 'count_day' => '1 Day', - 'count_days' => ':count Days', - 'web_session_timeout' => 'Web Session Timeout', - 'security_settings' => 'Security Settings', - 'resend_email' => 'Resend Email', - 'confirm_your_email_address' => 'Please confirm your email address', - 'freshbooks' => 'FreshBooks', - 'invoice2go' => 'Invoice2go', - 'invoicely' => 'Invoicely', - 'waveaccounting' => 'Wave Accounting', - 'zoho' => 'Zoho', - 'accounting' => 'Accounting', - 'required_files_missing' => 'Please provide all CSVs.', - 'migration_auth_label' => 'Let\'s continue by authenticating.', - 'api_secret' => 'API secret', - 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', - 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', - 'use_last_email' => 'Use last email', - 'activate_company' => 'Activate Company', - 'activate_company_help' => 'Enable emails, recurring invoices and notifications', - 'an_error_occurred_try_again' => 'An error occurred, please try again', - 'please_first_set_a_password' => 'Please first set a password', - 'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA', - 'help_translate' => 'Help Translate', - 'please_select_a_country' => 'Please select a country', - 'disabled_two_factor' => 'Successfully disabled 2FA', - 'connected_google' => 'Successfully connected account', - 'disconnected_google' => 'Successfully disconnected account', - 'delivered' => 'Delivered', - 'spam' => 'Spam', - 'view_docs' => 'View Docs', - 'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication', - 'send_sms' => 'Send SMS', - 'sms_code' => 'SMS Code', - 'connect_google' => 'Connect Google', - 'disconnect_google' => 'Disconnect Google', - 'disable_two_factor' => 'Disable Two Factor', - 'invoice_task_datelog' => 'Invoice Task Datelog', - 'invoice_task_datelog_help' => 'Add date details to the invoice line items', - 'promo_code' => 'Promo code', - 'recurring_invoice_issued_to' => 'Recurring invoice issued to', - 'subscription' => 'Subscription', - 'new_subscription' => 'New Subscription', - 'deleted_subscription' => 'Successfully deleted subscription', - 'removed_subscription' => 'Successfully removed subscription', - 'restored_subscription' => 'Successfully restored subscription', - 'search_subscription' => 'Search 1 Subscription', - 'search_subscriptions' => 'Search :count Subscriptions', - 'subdomain_is_not_available' => 'Subdomain is not available', - 'connect_gmail' => 'Connect Gmail', - 'disconnect_gmail' => 'Disconnect Gmail', - 'connected_gmail' => 'Successfully connected Gmail', - 'disconnected_gmail' => 'Successfully disconnected Gmail', - 'update_fail_help' => 'Changes to the codebase may be blocking the update, you can run this command to discard the changes:', - 'client_id_number' => 'Client ID Number', - 'count_minutes' => ':count Minutes', - 'password_timeout' => 'Password Timeout', - 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', + 'next' => 'Next', + 'next_step' => 'Next step', + 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', + 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', + 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', + 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', + 'reset_password_text' => 'Enter your email to reset your password.', + 'password_reset' => 'Password reset', + 'account_login_text' => 'Welcome back! Glad to see you.', + 'request_cancellation' => 'Request cancellation', + 'delete_payment_method' => 'Delete Payment Method', + 'about_to_delete_payment_method' => 'You are about to delete the payment method.', + 'action_cant_be_reversed' => 'Action can\'t be reversed', + 'profile_updated_successfully' => 'The profile has been updated successfully.', + 'currency_ethiopian_birr' => 'Ethiopian Birr', + 'client_information_text' => 'Use a permanent address where you can receive mail.', + 'status_id' => 'Invoice Status', + 'email_already_register' => 'This email is already linked to an account', + 'locations' => 'Locations', + 'freq_indefinitely' => 'Indefinitely', + 'cycles_remaining' => 'Cycles remaining', + 'i_understand_delete' => 'I understand, delete', + 'download_files' => 'Download Files', + 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', + 'new_signup' => 'New Signup', + 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', + 'notification_payment_paid_subject' => 'Payment was made by :client', + 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', + 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', + 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', + 'notification_bot' => 'Notification Bot', + 'invoice_number_placeholder' => 'Invoice # :invoice', + 'entity_number_placeholder' => ':entity # :entity_number', + 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', + 'display_log' => 'Display Log', + 'send_fail_logs_to_our_server' => 'Report errors in realtime', + 'setup' => 'Setup', + 'quick_overview_statistics' => 'Quick overview & statistics', + 'update_your_personal_info' => 'Update your personal information', + 'name_website_logo' => 'Name, website & logo', + 'make_sure_use_full_link' => 'Make sure you use full link to your site', + 'personal_address' => 'Personal address', + 'enter_your_personal_address' => 'Enter your personal address', + 'enter_your_shipping_address' => 'Enter your shipping address', + 'list_of_invoices' => 'List of invoices', + 'with_selected' => 'With selected', + 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', + 'list_of_recurring_invoices' => 'List of recurring invoices', + 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', + 'cancellation' => 'Cancellation', + 'about_cancellation' => 'In case you want to stop the recurring invoice, please click to request the cancellation.', + 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.', + 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', + 'list_of_payments' => 'List of payments', + 'payment_details' => 'Details of the payment', + 'list_of_payment_invoices' => 'List of invoices affected by the payment', + 'list_of_payment_methods' => 'List of payment methods', + 'payment_method_details' => 'Details of payment method', + 'permanently_remove_payment_method' => 'Permanently remove this payment method.', + 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', + 'confirmation' => 'Confirmation', + 'list_of_quotes' => 'Quotes', + 'waiting_for_approval' => 'Waiting for approval', + 'quote_still_not_approved' => 'This quote is still not approved', + 'list_of_credits' => 'Credits', + 'required_extensions' => 'Required extensions', + 'php_version' => 'PHP version', + 'writable_env_file' => 'Writable .env file', + 'env_not_writable' => '.env file is not writable by the current user.', + 'minumum_php_version' => 'Minimum PHP version', + 'satisfy_requirements' => 'Make sure all requirements are satisfied.', + 'oops_issues' => 'Oops, something does not look right!', + 'open_in_new_tab' => 'Open in new tab', + 'complete_your_payment' => 'Complete payment', + 'authorize_for_future_use' => 'Authorize payment method for future use', + 'page' => 'Page', + 'per_page' => 'Per page', + 'of' => 'Of', + 'view_credit' => 'View Credit', + 'to_view_entity_password' => 'To view the :entity you need to enter password.', + 'showing_x_of' => 'Showing :first to :last out of :total results', + 'no_results' => 'No results found.', + 'payment_failed_subject' => 'Payment failed for Client :client', + 'payment_failed_body' => 'A payment made by client :client failed with message :message', + 'register' => 'Register', + 'register_label' => 'Create your account in seconds', + 'password_confirmation' => 'Confirm your password', + 'verification' => 'Verification', + 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', + 'checkout_com' => 'Checkout.com', + 'footer_label' => 'Copyright © :year :company.', + 'credit_card_invalid' => 'Provided credit card number is not valid.', + 'month_invalid' => 'Provided month is not valid.', + 'year_invalid' => 'Provided year is not valid.', + 'https_required' => 'HTTPS is required, form will fail', + 'if_you_need_help' => 'If you need help you can post to our', + 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', + 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', + 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', + 'recommended_in_production' => 'Highly recommended in production', + 'enable_only_for_development' => 'Enable only for development', + 'test_pdf' => 'Test PDF', + 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', + 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', + 'node_status' => 'Node status', + 'npm_status' => 'NPM status', + 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', + 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', + 'locked_invoice' => 'This invoice is locked and unable to be modified', + 'downloads' => 'Downloads', + 'resource' => 'Resource', + 'document_details' => 'Details about the document', + 'hash' => 'Hash', + 'resources' => 'Resources', + 'allowed_file_types' => 'Allowed file types:', + 'common_codes' => 'Common codes and their meanings', + 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', + 'download_selected' => 'Download selected', + 'to_pay_invoices' => 'To pay invoices, you have to', + 'add_payment_method_first' => 'add payment method', + 'no_items_selected' => 'No items selected.', + 'payment_due' => 'Payment due', + 'account_balance' => 'Account balance', + 'thanks' => 'Thanks', + 'minimum_required_payment' => 'Minimum required payment is :amount', + 'under_payments_disabled' => 'Company doesn\'t support under payments.', + 'over_payments_disabled' => 'Company doesn\'t support over payments.', + 'saved_at' => 'Saved at :time', + 'credit_payment' => 'Credit applied to Invoice :invoice_number', + 'credit_subject' => 'New credit :number from :account', + 'credit_message' => 'To view your credit for :amount, click the link below.', + 'payment_type_Crypto' => 'Cryptocurrency', + 'payment_type_Credit' => 'Credit', + 'store_for_future_use' => 'Store for future use', + 'pay_with_credit' => 'Pay with credit', + 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', + 'pay_with' => 'Pay with', + 'n/a' => 'N/A', + 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', + 'not_specified' => 'Not specified', + 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', + 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', + 'pay' => 'Pay', + 'instructions' => 'Instructions', + 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', + 'assigned_user' => 'Assigned User', + 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', + 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', + 'minimum_payment' => 'Minimum Payment', + 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', + 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', + 'required_payment_information' => 'Required payment details', + 'required_payment_information_more' => 'To complete a payment we need more details about you.', + 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', + 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', + 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', + 'save_payment_method_details' => 'Save payment method details', + 'new_card' => 'New card', + 'new_bank_account' => 'New bank account', + 'company_limit_reached' => 'Limit of :limit companies per account.', + 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', + 'credit_number_taken' => 'Credit number already taken', + 'credit_not_found' => 'Credit not found', + 'invoices_dont_match_client' => 'Selected invoices are not from a single client', + 'duplicate_credits_submitted' => 'Duplicate credits submitted.', + 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', + 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', + 'client_id_required' => 'Client id is required', + 'expense_number_taken' => 'Expense number already taken', + 'invoice_number_taken' => 'Invoice number already taken', + 'payment_id_required' => 'Payment `id` required.', + 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', + 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', + 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', + 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', + 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', + 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', + 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', + 'project_client_do_not_match' => 'Project client does not match entity client', + 'quote_number_taken' => 'Quote number already taken', + 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', + 'user_not_associated_with_account' => 'User not associated with this account', + 'amounts_do_not_balance' => 'Amounts do not balance correctly.', + 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', + 'insufficient_credit_balance' => 'Insufficient balance on credit.', + 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', + 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', + 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', + 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', + 'migration_completed' => 'Migration completed', + 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', + 'api_404' => '404 | Nothing to see here!', + 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', + 'no_backup_exists' => 'No backup exists for this activity', + 'company_user_not_found' => 'Company User record not found', + 'no_credits_found' => 'No credits found.', + 'action_unavailable' => 'The requested action :action is not available.', + 'no_documents_found' => 'No Documents Found', + 'no_group_settings_found' => 'No group settings found', + 'access_denied' => 'Insufficient privileges to access/modify this resource', + 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', + 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', + 'route_not_available' => 'Route not available', + 'invalid_design_object' => 'Invalid custom design object', + 'quote_not_found' => 'Quote/s not found', + 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', + 'scheduler_has_run' => 'Scheduler has run', + 'scheduler_has_never_run' => 'Scheduler has never run', + 'self_update_not_available' => 'Self update not available on this system.', + 'user_detached' => 'User detached from company', + 'create_webhook_failure' => 'Failed to create Webhook', + 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', + 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', + 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', + 'vendor_address1' => 'Vendor Street', + 'vendor_address2' => 'Vendor Apt/Suite', + 'partially_unapplied' => 'Partially Unapplied', + 'select_a_gmail_user' => 'Please select a user authenticated with Gmail', + 'list_long_press' => 'List Long Press', + 'show_actions' => 'Show Actions', + 'start_multiselect' => 'Start Multiselect', + 'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address', + 'converted_paid_to_date' => 'Converted Paid to Date', + 'converted_credit_balance' => 'Converted Credit Balance', + 'converted_total' => 'Converted Total', + 'reply_to_name' => 'Reply-To Name', + 'payment_status_-2' => 'Partially Unapplied', + 'color_theme' => 'Color Theme', + 'start_migration' => 'Start Migration', + 'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact', + 'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice', + 'hello' => 'Hello', + 'group_documents' => 'Group documents', + 'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?', + 'migration_select_company_label' => 'Select companies to migrate', + 'force_migration' => 'Force migration', + 'require_password_with_social_login' => 'Require Password with Social Login', + 'stay_logged_in' => 'Stay Logged In', + 'session_about_to_expire' => 'Warning: Your session is about to expire', + 'count_hours' => ':count Hours', + 'count_day' => '1 Day', + 'count_days' => ':count Days', + 'web_session_timeout' => 'Web Session Timeout', + 'security_settings' => 'Security Settings', + 'resend_email' => 'Resend Email', + 'confirm_your_email_address' => 'Please confirm your email address', + 'freshbooks' => 'FreshBooks', + 'invoice2go' => 'Invoice2go', + 'invoicely' => 'Invoicely', + 'waveaccounting' => 'Wave Accounting', + 'zoho' => 'Zoho', + 'accounting' => 'Accounting', + 'required_files_missing' => 'Please provide all CSVs.', + 'migration_auth_label' => 'Let\'s continue by authenticating.', + 'api_secret' => 'API secret', + 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', + 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', + 'use_last_email' => 'Use last email', + 'activate_company' => 'Activate Company', + 'activate_company_help' => 'Enable emails, recurring invoices and notifications', + 'an_error_occurred_try_again' => 'An error occurred, please try again', + 'please_first_set_a_password' => 'Please first set a password', + 'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA', + 'help_translate' => 'Help Translate', + 'please_select_a_country' => 'Please select a country', + 'disabled_two_factor' => 'Successfully disabled 2FA', + 'connected_google' => 'Successfully connected account', + 'disconnected_google' => 'Successfully disconnected account', + 'delivered' => 'Delivered', + 'spam' => 'Spam', + 'view_docs' => 'View Docs', + 'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication', + 'send_sms' => 'Send SMS', + 'sms_code' => 'SMS Code', + 'connect_google' => 'Connect Google', + 'disconnect_google' => 'Disconnect Google', + 'disable_two_factor' => 'Disable Two Factor', + 'invoice_task_datelog' => 'Invoice Task Datelog', + 'invoice_task_datelog_help' => 'Add date details to the invoice line items', + 'promo_code' => 'Promo code', + 'recurring_invoice_issued_to' => 'Recurring invoice issued to', + 'subscription' => 'Subscription', + 'new_subscription' => 'New Subscription', + 'deleted_subscription' => 'Successfully deleted subscription', + 'removed_subscription' => 'Successfully removed subscription', + 'restored_subscription' => 'Successfully restored subscription', + 'search_subscription' => 'Search 1 Subscription', + 'search_subscriptions' => 'Search :count Subscriptions', + 'subdomain_is_not_available' => 'Subdomain is not available', + 'connect_gmail' => 'Connect Gmail', + 'disconnect_gmail' => 'Disconnect Gmail', + 'connected_gmail' => 'Successfully connected Gmail', + 'disconnected_gmail' => 'Successfully disconnected Gmail', + 'update_fail_help' => 'Changes to the codebase may be blocking the update, you can run this command to discard the changes:', + 'client_id_number' => 'Client ID Number', + 'count_minutes' => ':count Minutes', + 'password_timeout' => 'Password Timeout', + 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', 'activity_80' => ':user created subscription :subscription', 'activity_81' => ':user updated subscription :subscription', @@ -4294,6 +4296,7 @@ $LANG = array( 'przelewy24_accept' => 'I declare that I have familiarized myself with the regulations and information obligation of the Przelewy24 service.', 'giropay' => 'GiroPay', 'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.', + 'klarna' => 'Klarna', 'eps' => 'EPS', 'becs' => 'BECS Direct Debit', 'becs_mandate' => 'By providing your bank account details, you agree to this Direct Debit Request and the Direct Debit Request service agreement, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.', @@ -4776,7 +4779,6 @@ $LANG = array( 'client_email' => 'Client Email', 'invoice_task_project' => 'Invoice Task Project', 'invoice_task_project_help' => 'Add the project to the invoice line items', - 'bulk_action' => 'Bulk Action', 'phone_validation_error' => 'This mobile/cell phone number is not valid, please enter in E.164 format', 'transaction' => 'Transaction', From 8ee33e827ec5adaafef30b75bfa6ecb944afe1c2 Mon Sep 17 00:00:00 2001 From: Lars Kusch Date: Wed, 7 Dec 2022 09:18:09 +0100 Subject: [PATCH 4/4] Fixes for Klarna --- app/PaymentDrivers/Stripe/Klarna.php | 10 +++++----- app/PaymentDrivers/StripePaymentDriver.php | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/PaymentDrivers/Stripe/Klarna.php b/app/PaymentDrivers/Stripe/Klarna.php index dfafdef7a2a2..d3e2678d215c 100644 --- a/app/PaymentDrivers/Stripe/Klarna.php +++ b/app/PaymentDrivers/Stripe/Klarna.php @@ -50,7 +50,7 @@ class Klarna $invoice_numbers = collect($data['invoices'])->pluck('invoice_number'); - if ($invoice_numbers.length > 0) { + if ($invoice_numbers > 0) { $description = ctrans('texts.payment_provider_paymenttext', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]); } else { $description = ctrans('texts.payment_prvoder_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]); @@ -58,15 +58,15 @@ class Klarna $intent = \Stripe\PaymentIntent::create([ 'amount' => $data['stripe_amount'], - 'currency' => 'eur', + 'currency' => $this->stripe->client->getCurrencyCode(), 'payment_method_types' => ['klarna'], 'customer' => $this->stripe->findOrCreateCustomer(), 'description' => $description, 'metadata' => [ 'payment_hash' => $this->stripe->payment_hash->hash, - 'gateway_type_id' => GatewayType::GIROPAY, + 'gateway_type_id' => GatewayType::KLARNA, ], - ], $this->stripe->stripe_connect_auth); + ], array_merge($this->stripe->stripe_connect_auth, ['idempotency_key' => uniqid("st",true)])); $data['pi_client_secret'] = $intent->client_secret; @@ -109,7 +109,7 @@ class Klarna $data = [ 'payment_method' => $payment_intent, - 'payment_type' => PaymentType::GIROPAY, + 'payment_type' => PaymentType::KLARNA, 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), 'transaction_reference' => $payment_intent, 'gateway_type_id' => GatewayType::KLARNA, diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 09926793567d..acd39347054a 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -237,9 +237,9 @@ class StripePaymentDriver extends BaseDriver } if ($this->client && $this->client->currency() - && in_array($this->client->currency()->code, ['EUR', 'USD', 'GBP', 'DKK', 'SEK', 'NOK']) + && in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'USD', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF']) && isset($this->client->country) - && in_array($this->client->country->iso_3166_3, ['DE', 'AT', 'BE'])) { + && in_array($this->client->country->iso_3166_3, ['AUT','BEL','DNK','FIN','FRA','DEU','IRL','ITA','NLD','NOR','ESP','SWE','GBR','USA'])) { $types[] = GatewayType::KLARNA; }