mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Updates for ach payments
This commit is contained in:
parent
ad203ac228
commit
b12abf9103
@ -117,7 +117,7 @@ trait ChartQueries
|
|||||||
GROUP BY invoices.date
|
GROUP BY invoices.date
|
||||||
HAVING currency_id = :currency_id
|
HAVING currency_id = :currency_id
|
||||||
"), [
|
"), [
|
||||||
'company_currency' => $this->company->settings->currency_id,
|
'company_currency' => (int)$this->company->settings->currency_id,
|
||||||
'currency_id' => $currency_id,
|
'currency_id' => $currency_id,
|
||||||
'company_id' => $this->company->id,
|
'company_id' => $this->company->id,
|
||||||
'start_date' => $start_date,
|
'start_date' => $start_date,
|
||||||
|
@ -15,24 +15,24 @@
|
|||||||
|
|
||||||
@section('gateway_content')
|
@section('gateway_content')
|
||||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
|
|
||||||
|
<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="source" value="">
|
||||||
|
<input type="hidden" name="amount" value="{{ $amount }}">
|
||||||
|
<input type="hidden" name="currency" value="{{ $currency }}">
|
||||||
|
<input type="hidden" name="customer" value="{{ $customer->id }}">
|
||||||
|
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||||
|
<input type="hidden" name="client_secret" value="{{ $client_secret }}">
|
||||||
|
<input type="hidden" name="gateway_response" id="gateway_response" value="">
|
||||||
|
</form>
|
||||||
|
|
||||||
@if(count($tokens) > 0)
|
@if(count($tokens) > 0)
|
||||||
|
|
||||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||||
|
|
||||||
<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="source" value="">
|
|
||||||
<input type="hidden" name="amount" value="{{ $amount }}">
|
|
||||||
<input type="hidden" name="currency" value="{{ $currency }}">
|
|
||||||
<input type="hidden" name="customer" value="{{ $customer->id }}">
|
|
||||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
|
||||||
<input type="hidden" name="client_secret" value="{{ $client_secret }}">
|
|
||||||
<input type="hidden" name="gateway_response" id="gateway_response" value="">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||||
@if(count($tokens) > 0)
|
@if(count($tokens) > 0)
|
||||||
@foreach($tokens as $token)
|
@foreach($tokens as $token)
|
||||||
@ -52,6 +52,11 @@
|
|||||||
|
|
||||||
@else
|
@else
|
||||||
|
|
||||||
|
@component('portal.ninja2020.components.general.card-element-single')
|
||||||
|
<input type="checkbox" class="form-checkbox mr-1" id="accept-terms" required>
|
||||||
|
<label for="accept-terms" class="cursor-pointer">{{ ctrans('texts.ach_authorization', ['company' => auth()->user()->company->present()->name, 'email' => auth()->guard('contact')->user()->client->company->settings->email]) }}</label>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')])
|
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')])
|
||||||
<input class="input w-full" id="account-holder-name-field" type="text" placeholder="{{ ctrans('texts.name') }}" value="{{ $gateway->client->present()->first_name() }} {{ $gateway->client->present()->last_name(); }}"required>
|
<input class="input w-full" id="account-holder-name-field" type="text" placeholder="{{ ctrans('texts.name') }}" value="{{ $gateway->client->present()->first_name() }} {{ $gateway->client->present()->last_name(); }}"required>
|
||||||
@endcomponent
|
@endcomponent
|
||||||
@ -71,87 +76,40 @@
|
|||||||
<span>{{ $slot ?? ctrans('texts.new_bank_account') }}</span>
|
<span>{{ $slot ?? ctrans('texts.new_bank_account') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
|
||||||
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
|
||||||
<button class="button button-danger" @click="open = true" id="open-delete-popup" style="display: none;">
|
|
||||||
{{ ctrans('texts.remove_payment_method') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div x-show="open" class="fixed inset-x-0 bottom-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center" style="display: none;">
|
|
||||||
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
|
||||||
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
|
||||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
|
||||||
class="fixed inset-0 transition-opacity">
|
|
||||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div x-show="open" x-transition:enter="ease-out duration-300"
|
|
||||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
|
||||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
class="px-4 pt-5 pb-4 overflow-hidden transition-all transform bg-white rounded-lg shadow-xl sm:max-w-lg sm:w-full sm:p-6">
|
|
||||||
<div class="sm:flex sm:items-start">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-red-100 rounded-full sm:mx-0 sm:h-10 sm:w-10">
|
|
||||||
<svg class="w-6 h-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900" translate>
|
|
||||||
{{ ctrans('texts.confirmation') }}
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2">
|
|
||||||
<p class="text-sm leading-5 text-gray-500">
|
|
||||||
|
|
||||||
{{ ctrans('texts.ach_authorization', ['company' => auth()->user()->company->present()->name, 'email' => auth()->guard('contact')->user()->client->company->settings->email]) }}
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
||||||
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
|
||||||
<button type="submit" onclick="confirmPayment(() => this.disabled = true, 0); return true;" class="button button-danger button-block" dusk="confirm-payment-removal">
|
|
||||||
{{ ctrans('texts.confirm') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex w-full mt-3 rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
|
||||||
|
|
||||||
<button @click="open = false" type="button" class="button button-secondary button-block">
|
|
||||||
{{ ctrans('texts.cancel') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@push('footer')
|
@push('footer')
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
let payNow = document.getElementById('pay-now');
|
let payNow = document.getElementById('pay-now');
|
||||||
|
if(payNow)
|
||||||
let stripePaymentIntent = '';
|
{
|
||||||
|
|
||||||
|
Array
|
||||||
|
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||||
|
.forEach((element) => element.addEventListener('click', (element) => {
|
||||||
|
document.querySelector('input[name=source]').value = element.target.dataset.token;
|
||||||
|
}));
|
||||||
|
payNow.addEventListener('click', function () {
|
||||||
|
let payNowButton = document.getElementById('pay-now');
|
||||||
|
payNowButton.disabled = true;
|
||||||
|
payNowButton.querySelector('svg').classList.remove('hidden');
|
||||||
|
payNowButton.querySelector('span').classList.add('hidden');
|
||||||
|
document.getElementById('server-response').submit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.getElementById('new-bank').addEventListener('click', (ev) => {
|
||||||
|
if (!document.getElementById('accept-terms').checked) {
|
||||||
|
errors.textContent = "You must accept the mandate terms prior to making payment.";
|
||||||
|
errors.hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
errors.hidden = true;
|
||||||
let stripe;
|
let stripe;
|
||||||
|
|
||||||
let response;
|
|
||||||
|
|
||||||
let publishableKey = document.querySelector('meta[name="stripe-publishable-key"]').content
|
let publishableKey = document.querySelector('meta[name="stripe-publishable-key"]').content
|
||||||
let stripeConnect = document.querySelector('meta[name="stripe-account-id"]')?.content
|
let stripeConnect = document.querySelector('meta[name="stripe-account-id"]')?.content
|
||||||
|
|
||||||
@ -161,46 +119,14 @@
|
|||||||
else {
|
else {
|
||||||
stripe = Stripe(publishableKey);
|
stripe = Stripe(publishableKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(payNow)
|
|
||||||
{
|
|
||||||
|
|
||||||
Array
|
|
||||||
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
|
||||||
.forEach((element) => element.addEventListener('click', (element) => {
|
|
||||||
document.querySelector('input[name=source]').value = element.target.dataset.token;
|
|
||||||
}));
|
|
||||||
|
|
||||||
payNow.addEventListener('click', function () {
|
|
||||||
|
|
||||||
let payNowButton = document.getElementById('pay-now');
|
|
||||||
payNowButton.disabled = true;
|
|
||||||
payNowButton.querySelector('svg').classList.remove('hidden');
|
|
||||||
payNowButton.querySelector('span').classList.add('hidden');
|
|
||||||
|
|
||||||
document.getElementById('server-response').submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('new-bank').addEventListener('click', (ev) => {
|
|
||||||
|
|
||||||
errors.hidden = true;
|
|
||||||
|
|
||||||
let newBankButton = document.getElementById('new-bank');
|
let newBankButton = document.getElementById('new-bank');
|
||||||
newBankButton.disabled = true;
|
newBankButton.disabled = true;
|
||||||
newBankButton.querySelector('svg').classList.remove('hidden');
|
newBankButton.querySelector('svg').classList.remove('hidden');
|
||||||
newBankButton.querySelector('span').classList.add('hidden');
|
newBankButton.querySelector('span').classList.add('hidden');
|
||||||
|
|
||||||
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const accountHolderNameField = document.getElementById('account-holder-name-field');
|
const accountHolderNameField = document.getElementById('account-holder-name-field');
|
||||||
const emailField = document.getElementById('email-field');
|
const emailField = document.getElementById('email-field');
|
||||||
const clientSecret = document.querySelector('meta[name="client_secret"]')?.content;
|
const clientSecret = document.querySelector('meta[name="client_secret"]')?.content;
|
||||||
|
|
||||||
// Calling this method will open the instant verification dialog.
|
// Calling this method will open the instant verification dialog.
|
||||||
stripe.collectBankAccountForPayment({
|
stripe.collectBankAccountForPayment({
|
||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
@ -230,68 +156,39 @@
|
|||||||
// manually-entered. Display payment method details and mandate text
|
// manually-entered. Display payment method details and mandate text
|
||||||
// to the customer and confirm the intent once they accept
|
// to the customer and confirm the intent once they accept
|
||||||
// the mandate.
|
// the mandate.
|
||||||
stripePaymentIntent = paymentIntent;
|
confirmPayment(stripe, clientSecret);
|
||||||
|
|
||||||
showModal(paymentIntent);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
function confirmPayment(stripe, clientSecret){
|
||||||
function showModal(paymentIntent)
|
|
||||||
{
|
|
||||||
document.getElementById('open-delete-popup').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmPayment(){
|
|
||||||
|
|
||||||
const clientSecret = document.querySelector('meta[name="client_secret"]')?.content;
|
|
||||||
|
|
||||||
stripe.confirmUsBankAccountPayment(clientSecret)
|
stripe.confirmUsBankAccountPayment(clientSecret)
|
||||||
.then(({paymentIntent, error}) => {
|
.then(({paymentIntent, error}) => {
|
||||||
|
|
||||||
console.log(paymentIntent);
|
console.log(paymentIntent);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
// The payment failed for some reason.
|
// The payment failed for some reason.
|
||||||
} else if (paymentIntent.status === "requires_payment_method") {
|
} else if (paymentIntent.status === "requires_payment_method") {
|
||||||
// Confirmation failed. Attempt again with a different payment method.
|
// Confirmation failed. Attempt again with a different payment method.
|
||||||
|
|
||||||
errors.textContent = error.message;
|
errors.textContent = error.message;
|
||||||
errors.hidden = false;
|
errors.hidden = false;
|
||||||
resetButtons();
|
resetButtons();
|
||||||
|
|
||||||
} else if (paymentIntent.status === "processing") {
|
} else if (paymentIntent.status === "processing") {
|
||||||
// Confirmation succeeded! The account will be debited.
|
// Confirmation succeeded! The account will be debited.
|
||||||
// Display a message to customer.
|
|
||||||
|
let gateway_response = document.getElementById('gateway_response');
|
||||||
|
gateway_response.value = JSON.stringify(
|
||||||
|
paymentIntent
|
||||||
|
);
|
||||||
|
document.getElementById('server-response').submit();
|
||||||
|
|
||||||
} else if (paymentIntent.next_action?.type === "verify_with_microdeposits") {
|
} else if (paymentIntent.next_action?.type === "verify_with_microdeposits") {
|
||||||
// The account needs to be verified via microdeposits.
|
// The account needs to be verified via microdeposits.
|
||||||
// Display a message to consumer with next steps (consumer waits for
|
// Display a message to consumer with next steps (consumer waits for
|
||||||
// microdeposits, then enters a statement descriptor code on a page sent to them via email).
|
// microdeposits, then enters a statement descriptor code on a page sent to them via email).
|
||||||
}
|
}
|
||||||
}).finally((promise) => {
|
|
||||||
|
|
||||||
console.log(promise);
|
|
||||||
console.log("and we are finished")
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
resetButtons();
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function finalize()
|
|
||||||
{
|
|
||||||
|
|
||||||
document.getElementById('server-response').submit();
|
|
||||||
|
|
||||||
|
// resetButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetButtons()
|
function resetButtons()
|
||||||
@ -300,8 +197,6 @@
|
|||||||
newBankButton.disabled = false;
|
newBankButton.disabled = false;
|
||||||
newBankButton.querySelector('svg').classList.add('hidden');
|
newBankButton.querySelector('svg').classList.add('hidden');
|
||||||
newBankButton.querySelector('span').classList.remove('hidden');
|
newBankButton.querySelector('span').classList.remove('hidden');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
Loading…
x
Reference in New Issue
Block a user