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/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..76b3ca63ad5e --- /dev/null +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -0,0 +1,328 @@ +square_driver = $square_driver; + $this->square_driver->init(); + } + + public function authorizeView($data) + { + + $data['gateway'] = $this->square_driver; + + return render('gateways.square.credit_card.authorize', $data); + + } + + 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); + + $body = new \Square\Models\CreatePaymentRequest( + $request->sourceId, + Str::random(32), + $amount_money + ); + + $body->setAutocomplete(false); + $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); + $body->setReferenceId(Str::random(16)); + + $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); + + } else { + $errors = $api_response->getErrors(); + nlog($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" + } + } +} +*/ + + $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(); + nlog($result); + } else { + $errors = $api_response->getErrors(); + nlog($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 + } + } +} + +*/ + + + $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), + $payment->payment->id, + $card + ); + + $api_response = $this->square_driver + ->square + ->getCardsApi() + ->createCard($body); + + 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); + } + +/** + * +{ + "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->getId(); + $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->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_driver; + $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_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 = $this->square_driver->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_driver->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..d3c5ab247115 --- /dev/null +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -0,0 +1,105 @@ + CreditCard::class, //maps GatewayType => Implementation class + ]; + + 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, + ]); + + 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/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/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() + { + // + } +} 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..7bd9f85d4f56 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/square/credit_card/authorize.blade.php @@ -0,0 +1,167 @@ +@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.add_payment_method') }} + @endcomponent +@endsection + +@section('gateway_footer') + + + + + @endsection \ No newline at end of file