mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Square payments authorize
This commit is contained in:
parent
5c8976a50f
commit
19e9aac12b
@ -31,17 +31,17 @@ class CreditCard
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
|
|
||||||
public $square_class;
|
public $square_driver;
|
||||||
|
|
||||||
public function __construct(SquarePaymentDriver $square_class)
|
public function __construct(SquarePaymentDriver $square_driver)
|
||||||
{
|
{
|
||||||
$this->square_class = $square_class;
|
$this->square_driver = $square_driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorizeView($data)
|
public function authorizeView($data)
|
||||||
{
|
{
|
||||||
|
|
||||||
$data['gateway'] = $this->square_class;
|
$data['gateway'] = $this->square_driver;
|
||||||
|
|
||||||
return render('gateways.square.credit_card.authorize', $data);
|
return render('gateways.square.credit_card.authorize', $data);
|
||||||
|
|
||||||
@ -49,28 +49,21 @@ class CreditCard
|
|||||||
|
|
||||||
public function authorizeRequest($request)
|
public function authorizeRequest($request)
|
||||||
{
|
{
|
||||||
|
$amount_money = new \Square\Models\Money();
|
||||||
|
$amount_money->setAmount(100); //amount in cents
|
||||||
|
$amount_money->setCurrency($this->square_driver->client->currency()->code);
|
||||||
|
|
||||||
$billing_address = new \Square\Models\Address();
|
$body = new \Square\Models\CreatePaymentRequest(
|
||||||
$billing_address->setAddressLine1($this->square_class->client->address1);
|
$request->sourceId,
|
||||||
$billing_address->setAddressLine2($this->square_class->client->address2);
|
Str::random(32),
|
||||||
$billing_address->setLocality($this->square_class->client->city);
|
$amount_money
|
||||||
$billing_address->setAdministrativeDistrictLevel1($this->square_class->client->state);
|
|
||||||
$billing_address->setPostalCode($this->square_class->client->postal_code);
|
|
||||||
$billing_address->setCountry($this->square_class->client->country->iso_3166_2);
|
|
||||||
|
|
||||||
$card = new \Square\Models\Card();
|
|
||||||
$card->setCardholderName('Amelia Earhart');
|
|
||||||
$card->setBillingAddress($billing_address);
|
|
||||||
$card->setCustomerId('VDKXEEKPJN48QDG3BGGFAK05P8');
|
|
||||||
$card->setReferenceId('user-id-1');
|
|
||||||
|
|
||||||
$body = new \Square\Models\CreateCardRequest(
|
|
||||||
'4935a656-a929-4792-b97c-8848be85c27c',
|
|
||||||
'cnon:uIbfJXhXETSP197M3GB',
|
|
||||||
$card
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$api_response = $client->getCardsApi()->createCard($body);
|
$body->setAutocomplete(false);
|
||||||
|
$body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId'));
|
||||||
|
$body->setReferenceId(Str::random(16));
|
||||||
|
|
||||||
|
$api_response = $client->getPaymentsApi()->createPayment($body);
|
||||||
|
|
||||||
if ($api_response->isSuccess()) {
|
if ($api_response->isSuccess()) {
|
||||||
$result = $api_response->getResult();
|
$result = $api_response->getResult();
|
||||||
@ -79,13 +72,179 @@ class CreditCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Success response looks like this:
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"payment": {
|
||||||
|
"id": "Dv9xlBgSgVB8i6eT0imRYFjcrOaZY",
|
||||||
|
"created_at": "2021-03-31T20:56:13.220Z",
|
||||||
|
"updated_at": "2021-03-31T20:56:13.411Z",
|
||||||
|
"amount_money": {
|
||||||
|
"amount": 100,
|
||||||
|
"currency": "USD"
|
||||||
|
},
|
||||||
|
"status": "COMPLETED",
|
||||||
|
"delay_duration": "PT168H",
|
||||||
|
"source_type": "CARD",
|
||||||
|
"card_details": {
|
||||||
|
"status": "CAPTURED",
|
||||||
|
"card": {
|
||||||
|
"card_brand": "AMERICAN_EXPRESS",
|
||||||
|
"last_4": "6550",
|
||||||
|
"exp_month": 3,
|
||||||
|
"exp_year": 2023,
|
||||||
|
"fingerprint": "sq-1-hPdOWUYtEMft3yQ",
|
||||||
|
"card_type": "CREDIT",
|
||||||
|
"prepaid_type": "NOT_PREPAID",
|
||||||
|
"bin": "371263"
|
||||||
|
},
|
||||||
|
"entry_method": "KEYED",
|
||||||
|
"cvv_status": "CVV_ACCEPTED",
|
||||||
|
"avs_status": "AVS_ACCEPTED",
|
||||||
|
"statement_description": "SQ *DEFAULT TEST ACCOUNT",
|
||||||
|
"card_payment_timeline": {
|
||||||
|
"authorized_at": "2021-03-31T20:56:13.334Z",
|
||||||
|
"captured_at": "2021-03-31T20:56:13.411Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"location_id": "VJN4XSBFTVPK9",
|
||||||
|
"total_money": {
|
||||||
|
"amount": 100,
|
||||||
|
"currency": "USD"
|
||||||
|
},
|
||||||
|
"approved_money": {
|
||||||
|
"amount": 100,
|
||||||
|
"currency": "USD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
$billing_address = new \Square\Models\Address();
|
||||||
|
$billing_address->setAddressLine1($this->square_driver->client->address1);
|
||||||
|
$billing_address->setAddressLine2($this->square_driver->client->address2);
|
||||||
|
$billing_address->setLocality($this->square_driver->client->city);
|
||||||
|
$billing_address->setAdministrativeDistrictLevel1($this->square_driver->client->state);
|
||||||
|
$billing_address->setPostalCode($this->square_driver->client->postal_code);
|
||||||
|
$billing_address->setCountry($this->square_driver->client->country->iso_3166_2);
|
||||||
|
|
||||||
|
$body = new \Square\Models\CreateCustomerRequest();
|
||||||
|
$body->setGivenName($this->square_driver->client->present()->name());
|
||||||
|
$body->setFamilyName('');
|
||||||
|
$body->setEmailAddress($this->square_driver->client->present()->email());
|
||||||
|
$body->setAddress($address);
|
||||||
|
$body->setPhoneNumber($this->square_driver->client->phone);
|
||||||
|
$body->setReferenceId($this->square_driver->client->number);
|
||||||
|
$body->setNote('Created by Invoice Ninja.');
|
||||||
|
|
||||||
|
$api_response = $this->square_driver
|
||||||
|
->square
|
||||||
|
->getCustomersApi()
|
||||||
|
->createCustomer($body);
|
||||||
|
|
||||||
|
if ($api_response->isSuccess()) {
|
||||||
|
$result = $api_response->getResult();
|
||||||
|
} else {
|
||||||
|
$errors = $api_response->getErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Customer now created response
|
||||||
|
|
||||||
|
{
|
||||||
|
"customer": {
|
||||||
|
"id": "Q6VKKKGW8GWQNEYMDRMV01QMK8",
|
||||||
|
"created_at": "2021-03-31T18:27:07.803Z",
|
||||||
|
"updated_at": "2021-03-31T18:27:07Z",
|
||||||
|
"given_name": "Amelia",
|
||||||
|
"family_name": "Earhart",
|
||||||
|
"email_address": "Amelia.Earhart@example.com",
|
||||||
|
"preferences": {
|
||||||
|
"email_unsubscribed": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
$card = new \Square\Models\Card();
|
||||||
|
$card->setCardholderName($this->square_driver->client->present()->name());
|
||||||
|
$card->setBillingAddress($address);
|
||||||
|
$card->setCustomerId($result->customer->id);
|
||||||
|
$card->setReferenceId(Str::random(8));
|
||||||
|
|
||||||
|
$body = new \Square\Models\CreateCardRequest(
|
||||||
|
Str::random(32),
|
||||||
|
$request->sourceId,
|
||||||
|
$card
|
||||||
|
);
|
||||||
|
|
||||||
|
$api_response = $client->getCardsApi()->createCard($body);
|
||||||
|
|
||||||
|
if ($api_response->isSuccess()) {
|
||||||
|
$result = $api_response->getResult();
|
||||||
|
} else {
|
||||||
|
$errors = $api_response->getErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
{
|
||||||
|
"card": {
|
||||||
|
"id": "ccof:uIbfJXhXETSP197M3GB", //this is the token
|
||||||
|
"billing_address": {
|
||||||
|
"address_line_1": "500 Electric Ave",
|
||||||
|
"address_line_2": "Suite 600",
|
||||||
|
"locality": "New York",
|
||||||
|
"administrative_district_level_1": "NY",
|
||||||
|
"postal_code": "10003",
|
||||||
|
"country": "US"
|
||||||
|
},
|
||||||
|
"bin": "411111",
|
||||||
|
"card_brand": "VISA",
|
||||||
|
"card_type": "CREDIT",
|
||||||
|
"cardholder_name": "Amelia Earhart",
|
||||||
|
"customer_id": "Q6VKKKGW8GWQNEYMDRMV01QMK8",
|
||||||
|
"enabled": true,
|
||||||
|
"exp_month": 11,
|
||||||
|
"exp_year": 2018,
|
||||||
|
"last_4": "1111",
|
||||||
|
"prepaid_type": "NOT_PREPAID",
|
||||||
|
"reference_id": "user-id-1",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$cgt = [];
|
||||||
|
$cgt['token'] = $result->card->id;
|
||||||
|
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
$payment_meta = new \stdClass;
|
||||||
|
$payment_meta->exp_month = $result->card->exp_month;
|
||||||
|
$payment_meta->exp_year = $result->card->exp_year;
|
||||||
|
$payment_meta->brand = $result->card->card_brand;
|
||||||
|
$payment_meta->last4 = $result->card->last_4;
|
||||||
|
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
$cgt['payment_meta'] = $payment_meta;
|
||||||
|
|
||||||
|
$token = $this->square_driver->storeGatewayToken($cgt, []);
|
||||||
|
|
||||||
|
|
||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paymentView($data)
|
public function paymentView($data)
|
||||||
{
|
{
|
||||||
|
|
||||||
$data['gateway'] = $this->square_class;
|
$data['gateway'] = $this->square_driver;
|
||||||
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
|
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
|
||||||
|
|
||||||
return render('gateways.braintree.credit_card.pay', $data);
|
return render('gateways.braintree.credit_card.pay', $data);
|
||||||
@ -100,7 +259,7 @@ class CreditCard
|
|||||||
/* 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' */
|
||||||
private function processSuccessfulPayment($response)
|
private function processSuccessfulPayment($response)
|
||||||
{
|
{
|
||||||
$amount = array_sum(array_column($this->square_class->payment_hash->invoices(), 'amount')) + $this->square_class->payment_hash->fee_total;
|
$amount = array_sum(array_column($this->square_driver->payment_hash->invoices(), 'amount')) + $this->square_driver->payment_hash->fee_total;
|
||||||
|
|
||||||
$payment_record = [];
|
$payment_record = [];
|
||||||
$payment_record['amount'] = $amount;
|
$payment_record['amount'] = $amount;
|
||||||
@ -108,7 +267,7 @@ class CreditCard
|
|||||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||||
// $payment_record['transaction_reference'] = $response->transaction_id;
|
// $payment_record['transaction_reference'] = $response->transaction_id;
|
||||||
|
|
||||||
$payment = $this->square_class->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
$payment = $this->square_driver->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||||
|
|
||||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||||
|
|
||||||
@ -130,7 +289,7 @@ class CreditCard
|
|||||||
'error_code' => $error_code,
|
'error_code' => $error_code,
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->square_class->processUnsuccessfulTransaction($data);
|
return $this->square_driver->processUnsuccessfulTransaction($data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,54 +8,168 @@
|
|||||||
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
|
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
|
||||||
method="post" id="server_response">
|
method="post" id="server_response">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="txt" id=HPF_Token name= HPF_Token hidden>
|
<input type="text" name="sourceId" hidden>
|
||||||
<input type="txt" id=enc_key name= enc_key hidden>
|
|
||||||
<input type="text" name="token" hidden>
|
|
||||||
|
|
||||||
|
|
||||||
<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-single')
|
@component('portal.ninja2020.components.general.card-element-single')
|
||||||
<div id="card-container"></div>
|
<div id="card-container"></div>
|
||||||
<button id="card-button" type="button">Add</button>
|
|
||||||
<div id="payment-status-container"></div>
|
<div id="payment-status-container"></div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|
||||||
|
@component('portal.ninja2020.gateways.includes.pay_now')
|
||||||
|
{{ ctrans('texts.add_payment_method') }}
|
||||||
|
@endcomponent
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('gateway_footer')
|
@section('gateway_footer')
|
||||||
|
|
||||||
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>.
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://sandbox.web.squarecdn.com/v1/square.js"
|
||||||
|
></script>
|
||||||
<script>
|
<script>
|
||||||
const appId = "{{ $gateway->company_gateway->getConfigField('applicationId') }}";
|
const appId = "{{ $gateway->company_gateway->getConfigField('applicationId') }}";
|
||||||
const locationId = "{{ $gateway->company_gateway->getConfigField('locationId') }}";
|
const locationId = "{{ $gateway->company_gateway->getConfigField('locationId') }}";
|
||||||
|
|
||||||
async function initializeCard(payments) {
|
const darkModeCardStyle = {
|
||||||
const card = await payments.card();
|
'.input-container': {
|
||||||
await card.attach('#card-container');
|
borderColor: '#2D2D2D',
|
||||||
return card;
|
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',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
async function initializeCard(payments) {
|
||||||
|
const card = await payments.card({
|
||||||
if (!window.Square) {
|
style: darkModeCardStyle,
|
||||||
throw new Error('Square.js failed to load properly');
|
|
||||||
}
|
|
||||||
|
|
||||||
const payments = window.Square.payments(appId, locationId);
|
|
||||||
let card;
|
|
||||||
|
|
||||||
try {
|
|
||||||
card = await initializeCard(payments);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Initializing Card failed', e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5.2: create card payment
|
|
||||||
});
|
});
|
||||||
|
await card.attach('#card-container');
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPayment(token) {
|
||||||
|
|
||||||
|
document.getElementById('sourceId').value = token;
|
||||||
|
document.getElementById('server_response').submit();
|
||||||
|
|
||||||
|
const errorBody = await paymentResponse.text();
|
||||||
|
throw new Error(errorBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
const paymentResults = await createPayment(token);
|
||||||
|
displayPaymentResults('SUCCESS');
|
||||||
|
|
||||||
|
console.debug('Payment Success', paymentResults);
|
||||||
|
} 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
|
Loading…
x
Reference in New Issue
Block a user