mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Payment page script
This commit is contained in:
parent
e0b0879ed5
commit
2c6f7dfa6f
@ -136,9 +136,9 @@ class CreditCard
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processPaymentResponse($request)
|
public function paymentResponse($request)
|
||||||
{
|
{
|
||||||
|
// ..
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This method is stubbed ready to go - you just need to harvest the equivalent 'transaction_reference' */
|
/* This method is stubbed ready to go - you just need to harvest the equivalent 'transaction_reference' */
|
||||||
|
@ -2,170 +2,170 @@
|
|||||||
=> ctrans('texts.payment_type_credit_card')])
|
=> ctrans('texts.payment_type_credit_card')])
|
||||||
|
|
||||||
@section('gateway_head')
|
@section('gateway_head')
|
||||||
|
<meta name="square-appId" content="{{ $gateway->company_gateway->getConfigField('applicationId') }}">
|
||||||
|
<meta name="square-locationId" content="{{ $gateway->company_gateway->getConfigField('locationId') }}">
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('gateway_content')
|
@section('gateway_content')
|
||||||
|
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
|
||||||
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server_response">
|
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="store_card">
|
<input type="hidden" name="store_card">
|
||||||
<input type="text" name="sourceId" id="sourceId" hidden>
|
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||||
|
|
||||||
|
<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="sourceId" id="sourceId">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
|
|
||||||
|
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
|
||||||
|
{{ ctrans('texts.credit_card') }}
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||||
|
|
||||||
|
|
||||||
|
@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-credit-card" class="form-radio cursor-pointer" name="payment-type"
|
||||||
|
checked />
|
||||||
|
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
|
||||||
|
</label>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@include('portal.ninja2020.gateways.includes.save_card')
|
||||||
|
|
||||||
@component('portal.ninja2020.components.general.card-element-single')
|
@component('portal.ninja2020.components.general.card-element-single')
|
||||||
<div id="card-container"></div>
|
<div id="card-container"></div>
|
||||||
|
<div id="payment-status-container"></div>
|
||||||
<div id="payment-status-container"></div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|
||||||
@component('portal.ninja2020.gateways.includes.pay_now')
|
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||||
{{ ctrans('texts.pay_now') }}
|
|
||||||
@endcomponent
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('gateway_footer')
|
@section('gateway_footer')
|
||||||
|
@if ($gateway->company_gateway->getConfigField('testMode'))
|
||||||
|
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
|
||||||
|
@else
|
||||||
|
<script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if($gateway->company_gateway->getConfigField('testMode'))
|
<script>
|
||||||
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
|
class SquareCreditCard {
|
||||||
@else
|
constructor() {
|
||||||
<script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
|
this.appId = document.querySelector('meta[name=square-appId]').content;
|
||||||
@endif
|
this.locationId = document.querySelector('meta[name=square-locationId]').content;
|
||||||
|
}
|
||||||
|
|
||||||
<script>
|
async init() {
|
||||||
const appId = "{{ $gateway->company_gateway->getConfigField('applicationId') }}";
|
this.payments = Square.payments(this.appId, this.locationId);
|
||||||
const locationId = "{{ $gateway->company_gateway->getConfigField('locationId') }}";
|
|
||||||
|
|
||||||
const darkModeCardStyle = {
|
this.card = await this.payments.card();
|
||||||
'.input-container': {
|
|
||||||
borderColor: '#2D2D2D',
|
|
||||||
borderRadius: '6px',
|
|
||||||
},
|
|
||||||
'.input-container.is-focus': {
|
|
||||||
borderColor: '#006AFF',
|
|
||||||
},
|
|
||||||
'.input-container.is-error': {
|
|
||||||
borderColor: '#ff1600',
|
|
||||||
},
|
|
||||||
'.message-text': {
|
|
||||||
color: '#999999',
|
|
||||||
},
|
|
||||||
'.message-icon': {
|
|
||||||
color: '#999999',
|
|
||||||
},
|
|
||||||
'.message-text.is-error': {
|
|
||||||
color: '#ff1600',
|
|
||||||
},
|
|
||||||
'.message-icon.is-error': {
|
|
||||||
color: '#ff1600',
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
backgroundColor: '#2D2D2D',
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontFamily: 'helvetica neue, sans-serif',
|
|
||||||
},
|
|
||||||
'input::placeholder': {
|
|
||||||
color: '#999999',
|
|
||||||
},
|
|
||||||
'input.is-error': {
|
|
||||||
color: '#ff1600',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function initializeCard(payments) {
|
await this.card.attach('#card-container');
|
||||||
const card = await payments.card({
|
|
||||||
style: darkModeCardStyle,
|
|
||||||
});
|
|
||||||
await card.attach('#card-container');
|
|
||||||
|
|
||||||
return card;
|
let iframeContainer = document.querySelector('.sq-card-iframe-container');
|
||||||
}
|
|
||||||
|
|
||||||
async function tokenize(paymentMethod) {
|
if (iframeContainer) {
|
||||||
const tokenResult = await paymentMethod.tokenize();
|
iframeContainer.setAttribute('style', '150px !important');
|
||||||
if (tokenResult.status === 'OK') {
|
}
|
||||||
return tokenResult.token;
|
|
||||||
} else {
|
let toggleWithToken = document.querySelector('.toggle-payment-with-token');
|
||||||
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
|
||||||
if (tokenResult.errors) {
|
if (toggleWithToken) {
|
||||||
errorMessage += ` and errors: ${JSON.stringify(
|
document.getElementById('card-container').classList.add('hidden');
|
||||||
tokenResult.errors
|
}
|
||||||
)}`;
|
}
|
||||||
|
|
||||||
|
async completePaymentWithoutToken(e) {
|
||||||
|
document.getElementById('errors').hidden = true;
|
||||||
|
e.target.parentElement.disabled = true;
|
||||||
|
|
||||||
|
let result = await this.card.tokenize();
|
||||||
|
|
||||||
|
if (result.status === 'OK') {
|
||||||
|
document.getElementById('sourceId').value = result.token;
|
||||||
|
|
||||||
|
let tokenBillingCheckbox = document.querySelector(
|
||||||
|
'input[name="token-billing-checkbox"]:checked'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tokenBillingCheckbox) {
|
||||||
|
document.querySelector('input[name="store_card"]').value =
|
||||||
|
tokenBillingCheckbox.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.getElementById('server_response').submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('errors').textContent = result.errors[0].message;
|
||||||
|
document.getElementById('errors').hidden = false;
|
||||||
|
|
||||||
|
e.target.parentElement.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async completePaymentUsingToken(e) {
|
||||||
|
e.target.parentElement.disabled = true;
|
||||||
|
|
||||||
|
return document.getElementById('server_response').submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle() {
|
||||||
|
document
|
||||||
|
.getElementById('authorize-card')
|
||||||
|
?.addEventListener('click', (e) => this.completePaymentWithoutToken(e));
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('pay-now')
|
||||||
|
.addEventListener('click', (e) => {
|
||||||
|
let tokenInput = document.querySelector('input[name=token]');
|
||||||
|
|
||||||
|
if (tokenInput.value) {
|
||||||
|
return this.completePaymentUsingToken(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.completePaymentWithoutToken(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
Array
|
||||||
|
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||||
|
.forEach((element) => element.addEventListener('click', (element) => {
|
||||||
|
document.getElementById('card-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-credit-card')
|
||||||
|
.addEventListener('click', async (element) => {
|
||||||
|
await this.init();
|
||||||
|
|
||||||
|
document.getElementById('card-container').classList.remove('hidden');
|
||||||
|
document.getElementById('save-card--container').style.display = 'grid';
|
||||||
|
document.querySelector('input[name=token]').value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
let toggleWithToken = document.querySelector('.toggle-payment-with-token');
|
||||||
|
|
||||||
|
if (!toggleWithToken) {
|
||||||
|
document.getElementById('toggle-payment-with-credit-card').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(errorMessage);
|
new SquareCreditCard().handle();
|
||||||
}
|
</script>
|
||||||
}
|
@endsection
|
||||||
|
|
||||||
// status is either SUCCESS or FAILURE;
|
|
||||||
function displayPaymentResults(status) {
|
|
||||||
const statusContainer = document.getElementById(
|
|
||||||
'payment-status-container'
|
|
||||||
);
|
|
||||||
if (status === 'SUCCESS') {
|
|
||||||
statusContainer.classList.remove('is-failure');
|
|
||||||
statusContainer.classList.add('is-success');
|
|
||||||
} else {
|
|
||||||
statusContainer.classList.remove('is-success');
|
|
||||||
statusContainer.classList.add('is-failure');
|
|
||||||
}
|
|
||||||
|
|
||||||
statusContainer.style.visibility = 'visible';
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
|
||||||
if (!window.Square) {
|
|
||||||
throw new Error('Square.js failed to load properly');
|
|
||||||
}
|
|
||||||
|
|
||||||
let payments;
|
|
||||||
try {
|
|
||||||
payments = window.Square.payments(appId, locationId);
|
|
||||||
} catch {
|
|
||||||
const statusContainer = document.getElementById(
|
|
||||||
'payment-status-container'
|
|
||||||
);
|
|
||||||
statusContainer.className = 'missing-credentials';
|
|
||||||
statusContainer.style.visibility = 'visible';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let card;
|
|
||||||
try {
|
|
||||||
card = await initializeCard(payments);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Initializing Card failed', e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handlePaymentMethodSubmission(event, paymentMethod) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// disable the submit button as we await tokenization and make a payment request.
|
|
||||||
cardButton.disabled = true;
|
|
||||||
const token = await tokenize(paymentMethod);
|
|
||||||
|
|
||||||
document.getElementById('sourceId').value = token;
|
|
||||||
document.getElementById('server_response').submit();
|
|
||||||
|
|
||||||
displayPaymentResults('SUCCESS');
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
cardButton.disabled = false;
|
|
||||||
displayPaymentResults('FAILURE');
|
|
||||||
console.error(e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardButton = document.getElementById('pay-now');
|
|
||||||
cardButton.addEventListener('click', async function (event) {
|
|
||||||
await handlePaymentMethodSubmission(event, card);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
@endsection
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user