mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #6827 from beganovich/v5-726
Stripe: SEPA improvements
This commit is contained in:
commit
f038073b4a
@ -11,15 +11,15 @@
|
|||||||
|
|
||||||
namespace App\PaymentDrivers\Stripe;
|
namespace App\PaymentDrivers\Stripe;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||||
use App\PaymentDrivers\StripePaymentDriver;
|
|
||||||
use App\Jobs\Mail\PaymentFailureMailer;
|
use App\Jobs\Mail\PaymentFailureMailer;
|
||||||
use App\Jobs\Util\SystemLogger;
|
use App\Jobs\Util\SystemLogger;
|
||||||
use App\Models\GatewayType;
|
use App\Models\GatewayType;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Models\PaymentType;
|
use App\Models\PaymentType;
|
||||||
use App\Models\SystemLog;
|
use App\Models\SystemLog;
|
||||||
use App\Exceptions\PaymentFailed;
|
use App\PaymentDrivers\StripePaymentDriver;
|
||||||
|
|
||||||
class SEPA
|
class SEPA
|
||||||
{
|
{
|
||||||
@ -29,6 +29,8 @@ class SEPA
|
|||||||
public function __construct(StripePaymentDriver $stripe)
|
public function __construct(StripePaymentDriver $stripe)
|
||||||
{
|
{
|
||||||
$this->stripe = $stripe;
|
$this->stripe = $stripe;
|
||||||
|
|
||||||
|
$this->stripe->init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorizeView($data)
|
public function authorizeView($data)
|
||||||
@ -36,7 +38,8 @@ class SEPA
|
|||||||
return render('gateways.stripe.sepa.authorize', $data);
|
return render('gateways.stripe.sepa.authorize', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paymentView(array $data) {
|
public function paymentView(array $data)
|
||||||
|
{
|
||||||
$data['gateway'] = $this->stripe;
|
$data['gateway'] = $this->stripe;
|
||||||
$data['payment_method_id'] = GatewayType::SEPA;
|
$data['payment_method_id'] = GatewayType::SEPA;
|
||||||
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
|
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
|
||||||
@ -52,11 +55,19 @@ class SEPA
|
|||||||
'setup_future_usage' => 'off_session',
|
'setup_future_usage' => 'off_session',
|
||||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||||
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
|
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data['pi_client_secret'] = $intent->client_secret;
|
$data['pi_client_secret'] = $intent->client_secret;
|
||||||
|
|
||||||
|
if (count($data['tokens']) > 0) {
|
||||||
|
$setup_intent = $this->stripe->stripe->setupIntents->create([
|
||||||
|
'payment_method_types' => ['sepa_debit'],
|
||||||
|
'customer' => $this->stripe->findOrCreateCustomer()->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data['si_client_secret'] = $setup_intent->client_secret;
|
||||||
|
}
|
||||||
|
|
||||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);
|
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);
|
||||||
$this->stripe->payment_hash->save();
|
$this->stripe->payment_hash->save();
|
||||||
|
|
||||||
@ -65,28 +76,24 @@ class SEPA
|
|||||||
|
|
||||||
public function paymentResponse(PaymentResponseRequest $request)
|
public function paymentResponse(PaymentResponseRequest $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
$gateway_response = json_decode($request->gateway_response);
|
$gateway_response = json_decode($request->gateway_response);
|
||||||
|
|
||||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all());
|
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all());
|
||||||
$this->stripe->payment_hash->save();
|
$this->stripe->payment_hash->save();
|
||||||
|
|
||||||
if (property_exists($gateway_response, 'status') && $gateway_response->status == 'processing') {
|
if (property_exists($gateway_response, 'status') && ($gateway_response->status == 'processing' || $gateway_response->status === 'succeeded')) {
|
||||||
|
if ($request->store_card) {
|
||||||
$this->stripe->init();
|
|
||||||
$this->storePaymentMethod($gateway_response);
|
$this->storePaymentMethod($gateway_response);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->processSuccessfulPayment($gateway_response->id);
|
return $this->processSuccessfulPayment($gateway_response->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->processUnsuccessfulPayment();
|
return $this->processUnsuccessfulPayment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processSuccessfulPayment(string $payment_intent)
|
public function processSuccessfulPayment(string $payment_intent)
|
||||||
{
|
{
|
||||||
$this->stripe->init();
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'payment_method' => $payment_intent,
|
'payment_method' => $payment_intent,
|
||||||
'payment_type' => PaymentType::SEPA,
|
'payment_type' => PaymentType::SEPA,
|
||||||
@ -95,7 +102,7 @@ class SEPA
|
|||||||
'gateway_type_id' => GatewayType::SEPA,
|
'gateway_type_id' => GatewayType::SEPA,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->stripe->createPayment($data, Payment::STATUS_PENDING);
|
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);
|
||||||
|
|
||||||
SystemLogger::dispatch(
|
SystemLogger::dispatch(
|
||||||
['response' => $this->stripe->payment_hash->data, 'data' => $data],
|
['response' => $this->stripe->payment_hash->data, 'data' => $data],
|
||||||
@ -106,7 +113,7 @@ class SEPA
|
|||||||
$this->stripe->client->company,
|
$this->stripe->client->company,
|
||||||
);
|
);
|
||||||
|
|
||||||
return redirect()->route('client.payments.index');
|
return redirect()->route('client.payments.show', $payment->hashed_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processUnsuccessfulPayment()
|
public function processUnsuccessfulPayment()
|
||||||
@ -141,7 +148,6 @@ class SEPA
|
|||||||
private function storePaymentMethod($intent)
|
private function storePaymentMethod($intent)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$method = $this->stripe->getStripePaymentMethod($intent->payment_method);
|
$method = $this->stripe->getStripePaymentMethod($intent->payment_method);
|
||||||
|
|
||||||
$payment_meta = new \stdClass;
|
$payment_meta = new \stdClass;
|
||||||
|
2
public/js/clients/payments/stripe-sepa.js
vendored
2
public/js/clients/payments/stripe-sepa.js
vendored
File diff suppressed because one or more lines are too long
@ -20,7 +20,7 @@
|
|||||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
|
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
|
||||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
|
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
|
||||||
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",
|
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",
|
||||||
"/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=e7dc964c85085314b12c",
|
"/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=3f2fa0857dc804a85dcb",
|
||||||
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=231571942310348aa616",
|
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=231571942310348aa616",
|
||||||
"/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=f51400e03c5fdb6cdabe",
|
"/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=f51400e03c5fdb6cdabe",
|
||||||
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=1b8f9325aa6e8595e7fa",
|
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=1b8f9325aa6e8595e7fa",
|
||||||
|
164
resources/js/clients/payments/stripe-sepa.js
vendored
164
resources/js/clients/payments/stripe-sepa.js
vendored
@ -18,68 +18,143 @@ class ProcessSEPA {
|
|||||||
setupStripe = () => {
|
setupStripe = () => {
|
||||||
this.stripe = Stripe(this.key);
|
this.stripe = Stripe(this.key);
|
||||||
|
|
||||||
if(this.stripeConnect)
|
if (this.stripeConnect) this.stripe.stripeAccount = stripeConnect;
|
||||||
this.stripe.stripeAccount = stripeConnect;
|
|
||||||
const elements = this.stripe.elements();
|
const elements = this.stripe.elements();
|
||||||
var style = {
|
var style = {
|
||||||
base: {
|
base: {
|
||||||
color: "#32325d",
|
color: '#32325d',
|
||||||
fontFamily:
|
fontFamily:
|
||||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||||
fontSmoothing: "antialiased",
|
fontSmoothing: 'antialiased',
|
||||||
fontSize: "16px",
|
fontSize: '16px',
|
||||||
"::placeholder": {
|
'::placeholder': {
|
||||||
color: "#aab7c4"
|
color: '#aab7c4',
|
||||||
|
},
|
||||||
|
':-webkit-autofill': {
|
||||||
|
color: '#32325d',
|
||||||
},
|
},
|
||||||
":-webkit-autofill": {
|
|
||||||
color: "#32325d"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
invalid: {
|
invalid: {
|
||||||
color: "#fa755a",
|
color: '#fa755a',
|
||||||
iconColor: "#fa755a",
|
iconColor: '#fa755a',
|
||||||
":-webkit-autofill": {
|
':-webkit-autofill': {
|
||||||
color: "#fa755a"
|
color: '#fa755a',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
var options = {
|
var options = {
|
||||||
style: style,
|
style: style,
|
||||||
supportedCountries: ["SEPA"],
|
supportedCountries: ['SEPA'],
|
||||||
// If you know the country of the customer, you can optionally pass it to
|
// If you know the country of the customer, you can optionally pass it to
|
||||||
// the Element as placeholderCountry. The example IBAN that is being used
|
// the Element as placeholderCountry. The example IBAN that is being used
|
||||||
// as placeholder reflects the IBAN format of that country.
|
// as placeholder reflects the IBAN format of that country.
|
||||||
placeholderCountry: document.querySelector('meta[name="country"]').content
|
placeholderCountry: document.querySelector('meta[name="country"]')
|
||||||
|
.content,
|
||||||
};
|
};
|
||||||
this.iban = elements.create("iban", options);
|
this.iban = elements.create('iban', options);
|
||||||
this.iban.mount("#sepa-iban");
|
this.iban.mount('#sepa-iban');
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
handle = () => {
|
handle = () => {
|
||||||
document.getElementById('pay-now').addEventListener('click', (e) => {
|
|
||||||
|
|
||||||
let errors = document.getElementById('errors');
|
let errors = document.getElementById('errors');
|
||||||
|
|
||||||
if (document.getElementById('sepa-name').value === "") {
|
Array.from(
|
||||||
|
document.getElementsByClassName('toggle-payment-with-token')
|
||||||
|
).forEach((element) =>
|
||||||
|
element.addEventListener('click', (element) => {
|
||||||
|
document
|
||||||
|
.getElementById('stripe--payment-container')
|
||||||
|
.classList.add('hidden');
|
||||||
|
document.getElementById('save-card--container').style.display =
|
||||||
|
'none';
|
||||||
|
document.querySelector('input[name=token]').value =
|
||||||
|
element.target.dataset.token;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('toggle-payment-with-new-bank-account')
|
||||||
|
.addEventListener('click', (element) => {
|
||||||
|
document
|
||||||
|
.getElementById('stripe--payment-container')
|
||||||
|
.classList.remove('hidden');
|
||||||
|
document.getElementById('save-card--container').style.display =
|
||||||
|
'grid';
|
||||||
|
document.querySelector('input[name=token]').value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('pay-now').addEventListener('click', (e) => {
|
||||||
|
if (
|
||||||
|
document.querySelector('input[name=token]').value.length !== 0
|
||||||
|
) {
|
||||||
|
document.querySelector('#errors').hidden = true;
|
||||||
|
|
||||||
|
document.getElementById('pay-now').disabled = true;
|
||||||
|
document
|
||||||
|
.querySelector('#pay-now > svg')
|
||||||
|
.classList.remove('hidden');
|
||||||
|
document
|
||||||
|
.querySelector('#pay-now > span')
|
||||||
|
.classList.add('hidden');
|
||||||
|
|
||||||
|
this.stripe
|
||||||
|
.confirmSepaDebitSetup(
|
||||||
|
document.querySelector('meta[name=si-client-secret')
|
||||||
|
.content,
|
||||||
|
{
|
||||||
|
payment_method: document.querySelector(
|
||||||
|
'input[name=token]'
|
||||||
|
).value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((result) => {
|
||||||
|
if (result.error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector(
|
||||||
|
'input[name="gateway_response"]'
|
||||||
|
).value = JSON.stringify(result.setupIntent);
|
||||||
|
|
||||||
|
return document
|
||||||
|
.querySelector('#server-response')
|
||||||
|
.submit();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
errors.textContent = error;
|
||||||
|
errors.hidden = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('sepa-name').value === '') {
|
||||||
document.getElementById('sepa-name').focus();
|
document.getElementById('sepa-name').focus();
|
||||||
errors.textContent = "Name required.";
|
errors.textContent = document.querySelector(
|
||||||
|
'meta[name=translation-name-required]'
|
||||||
|
).content;
|
||||||
errors.hidden = false;
|
errors.hidden = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById('sepa-email-address').value === "") {
|
if (document.getElementById('sepa-email-address').value === '') {
|
||||||
document.getElementById('sepa-email-address').focus();
|
document.getElementById('sepa-email-address').focus();
|
||||||
errors.textContent = "Email required.";
|
errors.textContent = document.querySelector(
|
||||||
|
'meta[name=translation-email-required]'
|
||||||
|
).content;
|
||||||
errors.hidden = false;
|
errors.hidden = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!document.getElementById('sepa-mandate-acceptance').checked) {
|
if (!document.getElementById('sepa-mandate-acceptance').checked) {
|
||||||
errors.textContent = "Accept Terms";
|
errors.textContent = document.querySelector(
|
||||||
|
'meta[name=translation-terms-required]'
|
||||||
|
).content;
|
||||||
errors.hidden = false;
|
errors.hidden = false;
|
||||||
console.log("Terms");
|
console.log('Terms');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,18 +162,24 @@ class ProcessSEPA {
|
|||||||
document.querySelector('#pay-now > svg').classList.remove('hidden');
|
document.querySelector('#pay-now > svg').classList.remove('hidden');
|
||||||
document.querySelector('#pay-now > span').classList.add('hidden');
|
document.querySelector('#pay-now > span').classList.add('hidden');
|
||||||
|
|
||||||
this.stripe.confirmSepaDebitPayment(
|
this.stripe
|
||||||
document.querySelector('meta[name=pi-client-secret').content,
|
.confirmSepaDebitPayment(
|
||||||
|
document.querySelector('meta[name=pi-client-secret')
|
||||||
|
.content,
|
||||||
{
|
{
|
||||||
payment_method: {
|
payment_method: {
|
||||||
sepa_debit: this.iban,
|
sepa_debit: this.iban,
|
||||||
billing_details: {
|
billing_details: {
|
||||||
name: document.getElementById("sepa-name").value,
|
name: document.getElementById('sepa-name')
|
||||||
email: document.getElementById("sepa-email-address").value,
|
.value,
|
||||||
|
email: document.getElementById(
|
||||||
|
'sepa-email-address'
|
||||||
|
).value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then((result) => {
|
)
|
||||||
|
.then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
return this.handleFailure(result.error.message);
|
return this.handleFailure(result.error.message);
|
||||||
}
|
}
|
||||||
@ -113,6 +194,15 @@ class ProcessSEPA {
|
|||||||
'input[name="gateway_response"]'
|
'input[name="gateway_response"]'
|
||||||
).value = JSON.stringify(result.paymentIntent);
|
).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();
|
document.getElementById('server-response').submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,9 +219,9 @@ class ProcessSEPA {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishableKey = document.querySelector(
|
const publishableKey =
|
||||||
'meta[name="stripe-publishable-key"]'
|
document.querySelector('meta[name="stripe-publishable-key"]')?.content ??
|
||||||
)?.content ?? '';
|
'';
|
||||||
|
|
||||||
const stripeConnect =
|
const stripeConnect =
|
||||||
document.querySelector('meta[name="stripe-account-id"]')?.content ?? '';
|
document.querySelector('meta[name="stripe-account-id"]')?.content ?? '';
|
||||||
|
@ -4327,6 +4327,7 @@ $LANG = array(
|
|||||||
'giropay' => 'GiroPay',
|
'giropay' => 'GiroPay',
|
||||||
'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.',
|
'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.',
|
||||||
'eps' => 'EPS',
|
'eps' => 'EPS',
|
||||||
|
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
|
||||||
);
|
);
|
||||||
|
|
||||||
return $LANG;
|
return $LANG;
|
||||||
|
@ -7,9 +7,24 @@
|
|||||||
<meta name="country" content="{{ $country }}">
|
<meta name="country" content="{{ $country }}">
|
||||||
<meta name="customer" content="{{ $customer }}">
|
<meta name="customer" content="{{ $customer }}">
|
||||||
<meta name="pi-client-secret" content="{{ $pi_client_secret }}">
|
<meta name="pi-client-secret" content="{{ $pi_client_secret }}">
|
||||||
|
<meta name="si-client-secret" content="{{ $si_client_secret ?? '' }}">
|
||||||
|
|
||||||
|
<meta name="translation-name-required" content="{{ ctrans('texts.missing_account_holder_name') }}">
|
||||||
|
<meta name="translation-email-required" content="{{ ctrans('texts.provide_email') }}">
|
||||||
|
<meta name="translation-terms-required" content="{{ ctrans('texts.you_need_to_accept_the_terms_before_proceeding') }}">
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('gateway_content')
|
@section('gateway_content')
|
||||||
|
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="gateway_response">
|
||||||
|
<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="payment_hash" value="{{ $payment_hash }}">
|
||||||
|
<input type="hidden" name="store_card">
|
||||||
|
<input type="hidden" name="token">
|
||||||
|
</form>
|
||||||
|
|
||||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
|
|
||||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||||
@ -18,7 +33,48 @@
|
|||||||
{{ ctrans('texts.sepa') }} ({{ ctrans('texts.bank_transfer') }})
|
{{ ctrans('texts.sepa') }} ({{ ctrans('texts.bank_transfer') }})
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|
||||||
@include('portal.ninja2020.gateways.stripe.sepa.sepa_debit')
|
@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">**** {{ optional($token->meta)->last4 }}</span>
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<input type="radio" id="toggle-payment-with-new-bank-account" class="form-radio cursor-pointer" name="payment-type"
|
||||||
|
checked />
|
||||||
|
<span class="ml-1 cursor-pointer">{{ __('texts.new_bank_account') }}</span>
|
||||||
|
</label>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@component('portal.ninja2020.components.general.card-element-single')
|
||||||
|
<div id="stripe--payment-container">
|
||||||
|
<label for="sepa-name">
|
||||||
|
<input class="input w-full" id="sepa-name" type="text"
|
||||||
|
placeholder="{{ ctrans('texts.bank_account_holder') }}">
|
||||||
|
</label>
|
||||||
|
<label for="sepa-email" class="mt-4">
|
||||||
|
<input class="input w-full" id="sepa-email-address" type="email"
|
||||||
|
placeholder="{{ ctrans('texts.email') }}">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<div class="border p-3 rounded mt-2">
|
||||||
|
<div id="sepa-iban"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div id="mandate-acceptance" class="mt-4">
|
||||||
|
<input type="checkbox" id="sepa-mandate-acceptance" class="input mr-4">
|
||||||
|
<label for="sepa-mandate-acceptance" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.sepa_mandat', ['company' => $contact->company->present()->name()]) }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
@include('portal.ninja2020.gateways.includes.save_card')
|
@include('portal.ninja2020.gateways.includes.save_card')
|
||||||
@include('portal.ninja2020.gateways.includes.pay_now')
|
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||||
@endsection
|
@endsection
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
<div id="stripe--payment-container">
|
|
||||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.name')])
|
|
||||||
|
|
||||||
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="gateway_response">
|
|
||||||
<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="payment_hash" value="{{ $payment_hash }}">
|
|
||||||
<input type="hidden" name="store_card">
|
|
||||||
|
|
||||||
<label for="sepa-name">
|
|
||||||
<input class="input w-full" id="sepa-name" type="text" placeholder="{{ ctrans('texts.bank_account_holder') }}">
|
|
||||||
</label>
|
|
||||||
<label for="sepa-email" >
|
|
||||||
<input class="input w-full" id="sepa-email-address" type="email" placeholder="{{ ctrans('texts.email') }}">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<div class="border p-4 rounded">
|
|
||||||
<div id="sepa-iban"></div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div id="mandate-acceptance">
|
|
||||||
<input type="checkbox" id="sepa-mandate-acceptance" class="input mr-4">
|
|
||||||
<label for="sepa-mandate-acceptance">{{ctrans('texts.sepa_mandat', ['company' => auth('contact')->user()->company->present()->name()])}}</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@endcomponent
|
|
||||||
</div>
|
|
127
tests/Browser/ClientPortal/Gateways/Stripe/SEPATest.php
Normal file
127
tests/Browser/ClientPortal/Gateways/Stripe/SEPATest.php
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Browser\ClientPortal\Gateways\Stripe;
|
||||||
|
|
||||||
|
use App\DataMapper\FeesAndLimits;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\Pages\ClientPortal\Login;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class SEPATest extends DuskTestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
foreach (static::$browsers as $browser) {
|
||||||
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visit(new Login())
|
||||||
|
->auth();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->disableCompanyGateways();
|
||||||
|
|
||||||
|
// Enable Stripe.
|
||||||
|
CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->restore();
|
||||||
|
|
||||||
|
// Enable SEPA.
|
||||||
|
$cg = CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->firstOrFail();
|
||||||
|
$fees_and_limits = $cg->fees_and_limits;
|
||||||
|
$fees_and_limits->{GatewayType::SEPA} = new FeesAndLimits();
|
||||||
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
|
$cg->save();
|
||||||
|
|
||||||
|
// SEPA required DE to be billing country.
|
||||||
|
$client = Client::first();
|
||||||
|
$client->country_id = 276;
|
||||||
|
|
||||||
|
$settings = $client->settings;
|
||||||
|
$settings->currency_id = "3";
|
||||||
|
|
||||||
|
$client->settings = $settings;
|
||||||
|
$client->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPayingWithNewSEPABankAccount(): void
|
||||||
|
{
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visitRoute('client.invoices.index')
|
||||||
|
->click('@pay-now')
|
||||||
|
->click('@pay-now-dropdown')
|
||||||
|
->clickLink('SEPA Direct Debit')
|
||||||
|
->type('#sepa-name', 'John Doe')
|
||||||
|
->type('#sepa-email-address', 'test@invoiceninja.com')
|
||||||
|
->withinFrame('iframe', function (Browser $browser) {
|
||||||
|
$browser->type('iban', 'DE89370400440532013000');
|
||||||
|
})
|
||||||
|
->check('#sepa-mandate-acceptance', true)
|
||||||
|
->click('#pay-now')
|
||||||
|
->waitForText('Details of the payment', 60);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPayingWithNewSEPABankAccountAndSaveForFuture(): void
|
||||||
|
{
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visitRoute('client.invoices.index')
|
||||||
|
->click('@pay-now')
|
||||||
|
->click('@pay-now-dropdown')
|
||||||
|
->clickLink('SEPA Direct Debit')
|
||||||
|
->type('#sepa-name', 'John Doe')
|
||||||
|
->type('#sepa-email-address', 'test@invoiceninja.com')
|
||||||
|
->withinFrame('iframe', function (Browser $browser) {
|
||||||
|
$browser->type('iban', 'DE89370400440532013000');
|
||||||
|
})
|
||||||
|
->check('#sepa-mandate-acceptance', true)
|
||||||
|
->radio('#proxy_is_default', true)
|
||||||
|
->click('#pay-now')
|
||||||
|
->waitForText('Details of the payment', 60);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPayWithSavedBankAccount()
|
||||||
|
{
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visitRoute('client.invoices.index')
|
||||||
|
->click('@pay-now')
|
||||||
|
->click('@pay-now-dropdown')
|
||||||
|
->clickLink('SEPA Direct Debit')
|
||||||
|
->click('.toggle-payment-with-token')
|
||||||
|
->click('#pay-now')
|
||||||
|
->waitForText('Details of the payment', 60);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemoveBankAccount()
|
||||||
|
{
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser
|
||||||
|
->visitRoute('client.payment_methods.index')
|
||||||
|
->clickLink('View')
|
||||||
|
->press('Remove Payment Method')
|
||||||
|
->waitForText('Confirmation')
|
||||||
|
->click('@confirm-payment-removal')
|
||||||
|
->assertSee('Payment method has been successfully removed.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user