From 86e8533e5957b8ab33891afe8394bcff8d66736f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 14 Aug 2021 18:11:45 +1000 Subject: [PATCH 01/34] Square stubs --- app/Models/SystemLog.php | 3 +- app/PaymentDrivers/Square/CreditCard.php | 153 +++++++++++++++ app/PaymentDrivers/SquarePaymentDriver.php | 105 +++++++++++ composer.json | 1 + composer.lock | 175 +++++++++++++++++- ...021_08_14_054458_square_payment_driver.php | 49 +++++ 6 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 app/PaymentDrivers/Square/CreditCard.php create mode 100644 app/PaymentDrivers/SquarePaymentDriver.php create mode 100644 database/migrations/2021_08_14_054458_square_payment_driver.php diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index ab3d8cac8b68..1340bb4cebbe 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -70,7 +70,8 @@ class SystemLog extends Model const TYPE_PAYFAST = 310; const TYPE_PAYTRACE = 311; const TYPE_MOLLIE = 312; - + const TYPE_SQUARE = 320; + const TYPE_QUOTA_EXCEEDED = 400; const TYPE_UPSTREAM_FAILURE = 401; diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php new file mode 100644 index 000000000000..f83da6137fa6 --- /dev/null +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -0,0 +1,153 @@ +square_class = $square_class; + } + + public function authorizeView($data) + { + + $data['gateway'] = $this->square_class; + + return render('gateways.square.credit_card.authorize', $data); + + } + + public function authorizeRequest($request) + { + + $billing_address = new \Square\Models\Address(); + $billing_address->setAddressLine1($this->square_class->client->address1); + $billing_address->setAddressLine2($this->square_class->client->address2); + $billing_address->setLocality($this->square_class->client->city); + $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); + + if ($api_response->isSuccess()) { + $result = $api_response->getResult(); + } else { + $errors = $api_response->getErrors(); + } + + + return back(); + } + + public function paymentView($data) + { + + $data['gateway'] = $this->square_class; + $data['client_token'] = $this->braintree->gateway->clientToken()->generate(); + + return render('gateways.braintree.credit_card.pay', $data); + + } + + public function processPaymentResponse($request) + { + + } + + /* This method is stubbed ready to go - you just need to harvest the equivalent 'transaction_reference' */ + private function processSuccessfulPayment($response) + { + $amount = array_sum(array_column($this->square_class->payment_hash->invoices(), 'amount')) + $this->square_class->payment_hash->fee_total; + + $payment_record = []; + $payment_record['amount'] = $amount; + $payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER; + $payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD; + // $payment_record['transaction_reference'] = $response->transaction_id; + + $payment = $this->square_class->createPayment($payment_record, Payment::STATUS_COMPLETED); + + return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); + + } + + private function processUnsuccessfulPayment($response) + { + /*Harvest your own errors here*/ + // $error = $response->status_message; + + // if(property_exists($response, 'approval_message') && $response->approval_message) + // $error .= " - {$response->approval_message}"; + + // $error_code = property_exists($response, 'approval_message') ? $response->approval_message : 'Undefined code'; + + $data = [ + 'response' => $response, + 'error' => $error, + 'error_code' => $error_code, + ]; + + return $this->square_class->processUnsuccessfulTransaction($data); + + } + + + /* Helpers */ + + /* + You will need some helpers to handle successful and unsuccessful responses + + Some considerations after a succesful transaction include: + + Logging of events: success +/- failure + Recording a payment + Notifications + */ + + + + +} \ No newline at end of file diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php new file mode 100644 index 000000000000..10647bf54992 --- /dev/null +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -0,0 +1,105 @@ + CreditCard::class, //maps GatewayType => Implementation class + ]; + + const SYSTEM_LOG_TYPE = SystemLog::TYPE_SQUARE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model + + public function init() + { + + $this->square = new Square\SquareClient([ + 'accessToken' => 'EAAAEHeoSxEUZWXCd0makP0-HA0V4OLZ-S-T2Gmc91llp08ColiOX9NpP-LQZIId', + 'environment' => Square\Environment::SANDBOX, + ]); + + return $this; /* This is where you boot the gateway with your auth credentials*/ + } + + /* Returns an array of gateway types for the payment gateway */ + public function gatewayTypes(): array + { + $types = []; + + $types[] = GatewayType::CREDIT_CARD; + + return $types; + } + + /* Sets the payment method initialized */ + public function setPaymentMethod($payment_method_id) + { + $class = self::$methods[$payment_method_id]; + $this->payment_method = new $class($this); + return $this; + } + + public function authorizeView(array $data) + { + return $this->payment_method->authorizeView($data); //this is your custom implementation from here + } + + public function authorizeResponse($request) + { + return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here + } + + public function processPaymentView(array $data) + { + return $this->payment_method->paymentView($data); //this is your custom implementation from here + } + + public function processPaymentResponse($request) + { + return $this->payment_method->paymentResponse($request); //this is your custom implementation from here + } + + public function refund(Payment $payment, $amount, $return_client_response = false) + { + //this is your custom implementation from here + } + + public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) + { + //this is your custom implementation from here + } + + public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) + { + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 8595adacbbc3..41d1d8b1692c 100644 --- a/composer.json +++ b/composer.json @@ -69,6 +69,7 @@ "pragmarx/google2fa": "^8.0", "predis/predis": "^1.1", "sentry/sentry-laravel": "^2", + "square/square": "13.0.0.20210721", "stripe/stripe-php": "^7.50", "symfony/http-client": "^5.2", "tijsverkoyen/css-to-inline-styles": "^2.2", diff --git a/composer.lock b/composer.lock index 89eea29d9e7b..4a0615b198f0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,122 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "275a9dd3910b6ec79607b098406dc6c7", + "content-hash": "93253273cd8399a0e083a064160b70bf", "packages": [ + { + "name": "apimatic/jsonmapper", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/apimatic/jsonmapper.git", + "reference": "f7588f1ab692c402a9118e65cb9fd42b74e5e0db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/apimatic/jsonmapper/zipball/f7588f1ab692c402a9118e65cb9fd42b74e5e0db", + "reference": "f7588f1ab692c402a9118e65cb9fd42b74e5e0db", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "squizlabs/php_codesniffer": "^3.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "apimatic\\jsonmapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "christian.weiske@netresearch.de", + "homepage": "http://www.netresearch.de/", + "role": "Developer" + }, + { + "name": "Mehdi Jaffery", + "email": "mehdi.jaffery@apimatic.io", + "homepage": "http://apimatic.io/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "mehdi.jaffery@apimatic.io", + "issues": "https://github.com/apimatic/jsonmapper/issues", + "source": "https://github.com/apimatic/jsonmapper/tree/v2.0.3" + }, + "time": "2021-07-16T09:02:23+00:00" + }, + { + "name": "apimatic/unirest-php", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/apimatic/unirest-php.git", + "reference": "b4e399a8970c3a5c611f734282f306381f9d1eee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/apimatic/unirest-php/zipball/b4e399a8970c3a5c611f734282f306381f9d1eee", + "reference": "b4e399a8970c3a5c611f734282f306381f9d1eee", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5 || ^6 || ^7" + }, + "suggest": { + "ext-json": "Allows using JSON Bodies for sending and parsing requests" + }, + "type": "library", + "autoload": { + "psr-0": { + "Unirest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mashape", + "email": "opensource@mashape.com", + "homepage": "https://www.mashape.com", + "role": "Developer" + }, + { + "name": "APIMATIC", + "email": "opensource@apimatic.io", + "homepage": "https://www.apimatic.io", + "role": "Developer" + } + ], + "description": "Unirest PHP", + "homepage": "https://github.com/apimatic/unirest-php", + "keywords": [ + "client", + "curl", + "http", + "https", + "rest" + ], + "support": { + "email": "opensource@apimatic.io", + "issues": "https://github.com/apimatic/unirest-php/issues", + "source": "https://github.com/apimatic/unirest-php/tree/2.0.0" + }, + "time": "2020-04-07T17:16:29+00:00" + }, { "name": "asm/php-ansible", "version": "dev-main", @@ -7459,6 +7573,63 @@ ], "time": "2021-06-16T09:26:40+00:00" }, + { + "name": "square/square", + "version": "13.0.0.20210721", + "source": { + "type": "git", + "url": "https://github.com/square/square-php-sdk.git", + "reference": "03d90445854cd3b500f75061a9c63956799b8ecf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/square/square-php-sdk/zipball/03d90445854cd3b500f75061a9c63956799b8ecf", + "reference": "03d90445854cd3b500f75061a9c63956799b8ecf", + "shasum": "" + }, + "require": { + "apimatic/jsonmapper": "^2.0.2", + "apimatic/unirest-php": "^2.0", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.2" + }, + "require-dev": { + "phan/phan": "^3.0", + "phpunit/phpunit": "^7.5 || ^8.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Square\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Square Developer Platform", + "email": "developers@squareup.com", + "homepage": "https://squareup.com/developers" + } + ], + "description": "Use Square APIs to manage and run business including payment, customer, product, inventory, and employee management.", + "homepage": "https://squareup.com/developers", + "keywords": [ + "api", + "sdk", + "square" + ], + "support": { + "issues": "https://github.com/square/square-php-sdk/issues", + "source": "https://github.com/square/square-php-sdk/tree/13.0.0.20210721" + }, + "time": "2021-07-21T06:43:15+00:00" + }, { "name": "stripe/stripe-php", "version": "v7.88.0", @@ -14972,5 +15143,5 @@ "platform-dev": { "php": "^7.3|^7.4|^8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } diff --git a/database/migrations/2021_08_14_054458_square_payment_driver.php b/database/migrations/2021_08_14_054458_square_payment_driver.php new file mode 100644 index 000000000000..7da0bb5949fa --- /dev/null +++ b/database/migrations/2021_08_14_054458_square_payment_driver.php @@ -0,0 +1,49 @@ +accessToken = ""; + $fields->applicationId = ""; + $fields->locationId = ""; + $fields->testMode = false; + + $square = new Gateway(); + $square->id = 57; + $square->name = "Square"; + $square->provider = "Square"; + $square->key = '65faab2ab6e3223dbe848b1686490baz'; + $square->sort_order = 4343; + $square->is_offsite = false; + $square->visible = true; + $square->fields = json_encode($fields); + $square->save(); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} From 77c072fd10266ba83f7d12aad05f88e17cd1626f Mon Sep 17 00:00:00 2001 From: = Date: Sat, 14 Aug 2021 19:32:16 +1000 Subject: [PATCH 02/34] Square payments --- app/Console/Commands/CreateSingleAccount.php | 21 +++++++ app/Models/Gateway.php | 11 +++- app/PaymentDrivers/SquarePaymentDriver.php | 10 ++-- config/ninja.php | 1 + database/seeders/PaymentLibrariesSeeder.php | 3 +- .../square/credit_card/authorize.blade.php | 57 +++++++++++++++++++ 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index ca7201960326..cb56c2e60019 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -787,6 +787,27 @@ class CreateSingleAccount extends Command $cg->fees_and_limits = $fees_and_limits; $cg->save(); } + + if (config('ninja.testvars.square') && ($this->gateway == 'all' || $this->gateway == 'square')) { + $cg = new CompanyGateway; + $cg->company_id = $company->id; + $cg->user_id = $user->id; + $cg->gateway_key = '65faab2ab6e3223dbe848b1686490baz'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt(config('ninja.testvars.square')); + $cg->save(); + + $gateway_types = $cg->driver(new Client)->gatewayTypes(); + + $fees_and_limits = new stdClass; + $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } } private function createRecurringInvoice($client) diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 424d3b843486..573ac39c5f8f 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -81,9 +81,13 @@ class Gateway extends StaticModel case 1: return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]];//Authorize.net break; - case 1: + case 11: return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast break; + case 7: + return [ + GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie + ]; case 15: return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal break; @@ -110,11 +114,12 @@ class Gateway extends StaticModel GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true] ]; break; - case 7: + case 57: return [ - GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie + GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Square ]; break; + break; default: return []; break; diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 10647bf54992..d500e420f9ef 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -30,22 +30,22 @@ class SquarePaymentDriver extends BaseDriver public $can_authorise_credit_card = true; //does this gateway support authorizations? - public $square; //initialized gateway + public $square; - public $payment_method; //initialized payment method + public $payment_method; public static $methods = [ GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class ]; - const SYSTEM_LOG_TYPE = SystemLog::TYPE_SQUARE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model + const SYSTEM_LOG_TYPE = SystemLog::TYPE_SQUARE; public function init() { $this->square = new Square\SquareClient([ - 'accessToken' => 'EAAAEHeoSxEUZWXCd0makP0-HA0V4OLZ-S-T2Gmc91llp08ColiOX9NpP-LQZIId', - 'environment' => Square\Environment::SANDBOX, + 'accessToken' => $this->company_gateway->getConfigField('accessToken'), + 'environment' => $this->company_gateway->getConfigField('testMode') ? Square\Environment::SANDBOX : Square\Environment::PRODUCTION, ]); return $this; /* This is where you boot the gateway with your auth credentials*/ diff --git a/config/ninja.php b/config/ninja.php index eb3f54f5b066..321ec8e009f6 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -90,6 +90,7 @@ return [ 'decrypted' => env('PAYTRACE_KEYS', ''), ], 'mollie' => env('MOLLIE_KEYS', ''), + 'square' => env('SQUARE_KEYS',''), ], 'contact' => [ 'email' => env('MAIL_FROM_ADDRESS'), diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php index c74ab2940ae8..df2ded688cc1 100644 --- a/database/seeders/PaymentLibrariesSeeder.php +++ b/database/seeders/PaymentLibrariesSeeder.php @@ -80,6 +80,7 @@ class PaymentLibrariesSeeder extends Seeder ['id' => 53, 'name' => 'PagSeguro', 'provider' => 'PagSeguro', 'key' => 'ef498756b54db63c143af0ec433da803', 'fields' => '{"email":"","token":"","sandbox":false}'], ['id' => 54, 'name' => 'PAYMILL', 'provider' => 'Paymill', 'key' => 'ca52f618a39367a4c944098ebf977e1c', 'fields' => '{"apiKey":""}'], ['id' => 55, 'name' => 'Custom', 'provider' => 'Custom', 'is_offsite' => true, 'sort_order' => 21, 'key' => '54faab2ab6e3223dbe848b1686490baa', 'fields' => '{"name":"","text":""}'], + ['id' => 57, 'name' => 'Square', 'provider' => 'Square', 'is_offsite' => false, 'sort_order' => 21, 'key' => '65faab2ab6e3223dbe848b1686490baz', 'fields' => '{"accessToken":"","applicationId":"","locationId":"","testMode":"false"}'], ]; foreach ($gateways as $gateway) { @@ -96,7 +97,7 @@ class PaymentLibrariesSeeder extends Seeder Gateway::query()->update(['visible' => 0]); - Gateway::whereIn('id', [1,7,15,20,39,46,55,50])->update(['visible' => 1]); + Gateway::whereIn('id', [1,7,15,20,39,46,55,50,57])->update(['visible' => 1]); if (Ninja::isHosted()) { Gateway::whereIn('id', [20])->update(['visible' => 0]); diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php new file mode 100644 index 000000000000..e3a431097476 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -0,0 +1,57 @@ +@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') +
+ @csrf + + + + + + + + @component('portal.ninja2020.components.general.card-element-single') +
+ +
+ +
+ @endcomponent + +@endsection + +@section('gateway_footer') + +. + + + @endsection \ No newline at end of file From 3119efa3da0353e9c3d8ca0f8ebed37668eb5aa0 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 14 Aug 2021 20:07:27 +1000 Subject: [PATCH 03/34] Square payments --- .../ninja2020/gateways/square/credit_card/authorize.blade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index e3a431097476..906608b1e461 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -31,6 +31,7 @@ . @endsection \ No newline at end of file From 19e9aac12bcd93a51a827c8e481cf69bd732cb8b Mon Sep 17 00:00:00 2001 From: = Date: Sat, 14 Aug 2021 21:00:52 +1000 Subject: [PATCH 06/34] Square payments authorize --- app/PaymentDrivers/Square/CreditCard.php | 213 +++++++++++++++--- .../square/credit_card/authorize.blade.php | 174 +++++++++++--- 2 files changed, 330 insertions(+), 57 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index f83da6137fa6..89f8e275c8db 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -31,17 +31,17 @@ class CreditCard { 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) { - $data['gateway'] = $this->square_class; + $data['gateway'] = $this->square_driver; return render('gateways.square.credit_card.authorize', $data); @@ -49,28 +49,21 @@ class CreditCard 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(); - $billing_address->setAddressLine1($this->square_class->client->address1); - $billing_address->setAddressLine2($this->square_class->client->address2); - $billing_address->setLocality($this->square_class->client->city); - $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 + $body = new \Square\Models\CreatePaymentRequest( + $request->sourceId, + Str::random(32), + $amount_money ); - $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()) { $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(); } public function paymentView($data) { - $data['gateway'] = $this->square_class; + $data['gateway'] = $this->square_driver; $data['client_token'] = $this->braintree->gateway->clientToken()->generate(); 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' */ 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['amount'] = $amount; @@ -108,7 +267,7 @@ class CreditCard $payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD; // $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)]); @@ -130,7 +289,7 @@ class CreditCard 'error_code' => $error_code, ]; - return $this->square_class->processUnsuccessfulTransaction($data); + return $this->square_driver->processUnsuccessfulTransaction($data); } diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index 27d7fc0a1942..81a7a32addd1 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -8,54 +8,168 @@
@csrf - - - + @component('portal.ninja2020.components.general.card-element-single') -
- +
+
@endcomponent + @component('portal.ninja2020.gateways.includes.pay_now') + {{ ctrans('texts.add_payment_method') }} + @endcomponent @endsection @section('gateway_footer') -. + @endsection \ No newline at end of file From 27cdfd24f119b76da7e10bc43080183a7f4525b0 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 14 Aug 2021 21:37:04 +1000 Subject: [PATCH 07/34] Authorize Credit Card --- app/PaymentDrivers/Square/CreditCard.php | 58 ++++++++++--------- app/PaymentDrivers/SquarePaymentDriver.php | 4 +- .../square/credit_card/authorize.blade.php | 20 ++----- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 89f8e275c8db..052480e1eff4 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -36,6 +36,7 @@ class CreditCard public function __construct(SquarePaymentDriver $square_driver) { $this->square_driver = $square_driver; + $this->square_driver->init(); } public function authorizeView($data) @@ -47,7 +48,7 @@ class CreditCard } - public function authorizeRequest($request) + public function authorizeResponse($request) { $amount_money = new \Square\Models\Money(); $amount_money->setAmount(100); //amount in cents @@ -63,12 +64,14 @@ class CreditCard $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); $body->setReferenceId(Str::random(16)); - $api_response = $client->getPaymentsApi()->createPayment($body); + $api_response = $this->square_driver->square->getPaymentsApi()->createPayment($body); if ($api_response->isSuccess()) { $result = $api_response->getResult(); + nlog($result); } else { $errors = $api_response->getErrors(); + nlog($errors); } @@ -135,7 +138,7 @@ Success response looks like this: $body->setGivenName($this->square_driver->client->present()->name()); $body->setFamilyName(''); $body->setEmailAddress($this->square_driver->client->present()->email()); - $body->setAddress($address); + $body->setAddress($billing_address); $body->setPhoneNumber($this->square_driver->client->phone); $body->setReferenceId($this->square_driver->client->number); $body->setNote('Created by Invoice Ninja.'); @@ -147,8 +150,10 @@ Success response looks like this: if ($api_response->isSuccess()) { $result = $api_response->getResult(); + nlog($result); } else { $errors = $api_response->getErrors(); + nlog($errors); } /*Customer now created response @@ -170,25 +175,28 @@ Success response looks like this: */ -$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)); + $card = new \Square\Models\Card(); + $card->setCardholderName($this->square_driver->client->present()->name()); + $card->setBillingAddress($billing_address); + $card->setCustomerId($result->getCustomer()->getId()); + $card->setReferenceId(Str::random(8)); -$body = new \Square\Models\CreateCardRequest( - Str::random(32), - $request->sourceId, - $card -); + $body = new \Square\Models\CreateCardRequest( + Str::random(32), + $request->sourceId, + $card + ); -$api_response = $client->getCardsApi()->createCard($body); + $api_response = $this->square_driver + ->square + ->getCardsApi() + ->createCard($body); -if ($api_response->isSuccess()) { - $result = $api_response->getResult(); -} else { - $errors = $api_response->getErrors(); -} + if ($api_response->isSuccess()) { + $result = $api_response->getResult(); + } else { + $errors = $api_response->getErrors(); + } /** * @@ -220,17 +228,15 @@ if ($api_response->isSuccess()) { */ - - $cgt = []; - $cgt['token'] = $result->card->id; + $cgt['token'] = $result->getCard()->getId(); $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->exp_month = $result->getCard()->getExpMonth(); + $payment_meta->exp_year = $result->getCard()->getExpYear(); + $payment_meta->brand = $result->getCard()->getCardBrand(); + $payment_meta->last4 = $result->getCard()->getLast4(); $payment_meta->type = GatewayType::CREDIT_CARD; $cgt['payment_meta'] = $payment_meta; diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index d500e420f9ef..d3c5ab247115 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -43,9 +43,9 @@ class SquarePaymentDriver extends BaseDriver public function init() { - $this->square = new Square\SquareClient([ + $this->square = new \Square\SquareClient([ 'accessToken' => $this->company_gateway->getConfigField('accessToken'), - 'environment' => $this->company_gateway->getConfigField('testMode') ? Square\Environment::SANDBOX : Square\Environment::PRODUCTION, + 'environment' => $this->company_gateway->getConfigField('testMode') ? \Square\Environment::SANDBOX : \Square\Environment::PRODUCTION, ]); return $this; /* This is where you boot the gateway with your auth credentials*/ diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index 81a7a32addd1..7bd9f85d4f56 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -8,13 +8,12 @@
@csrf - + - @component('portal.ninja2020.components.general.card-element-single') -
+
@@ -81,15 +80,6 @@ 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') { @@ -154,10 +144,12 @@ // 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); + + document.getElementById('sourceId').value = token; + document.getElementById('server_response').submit(); + displayPaymentResults('SUCCESS'); - console.debug('Payment Success', paymentResults); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); From 6cf3ef9b77a5229242f3aa94a415230cd5bf00ff Mon Sep 17 00:00:00 2001 From: = Date: Sat, 14 Aug 2021 22:17:35 +1000 Subject: [PATCH 08/34] Square authorize --- app/PaymentDrivers/Square/CreditCard.php | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 052480e1eff4..76b3ca63ad5e 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -50,6 +50,8 @@ class CreditCard public function authorizeResponse($request) { + $payment = false; + $amount_money = new \Square\Models\Money(); $amount_money->setAmount(100); //amount in cents $amount_money->setCurrency($this->square_driver->client->currency()->code); @@ -67,8 +69,11 @@ class CreditCard $api_response = $this->square_driver->square->getPaymentsApi()->createPayment($body); if ($api_response->isSuccess()) { - $result = $api_response->getResult(); - nlog($result); + // $result = $api_response->getResult(); + + $result = $api_response->getBody(); + $payment = json_decode($result); + } else { $errors = $api_response->getErrors(); nlog($errors); @@ -183,7 +188,7 @@ Success response looks like this: $body = new \Square\Models\CreateCardRequest( Str::random(32), - $request->sourceId, + $payment->payment->id, $card ); @@ -194,8 +199,13 @@ Success response looks like this: if ($api_response->isSuccess()) { $result = $api_response->getResult(); + nlog($result->getBody()); + nlog("ressy"); + nlog($result); } else { $errors = $api_response->getErrors(); + nlog("i got errors"); + nlog($errors); } /** @@ -229,14 +239,14 @@ Success response looks like this: */ $cgt = []; - $cgt['token'] = $result->getCard()->getId(); + $cgt['token'] = $result->getId(); $cgt['payment_method_id'] = GatewayType::CREDIT_CARD; $payment_meta = new \stdClass; - $payment_meta->exp_month = $result->getCard()->getExpMonth(); - $payment_meta->exp_year = $result->getCard()->getExpYear(); - $payment_meta->brand = $result->getCard()->getCardBrand(); - $payment_meta->last4 = $result->getCard()->getLast4(); + $payment_meta->exp_month = $result->getExpMonth(); + $payment_meta->exp_year = $result->getExpYear(); + $payment_meta->brand = $result->getCardBrand(); + $payment_meta->last4 = $result->getLast4(); $payment_meta->type = GatewayType::CREDIT_CARD; $cgt['payment_meta'] = $payment_meta; From 60dd8d3b6cfe179a90f232b90f4ed5f663c6c1ee Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Aug 2021 20:27:52 +1000 Subject: [PATCH 09/34] Add Payment Method --- app/PaymentDrivers/Square/CreditCard.php | 25 ++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 76b3ca63ad5e..495317a84fdd 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -73,6 +73,7 @@ class CreditCard $result = $api_response->getBody(); $payment = json_decode($result); + nlog($payment); } else { $errors = $api_response->getErrors(); @@ -178,7 +179,8 @@ Success response looks like this: } */ - + nlog("customer id = ".$result->getCustomer()->getId()); + nlog("source_id = ".$payment->payment->id); $card = new \Square\Models\Card(); $card->setCardholderName($this->square_driver->client->present()->name()); @@ -197,9 +199,13 @@ Success response looks like this: ->getCardsApi() ->createCard($body); + $card = false; + if ($api_response->isSuccess()) { - $result = $api_response->getResult(); - nlog($result->getBody()); + $card = $api_response->getBody(); + nlog($card); + $card = json_decode($card); + nlog("ressy"); nlog($result); } else { @@ -239,22 +245,21 @@ Success response looks like this: */ $cgt = []; - $cgt['token'] = $result->getId(); + $cgt['token'] = $card->card->id; $cgt['payment_method_id'] = GatewayType::CREDIT_CARD; $payment_meta = new \stdClass; - $payment_meta->exp_month = $result->getExpMonth(); - $payment_meta->exp_year = $result->getExpYear(); - $payment_meta->brand = $result->getCardBrand(); - $payment_meta->last4 = $result->getLast4(); + $payment_meta->exp_month = $card->card->exp_month; + $payment_meta->exp_year = $card->card->exp_year; + $payment_meta->brand = $card->card->card_brand; + $payment_meta->last4 = $card->card->last_4; $payment_meta->type = GatewayType::CREDIT_CARD; $cgt['payment_meta'] = $payment_meta; $token = $this->square_driver->storeGatewayToken($cgt, []); - - return back(); + return redirect()->route('client.payment_methods.index'); } public function paymentView($data) From 5c72aa3052555f095e2326a1e42d9c115f0e753b Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Aug 2021 20:39:05 +1000 Subject: [PATCH 10/34] Clean up Square --- app/PaymentDrivers/Square/CreditCard.php | 144 ++++------------------- 1 file changed, 20 insertions(+), 124 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 495317a84fdd..65a790df0542 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -50,6 +50,7 @@ class CreditCard public function authorizeResponse($request) { + /* Step one - process a $1 payment - but don't complete it*/ $payment = false; $amount_money = new \Square\Models\Money(); @@ -69,69 +70,14 @@ class CreditCard $api_response = $this->square_driver->square->getPaymentsApi()->createPayment($body); if ($api_response->isSuccess()) { - // $result = $api_response->getResult(); - $result = $api_response->getBody(); $payment = json_decode($result); - nlog($payment); - } else { $errors = $api_response->getErrors(); - nlog($errors); + return $this->processUnsuccessfulPayment($errors); } - - -/* -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" - } - } -} -*/ - + /* Step two - create the customer */ $billing_address = new \Square\Models\Address(); $billing_address->setAddressLine1($this->square_driver->client->address1); $billing_address->setAddressLine2($this->square_driver->client->address2); @@ -156,32 +102,13 @@ Success response looks like this: if ($api_response->isSuccess()) { $result = $api_response->getResult(); - nlog($result); + } else { $errors = $api_response->getErrors(); - nlog($errors); + return $this->processUnsuccessfulPayment($errors); } -/*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 - } - } -} - -*/ - nlog("customer id = ".$result->getCustomer()->getId()); - nlog("source_id = ".$payment->payment->id); - + /* Step 3 create the card */ $card = new \Square\Models\Card(); $card->setCardholderName($this->square_driver->client->present()->name()); $card->setBillingAddress($billing_address); @@ -203,47 +130,14 @@ Success response looks like this: if ($api_response->isSuccess()) { $card = $api_response->getBody(); - nlog($card); $card = json_decode($card); - - nlog("ressy"); - nlog($result); } else { $errors = $api_response->getErrors(); - nlog("i got errors"); - nlog($errors); + + return $this->processUnsuccessfulPayment($errors); } -/** - * -{ - "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 - } -} - -*/ - + /* Create the token in Invoice Ninja*/ $cgt = []; $cgt['token'] = $card->card->id; $cgt['payment_method_id'] = GatewayType::CREDIT_CARD; @@ -296,18 +190,20 @@ Success response looks like this: private function processUnsuccessfulPayment($response) { - /*Harvest your own errors here*/ - // $error = $response->status_message; - - // if(property_exists($response, 'approval_message') && $response->approval_message) - // $error .= " - {$response->approval_message}"; - - // $error_code = property_exists($response, 'approval_message') ? $response->approval_message : 'Undefined code'; + // array ( + // 0 => + // Square\Models\Error::__set_state(array( + // 'category' => 'INVALID_REQUEST_ERROR', + // 'code' => 'INVALID_CARD_DATA', + // 'detail' => 'Invalid card data.', + // 'field' => 'source_id', + // )), + // ) $data = [ 'response' => $response, - 'error' => $error, - 'error_code' => $error_code, + 'error' => $response[0]['detail'], + 'error_code' => '', ]; return $this->square_driver->processUnsuccessfulTransaction($data); From e76fc995ed646135ff5d4c592f5f934baee1208e Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Aug 2021 21:14:18 +1000 Subject: [PATCH 11/34] Refactor for Square --- app/PaymentDrivers/Square/CreditCard.php | 119 ++++++++++++++--------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 65a790df0542..b2f2a7239161 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -77,42 +77,12 @@ class CreditCard return $this->processUnsuccessfulPayment($errors); } - /* Step two - create the customer */ - $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($billing_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(); - return $this->processUnsuccessfulPayment($errors); - } - + /* Step 3 create the card */ $card = new \Square\Models\Card(); $card->setCardholderName($this->square_driver->client->present()->name()); - $card->setBillingAddress($billing_address); - $card->setCustomerId($result->getCustomer()->getId()); + // $card->setBillingAddress($billing_address); + $card->setCustomerId($this->findOrCreateClient()); $card->setReferenceId(Str::random(8)); $body = new \Square\Models\CreateCardRequest( @@ -211,19 +181,80 @@ class CreditCard } - /* Helpers */ - - /* - You will need some helpers to handle successful and unsuccessful responses - - Some considerations after a succesful transaction include: - - Logging of events: success +/- failure - Recording a payment - Notifications - */ + private function findOrCreateClient() + { + $email_address = new \Square\Models\CustomerTextFilter(); + $email_address->setExact($this->square_driver->client->present()->email()); + + $filter = new \Square\Models\CustomerFilter(); + $filter->setEmailAddress($email_address); + + $query = new \Square\Models\CustomerQuery(); + $query->setFilter($filter); + + $body = new \Square\Models\SearchCustomersRequest(); + $body->setQuery($query); + + $api_response = $this->square_driver + ->init() + ->square + ->getCustomersApi() + ->searchCustomers($body); + + $customers = false; + + if ($api_response->isSuccess()) { + $customers = $api_response->getBody(); + $customers = json_decode($customers); + } else { + $errors = $api_response->getErrors(); + } + + if($customers) + return $customers->customers[0]->id; + + return $this->createClient(); + } + + private function createClient() + { + + /* Step two - create the customer */ + $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($billing_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 + ->init() + ->square + ->getCustomersApi() + ->createCustomer($body); + + if ($api_response->isSuccess()) { + $result = $api_response->getResult(); + return $result->getCustomer()->getId(); + + } else { + $errors = $api_response->getErrors(); + return $this->processUnsuccessfulPayment($errors); + } + + } } \ No newline at end of file From 04a16fca905ca79047a011eca4d888391107b964 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 11:11:08 +1000 Subject: [PATCH 12/34] Square - pay --- app/PaymentDrivers/Square/CreditCard.php | 4 +- app/PaymentDrivers/SquarePaymentDriver.php | 45 ++++ .../square/credit_card/authorize.blade.php | 234 +++++++++--------- .../gateways/square/credit_card/pay.blade.php | 171 +++++++++++++ 4 files changed, 336 insertions(+), 118 deletions(-) create mode 100644 resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index b2f2a7239161..75848bec07d5 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -130,9 +130,9 @@ class CreditCard { $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); } diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index d3c5ab247115..818c1e501a3c 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -102,4 +102,49 @@ class SquarePaymentDriver extends BaseDriver 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; + } } \ No newline at end of file diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index 7bd9f85d4f56..5b761b676177 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -27,141 +27,143 @@ @section('gateway_footer') - - + @else + + @endif + + + }); + @endsection \ No newline at end of file diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php new file mode 100644 index 000000000000..e29ed3386375 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php @@ -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') + + + @csrf + + + + + + + @component('portal.ninja2020.components.general.card-element-single') +
+ +
+ + + @endcomponent + + @component('portal.ninja2020.gateways.includes.pay_now') + {{ ctrans('texts.pay_now') }} + @endcomponent +@endsection + +@section('gateway_footer') + + @if($gateway->company_gateway->getConfigField('testMode')) + + @else + + @endif + + + +@endsection \ No newline at end of file From 613b649baac371b0575654a955d1e000402fe16d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Aug 2021 11:54:34 +1000 Subject: [PATCH 13/34] Square Test Mode --- .../gateways/square/credit_card/authorize.blade.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index 5b761b676177..0c5eaf2a284a 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -10,14 +10,14 @@ @csrf - + @component('portal.ninja2020.components.general.card-element-single') -
+
-
+
- + @endcomponent @component('portal.ninja2020.gateways.includes.pay_now') @@ -32,7 +32,7 @@ @else @endif - + + @else + + @endif - @if($gateway->company_gateway->getConfigField('testMode')) - - @else - - @endif + - - @endsection \ No newline at end of file + new SquareCreditCard().handle(); + +@endsection From 2c6f7dfa6faf21670b09024c37acffb8d5fd5eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 17 Aug 2021 14:20:35 +0200 Subject: [PATCH 15/34] Payment page script --- app/PaymentDrivers/Square/CreditCard.php | 4 +- .../gateways/square/credit_card/pay.blade.php | 290 +++++++++--------- 2 files changed, 147 insertions(+), 147 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 75848bec07d5..ba2ab43b2cac 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -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' */ diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php index e29ed3386375..ec4ade5214bd 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php @@ -2,170 +2,170 @@ => ctrans('texts.payment_type_credit_card')]) @section('gateway_head') + + @endsection @section('gateway_content') - -
+ @csrf - + + + + + + +
- + + @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) + + @endforeach + @endisset + + + @endcomponent + + @include('portal.ninja2020.gateways.includes.save_card') + @component('portal.ninja2020.components.general.card-element-single') -
- -
- - +
+
@endcomponent - @component('portal.ninja2020.gateways.includes.pay_now') - {{ ctrans('texts.pay_now') }} - @endcomponent + @include('portal.ninja2020.gateways.includes.pay_now') @endsection @section('gateway_footer') + @if ($gateway->company_gateway->getConfigField('testMode')) + + @else + + @endif - @if($gateway->company_gateway->getConfigField('testMode')) - - @else - - @endif + - -@endsection \ No newline at end of file + new SquareCreditCard().handle(); + +@endsection From f63869d423c54668f691f2fa34a83d78a66e4b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:07:15 +0200 Subject: [PATCH 16/34] Apply php-cs-fixer --- app/PaymentDrivers/Square/CreditCard.php | 25 +++++------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index ba2ab43b2cac..daed673bbf3b 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -12,19 +12,12 @@ namespace App\PaymentDrivers\Square; -use App\Exceptions\PaymentFailed; -use App\Jobs\Mail\PaymentFailureMailer; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; use App\Models\GatewayType; use App\Models\Payment; -use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; use App\PaymentDrivers\SquarePaymentDriver; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; class CreditCard @@ -41,11 +34,9 @@ class CreditCard public function authorizeView($data) { - $data['gateway'] = $this->square_driver; return render('gateways.square.credit_card.authorize', $data); - } public function authorizeResponse($request) @@ -128,12 +119,10 @@ class CreditCard public function paymentView($data) { - $data['gateway'] = $this->square_driver; return render('gateways.square.credit_card.pay', $data); - } public function paymentResponse($request) @@ -155,20 +144,19 @@ class CreditCard $payment = $this->square_driver->createPayment($payment_record, Payment::STATUS_COMPLETED); return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); - } private function processUnsuccessfulPayment($response) { // array ( - // 0 => + // 0 => // Square\Models\Error::__set_state(array( // 'category' => 'INVALID_REQUEST_ERROR', // 'code' => 'INVALID_CARD_DATA', // 'detail' => 'Invalid card data.', // 'field' => 'source_id', // )), - // ) + // ) $data = [ 'response' => $response, @@ -177,7 +165,6 @@ class CreditCard ]; return $this->square_driver->processUnsuccessfulTransaction($data); - } @@ -186,7 +173,6 @@ class CreditCard private function findOrCreateClient() { - $email_address = new \Square\Models\CustomerTextFilter(); $email_address->setExact($this->square_driver->client->present()->email()); @@ -214,8 +200,9 @@ class CreditCard $errors = $api_response->getErrors(); } - if($customers) + if ($customers) { return $customers->customers[0]->id; + } return $this->createClient(); } @@ -250,11 +237,9 @@ class CreditCard if ($api_response->isSuccess()) { $result = $api_response->getResult(); return $result->getCustomer()->getId(); - } else { $errors = $api_response->getErrors(); return $this->processUnsuccessfulPayment($errors); } - } -} \ No newline at end of file +} From 41f31ff64c14caf98c953e1079ab04fc0e92e3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:23:31 +0200 Subject: [PATCH 17/34] Process payments with credit card --- app/PaymentDrivers/Square/CreditCard.php | 29 ++++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index daed673bbf3b..466de67a6a22 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -19,6 +19,7 @@ use App\PaymentDrivers\SquarePaymentDriver; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; use Illuminate\Support\Str; +use Square\Http\ApiResponse; class CreditCard { @@ -127,26 +128,44 @@ class CreditCard public function paymentResponse($request) { - // .. + $amount_money = new \Square\Models\Money(); + $amount_money->setAmount(100); + $amount_money->setCurrency($this->square_driver->client->currency()->code); + + $body = new \Square\Models\CreatePaymentRequest($request->sourceId, Str::random(32), $amount_money); + + $body->setAutocomplete(true); + $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); + $body->setReferenceId(Str::random(16)); + + /** @var ApiResponse */ + $response = $this->square_driver->square->getPaymentsApi()->createPayment($body); + + if ($response->isSuccess()) { + return $this->processSuccessfulPayment($response); + } + + return $this->processUnsuccessfulPayment($response); } - /* This method is stubbed ready to go - you just need to harvest the equivalent 'transaction_reference' */ - private function processSuccessfulPayment($response) + private function processSuccessfulPayment(ApiResponse $response) { + $body = json_decode($response->getBody()); + $amount = array_sum(array_column($this->square_driver->payment_hash->invoices(), 'amount')) + $this->square_driver->payment_hash->fee_total; $payment_record = []; $payment_record['amount'] = $amount; $payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER; $payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD; - // $payment_record['transaction_reference'] = $response->transaction_id; + $payment_record['transaction_reference'] = $body->payment->id; $payment = $this->square_driver->createPayment($payment_record, Payment::STATUS_COMPLETED); return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); } - private function processUnsuccessfulPayment($response) + private function processUnsuccessfulPayment(ApiResponse $response) { // array ( // 0 => From 99da2128d15dad3d5863af93b70cdf139aa4b837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:48:02 +0200 Subject: [PATCH 18/34] Fixes for Javascript --- .../ninja2020/gateways/square/credit_card/pay.blade.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php index ec4ade5214bd..e6f8d956e425 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php @@ -68,6 +68,7 @@ constructor() { this.appId = document.querySelector('meta[name=square-appId]').content; this.locationId = document.querySelector('meta[name=square-locationId]').content; + this.isLoaded = false; } async init() { @@ -77,6 +78,8 @@ await this.card.attach('#card-container'); + this.isLoaded = true; + let iframeContainer = document.querySelector('.sq-card-iframe-container'); if (iframeContainer) { @@ -151,11 +154,13 @@ 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 = ""; + + if (!this.isLoaded) { + await this.init(); + } }); let toggleWithToken = document.querySelector('.toggle-payment-with-token'); From 2065145ffb16691b4e2c53c0584b016b0dc27d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:52:32 +0200 Subject: [PATCH 19/34] Extract scripts into separate file --- .../js/clients/payments/square-credit-card.js | 136 ++++++++++++++++++ .../square/credit_card/authorize.blade.php | 45 +----- .../gateways/square/credit_card/pay.blade.php | 111 +------------- webpack.mix.js | 4 + 4 files changed, 142 insertions(+), 154 deletions(-) create mode 100644 resources/js/clients/payments/square-credit-card.js diff --git a/resources/js/clients/payments/square-credit-card.js b/resources/js/clients/payments/square-credit-card.js new file mode 100644 index 000000000000..a8eabd2161e5 --- /dev/null +++ b/resources/js/clients/payments/square-credit-card.js @@ -0,0 +1,136 @@ +/** + * 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://www.elastic.co/licensing/elastic-license + */ + +class SquareCreditCard { + constructor() { + this.appId = document.querySelector('meta[name=square-appId]').content; + this.locationId = document.querySelector( + 'meta[name=square-locationId]' + ).content; + this.isLoaded = false; + } + + async init() { + this.payments = Square.payments(this.appId, this.locationId); + + this.card = await this.payments.card(); + + await this.card.attach('#card-container'); + + this.isLoaded = true; + + let iframeContainer = document.querySelector( + '.sq-card-iframe-container' + ); + + if (iframeContainer) { + iframeContainer.setAttribute('style', '150px !important'); + } + + let toggleWithToken = document.querySelector( + '.toggle-payment-with-token' + ); + + if (toggleWithToken) { + document.getElementById('card-container').classList.add('hidden'); + } + } + + 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) => { + document + .getElementById('card-container') + .classList.remove('hidden'); + document.getElementById('save-card--container').style.display = + 'grid'; + document.querySelector('input[name=token]').value = ''; + + if (!this.isLoaded) { + await this.init(); + } + }); + + let toggleWithToken = document.querySelector( + '.toggle-payment-with-token' + ); + + if (!toggleWithToken) { + document.getElementById('toggle-payment-with-credit-card').click(); + } + } +} + +new SquareCreditCard().handle(); diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index 71276af89889..3af1b4b17066 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -32,48 +32,5 @@ @endif - + @endsection diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php index e6f8d956e425..92c121e912c1 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/pay.blade.php @@ -63,114 +63,5 @@ @endif - + @endsection diff --git a/webpack.mix.js b/webpack.mix.js index d482c62934cb..c3f00d136572 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -89,6 +89,10 @@ mix.js("resources/js/app.js", "public/js") .js( "resources/js/clients/payments/mollie-credit-card.js", "public/js/clients/payments/mollie-credit-card.js" + ) + .js( + "resources/js/clients/payments/square-credit-card.js", + "public/js/clients/payments/square-credit-card.js" ); mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css'); From 75f55b8113baa17b9fbe1d1c8062b472455dfaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:54:51 +0200 Subject: [PATCH 20/34] Scaffold test case for credit card --- .../Gateways/Square/CreditCardTest.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php diff --git a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php new file mode 100644 index 000000000000..bd8087476fec --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php @@ -0,0 +1,35 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } +} From 6b4e6694622edaecb4c583af97333ba93670116a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:58:17 +0200 Subject: [PATCH 21/34] Tests: Payment with new credit card Payment with new credit card --- .../Gateways/Square/CreditCardTest.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php index bd8087476fec..ec9e08addea1 100644 --- a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php @@ -32,4 +32,25 @@ class CreditCardTest extends DuskTestCase ->auth(); }); } + + public function testPaymentWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->type('#cardholder-name', 'John Doe') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('#cardNumber', '4111 1111 1111 1111') + ->type('#expirationDate', '04/22') + ->type('#cvv', '1111') + ->type('#postalCode', '12345'); + }) + ->click('#pay-now') + ->waitForText('Details of the payment', 60); + }); + } } From 59c254fcf224ff142b6a5d25db2d1885bd2c1acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:58:24 +0200 Subject: [PATCH 22/34] Production build of assets --- public/js/clients/payments/square-credit-card.js | 2 ++ .../clients/payments/square-credit-card.js.LICENSE.txt | 9 +++++++++ public/mix-manifest.json | 1 + 3 files changed, 12 insertions(+) create mode 100644 public/js/clients/payments/square-credit-card.js create mode 100644 public/js/clients/payments/square-credit-card.js.LICENSE.txt diff --git a/public/js/clients/payments/square-credit-card.js b/public/js/clients/payments/square-credit-card.js new file mode 100644 index 000000000000..0f386c08687e --- /dev/null +++ b/public/js/clients/payments/square-credit-card.js @@ -0,0 +1,2 @@ +/*! For license information please see square-credit-card.js.LICENSE.txt */ +!function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="/",r(r.s=23)}({23:function(t,e,r){t.exports=r("flnV")},flnV:function(t,e,r){"use strict";r.r(e);var n=r("o0o1"),o=r.n(n);function i(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function a(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var a=t.apply(e,r);function c(t){i(a,n,o,c,u,"next",t)}function u(t){i(a,n,o,c,u,"throw",t)}c(void 0)}))}}function c(t,e){for(var r=0;r=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var c=r.call(i,"catchLoc"),u=r.call(i,"finallyLoc");if(c&&u){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),E(r),l}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;E(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:L(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),l}},t}(t.exports);try{regeneratorRuntime=n}catch(t){Function("r","regeneratorRuntime = r")(n)}},o0o1:function(t,e,r){t.exports=r("ls82")}}); \ No newline at end of file diff --git a/public/js/clients/payments/square-credit-card.js.LICENSE.txt b/public/js/clients/payments/square-credit-card.js.LICENSE.txt new file mode 100644 index 000000000000..6b30888ddb5b --- /dev/null +++ b/public/js/clients/payments/square-credit-card.js.LICENSE.txt @@ -0,0 +1,9 @@ +/** + * 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://www.elastic.co/licensing/elastic-license + */ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 51c0c6dc95e8..39033340b1a9 100755 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -13,6 +13,7 @@ "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=065e5450233cc5b47020", "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549", "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c8d3808a4c02d1392e96", + "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=a0fead5d3109a4971651", "/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-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344", From c10a636272b772ed4fec3590b9797cc649098704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:58:50 +0200 Subject: [PATCH 23/34] Tests: Payment with new credit card & save for future use --- .../Gateways/Square/CreditCardTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php index ec9e08addea1..539cc6ee3120 100644 --- a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php @@ -53,4 +53,29 @@ class CreditCardTest extends DuskTestCase ->waitForText('Details of the payment', 60); }); } + + public function testPayWithNewCardAndSaveForFutureUse() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->type('#cardholder-name', 'John Doe') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('#cardNumber', '4111 1111 1111 1111') + ->type('#expirationDate', '04/22') + ->type('#cvv', '1111') + ->type('#postalCode', '12345'); + }) + ->radio('#proxy_is_default', true) + ->click('#pay-now') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('4242'); + }); + } } From aaf84e0e3b393ffe6f54c35c47ffa12db1dd21c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:59:12 +0200 Subject: [PATCH 24/34] Tests: Payment with saved credit card --- .../Gateways/Square/CreditCardTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php index 539cc6ee3120..d0be25dcb7ee 100644 --- a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php @@ -78,4 +78,18 @@ class CreditCardTest extends DuskTestCase ->assertSee('4242'); }); } + + public function testPayWithSavedCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->click('#pay-now') + ->waitForText('Details of the payment', 60); + }); + } } From 7cf209e87ad9c79eeb119e3d97ccfa12cccaf4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 17:59:31 +0200 Subject: [PATCH 25/34] Tests: Removing th ecredit card --- .../ClientPortal/Gateways/Square/CreditCardTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php index d0be25dcb7ee..cdc48ea7fa80 100644 --- a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php @@ -92,4 +92,17 @@ class CreditCardTest extends DuskTestCase ->waitForText('Details of the payment', 60); }); } + + public function testRemoveCreditCard() + { + $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.'); + }); + } } From 7fe8fff43b6f9875c1b1a6940459340bd0234732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 18 Aug 2021 18:00:03 +0200 Subject: [PATCH 26/34] Tests: Standalone credit card authorization --- .../Gateways/Square/CreditCardTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php index cdc48ea7fa80..9fc30cbac12e 100644 --- a/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php +++ b/tests/Browser/ClientPortal/Gateways/Square/CreditCardTest.php @@ -105,4 +105,24 @@ class CreditCardTest extends DuskTestCase ->assertSee('Payment method has been successfully removed.'); }); } + + public function testAddingCreditCardStandalone() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->type('#cardholder-name', 'John Doe') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('#cardNumber', '4111 1111 1111 1111') + ->type('#expirationDate', '04/22') + ->type('#cvv', '1111') + ->type('#postalCode', '12345'); + }) + ->press('Add Payment Method') + ->waitForText('**** 1111'); + }); + } } From 6186cc1420a1420bffee19b5f6b4a896c13cb678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 11:05:06 +0200 Subject: [PATCH 27/34] Fixes for authorize page --- resources/js/clients/payments/square-credit-card.js | 10 +++++++--- .../gateways/square/credit_card/authorize.blade.php | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/js/clients/payments/square-credit-card.js b/resources/js/clients/payments/square-credit-card.js index a8eabd2161e5..a72e701b93a6 100644 --- a/resources/js/clients/payments/square-credit-card.js +++ b/resources/js/clients/payments/square-credit-card.js @@ -78,13 +78,17 @@ class SquareCreditCard { } async handle() { + if (document.querySelector('meta[name=square-authorize]')) { + await this.init(); + } + document .getElementById('authorize-card') ?.addEventListener('click', (e) => this.completePaymentWithoutToken(e) ); - document.getElementById('pay-now').addEventListener('click', (e) => { + document.getElementById('pay-now')?.addEventListener('click', (e) => { let tokenInput = document.querySelector('input[name=token]'); if (tokenInput.value) { @@ -110,7 +114,7 @@ class SquareCreditCard { document .getElementById('toggle-payment-with-credit-card') - .addEventListener('click', async (element) => { + ?.addEventListener('click', async (element) => { document .getElementById('card-container') .classList.remove('hidden'); @@ -128,7 +132,7 @@ class SquareCreditCard { ); if (!toggleWithToken) { - document.getElementById('toggle-payment-with-credit-card').click(); + document.getElementById('toggle-payment-with-credit-card')?.click(); } } } diff --git a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php index 3af1b4b17066..5278f6c32e44 100644 --- a/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -4,6 +4,7 @@ @section('gateway_head') + @endsection @section('gateway_content') From 22ce56b7c54ef5b7d1fd6400634bea9486aff5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 13:34:18 +0200 Subject: [PATCH 28/34] Add `shouldUseToken(): bool` to PaymentResponseRequest --- .../ClientPortal/Payments/PaymentResponseRequest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php b/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php index 3a917f339e2f..e9a9851a1839 100644 --- a/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php +++ b/app/Http/Requests/ClientPortal/Payments/PaymentResponseRequest.php @@ -56,4 +56,9 @@ class PaymentResponseRequest extends FormRequest ]); } } + + public function shouldUseToken(): bool + { + return (bool) $this->token; + } } From 7195436950409f22ad4b64136e4d8338d01ca5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 13:34:45 +0200 Subject: [PATCH 29/34] Token billing with credit card --- app/PaymentDrivers/Square/CreditCard.php | 79 +++++++++++++++++++----- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 466de67a6a22..7ddc6dfa18af 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -12,12 +12,13 @@ namespace App\PaymentDrivers\Square; +use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Models\ClientGatewayToken; use App\Models\GatewayType; use App\Models\Payment; use App\Models\PaymentType; use App\PaymentDrivers\SquarePaymentDriver; use App\Utils\Traits\MakesHash; -use Illuminate\Http\Request; use Illuminate\Support\Str; use Square\Http\ApiResponse; @@ -113,7 +114,9 @@ class CreditCard $cgt['payment_meta'] = $payment_meta; - $token = $this->square_driver->storeGatewayToken($cgt, []); + $token = $this->square_driver->storeGatewayToken($cgt, [ + 'gateway_customer_reference' => $this->findOrCreateClient(), + ]); return redirect()->route('client.payment_methods.index'); } @@ -126,28 +129,84 @@ class CreditCard return render('gateways.square.credit_card.pay', $data); } - public function paymentResponse($request) + public function paymentResponse(PaymentResponseRequest $request) { + $token = $request->sourceId; + + if ($request->shouldUseToken()) { + $cgt = ClientGatewayToken::where('token', $request->token)->first(); + $token = $cgt->token; + } + $amount_money = new \Square\Models\Money(); $amount_money->setAmount(100); $amount_money->setCurrency($this->square_driver->client->currency()->code); - $body = new \Square\Models\CreatePaymentRequest($request->sourceId, Str::random(32), $amount_money); + $body = new \Square\Models\CreatePaymentRequest($token, Str::random(32), $amount_money); $body->setAutocomplete(true); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); $body->setReferenceId(Str::random(16)); + if ($request->shouldUseToken()) { + $body->setCustomerId($cgt->gateway_customer_reference); + } + /** @var ApiResponse */ $response = $this->square_driver->square->getPaymentsApi()->createPayment($body); if ($response->isSuccess()) { + if ($request->shouldStoreToken()) { + $this->storePaymentMethod($response); + } + return $this->processSuccessfulPayment($response); } return $this->processUnsuccessfulPayment($response); } + private function storePaymentMethod(ApiResponse $response) + { + $payment = \json_decode($response->getBody()); + + $card = new \Square\Models\Card(); + $card->setCardholderName($this->square_driver->client->present()->name()); + $card->setCustomerId($this->findOrCreateClient()); + $card->setReferenceId(Str::random(8)); + + $body = new \Square\Models\CreateCardRequest(Str::random(32), $payment->payment->id, $card); + + /** @var ApiResponse */ + $api_response = $this->square_driver + ->square + ->getCardsApi() + ->createCard($body); + + if (!$api_response->isSuccess()) { + return $this->processUnsuccessfulPayment($api_response); + } + + $card = \json_decode($api_response->getBody()); + + $cgt = []; + $cgt['token'] = $card->card->id; + $cgt['payment_method_id'] = GatewayType::CREDIT_CARD; + + $payment_meta = new \stdClass; + $payment_meta->exp_month = $card->card->exp_month; + $payment_meta->exp_year = $card->card->exp_year; + $payment_meta->brand = $card->card->card_brand; + $payment_meta->last4 = $card->card->last_4; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $cgt['payment_meta'] = $payment_meta; + + $this->square_driver->storeGatewayToken($cgt, [ + 'gateway_customer_reference' => $this->findOrCreateClient(), + ]); + } + private function processSuccessfulPayment(ApiResponse $response) { $body = json_decode($response->getBody()); @@ -167,19 +226,11 @@ class CreditCard private function processUnsuccessfulPayment(ApiResponse $response) { - // array ( - // 0 => - // Square\Models\Error::__set_state(array( - // 'category' => 'INVALID_REQUEST_ERROR', - // 'code' => 'INVALID_CARD_DATA', - // 'detail' => 'Invalid card data.', - // 'field' => 'source_id', - // )), - // ) + $body = \json_decode($response->getBody()); $data = [ 'response' => $response, - 'error' => $response[0]['detail'], + 'error' => $body->errors[0]->detail, 'error_code' => '', ]; From 9d51137a5ac4852ba67eec8b4fbbf55d6e3fff85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 13:34:51 +0200 Subject: [PATCH 30/34] Fixes for scripts --- resources/js/clients/payments/square-credit-card.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/resources/js/clients/payments/square-credit-card.js b/resources/js/clients/payments/square-credit-card.js index a72e701b93a6..6c02a0ee0068 100644 --- a/resources/js/clients/payments/square-credit-card.js +++ b/resources/js/clients/payments/square-credit-card.js @@ -78,9 +78,7 @@ class SquareCreditCard { } async handle() { - if (document.querySelector('meta[name=square-authorize]')) { - await this.init(); - } + await this.init(); document .getElementById('authorize-card') @@ -121,10 +119,6 @@ class SquareCreditCard { document.getElementById('save-card--container').style.display = 'grid'; document.querySelector('input[name=token]').value = ''; - - if (!this.isLoaded) { - await this.init(); - } }); let toggleWithToken = document.querySelector( From 5dd7f2e5678dd13653c72714255db99126467c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 13:38:15 +0200 Subject: [PATCH 31/34] Production builds --- public/js/clients/payments/square-credit-card.js | 2 +- public/mix-manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/clients/payments/square-credit-card.js b/public/js/clients/payments/square-credit-card.js index 0f386c08687e..5627e81e5526 100644 --- a/public/js/clients/payments/square-credit-card.js +++ b/public/js/clients/payments/square-credit-card.js @@ -1,2 +1,2 @@ /*! For license information please see square-credit-card.js.LICENSE.txt */ -!function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="/",r(r.s=23)}({23:function(t,e,r){t.exports=r("flnV")},flnV:function(t,e,r){"use strict";r.r(e);var n=r("o0o1"),o=r.n(n);function i(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function a(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var a=t.apply(e,r);function c(t){i(a,n,o,c,u,"next",t)}function u(t){i(a,n,o,c,u,"throw",t)}c(void 0)}))}}function c(t,e){for(var r=0;r=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var c=r.call(i,"catchLoc"),u=r.call(i,"finallyLoc");if(c&&u){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),E(r),l}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;E(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:L(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),l}},t}(t.exports);try{regeneratorRuntime=n}catch(t){Function("r","regeneratorRuntime = r")(n)}},o0o1:function(t,e,r){t.exports=r("ls82")}}); \ No newline at end of file +!function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="/",r(r.s=23)}({23:function(t,e,r){t.exports=r("flnV")},flnV:function(t,e,r){"use strict";r.r(e);var n=r("o0o1"),o=r.n(n);function i(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function a(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var a=t.apply(e,r);function c(t){i(a,n,o,c,u,"next",t)}function u(t){i(a,n,o,c,u,"throw",t)}c(void 0)}))}}function c(t,e){for(var r=0;r=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var c=r.call(i,"catchLoc"),u=r.call(i,"finallyLoc");if(c&&u){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),E(r),l}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;E(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:L(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),l}},t}(t.exports);try{regeneratorRuntime=n}catch(t){Function("r","regeneratorRuntime = r")(n)}},o0o1:function(t,e,r){t.exports=r("ls82")}}); \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 39033340b1a9..148ab260165b 100755 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -13,7 +13,7 @@ "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=065e5450233cc5b47020", "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549", "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c8d3808a4c02d1392e96", - "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=a0fead5d3109a4971651", + "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=352f9df0c035939a0bbc", "/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-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344", From a6051330fc538ffa1e2e5846825b302a4b6c79cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 13:50:34 +0200 Subject: [PATCH 32/34] Token billing --- app/PaymentDrivers/Square/CreditCard.php | 6 +- app/PaymentDrivers/SquarePaymentDriver.php | 104 +++++++++++++++++++-- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 7ddc6dfa18af..b5bf6eec93ef 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -132,6 +132,10 @@ class CreditCard public function paymentResponse(PaymentResponseRequest $request) { $token = $request->sourceId; + + $amount = $this->square_driver->convertAmount( + $this->square_driver->payment_hash->data->amount_with_fee + ); if ($request->shouldUseToken()) { $cgt = ClientGatewayToken::where('token', $request->token)->first(); @@ -139,7 +143,7 @@ class CreditCard } $amount_money = new \Square\Models\Money(); - $amount_money->setAmount(100); + $amount_money->setAmount($amount); $amount_money->setCurrency($this->square_driver->client->currency()->code); $body = new \Square\Models\CreatePaymentRequest($token, Str::random(32), $amount_money); diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 818c1e501a3c..793dc25e03fc 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -12,13 +12,18 @@ namespace App\PaymentDrivers; use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Jobs\Mail\PaymentFailureMailer; +use App\Jobs\Util\SystemLogger; use App\Models\ClientGatewayToken; use App\Models\GatewayType; +use App\Models\Invoice; use App\Models\Payment; use App\Models\PaymentHash; +use App\Models\PaymentType; use App\Models\SystemLog; use App\PaymentDrivers\Square\CreditCard; use App\Utils\Traits\MakesHash; +use Square\Http\ApiResponse; class SquarePaymentDriver extends BaseDriver { @@ -30,7 +35,7 @@ class SquarePaymentDriver extends BaseDriver public $can_authorise_credit_card = true; //does this gateway support authorizations? - public $square; + public $square; public $payment_method; @@ -38,11 +43,10 @@ class SquarePaymentDriver extends BaseDriver GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class ]; - const SYSTEM_LOG_TYPE = SystemLog::TYPE_SQUARE; + const SYSTEM_LOG_TYPE = SystemLog::TYPE_SQUARE; public function init() { - $this->square = new \Square\SquareClient([ 'accessToken' => $this->company_gateway->getConfigField('accessToken'), 'environment' => $this->company_gateway->getConfigField('testMode') ? \Square\Environment::SANDBOX : \Square\Environment::PRODUCTION, @@ -56,7 +60,7 @@ class SquarePaymentDriver extends BaseDriver { $types = []; - $types[] = GatewayType::CREDIT_CARD; + $types[] = GatewayType::CREDIT_CARD; return $types; } @@ -96,7 +100,76 @@ class SquarePaymentDriver extends BaseDriver public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) { - //this is your custom implementation from here + $this->init(); + + $amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total; + $amount = $this->convertAmount($amount); + + $invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first(); + + if ($invoice) { + $description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}"; + } else { + $description = "Payment with no invoice for amount {$amount} for client {$this->client->present()->name()}"; + } + + $amount_money = new \Square\Models\Money(); + $amount_money->setAmount($amount); + $amount_money->setCurrency($this->client->currency()->code); + + $body = new \Square\Models\CreatePaymentRequest($cgt->token, \Illuminate\Support\Str::random(32), $amount_money); + + /** @var ApiResponse */ + $response = $this->square->getPaymentsApi()->createPayment($body); + $body = json_decode($response->getBody()); + + if ($response->isSuccess()) { + $amount = array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total; + + $payment_record = []; + $payment_record['amount'] = $amount; + $payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER; + $payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD; + $payment_record['transaction_reference'] = $body->payment->id; + + $payment = $this->createPayment($payment_record, Payment::STATUS_COMPLETED); + + SystemLogger::dispatch( + ['response' => $response, 'data' => $payment_record], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_CHECKOUT, + $this->client, + $this->client->company, + ); + + return $payment; + } + + $this->unWindGatewayFees($payment_hash); + + PaymentFailureMailer::dispatch( + $this->client, + $body->errors[0]->detail, + $this->client->company, + $amount + ); + + $message = [ + 'server_response' => $response, + 'data' => $payment_hash->data, + ]; + + SystemLogger::dispatch( + $message, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_SQUARE, + $this->client, + $this->client->company, + ); + + return false; } public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) @@ -147,4 +220,23 @@ class SquarePaymentDriver extends BaseDriver return $fields; } -} \ No newline at end of file + + public function convertAmount($amount): bool + { + $precision = $this->client->currency()->precision; + + if ($precision == 0) { + return $amount; + } + + if ($precision == 1) { + return $amount*10; + } + + if ($precision == 2) { + return $amount*100; + } + + return $amount; + } +} From 028f63a418d0ba4d86c20e15c6c80bd9ff1e80d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 19 Aug 2021 14:05:11 +0200 Subject: [PATCH 33/34] Scaffold refunds --- app/PaymentDrivers/SquarePaymentDriver.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 793dc25e03fc..b1f3c6eaee48 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -95,7 +95,26 @@ class SquarePaymentDriver extends BaseDriver public function refund(Payment $payment, $amount, $return_client_response = false) { - //this is your custom implementation from here + $this->init(); + + $amount_money = new \Square\Models\Money(); + $amount_money->setAmount($this->convertAmount($amount)); + $amount_money->setCurrency($this->square_driver->client->currency()->code); + + $body = new \Square\Models\RefundPaymentRequest(\Illuminate\Support\Str::random(32), $amount_money, $payment->transaction_reference); + + /** @var ApiResponse */ + $response = $this->square->getRefundsApi()->refund($body); + + // if ($response->isSuccess()) { + // return [ + // 'transaction_reference' => $refund->action_id, + // 'transaction_response' => json_encode($response), + // 'success' => $checkout_payment->status == 'Refunded', + // 'description' => $checkout_payment->status, + // 'code' => $checkout_payment->http_code, + // ]; + // } } public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) From ad73273a480b70e0eaf4d3ed94ba9b566e3c9a90 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 21 Aug 2021 22:22:22 +1000 Subject: [PATCH 34/34] Fixes for Square --- app/Models/Gateway.php | 16 ++++++++-------- app/PaymentDrivers/SquarePaymentDriver.php | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 573ac39c5f8f..0b974598a93c 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -92,13 +92,6 @@ class Gateway extends StaticModel return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal break; case 20: - case 56: - return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], - GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']], - GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false], - GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false], - GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; //Stripe - break; case 39: return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout break; @@ -114,9 +107,16 @@ class Gateway extends StaticModel GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true] ]; break; + case 56: + return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], + GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']], + GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false], + GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false], + GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; //Stripe + break; case 57: return [ - GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Square + GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], //Square ]; break; break; diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index b1f3c6eaee48..43d16b30089e 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -29,7 +29,7 @@ class SquarePaymentDriver extends BaseDriver { use MakesHash; - public $refundable = true; //does this gateway support refunds? + public $refundable = false; //does this gateway support refunds? public $token_billing = true; //does this gateway support token billing? @@ -240,7 +240,7 @@ class SquarePaymentDriver extends BaseDriver return $fields; } - public function convertAmount($amount): bool + public function convertAmount($amount) { $precision = $this->client->currency()->precision;