1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Merge pull request #8079 from LarsK1/v5-develop

Stripe: Add BACS
This commit is contained in:
David Bomba 2023-03-11 18:35:02 +11:00 committed by GitHub
commit a1b19d3067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 635 additions and 61 deletions

View File

@ -15,6 +15,7 @@ namespace App\Http\Controllers\ClientPortal;
use App\Events\Payment\Methods\MethodDeleted;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\PaymentMethod\CreatePaymentMethodRequest;
use App\Http\Requests\ClientPortal\PaymentMethod\VerifyPaymentMethodRequest;
use App\Http\Requests\Request;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
@ -56,7 +57,6 @@ class PaymentMethodController extends Controller
$data['gateway'] = $gateway;
$data['client'] = auth()->user()->client;
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod($request->query('method'))
@ -147,6 +147,9 @@ class PaymentMethodController extends Controller
if (request()->query('method') == GatewayType::CREDIT_CARD) {
return auth()->user()->client->getCreditCardGateway();
}
if (request()->query('method') == GatewayType::BACS) {
return auth()->user()->client->getBACSGateway();
}
if (in_array(request()->query('method'), [GatewayType::BANK_TRANSFER, GatewayType::DIRECT_DEBIT, GatewayType::SEPA])) {
return auth()->user()->client->getBankTransferGateway();

View File

@ -587,6 +587,29 @@ class Client extends BaseModel implements HasLocalePreference
return null;
}
public function getBACSGateway() :?CompanyGateway
{
$pms = $this->service()->getPaymentMethods(-1);
foreach ($pms as $pm) {
if ($pm['gateway_type_id'] == GatewayType::BACS) {
$cg = CompanyGateway::find($pm['company_gateway_id']);
if ($cg && ! property_exists($cg->fees_and_limits, GatewayType::BACS)) {
$fees_and_limits = $cg->fees_and_limits;
$fees_and_limits->{GatewayType::BACS} = new FeesAndLimits;
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if ($cg && $cg->fees_and_limits->{GatewayType::BACS}->is_enabled) {
return $cg;
}
}
}
return null;
}
//todo refactor this - it is only searching for existing tokens
public function getBankTransferGateway() :?CompanyGateway

View File

@ -144,6 +144,7 @@ class Gateway extends StaticModel
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing','payment_intent.succeeded','payment_intent.partially_funded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
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']],
@ -190,6 +191,7 @@ class Gateway extends StaticModel
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],

View File

@ -80,6 +80,8 @@ class GatewayType extends StaticModel
const KLARNA = 23;
const BACS = 24;
public function gateway()
{
return $this->belongsTo(Gateway::class);
@ -127,6 +129,8 @@ class GatewayType extends StaticModel
return ctrans('texts.eps');
case self::BECS:
return ctrans('texts.becs');
case self::BACS:
return ctrans('texts.bacs');
case self::ACSS:
return ctrans('texts.acss');
case self::DIRECT_DEBIT:

View File

@ -73,6 +73,7 @@ class PaymentType extends StaticModel
const FPX = 46;
const KLARNA = 47;
const Interac_E_Transfer = 48;
const BACS = 49;
public array $type_names = [
self::CREDIT => 'payment_type_Credit',

View File

@ -0,0 +1,191 @@
<?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\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\StripePaymentDriver;
use App\PaymentDrivers\Stripe\Jobs\UpdateCustomer;
use Stripe\Checkout\Session;
use Stripe\PaymentIntent;
use Stripe\PaymentMethod;
use App\Utils\Number;
class BACS
{
public $stripe;
public function __construct(StripePaymentDriver $stripe)
{
$this->stripe = $stripe;
}
public function authorizeView(array $data)
{
$customer = $this->stripe->findOrCreateCustomer();
$data['session'] = Session::create([
'payment_method_types' => ['bacs_debit'],
'mode' => 'setup',
'customer' => $customer->id,
'success_url' => str_replace("%7B", "{", str_replace("%7D", "}", $this->buildAuthorizeUrl())),
'cancel_url' => route('client.payment_methods.index'),
]);
return render('gateways.stripe.bacs.authorize', $data);
}
private function buildAuthorizeUrl(): string
{
return route('client.payment_methods.confirm', [
'method' => GatewayType::BACS,
'session_id' => "{CHECKOUT_SESSION_ID}",
]);
}
public function authorizeResponse($request)
{
$this->stripe->init();
if ($request->session_id) {
$session = $this->stripe->stripe->checkout->sessions->retrieve($request->session_id, ['expand' => ['setup_intent']]);
$customer = $this->stripe->findOrCreateCustomer();
$this->stripe->attach($session->setup_intent->payment_method, $customer);
$payment_method = $this->stripe->getStripePaymentMethod($session->setup_intent->payment_method);
$this->storePaymentMethod($payment_method, $customer);
}
return redirect()->route('client.payment_methods.index');
}
public function paymentView(array $data)
{
$data['gateway'] = $this->stripe;
$data['amount'] = $data['total']['amount_with_fee'];
$data['payment_hash'] = $this->stripe->payment_hash->hash;
return render('gateways.stripe.bacs.pay', $data);
}
public function paymentResponse(PaymentResponseRequest $request)
{
$this->stripe->init();
$invoice_numbers = collect($this->stripe->payment_hash->invoices())->pluck('invoice_number')->implode(',');
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($request->amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$payment_intent_data = [
'amount' => $this->stripe->convertToStripeAmount($request->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $description,
'payment_method_types' => ['bacs_debit'],
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::BACS,
],
'payment_method' => $request->token,
'confirm' => true,
];
$state = [
'payment_hash' => $this->stripe->payment_hash->hash,
'payment_intent' => $this->stripe->createPaymentIntent($payment_intent_data),
];
$state = array_merge($state, $request->all());
$state['customer'] = $state['payment_intent']->customer;
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $state);
$this->stripe->payment_hash->save();
if ($state['payment_intent']->status == 'processing') {
$this->stripe->logSuccessfulGatewayResponse(['response' => $state['payment_intent'], 'data' => $this->stripe->payment_hash], SystemLog::TYPE_STRIPE);
return $this->processSuccessfulPayment($state['payment_intent']);
}
return $this->processUnsuccessfulPayment("An unknown error occured.");
}
public function processSuccessfulPayment($payment_intent)
{
UpdateCustomer::dispatch($this->stripe->company_gateway->company->company_key, $this->stripe->company_gateway->id, $this->stripe->client->id);
$data = [
'payment_method' => $payment_intent['id'],
'payment_type' => PaymentType::BACS,
'amount' => $this->stripe->convertFromStripeAmount($payment_intent->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
'transaction_reference' => $payment_intent['id'],
'gateway_type_id' => GatewayType::BACS,
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);
SystemLogger::dispatch(
['response' => $payment_intent, '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.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
}
public function processUnsuccessfulPayment($server_response)
{
$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('Failed to process the payment.', 500);
}
private function storePaymentMethod($method, $customer)
{
try {
$payment_meta = new \stdClass;
$payment_meta->brand = (string) $method->bacs_debit->sort_code;
$payment_meta->last4 = (string) $method->bacs_debit->last4;
$payment_meta->state = 'unauthorized';
$payment_meta->type = GatewayType::BACS;
$data = [
'payment_meta' => $payment_meta,
'token' => $method->id,
'payment_method_id' => GatewayType::BACS,
];
$clientgateway = ClientGatewayToken::query()
->where('token', $method->id)
->first();
if (!$clientgateway){
$this->stripe->storeGatewayToken($data, ['gateway_customer_reference' => $customer->id]);
}
} catch (\Exception $e) {
return $this->stripe->processInternallyFailedPayment($this->stripe, $e);
}
}
}

View File

@ -79,6 +79,9 @@ class Charge
if ($cgt->gateway_type_id == GatewayType::SEPA) {
$data['payment_method_types'] = ['sepa_debit'];
}
if ($cgt->gateway_type_id == GatewayType::BACS) {
$data['payment_method_types'] = ['bacs_debit'];
}
/* Should improve token billing with client not present */
if (!auth()->guard('contact')->check()) {
@ -135,8 +138,15 @@ class Charge
if ($cgt->gateway_type_id == GatewayType::SEPA) {
$payment_method_type = PaymentType::SEPA;
$status = Payment::STATUS_PENDING;
} else {
if (isset($response->latest_charge)) {
} elseif ($cgt->gateway_type_id == GatewayType::BACS){
$payment_method_type = PaymentType::BACS;
$status = Payment::STATUS_PENDING;
}
else {
if(isset($response->latest_charge)) {
$charge = \Stripe\Charge::retrieve($response->latest_charge, $this->stripe->stripe_connect_auth);
$payment_method_type = $charge->payment_method_details->card->brand;
} elseif (isset($response->charges->data[0]->payment_method_details->card->brand)) {
@ -147,9 +157,11 @@ class Charge
$status = Payment::STATUS_COMPLETED;
}
if (!in_array($response?->status, ['succeeded', 'processing'])) {
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.', 400));
if(!in_array($response?->status, ['succeeded', 'processing'])){
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
}
$data = [
@ -195,6 +207,8 @@ class Charge
break;
case PaymentType::SEPA:
return PaymentType::SEPA;
case PaymentType::BACS:
return PaymentType::BACS;
default:
return PaymentType::CREDIT_CARD_OTHER;
break;

View File

@ -89,6 +89,7 @@ class PaymentIntentWebhook implements ShouldQueue
$charge_id = false;
if (isset($this->stripe_request['object']['charges']) && optional($this->stripe_request['object']['charges']['data'][0])['id']) {
$charge_id = $this->stripe_request['object']['charges']['data'][0]['id'];
} // API VERSION 2018
@ -119,8 +120,9 @@ class PaymentIntentWebhook implements ShouldQueue
->where('transaction_reference', $charge['id'])
->first();
//return early
if ($payment && $payment->status_id == Payment::STATUS_COMPLETED) {
//return early
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
nlog(" payment found and status correct - returning ");
return;
} elseif ($payment) {
@ -186,7 +188,11 @@ class PaymentIntentWebhook implements ShouldQueue
}
$this->updateAchPayment($payment_hash, $client, $meta);
} elseif(isset($pi['payment_method_types']) && in_array('bacs_debit', $pi['payment_method_types'])){
return;
}
}
private function updateAchPayment($payment_hash, $client, $meta)
@ -206,7 +212,7 @@ class PaymentIntentWebhook implements ShouldQueue
'transaction_reference' => $meta['transaction_reference'],
'gateway_type_id' => GatewayType::BANK_TRANSFER,
];
$payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
@ -254,13 +260,14 @@ class PaymentIntentWebhook implements ShouldQueue
}
$driver->storeGatewayToken($data, $additional_data);
} catch(\Exception $e) {
nlog("failed to import payment methods");
nlog($e->getMessage());
}
}
private function updateCreditCardPayment($payment_hash, $client, $meta)
{
$company_gateway = CompanyGateway::find($this->company_gateway_id);
@ -278,7 +285,7 @@ class PaymentIntentWebhook implements ShouldQueue
'transaction_reference' => $meta['transaction_reference'],
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
@ -290,4 +297,5 @@ class PaymentIntentWebhook implements ShouldQueue
$client->company,
);
}
}

View File

@ -41,6 +41,9 @@ class StripeWebhook implements ShouldQueue
'charge.failed',
'payment_intent.succeeded',
'payment_intent.payment_failed',
'mandate.updated',
'checkout.session.completed',
'payment_method.automatically_updated'
];
public function __construct(string $company_key, int $company_gateway_id)

View File

@ -26,6 +26,7 @@ use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Http\Requests\Request;
use App\Jobs\Util\SystemLogger;
use App\Models\Client;
use App\Utils\Traits\MakesHash;
use App\Exceptions\PaymentFailed;
use App\Models\ClientGatewayToken;
@ -33,6 +34,9 @@ use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\Stripe\EPS;
use App\PaymentDrivers\Stripe\FPX;
use App\PaymentDrivers\Stripe\ACSS;
use App\PaymentDrivers\Stripe\Alipay;
use App\PaymentDrivers\Stripe\ApplePay;
use App\PaymentDrivers\Stripe\BACS;
use App\PaymentDrivers\Stripe\BECS;
use App\PaymentDrivers\Stripe\SEPA;
use App\PaymentDrivers\Stripe\iDeal;
@ -59,6 +63,7 @@ use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook;
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook;
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook;
class StripePaymentDriver extends BaseDriver
{
use MakesHash, Utilities;
@ -96,6 +101,7 @@ class StripePaymentDriver extends BaseDriver
GatewayType::ACSS => ACSS::class,
GatewayType::FPX => FPX::class,
GatewayType::KLARNA => Klarna::class,
GatewayType::BACS => BACS::class,
GatewayType::DIRECT_DEBIT => BankTransfer::class,
];
@ -234,6 +240,14 @@ 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, ['GBP'])
&& isset($this->client->country)
&& in_array($this->client->company->country()->getID(), ['826'])
&& in_array($this->client->country->iso_3166_3, ['GBR'])) {
$types[] = GatewayType::BACS;
}
if ($this->client
&& $this->client->currency()
&& in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF'])
@ -245,7 +259,7 @@ class StripePaymentDriver extends BaseDriver
&& $this->client->currency()
&& in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF', 'USD'])
&& isset($this->client->country)
&& in_array($this->client->company->country()->getID(), ['840'])
&& in_array($this->client->company->country()->id, ['840'])
&& 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;
}
@ -310,6 +324,8 @@ class StripePaymentDriver extends BaseDriver
return 'gateways.stripe.bancontact';
case GatewayType::BECS:
return 'gateways.stripe.becs';
case GatewayType::BACS:
return 'gateways.stripe.bacs';
case GatewayType::ACSS:
return 'gateways.stripe.acss';
case GatewayType::FPX:
@ -663,8 +679,6 @@ class StripePaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request)
{
// if($request->type === 'payment_intent.requires_action')
// nlog($request->all());
if ($request->type === 'customer.source.updated') {
$ach = new ACH($this);
@ -752,6 +766,63 @@ class StripePaymentDriver extends BaseDriver
}
}
}
} elseif ($request->type === "payment_method.automatically_updated"){
// Will notify customer on updated information
return response()->json([], 200);
} elseif ($request->type === "checkout.session.completed"){
// Store payment token for Stripe BACS
$this->init();
$setup_intent = $this->stripe->setupIntents->retrieve($request->data['object']['setup_intent'], []);
$clientpayment_token = ClientGatewayToken::where('gateway_customer_reference', $request->data['object']['customer'])->first();
if ($clientpayment_token){
$this->client = Client::where('id', $clientpayment_token->client_id)->first();
$customer = $this->findOrCreateCustomer();
$this->attach($setup_intent->payment_method, $customer);
$payment_method = $this->getStripePaymentMethod($setup_intent->payment_method);
$payment_meta = new \stdClass;
$payment_meta->brand = (string) $payment_method->bacs_debit->sort_code;
$payment_meta->last4 = (string) $payment_method->bacs_debit->last4;
$payment_meta->state = 'unauthorized';
$payment_meta->type = GatewayType::BACS;
$data = [
'payment_meta' => $payment_meta,
'token' => $payment_method->id,
'payment_method_id' => GatewayType::BACS,
];
$clientgateway = ClientGatewayToken::query()
->where('token', $payment_method)
->first();
if (!$clientgateway){
$this->storeGatewayToken($data, ['gateway_customer_reference' => $customer->id]);
}
}
return response()->json([], 200);
} elseif ($request->type === "mandate.updated"){
// Check if payment method BACS is still valid
if ($request->data['object']['status'] === "active"){
// Check if payment method exists
$payment_method = (string) $request->data['object']['payment_method'];
$clientgateway = ClientGatewayToken::query()
->where('token', $payment_method)
->first();
if ($clientgateway){
$clientgateway->meta->state = 'authorized';
$clientgateway->update();
};
return response()->json([], 200);
}
elseif ($request->data['object']['status'] === "inactive" && $request->data['object']['payment_method']){
// Delete payment method
$clientgateway = ClientGatewayToken::query()
->where('token', $request->data['object']['payment_method'])
->first();
$clientgateway->delete();
return response()->json([], 200);
}
elseif ($request->data['object']['status'] === "pending"){
return response()->json([], 200);
}
}
return response()->json([], 200);

View File

@ -0,0 +1,39 @@
<?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()
{
$pt = PaymentType::find(49);
if(!$pt)
{
$type = new PaymentType();
$type->id = 49;
$type->name = 'BACS';
$type->gateway_type_id = GatewayType::BACS;
$type->save();
}
$gt = GatewayType::find(24);
if(!$gt)
{
$type = new GatewayType();
$type->id = 24;
$type->alias = 'bacs';
$type->name = 'BACS';
$type->save();
}
}
};

View File

@ -4299,6 +4299,9 @@ $LANG = array(
'klarna' => 'Klarna',
'eps' => 'EPS',
'becs' => 'BECS Direct Debit',
'bacs' => 'BACS Direct Debit',
'payment_type_BACS' => 'BACS Direct Debit',
'missing_payment_method' => 'Please add a payment method first, before trying to pay.',
'becs_mandate' => 'By providing your bank account details, you agree to this <a class="underline" href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, 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.',
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
'direct_debit' => 'Direct Debit',
@ -4907,7 +4910,7 @@ $LANG = array(
'export_company' => 'Create company backup',
'backup' => 'Backup',
'notification_purchase_order_created_body' => 'The following purchase_order :purchase_order was created for vendor :vendor for :amount.',
'notification_purchase_order_created_subject' => 'Purchase Order :purchase_order was created for :vendor',
'notification_purchase_order_created_subject' => 'Purchase Order :purchase_order was created for :vendor',
'notification_purchase_order_sent_subject' => 'Purchase Order :purchase_order was sent to :vendor',
'notification_purchase_order_sent' => 'The following vendor :vendor was emailed Purchase Order :purchase_order for :amount.',
'subscription_blocked' => 'This product is a restricted item, please contact the vendor for further information.',

View File

@ -0,0 +1,2 @@
/*! For license information please see stripe-bacs.js.LICENSE.txt */
(()=>{var e,t,n,o,r,a;function i(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}function u(e,t,n){return t&&i(e.prototype,t),n&&i(e,n),Object.defineProperty(e,"prototype",{writable:!1}),e}function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var d=u((function e(t,n){var o=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),c(this,"setupStripe",(function(){return o.stripeConnect?o.stripe=Stripe(o.key,{stripeAccount:o.stripeConnect}):o.stripe=Stripe(o.key),o})),c(this,"payment_data",void 0),c(this,"handle",(function(){o.onlyAuthorization?document.getElementById("authorize-bacs").addEventListener("click",(function(e){document.getElementById("authorize-bacs").disabled=!0,document.querySelector("#authorize-bacs > svg").classList.remove("hidden"),document.querySelector("#authorize-bacs > span").classList.add("hidden"),location.href=document.querySelector("meta[name=stripe-redirect-url]").content})):(o.payNowButton=document.getElementById("pay-now"),document.getElementById("pay-now").addEventListener("click",(function(e){o.payNowButton.disabled=!0,o.payNowButton.querySelector("svg").classList.remove("hidden"),o.payNowButton.querySelector("span").classList.add("hidden"),document.getElementById("server-response").submit()})),o.payment_data=Array.from(document.getElementsByClassName("toggle-payment-with-token")),o.payment_data.length>0?o.payment_data.forEach((function(e){return e.addEventListener("click",(function(e){document.querySelector("input[name=token]").value=e.target.dataset.token}))})):(o.errors.textContent=document.querySelector("meta[name=translation-payment-method-required]").content,o.errors.hidden=!1,o.payNowButton.disabled=!0,o.payNowButton.querySelector("span").classList.remove("hidden"),o.payNowButton.querySelector("svg").classList.add("hidden")))})),this.key=t,this.errors=document.getElementById("errors"),this.stripeConnect=n,this.onlyAuthorization=m})),s=null!==(e=null===(t=document.querySelector('meta[name="stripe-publishable-key"]'))||void 0===t?void 0:t.content)&&void 0!==e?e:"",l=null!==(n=null===(o=document.querySelector('meta[name="stripe-account-id"]'))||void 0===o?void 0:o.content)&&void 0!==n?n:"",m=null!==(r=null===(a=document.querySelector('meta[name="only-authorization"]'))||void 0===a?void 0:a.content)&&void 0!==r?r:"";new d(s,l).setupStripe().handle()})();

View File

@ -0,0 +1,9 @@
/**
* 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
*/

View File

@ -1,49 +1,50 @@
{
"/js/app.js": "/js/app.js?id=7b6124b74168ccb1cc7da22f7a2bc9ed",
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=b6723e0b8ea33f1f50617fa5f289a9d3",
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=faf4828cc6b3b73b69c53d3046661884",
"/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js?id=f42dd0caddb3603e71db061924c4b172",
"/js/clients/payments/forte-ach-payment.js": "/js/clients/payments/forte-ach-payment.js?id=b8173c7c0dee76bf9ae6312a963ae0e4",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=207f218c44553470287f35f33a7eb154",
"/js/clients/payments/stripe-klarna.js": "/js/clients/payments/stripe-klarna.js?id=7268f9282c6bb3b04d19d11a7b0c1681",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=404b7ee18e420de0e73f5402b7e39122",
"/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=2f0c4e3bab30a98e33ac768255113174",
"/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=9bb483a89a887f753e49c0b635d6276a",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=752e2bb6390f1a422e31868cf2a2bf67",
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=4fc5dec1bc4fc21b9e32b1b490c3e7ae",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=0a4361f9c468fa087ff543f1793adc7d",
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=7cb96275b3eb4901054564c654fb60e3",
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=3a4c5cfac7dd4c9218be55945c3c8e85",
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=00615ec62b99e3245769d4603e6053ce",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=6e7c8ab039a239727317ae8622de10db",
"/js/setup/setup.js": "/js/setup/setup.js?id=8cab3339ef48418e1fb2e7a9259d51ca",
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=cf50b5ba1fcd1d184bf0c10d710672c8",
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=682de6347049b32c9488f39c78a68ace",
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=d3c404bb646f1aeaf2382a8c57ab8e1a",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=e1c0599d6f7dc163b549a6df0b3490b4",
"/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=8b036822abaa4ceb379008fc14208dc2",
"/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=de0b1d0c6da7ff509bef3aee8d09e7f8",
"/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=92ef8632637d335cd0e4bc29a05b7df8",
"/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js?id=af85b3f6d53c55b5d0e3a80ef58ce0de",
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=3869bc6d80acc83f81d9afe8efaae728",
"/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=7cd5a1d95d33ada211ce185ad6e4bb33",
"/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=27274d334aed0824ce4654fa22132f7f",
"/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=f85ebb6a77002afd350086d1274b6af5",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=238e7001420a22b001856193689a1e70",
"/js/clients/statements/view.js": "/js/clients/statements/view.js?id=13e043123f1e58409394458a70461d63",
"/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=494f58d2fd8984792833ba7d3055de08",
"/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=77d4e397d193196e482af80737bff64a",
"/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=659c4287fb8ef1c458071c206c4d965d",
"/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js?id=852a9abf5f3a29f5d7d2f989cbeab374",
"/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js?id=447c587a5eeb0c1de3091c8358db7ad7",
"/js/clients/payments/stripe-bancontact.js": "/js/clients/payments/stripe-bancontact.js?id=f694d3f9f01e4550cb5a3eb6cb43c12d",
"/js/clients/payments/stripe-becs.js": "/js/clients/payments/stripe-becs.js?id=97ea3555a8504662eda5fce9c9115e5a",
"/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js?id=146d48d03f5fc4b1cf122189119e2877",
"/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js?id=34cf4ee3f189427fb69d0df8f5a4b766",
"/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=cfdcc5bf20d6bfa09700708dfdd943e2",
"/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=7015e43eb5f9f9f2f45f54b41b5780a0",
"/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=243c2929386b10c6a0c49ca3bcabfb2d",
"/css/app.css": "/css/app.css?id=d3b1387842383d1983c767978d20ec86",
"/js/app.js": "/js/app.js?id=19300612c6880925e8043b61e8d49632",
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=9fb77e87fe0f85a367050e08f79ec9df",
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=803182f668c39d631ca5c55437876da4",
"/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js?id=6e9f466c5504d3753f9b4ffc6f947095",
"/js/clients/payments/forte-ach-payment.js": "/js/clients/payments/forte-ach-payment.js?id=1d10fcc52a1f15858e5da216f1df45ec",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=7bed15f51bca764378d9a3aa605b8664",
"/js/clients/payments/stripe-klarna.js": "/js/clients/payments/stripe-klarna.js?id=5770e0d82d3843c68903744530f5ae73",
"/js/clients/payments/stripe-bacs.js": "/js/clients/payments/stripe-bacs.js?id=5739aad61ac7bbb20f2149d203849ff7",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=d4f86ddee4e8a1d6e9719010aa0fe62b",
"/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=160b8161599fc2429b449b0970d3ba6c",
"/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=ddd4aa4069ea79411eeec367b7d5986d",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=28221de8f1cb37f845ba4ec59bcd8867",
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=1c5493a4c53a5b862d07ee1818179ea9",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=0274ab4f8d2b411f2a2fe5142301e7af",
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=4bd34a0b160f6f29b3096d870ac4d308",
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=6fb63bae43d077b5061f4dadfe8dffc8",
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=61a346e1977d3a1fec3634b234baa25c",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=809de47258a681f0ffebe787dd6a9a93",
"/js/setup/setup.js": "/js/setup/setup.js?id=27560b012f166f8b9417ced2188aab70",
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=8ce33c3deae058ad314fb8357e5be63b",
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=be5307abc990bb44f2f92628103b1d98",
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=c2caa29f753ad1f3a12ca45acddacd72",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=2b2fe55f926789abc52f19111006e1ec",
"/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=8e3b1c4c78c976ff0c43cb739c26b1f3",
"/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=5764a8d406c1eda848d073f10d178626",
"/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=dbba20d70fbebb326ddbc46115af9771",
"/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js?id=b8706d7de6127f184ad19b2a810880be",
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=e0b1231a7bf6252672836222285c0f52",
"/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=bbab588ed009a93345bec520cbe66869",
"/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=31d068e55757636f34834bc2494250df",
"/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=6d8c7fd66d911b20cdc4248e33db1b3a",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=b180fd6378d3723d3e9133e0b1943ac6",
"/js/clients/statements/view.js": "/js/clients/statements/view.js?id=7971b212e8a849fe36bfe915f81023bd",
"/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=c36ab5621413ef1de7c864bc8eb7439e",
"/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=b258636d8bae366e9d8f54274f437181",
"/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=e43f862d70d8710761f0856e528ec3d1",
"/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js?id=72ad4ad19297f211c2e6d0fa1fa1f76d",
"/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js?id=90b1805b1ca0264474b38054a2664c5b",
"/js/clients/payments/stripe-bancontact.js": "/js/clients/payments/stripe-bancontact.js?id=03e5d7ee187e76b0b7c16bfa91804a8a",
"/js/clients/payments/stripe-becs.js": "/js/clients/payments/stripe-becs.js?id=de2bd0ef2859e19e4f98ea9d6d11cb54",
"/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js?id=213d9ad34a79144a0d3345cb6a262e95",
"/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js?id=0a6b434e3849db26c35a143e0347e914",
"/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=3d53d2f7d0291d9f92cf7414dd2d351c",
"/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=db71055862995fd6ae21becfc587a3de",
"/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=914a6846ad1e5584635e7430fef76875",
"/css/app.css": "/css/app.css?id=aeba2a01bf369ac522071ab602096c66",
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c",
"/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d"
}

View File

@ -0,0 +1,87 @@
/**
* 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 ProcessBACS {
constructor(key, stripeConnect) {
this.key = key;
this.errors = document.getElementById('errors');
this.stripeConnect = stripeConnect;
this.onlyAuthorization = onlyAuthorization;
}
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;
};
payment_data;
handle = () => {
if (this.onlyAuthorization) {
document.getElementById('authorize-bacs').addEventListener('click', (e) => {
document.getElementById('authorize-bacs').disabled = true;
document.querySelector('#authorize-bacs > svg').classList.remove('hidden');
document.querySelector('#authorize-bacs > span').classList.add('hidden');
location.href=document.querySelector('meta[name=stripe-redirect-url]').content;
});}
else{
this.payNowButton = document.getElementById('pay-now');
document.getElementById('pay-now').addEventListener('click', (e) => {
this.payNowButton.disabled = true;
this.payNowButton.querySelector('svg').classList.remove('hidden');
this.payNowButton.querySelector('span').classList.add('hidden');
document.getElementById('server-response').submit();
});
this.payment_data = Array.from(document.getElementsByClassName('toggle-payment-with-token'));
if (this.payment_data.length > 0){
this.payment_data.forEach((element) =>
element.addEventListener('click', (element) => {
document.querySelector('input[name=token]').value =
element.target.dataset.token;
})
);}
else{
this.errors.textContent = document.querySelector(
'meta[name=translation-payment-method-required]'
).content;
this.errors.hidden = false;
this.payNowButton.disabled = true;
this.payNowButton.querySelector('span').classList.remove('hidden');
this.payNowButton.querySelector('svg').classList.add('hidden');
}}
}
}
const publishableKey = document.querySelector(
'meta[name="stripe-publishable-key"]'
)?.content ?? '';
const stripeConnect =
document.querySelector('meta[name="stripe-account-id"]')?.content ?? '';
const onlyAuthorization =
document.querySelector('meta[name="only-authorization"]')?.content ?? '';
new ProcessBACS(publishableKey, stripeConnect).setupStripe().handle();

View File

@ -5,7 +5,7 @@
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
* @license https://www.elastic.co/licensing/elastic-license
*/
class ProcessSEPA {
@ -215,6 +215,22 @@ class ProcessSEPA {
document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('hidden');
}
handleSuccess(result) {
document.querySelector(
'input[name="gateway_response"]'
).value = JSON.stringify(result.paymentIntent);
let tokenBillingCheckbox = document.querySelector(
'input[name="token-billing-checkbox"]:checked'
);
if (tokenBillingCheckbox) {
document.querySelector('input[name="store_card"]').value =
tokenBillingCheckbox.value;
}
document.getElementById('server-response').submit();
}
}
const publishableKey =

View File

@ -25,6 +25,11 @@
{{ ctrans('texts.bank_account') }}
</a>
@endif
@if($client->getBACSGateway())
<a data-cy="add-bacs-link" href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::BACS]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
{{ ctrans('texts.bacs') }}
</a>
@endif
</div>
</div>
@endif

View File

@ -0,0 +1,38 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'BACS', 'card_title' => 'BACS'])
@section('gateway_head')
@if($gateway->getConfigField('account_id'))
<meta name="stripe-account-id" content="{{ $gateway->getConfigField('account_id') }}">
<meta name="stripe-publishable-key" content="{{ config('ninja.ninja_stripe_publishable_key') }}">
@else
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
@endif
<meta name="stripe-redirect-url" content="{{ $session->url }}">
<meta name="only-authorization" content="true">
@endsection
@section('gateway_content')
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::BACS]) }}" method="post" id="server_response">
@csrf
<input type="hidden" name="company_gateway_id" value="{{ $gateway->gateway_id }}">
<input type="hidden" name="payment_method_id" value="1">
<input type="hidden" name="gateway_response" id="gateway_response">
<input type="hidden" name="is_default" id="is_default">
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')])
{{ ctrans('texts.bacs') }}
@endcomponent
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-bacs'])
{{ ctrans('texts.add_payment_method') }}
@endcomponent
@endsection
@section('gateway_footer')
<script src="https://js.stripe.com/v3/"></script>
<script src="{{ asset('js/clients/payments/stripe-bacs.js') }}"></script>
@endsection

View File

@ -0,0 +1,50 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'BACS', 'card_title' => 'BACS'])
@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="only-authorization" content="">
<meta name="translation-payment-method-required" content="{{ ctrans('texts.missing_payment_method') }}">
@endsection
@section('gateway_content')
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
@csrf
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="token">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="amount" value={{ $amount }}>
</form>
<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.bacs') }} ({{ ctrans('texts.bank_transfer') }})
@endcomponent
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
@if (count($tokens) > 0)
@foreach ($tokens as $token)
<label class="mr-4">
<input type="radio" data-token="{{ $token->token }}" name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token" />
<span class="ml-1 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
</label>
@endforeach
@endisset
@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-bacs.js') }}"></script>
@endpush

4
webpack.mix.js vendored
View File

@ -26,6 +26,10 @@ mix.js("resources/js/app.js", "public/js")
"resources/js/clients/payments/stripe-klarna.js",
"public/js/clients/payments/stripe-klarna.js"
)
.js(
"resources/js/clients/payments/stripe-bacs.js",
"public/js/clients/payments/stripe-bacs.js"
)
.js(
"resources/js/clients/invoices/action-selectors.js",
"public/js/clients/invoices/action-selectors.js"