mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-01 20:17:35 -04:00 
			
		
		
		
	Add Support for Klarna
This commit is contained in:
		
							parent
							
								
									9a08eb0940
								
							
						
					
					
						commit
						701344947f
					
				| @ -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']], | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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) | ||||
|     { | ||||
|  | ||||
							
								
								
									
										154
									
								
								app/PaymentDrivers/Stripe/Klarna.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								app/PaymentDrivers/Stripe/Klarna.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,154 @@ | ||||
| <?php | ||||
| 
 | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\PaymentDrivers\Stripe; | ||||
| 
 | ||||
| use App\Exceptions\PaymentFailed; | ||||
| use App\Jobs\Util\SystemLogger; | ||||
| use App\Models\GatewayType; | ||||
| use App\Models\Payment; | ||||
| use App\Models\PaymentType; | ||||
| use App\Models\SystemLog; | ||||
| use App\PaymentDrivers\StripePaymentDriver; | ||||
| 
 | ||||
| class Klarna | ||||
| { | ||||
|     /** @var StripePaymentDriver */ | ||||
|     public StripePaymentDriver $stripe; | ||||
| 
 | ||||
|     public function __construct(StripePaymentDriver $stripe) | ||||
|     { | ||||
|         $this->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); | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|  | ||||
							
								
								
									
										34
									
								
								database/migrations/2022_05_12_56879_add_stripe_klarna.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								database/migrations/2022_05_12_56879_add_stripe_klarna.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| <?php | ||||
| 
 | ||||
| use App\Models\GatewayType; | ||||
| use App\Models\PaymentType; | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| return new class extends Migration { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function up() | ||||
|     { | ||||
|         Schema::table('payment_types', function (Blueprint $table) { | ||||
|             $type = new PaymentType(); | ||||
| 
 | ||||
|             $type->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(); | ||||
|     } | ||||
| }; | ||||
| @ -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 <b> :company_name </b>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 <b>sent</b>', | ||||
|     '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; | ||||
|  | ||||
							
								
								
									
										68
									
								
								resources/js/clients/payments/stripe-klarna.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								resources/js/clients/payments/stripe-klarna.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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(); | ||||
| @ -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 | ||||
| @ -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')) | ||||
|     <meta name="stripe-account-id" content="{{ $gateway->company_gateway->getConfigField('account_id') }}"> | ||||
|     <meta name="stripe-publishable-key" content="{{ config('ninja.ninja_stripe_publishable_key') }}"> | ||||
|     @else | ||||
|     <meta name="stripe-publishable-key" content="{{ $gateway->company_gateway->getPublishableKey() }}"> | ||||
|     @endif | ||||
|     <meta name="return-url" content="{{ $return_url }}"> | ||||
|     <meta name="amount" content="{{ $stripe_amount }}"> | ||||
|     <meta name="country" content="{{ $country }}"> | ||||
|     <meta name="customer" content="{{ $customer }}"> | ||||
|     <meta name="pi-client-secret" content="{{ $pi_client_secret }}"> | ||||
| @endsection | ||||
| 
 | ||||
| @section('gateway_content') | ||||
|     <div class="alert alert-failure mb-4" hidden id="errors"></div> | ||||
| 
 | ||||
|     @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') | ||||
|     <script src="https://js.stripe.com/v3/"></script> | ||||
|     <script src="{{ asset('js/clients/payments/stripe-klarna.js') }}"></script> | ||||
| @endpush | ||||
							
								
								
									
										4
									
								
								webpack.mix.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								webpack.mix.js
									
									
									
									
										vendored
									
									
								
							| @ -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" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user