mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-13 06:32:40 +01:00
Square - pay
This commit is contained in:
parent
0007bb4538
commit
04a16fca90
@ -130,9 +130,9 @@ class CreditCard
|
|||||||
{
|
{
|
||||||
|
|
||||||
$data['gateway'] = $this->square_driver;
|
$data['gateway'] = $this->square_driver;
|
||||||
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
|
|
||||||
|
|
||||||
return render('gateways.braintree.credit_card.pay', $data);
|
|
||||||
|
return render('gateways.square.credit_card.pay', $data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,4 +102,49 @@ class SquarePaymentDriver extends BaseDriver
|
|||||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getClientRequiredFields(): array
|
||||||
|
{
|
||||||
|
$fields = [];
|
||||||
|
|
||||||
|
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
|
||||||
|
if ($this->company_gateway->require_client_name) {
|
||||||
|
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->company_gateway->require_contact_name) {
|
||||||
|
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->company_gateway->require_contact_email) {
|
||||||
|
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->company_gateway->require_client_phone) {
|
||||||
|
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->company_gateway->require_billing_address) {
|
||||||
|
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
// $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable'];
|
||||||
|
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->company_gateway->require_shipping_address) {
|
||||||
|
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
// $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes'];
|
||||||
|
$fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
$fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
$fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
$fields[] = ['name' => 'client_shipping_country_id', 'label' => ctrans('texts.shipping_country'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
}
|
}
|
@ -27,141 +27,143 @@
|
|||||||
|
|
||||||
@section('gateway_footer')
|
@section('gateway_footer')
|
||||||
|
|
||||||
<script
|
@if($gateway->company_gateway->getConfigField('testMode'))
|
||||||
type="text/javascript"
|
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
|
||||||
src="https://sandbox.web.squarecdn.com/v1/square.js"
|
@else
|
||||||
></script>
|
<script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
|
||||||
<script>
|
@endif
|
||||||
const appId = "{{ $gateway->company_gateway->getConfigField('applicationId') }}";
|
|
||||||
const locationId = "{{ $gateway->company_gateway->getConfigField('locationId') }}";
|
<script>
|
||||||
|
const appId = "{{ $gateway->company_gateway->getConfigField('applicationId') }}";
|
||||||
|
const locationId = "{{ $gateway->company_gateway->getConfigField('locationId') }}";
|
||||||
|
|
||||||
const darkModeCardStyle = {
|
const darkModeCardStyle = {
|
||||||
'.input-container': {
|
'.input-container': {
|
||||||
borderColor: '#2D2D2D',
|
borderColor: '#2D2D2D',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
},
|
},
|
||||||
'.input-container.is-focus': {
|
'.input-container.is-focus': {
|
||||||
borderColor: '#006AFF',
|
borderColor: '#006AFF',
|
||||||
},
|
},
|
||||||
'.input-container.is-error': {
|
'.input-container.is-error': {
|
||||||
borderColor: '#ff1600',
|
borderColor: '#ff1600',
|
||||||
},
|
},
|
||||||
'.message-text': {
|
'.message-text': {
|
||||||
color: '#999999',
|
color: '#999999',
|
||||||
},
|
},
|
||||||
'.message-icon': {
|
'.message-icon': {
|
||||||
color: '#999999',
|
color: '#999999',
|
||||||
},
|
},
|
||||||
'.message-text.is-error': {
|
'.message-text.is-error': {
|
||||||
color: '#ff1600',
|
color: '#ff1600',
|
||||||
},
|
},
|
||||||
'.message-icon.is-error': {
|
'.message-icon.is-error': {
|
||||||
color: '#ff1600',
|
color: '#ff1600',
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: '#2D2D2D',
|
backgroundColor: '#2D2D2D',
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
fontFamily: 'helvetica neue, sans-serif',
|
fontFamily: 'helvetica neue, sans-serif',
|
||||||
},
|
},
|
||||||
'input::placeholder': {
|
'input::placeholder': {
|
||||||
color: '#999999',
|
color: '#999999',
|
||||||
},
|
},
|
||||||
'input.is-error': {
|
'input.is-error': {
|
||||||
color: '#ff1600',
|
color: '#ff1600',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function initializeCard(payments) {
|
async function initializeCard(payments) {
|
||||||
const card = await payments.card({
|
const card = await payments.card({
|
||||||
style: darkModeCardStyle,
|
style: darkModeCardStyle,
|
||||||
});
|
});
|
||||||
await card.attach('#card-container');
|
await card.attach('#card-container');
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tokenize(paymentMethod) {
|
async function tokenize(paymentMethod) {
|
||||||
const tokenResult = await paymentMethod.tokenize();
|
const tokenResult = await paymentMethod.tokenize();
|
||||||
if (tokenResult.status === 'OK') {
|
if (tokenResult.status === 'OK') {
|
||||||
return tokenResult.token;
|
return tokenResult.token;
|
||||||
} else {
|
} else {
|
||||||
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
||||||
if (tokenResult.errors) {
|
if (tokenResult.errors) {
|
||||||
errorMessage += ` and errors: ${JSON.stringify(
|
errorMessage += ` and errors: ${JSON.stringify(
|
||||||
tokenResult.errors
|
tokenResult.errors
|
||||||
)}`;
|
)}`;
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
// status is either SUCCESS or FAILURE;
|
statusContainer.style.visibility = 'visible';
|
||||||
function displayPaymentResults(status) {
|
}
|
||||||
|
|
||||||
|
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(
|
const statusContainer = document.getElementById(
|
||||||
'payment-status-container'
|
'payment-status-container'
|
||||||
);
|
);
|
||||||
if (status === 'SUCCESS') {
|
statusContainer.className = 'missing-credentials';
|
||||||
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';
|
statusContainer.style.visibility = 'visible';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
let card;
|
||||||
if (!window.Square) {
|
try {
|
||||||
throw new Error('Square.js failed to load properly');
|
card = await initializeCard(payments);
|
||||||
}
|
} catch (e) {
|
||||||
|
console.error('Initializing Card failed', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let payments;
|
async function handlePaymentMethodSubmission(event, paymentMethod) {
|
||||||
try {
|
event.preventDefault();
|
||||||
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 {
|
try {
|
||||||
card = await initializeCard(payments);
|
// 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) {
|
} catch (e) {
|
||||||
console.error('Initializing Card failed', e);
|
cardButton.disabled = false;
|
||||||
return;
|
displayPaymentResults('FAILURE');
|
||||||
|
console.error(e.message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePaymentMethodSubmission(event, paymentMethod) {
|
const cardButton = document.getElementById('pay-now');
|
||||||
event.preventDefault();
|
cardButton.addEventListener('click', async function (event) {
|
||||||
|
await handlePaymentMethodSubmission(event, card);
|
||||||
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>
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
@ -0,0 +1,171 @@
|
|||||||
|
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
|
||||||
|
=> ctrans('texts.payment_type_credit_card')])
|
||||||
|
|
||||||
|
@section('gateway_head')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('gateway_content')
|
||||||
|
|
||||||
|
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server_response">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="store_card">
|
||||||
|
<input type="text" name="sourceId" id="sourceId" hidden>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
|
|
||||||
|
@component('portal.ninja2020.components.general.card-element-single')
|
||||||
|
<div id="card-container"></div>
|
||||||
|
|
||||||
|
<div id="payment-status-container"></div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@component('portal.ninja2020.gateways.includes.pay_now')
|
||||||
|
{{ ctrans('texts.pay_now') }}
|
||||||
|
@endcomponent
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const appId = "{{ $gateway->company_gateway->getConfigField('applicationId') }}";
|
||||||
|
const locationId = "{{ $gateway->company_gateway->getConfigField('locationId') }}";
|
||||||
|
|
||||||
|
const darkModeCardStyle = {
|
||||||
|
'.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) {
|
||||||
|
const card = await payments.card({
|
||||||
|
style: darkModeCardStyle,
|
||||||
|
});
|
||||||
|
await card.attach('#card-container');
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tokenize(paymentMethod) {
|
||||||
|
const tokenResult = await paymentMethod.tokenize();
|
||||||
|
if (tokenResult.status === 'OK') {
|
||||||
|
return tokenResult.token;
|
||||||
|
} else {
|
||||||
|
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
||||||
|
if (tokenResult.errors) {
|
||||||
|
errorMessage += ` and errors: ${JSON.stringify(
|
||||||
|
tokenResult.errors
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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…
Reference in New Issue
Block a user