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

View File

@ -587,6 +587,29 @@ class Client extends BaseModel implements HasLocalePreference
return null; 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 //todo refactor this - it is only searching for existing tokens
public function getBankTransferGateway() :?CompanyGateway 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::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::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['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::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::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::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::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::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::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::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::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']], 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 KLARNA = 23;
const BACS = 24;
public function gateway() public function gateway()
{ {
return $this->belongsTo(Gateway::class); return $this->belongsTo(Gateway::class);
@ -127,6 +129,8 @@ class GatewayType extends StaticModel
return ctrans('texts.eps'); return ctrans('texts.eps');
case self::BECS: case self::BECS:
return ctrans('texts.becs'); return ctrans('texts.becs');
case self::BACS:
return ctrans('texts.bacs');
case self::ACSS: case self::ACSS:
return ctrans('texts.acss'); return ctrans('texts.acss');
case self::DIRECT_DEBIT: case self::DIRECT_DEBIT:

View File

@ -73,6 +73,7 @@ class PaymentType extends StaticModel
const FPX = 46; const FPX = 46;
const KLARNA = 47; const KLARNA = 47;
const Interac_E_Transfer = 48; const Interac_E_Transfer = 48;
const BACS = 49;
public array $type_names = [ public array $type_names = [
self::CREDIT => 'payment_type_Credit', 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) { if ($cgt->gateway_type_id == GatewayType::SEPA) {
$data['payment_method_types'] = ['sepa_debit']; $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 */ /* Should improve token billing with client not present */
if (!auth()->guard('contact')->check()) { if (!auth()->guard('contact')->check()) {
@ -135,8 +138,15 @@ class Charge
if ($cgt->gateway_type_id == GatewayType::SEPA) { if ($cgt->gateway_type_id == GatewayType::SEPA) {
$payment_method_type = PaymentType::SEPA; $payment_method_type = PaymentType::SEPA;
$status = Payment::STATUS_PENDING; $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); $charge = \Stripe\Charge::retrieve($response->latest_charge, $this->stripe->stripe_connect_auth);
$payment_method_type = $charge->payment_method_details->card->brand; $payment_method_type = $charge->payment_method_details->card->brand;
} elseif (isset($response->charges->data[0]->payment_method_details->card->brand)) { } elseif (isset($response->charges->data[0]->payment_method_details->card->brand)) {
@ -148,8 +158,10 @@ class Charge
$status = Payment::STATUS_COMPLETED; $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 = [ $data = [
@ -195,6 +207,8 @@ class Charge
break; break;
case PaymentType::SEPA: case PaymentType::SEPA:
return PaymentType::SEPA; return PaymentType::SEPA;
case PaymentType::BACS:
return PaymentType::BACS;
default: default:
return PaymentType::CREDIT_CARD_OTHER; return PaymentType::CREDIT_CARD_OTHER;
break; break;

View File

@ -89,6 +89,7 @@ class PaymentIntentWebhook implements ShouldQueue
$charge_id = false; $charge_id = false;
if (isset($this->stripe_request['object']['charges']) && optional($this->stripe_request['object']['charges']['data'][0])['id']) { 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']; $charge_id = $this->stripe_request['object']['charges']['data'][0]['id'];
} // API VERSION 2018 } // API VERSION 2018
@ -120,7 +121,8 @@ class PaymentIntentWebhook implements ShouldQueue
->first(); ->first();
//return early //return early
if ($payment && $payment->status_id == Payment::STATUS_COMPLETED) { if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
nlog(" payment found and status correct - returning "); nlog(" payment found and status correct - returning ");
return; return;
} elseif ($payment) { } elseif ($payment) {
@ -186,7 +188,11 @@ class PaymentIntentWebhook implements ShouldQueue
} }
$this->updateAchPayment($payment_hash, $client, $meta); $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) private function updateAchPayment($payment_hash, $client, $meta)
@ -254,13 +260,14 @@ class PaymentIntentWebhook implements ShouldQueue
} }
$driver->storeGatewayToken($data, $additional_data); $driver->storeGatewayToken($data, $additional_data);
} catch(\Exception $e) { } catch(\Exception $e) {
nlog("failed to import payment methods"); nlog("failed to import payment methods");
nlog($e->getMessage()); nlog($e->getMessage());
} }
} }
private function updateCreditCardPayment($payment_hash, $client, $meta) private function updateCreditCardPayment($payment_hash, $client, $meta)
{ {
$company_gateway = CompanyGateway::find($this->company_gateway_id); $company_gateway = CompanyGateway::find($this->company_gateway_id);
@ -290,4 +297,5 @@ class PaymentIntentWebhook implements ShouldQueue
$client->company, $client->company,
); );
} }
} }

View File

@ -41,6 +41,9 @@ class StripeWebhook implements ShouldQueue
'charge.failed', 'charge.failed',
'payment_intent.succeeded', 'payment_intent.succeeded',
'payment_intent.payment_failed', 'payment_intent.payment_failed',
'mandate.updated',
'checkout.session.completed',
'payment_method.automatically_updated'
]; ];
public function __construct(string $company_key, int $company_gateway_id) 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\Models\PaymentHash;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\Client;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
@ -33,6 +34,9 @@ use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\Stripe\EPS; use App\PaymentDrivers\Stripe\EPS;
use App\PaymentDrivers\Stripe\FPX; use App\PaymentDrivers\Stripe\FPX;
use App\PaymentDrivers\Stripe\ACSS; 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\BECS;
use App\PaymentDrivers\Stripe\SEPA; use App\PaymentDrivers\Stripe\SEPA;
use App\PaymentDrivers\Stripe\iDeal; 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\PaymentIntentProcessingWebhook;
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook;
class StripePaymentDriver extends BaseDriver class StripePaymentDriver extends BaseDriver
{ {
use MakesHash, Utilities; use MakesHash, Utilities;
@ -96,6 +101,7 @@ class StripePaymentDriver extends BaseDriver
GatewayType::ACSS => ACSS::class, GatewayType::ACSS => ACSS::class,
GatewayType::FPX => FPX::class, GatewayType::FPX => FPX::class,
GatewayType::KLARNA => Klarna::class, GatewayType::KLARNA => Klarna::class,
GatewayType::BACS => BACS::class,
GatewayType::DIRECT_DEBIT => BankTransfer::class, GatewayType::DIRECT_DEBIT => BankTransfer::class,
]; ];
@ -234,6 +240,14 @@ class StripePaymentDriver extends BaseDriver
&& in_array($this->client->country->iso_3166_3, ['CAN', 'USA'])) { && in_array($this->client->country->iso_3166_3, ['CAN', 'USA'])) {
$types[] = GatewayType::ACSS; $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 if ($this->client
&& $this->client->currency() && $this->client->currency()
&& in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF']) && 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() && $this->client->currency()
&& in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF', 'USD']) && in_array($this->client->currency()->code, ['EUR', 'DKK', 'GBP', 'NOK', 'SEK', 'AUD', 'NZD', 'CAD', 'PLN', 'CHF', 'USD'])
&& isset($this->client->country) && 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'])) { && 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; $types[] = GatewayType::KLARNA;
} }
@ -310,6 +324,8 @@ class StripePaymentDriver extends BaseDriver
return 'gateways.stripe.bancontact'; return 'gateways.stripe.bancontact';
case GatewayType::BECS: case GatewayType::BECS:
return 'gateways.stripe.becs'; return 'gateways.stripe.becs';
case GatewayType::BACS:
return 'gateways.stripe.bacs';
case GatewayType::ACSS: case GatewayType::ACSS:
return 'gateways.stripe.acss'; return 'gateways.stripe.acss';
case GatewayType::FPX: case GatewayType::FPX:
@ -663,8 +679,6 @@ class StripePaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request) public function processWebhookRequest(PaymentWebhookRequest $request)
{ {
// if($request->type === 'payment_intent.requires_action')
// nlog($request->all());
if ($request->type === 'customer.source.updated') { if ($request->type === 'customer.source.updated') {
$ach = new ACH($this); $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); 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', 'klarna' => 'Klarna',
'eps' => 'EPS', 'eps' => 'EPS',
'becs' => 'BECS Direct Debit', '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.', '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.', 'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
'direct_debit' => 'Direct Debit', 'direct_debit' => 'Direct Debit',

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

@ -215,6 +215,22 @@ class ProcessSEPA {
document.querySelector('#pay-now > svg').classList.add('hidden'); document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('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 = const publishableKey =

View File

@ -25,6 +25,11 @@
{{ ctrans('texts.bank_account') }} {{ ctrans('texts.bank_account') }}
</a> </a>
@endif @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>
</div> </div>
@endif @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", "resources/js/clients/payments/stripe-klarna.js",
"public/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( .js(
"resources/js/clients/invoices/action-selectors.js", "resources/js/clients/invoices/action-selectors.js",
"public/js/clients/invoices/action-selectors.js" "public/js/clients/invoices/action-selectors.js"