From cfde7414d75546319e1cff83c7b7cbfc69ead2d7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 10:14:46 +1000 Subject: [PATCH 01/10] Square upgrades --- app/PaymentDrivers/Square/SquareWebhook.php | 59 +++++++++++++++++++++ app/PaymentDrivers/SquarePaymentDriver.php | 38 +++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 app/PaymentDrivers/Square/SquareWebhook.php diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php new file mode 100644 index 000000000000..b28cbb2b8aba --- /dev/null +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -0,0 +1,59 @@ +company_key); + + $this->company_gateway = CompanyGateway::withTrashed()->find($this->company_gateway_id); + + // if(!isset($this->webhook_array['type'])) + // nlog("Checkout Webhook type not set"); + + // match($this->webhook_array['type']){ + // 'payment_approved' => $this->paymentApproved(), + // }; + + } +} \ No newline at end of file diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index cdae15876a2e..0cb6f87cadb3 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -177,8 +177,46 @@ class SquarePaymentDriver extends BaseDriver return false; } + public function createWebhooks() + { + $this->init(); + + $event_types = ['payment.created', 'payment.updated']; + $subscription = new \Square\Models\WebhookSubscription(); + $subscription->setName('Invoice Ninja Webhook Subscription'); + $subscription->setEventTypes($event_types); + $subscription->setNotificationUrl($this->company_gateway->webhookUrl()); + // $subscription->setApiVersion('2021-12-15'); + + $body = new \Square\Models\CreateWebhookSubscriptionRequest($subscription); + $body->setIdempotencyKey(\Illuminate\Support\Str::uuid()); + + $api_response = $this->square->getWebhookSubscriptionsApi()->createWebhookSubscription($body); + + if ($api_response->isSuccess()) { + $result = $api_response->getResult(); + } else { + $errors = $api_response->getErrors(); + } + + } + public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) { + + // header('Content-Type: text/plain'); + // $webhook_payload = file_get_contents('php://input'); + + // if($request->header('cko-signature') == hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)) { + // CheckoutWebhook::dispatch($request->all(), $request->company_key, $this->company_gateway->id)->delay(10); + // } else { + // nlog("Hash Mismatch = {$request->header('cko-signature')} ".hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)); + // nlog($request->all()); + // } + + // return response()->json(['success' => true]); + + } public function convertAmount($amount) From 984496d222bcfc07f8771e9ea16936ee3e90b363 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 10:39:24 +1000 Subject: [PATCH 02/10] Updates for Square payments --- app/PaymentDrivers/Square/CreditCard.php | 1 + app/PaymentDrivers/SquarePaymentDriver.php | 1 + composer.json | 2 +- composer.lock | 265 ++++++++++++++++----- 4 files changed, 214 insertions(+), 55 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 9d61f652ffa1..5c5786f96ba4 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -112,6 +112,7 @@ class CreditCard implements MethodInterface $amount_money->setCurrency($this->square_driver->client->currency()->code); $body = new \Square\Models\CreatePaymentRequest($token, $request->idempotencyKey, $amount_money); + $body->setAmountMoney($amount_money); $body->setAutocomplete(true); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 0cb6f87cadb3..419a56d63561 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -128,6 +128,7 @@ class SquarePaymentDriver extends BaseDriver $body = new \Square\Models\CreatePaymentRequest($cgt->token, \Illuminate\Support\Str::random(32), $amount_money); $body->setCustomerId($cgt->gateway_customer_reference); + $body->setAmountMoney($amount_money); /** @var ApiResponse */ $response = $this->square->getPaymentsApi()->createPayment($body); diff --git a/composer.json b/composer.json index 0d071ca1da10..d9bb6348f79c 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,7 @@ "socialiteproviders/microsoft": "^4.1", "spatie/laravel-data": "^3.5", "sprain/swiss-qr-bill": "^3.2", - "square/square": "13.0.0.20210721", + "square/square": "30.0.0.*", "stripe/stripe-php": "^7.50", "symfony/http-client": "^6.0", "symfony/mailgun-mailer": "^6.1", diff --git a/composer.lock b/composer.lock index 2be5001245e6..31584f8b7590 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9d7348352c913eb82fcca2e67670e1f8", + "content-hash": "673ca66ddfdb05c3ea29012594a196d3", "packages": [ { "name": "adrienrn/php-mimetyper", @@ -99,21 +99,121 @@ "time": "2023-05-02T15:11:17+00:00" }, { - "name": "apimatic/jsonmapper", - "version": "v2.0.3", + "name": "apimatic/core", + "version": "0.3.2", "source": { "type": "git", - "url": "https://github.com/apimatic/jsonmapper.git", - "reference": "f7588f1ab692c402a9118e65cb9fd42b74e5e0db" + "url": "https://github.com/apimatic/core-lib-php.git", + "reference": "32238fb83ce9a3ebef38c726b497c0f218d6e6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/apimatic/jsonmapper/zipball/f7588f1ab692c402a9118e65cb9fd42b74e5e0db", - "reference": "f7588f1ab692c402a9118e65cb9fd42b74e5e0db", + "url": "https://api.github.com/repos/apimatic/core-lib-php/zipball/32238fb83ce9a3ebef38c726b497c0f218d6e6c9", + "reference": "32238fb83ce9a3ebef38c726b497c0f218d6e6c9", "shasum": "" }, + "require": { + "apimatic/core-interfaces": "~0.1.0", + "apimatic/jsonmapper": "^3.1.1", + "ext-curl": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "php": "^7.2 || ^8.0", + "php-jsonpointer/php-jsonpointer": "^3.0.2" + }, "require-dev": { - "phpunit/phpunit": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "phan/phan": "5.4.2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Core\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Core logic and the utilities for the Apimatic's PHP SDK", + "homepage": "https://github.com/apimatic/core-lib-php", + "keywords": [ + "apimatic", + "core", + "corelib", + "php" + ], + "support": { + "issues": "https://github.com/apimatic/core-lib-php/issues", + "source": "https://github.com/apimatic/core-lib-php/tree/0.3.2" + }, + "time": "2023-07-11T09:30:32+00:00" + }, + { + "name": "apimatic/core-interfaces", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/apimatic/core-interfaces-php.git", + "reference": "183214195a79784c382a446795c46ca8c1f43cc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/apimatic/core-interfaces-php/zipball/183214195a79784c382a446795c46ca8c1f43cc1", + "reference": "183214195a79784c382a446795c46ca8c1f43cc1", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "CoreInterfaces\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Definition of the behavior of apimatic/core, apimatic/unirest-php and Apimatic's PHP SDK", + "homepage": "https://github.com/apimatic/core-interfaces-php", + "keywords": [ + "apimatic", + "core", + "corelib", + "interface", + "php", + "unirest" + ], + "support": { + "issues": "https://github.com/apimatic/core-interfaces-php/issues", + "source": "https://github.com/apimatic/core-interfaces-php/tree/0.1.2" + }, + "time": "2023-04-04T06:40:52+00:00" + }, + { + "name": "apimatic/jsonmapper", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/apimatic/jsonmapper.git", + "reference": "6673a946c21f2ceeec0cb60d17541c11a22bc79d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/apimatic/jsonmapper/zipball/6673a946c21f2ceeec0cb60d17541c11a22bc79d", + "reference": "6673a946c21f2ceeec0cb60d17541c11a22bc79d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "squizlabs/php_codesniffer": "^3.0.0" }, "type": "library", @@ -144,37 +244,38 @@ "support": { "email": "mehdi.jaffery@apimatic.io", "issues": "https://github.com/apimatic/jsonmapper/issues", - "source": "https://github.com/apimatic/jsonmapper/tree/v2.0.3" + "source": "https://github.com/apimatic/jsonmapper/tree/3.1.2" }, - "time": "2021-07-16T09:02:23+00:00" + "time": "2023-06-08T04:27:10+00:00" }, { "name": "apimatic/unirest-php", - "version": "2.3.0", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/apimatic/unirest-php.git", - "reference": "52e226fb3b7081dc9ef64aee876142a240a5f0f9" + "reference": "e16754010c16be5473289470f129d87a0f41b55e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/apimatic/unirest-php/zipball/52e226fb3b7081dc9ef64aee876142a240a5f0f9", - "reference": "52e226fb3b7081dc9ef64aee876142a240a5f0f9", + "url": "https://api.github.com/repos/apimatic/unirest-php/zipball/e16754010c16be5473289470f129d87a0f41b55e", + "reference": "e16754010c16be5473289470f129d87a0f41b55e", "shasum": "" }, "require": { + "apimatic/core-interfaces": "^0.1.0", "ext-curl": "*", - "php": ">=5.6.0" + "ext-json": "*", + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^5 || ^6 || ^7 || ^8 || ^9" - }, - "suggest": { - "ext-json": "Allows using JSON Bodies for sending and parsing requests" + "phan/phan": "5.4.2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "autoload": { - "psr-0": { + "psr-4": { "Unirest\\": "src/" } }, @@ -208,9 +309,9 @@ "support": { "email": "opensource@apimatic.io", "issues": "https://github.com/apimatic/unirest-php/issues", - "source": "https://github.com/apimatic/unirest-php/tree/2.3.0" + "source": "https://github.com/apimatic/unirest-php/tree/4.0.5" }, - "time": "2022-06-15T08:29:49+00:00" + "time": "2023-04-25T14:19:45+00:00" }, { "name": "asm/php-ansible", @@ -424,16 +525,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.279.0", + "version": "3.279.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7b3d38cfccd393add0ea0ce281de91846967c61e" + "reference": "ebd5e47c5be0425bb5cf4f80737850ed74767107" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7b3d38cfccd393add0ea0ce281de91846967c61e", - "reference": "7b3d38cfccd393add0ea0ce281de91846967c61e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ebd5e47c5be0425bb5cf4f80737850ed74767107", + "reference": "ebd5e47c5be0425bb5cf4f80737850ed74767107", "shasum": "" }, "require": { @@ -513,9 +614,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.279.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.279.2" }, - "time": "2023-08-16T18:18:34+00:00" + "time": "2023-08-18T18:13:09+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1351,16 +1452,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.5", + "version": "3.6.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf" + "reference": "63646ffd71d1676d2f747f871be31b7e921c7864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/96d5a70fd91efdcec81fc46316efc5bf3da17ddf", - "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/63646ffd71d1676d2f747f871be31b7e921c7864", + "reference": "63646ffd71d1676d2f747f871be31b7e921c7864", "shasum": "" }, "require": { @@ -1376,10 +1477,11 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.21", + "phpstan/phpstan": "1.10.29", "phpstan/phpstan-strict-rules": "^1.5", "phpunit/phpunit": "9.6.9", "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", @@ -1443,7 +1545,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.5" + "source": "https://github.com/doctrine/dbal/tree/3.6.6" }, "funding": [ { @@ -1459,7 +1561,7 @@ "type": "tidelift" } ], - "time": "2023-07-17T09:15:50+00:00" + "time": "2023-08-17T05:38:17+00:00" }, { "name": "doctrine/deprecations", @@ -3348,16 +3450,16 @@ }, { "name": "horstoeko/zugferd", - "version": "v1.0.23", + "version": "v1.0.26", "source": { "type": "git", "url": "https://github.com/horstoeko/zugferd.git", - "reference": "bb55417be4c4de8deb0113e832feeaf7b4d3984e" + "reference": "2a7541a35f00499c206391273f30159dc2c7072a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/bb55417be4c4de8deb0113e832feeaf7b4d3984e", - "reference": "bb55417be4c4de8deb0113e832feeaf7b4d3984e", + "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/2a7541a35f00499c206391273f30159dc2c7072a", + "reference": "2a7541a35f00499c206391273f30159dc2c7072a", "shasum": "" }, "require": { @@ -3415,9 +3517,9 @@ ], "support": { "issues": "https://github.com/horstoeko/zugferd/issues", - "source": "https://github.com/horstoeko/zugferd/tree/v1.0.23" + "source": "https://github.com/horstoeko/zugferd/tree/v1.0.26" }, - "time": "2023-08-16T17:39:36+00:00" + "time": "2023-08-18T03:05:43+00:00" }, { "name": "http-interop/http-factory-guzzle", @@ -7664,6 +7766,62 @@ }, "time": "2020-07-07T09:29:14+00:00" }, + { + "name": "php-jsonpointer/php-jsonpointer", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/raphaelstolt/php-jsonpointer.git", + "reference": "4428f86c6f23846e9faa5a420c4ef14e485b3afb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/raphaelstolt/php-jsonpointer/zipball/4428f86c6f23846e9faa5a420c4ef14e485b3afb", + "reference": "4428f86c6f23846e9faa5a420c4ef14e485b3afb", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.11", + "phpunit/phpunit": "4.6.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Rs\\Json": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Stolt", + "email": "raphael.stolt@gmail.com", + "homepage": "http://raphaelstolt.blogspot.com/" + } + ], + "description": "Implementation of JSON Pointer (http://tools.ietf.org/html/rfc6901)", + "homepage": "https://github.com/raphaelstolt/php-jsonpointer", + "keywords": [ + "json", + "json pointer", + "json traversal" + ], + "support": { + "issues": "https://github.com/raphaelstolt/php-jsonpointer/issues", + "source": "https://github.com/raphaelstolt/php-jsonpointer/tree/master" + }, + "time": "2016-08-29T08:51:01+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -10037,29 +10195,28 @@ }, { "name": "square/square", - "version": "13.0.0.20210721", + "version": "30.0.0.20230816", "source": { "type": "git", "url": "https://github.com/square/square-php-sdk.git", - "reference": "03d90445854cd3b500f75061a9c63956799b8ecf" + "reference": "fedfea8b6c6f16b6a90ef0d629743ae9ae25e13d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/square/square-php-sdk/zipball/03d90445854cd3b500f75061a9c63956799b8ecf", - "reference": "03d90445854cd3b500f75061a9c63956799b8ecf", + "url": "https://api.github.com/repos/square/square-php-sdk/zipball/fedfea8b6c6f16b6a90ef0d629743ae9ae25e13d", + "reference": "fedfea8b6c6f16b6a90ef0d629743ae9ae25e13d", "shasum": "" }, "require": { - "apimatic/jsonmapper": "^2.0.2", - "apimatic/unirest-php": "^2.0", - "ext-curl": "*", + "apimatic/core": "~0.3.0", + "apimatic/core-interfaces": "~0.1.0", + "apimatic/unirest-php": "^4.0.0", "ext-json": "*", - "ext-mbstring": "*", - "php": ">=7.2" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phan/phan": "^3.0", - "phpunit/phpunit": "^7.5 || ^8.5", + "phan/phan": "5.4.2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", @@ -10088,9 +10245,9 @@ ], "support": { "issues": "https://github.com/square/square-php-sdk/issues", - "source": "https://github.com/square/square-php-sdk/tree/13.0.0.20210721" + "source": "https://github.com/square/square-php-sdk/tree/30.0.0.20230816" }, - "time": "2021-07-21T06:43:15+00:00" + "time": "2023-08-15T21:45:55+00:00" }, { "name": "stripe/stripe-php", From 5bb40840d979233376af607b9dffb702db9dd81d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 11:13:52 +1000 Subject: [PATCH 03/10] Working on square upgrades --- app/Models/Gateway.php | 2 +- app/PaymentDrivers/SquarePaymentDriver.php | 121 +++++++++++++++++++-- 2 files changed, 112 insertions(+), 11 deletions(-) diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 79677bf45b5c..80c5b391d7c0 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -171,7 +171,7 @@ class Gateway extends StaticModel ]; case 57: return [ - GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], //Square + GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Square ]; case 52: return [ diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 419a56d63561..0ec08988c6b7 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -11,18 +11,19 @@ namespace App\PaymentDrivers; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; -use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; +use App\Models\SystemLog; +use App\Models\GatewayType; 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; +use App\Jobs\Util\SystemLogger; +use App\Utils\Traits\MakesHash; +use App\Models\ClientGatewayToken; +use App\PaymentDrivers\Square\CreditCard; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use Square\Models\Builders\RefundPaymentRequestBuilder; class SquarePaymentDriver extends BaseDriver { @@ -96,15 +97,115 @@ class SquarePaymentDriver extends BaseDriver public function refund(Payment $payment, $amount, $return_client_response = false) { $this->init(); + $this->client = $payment->client; $amount_money = new \Square\Models\Money(); $amount_money->setAmount($this->convertAmount($amount)); $amount_money->setCurrency($this->client->currency()->code); - $body = new \Square\Models\RefundPaymentRequest(\Illuminate\Support\Str::random(32), $amount_money, $payment->transaction_reference); + $body = RefundPaymentRequestBuilder::init(\Illuminate\Support\Str::random(32), $amount_money) + ->paymentId($payment->transaction_reference) + ->reason('Refund Request') + ->build(); + + $apiResponse = $this->square->getRefundsApi()->refundPayment($body); + + if ($apiResponse->isSuccess()) { + + $refundPaymentResponse = $apiResponse->getResult(); + + nlog($refundPaymentResponse); + + /** + * - `PENDING` - Awaiting approval. + * - `COMPLETED` - Successfully completed. + * - `REJECTED` - The refund was rejected. + * - `FAILED` - An error occurred. + */ + + $status = $refundPaymentResponse->getRefund()->getStatus(); + + if(in_array($status, ['COMPLETED', 'PENDING'])){ + + $transaction_reference = $refundPaymentResponse->getRefund()->getId(); + + $data = [ + 'transaction_reference' => $transaction_reference, + 'transaction_response' => json_encode($refundPaymentResponse->getRefund()->jsonSerialize()), + 'success' => true, + 'description' => $refundPaymentResponse->getRefund()->getReason(), + 'code' => $refundPaymentResponse->getRefund()->getReason(), + ]; + + SystemLogger::dispatch( + [ + 'server_response' => $data, + 'data' => request()->all() + ], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_SQUARE, + $this->client, + $this->client->company + ); + + return $data; + } + elseif(in_array($status, ['REJECTED', 'FAILED'])) { + + $transaction_reference = $refundPaymentResponse->getRefund()->getId(); + + $data = [ + 'transaction_reference' => $transaction_reference, + 'transaction_response' => json_encode($refundPaymentResponse->getRefund()->jsonSerialize()), + 'success' => false, + 'description' => $refundPaymentResponse->getRefund()->getReason(), + 'code' => $refundPaymentResponse->getRefund()->getReason(), + ]; + + SystemLogger::dispatch( + [ + 'server_response' => $data, + 'data' => request()->all() + ], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_SQUARE, + $this->client, + $this->client->company + ); + + return $data; + } + + } else { + + /** @var \Square\Models\Error $error */ + $error = end($apiResponse->getErrors()); + + $data = [ + 'transaction_reference' => $payment->transaction_reference, + 'transaction_response' => $error->jsonSerialize(), + 'success' => false, + 'description' => $error->getDetail(), + 'code' => $error->getCode(), + ]; + + SystemLogger::dispatch( + [ + 'server_response' => $data, + 'data' => request()->all() + ], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_SQUARE, + $this->client, + $this->client->company + ); + + return $data; + } - /** @var ApiResponse */ - $response = $this->square->getRefundsApi()->refund($body); } public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) From cb9c3a0a770b91327200afbdcc960934de482135 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 12:23:37 +1000 Subject: [PATCH 04/10] Upgrades for Square --- app/Models/PaymentHash.php | 3 +- app/PaymentDrivers/BaseDriver.php | 2 + app/PaymentDrivers/Square/CreditCard.php | 14 +- app/PaymentDrivers/Square/SquareWebhook.php | 189 ++++++++++++++++++++ app/PaymentDrivers/SquarePaymentDriver.php | 54 ++++-- database/seeders/PaymentLibrariesSeeder.php | 2 +- 6 files changed, 249 insertions(+), 15 deletions(-) diff --git a/app/Models/PaymentHash.php b/app/Models/PaymentHash.php index daf6b6197d9f..a870489ce02f 100644 --- a/app/Models/PaymentHash.php +++ b/app/Models/PaymentHash.php @@ -17,7 +17,7 @@ use Illuminate\Database\Eloquent\Model; * App\Models\PaymentHash * * @property int $id - * @property string $hash + * @property string $hash 32 char length AlphaNum * @property float $fee_total * @property int|null $fee_invoice_id * @property \stdClass $data @@ -41,6 +41,7 @@ class PaymentHash extends Model /** * @class \App\Models\PaymentHash $this * @property \App\Models\PaymentHash $data + * @property \App\Modes\PaymentHash $hash 32 char length AlphaNum * @class \stdClass $data * @property string $raw_value */ diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 43290710a70d..940727d9f917 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -263,6 +263,8 @@ class BaseDriver extends AbstractPaymentDriver public function setClient(Client $client) { $this->client = $client; + + return $this; } /************************** Helper methods *************************************/ diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 5c5786f96ba4..16e22bc1dc45 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -12,6 +12,7 @@ namespace App\PaymentDrivers\Square; +use App\Models\Invoice; use App\Models\Payment; use App\Models\SystemLog; use Illuminate\View\View; @@ -107,6 +108,14 @@ class CreditCard implements MethodInterface $token = $cgt->token; } + $invoice = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->square_driver->payment_hash->invoices(), 'invoice_id')))->withTrashed()->first(); + + if ($invoice) { + $description = "Invoice {$invoice->number} for {$amount} for client {$this->square_driver->client->present()->name()}"; + } else { + $description = "Payment with no invoice for amount {$amount} for client {$this->square_driver->client->present()->name()}"; + } + $amount_money = new \Square\Models\Money(); $amount_money->setAmount($amount); $amount_money->setCurrency($this->square_driver->client->currency()->code); @@ -116,8 +125,9 @@ class CreditCard implements MethodInterface $body->setAutocomplete(true); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); - $body->setReferenceId(Str::random(16)); - + $body->setReferenceId($this->square_driver->payment_hash->hash); + $body->setNote($description); + if ($request->shouldUseToken()) { $body->setCustomerId($cgt->gateway_customer_reference); }elseif ($request->has('verificationToken') && $request->input('verificationToken')) { diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index b28cbb2b8aba..3e35aa1211c0 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -36,10 +36,62 @@ class SquareWebhook implements ShouldQueue public CompanyGateway $company_gateway; + private array $source_type = [ + 'CARD' => PaymentType::CREDIT_CARD_OTHER, + 'BANK_ACCOUNT' => PaymentType::ACH, + 'WALLET' => PaymentType::CREDIT_CARD_OTHER, + 'BUY_NOW_PAY_LATER' => PaymentType::CREDIT_CARD_OTHER, + 'SQUARE_ACCOUNT' => PaymentType::CREDIT_CARD_OTHER, + 'CASH' => PaymentType::CASH, + 'EXTERNAL' =>PaymentType::CREDIT_CARD_OTHER + ]; + public function __construct(public array $webhook_array, public string $company_key, public int $company_gateway_id) { } + +/** + * { + * "merchant_id": "6SSW7HV8K2ST5", + * "type": "payment.created", + * "event_id": "13b867cf-db3d-4b1c-90b6-2f32a9d78124", + * "created_at": "2020-02-06T21:27:30.792Z", + * "data": { + * "type": "payment", + * "id": "KkAkhdMsgzn59SM8A89WgKwekxLZY", + * "object": { + * "payment": { + * "id": "hYy9pRFVxpDsO1FB05SunFWUe9JZY", + * "created_at": "2020-11-22T21:16:51.086Z", + * "updated_at": "2020-11-22T21:16:51.198Z", + * "amount_money": { + * "amount": 100, + * "currency": "USD" + * }, + * "status": "APPROVED", + * "delay_duration": "PT168H", + * "source_type": "CARD", + * "card_details": { + * "status": "AUTHORIZED", + * "card": { + * "card_brand": "MASTERCARD", + * "last_4": "9029", + * "exp_month": 11, + * "exp_year": 2022, + * "fingerprint": "sq-1-Tvruf3vPQxlvI6n0IcKYfBukrcv6IqWr8UyBdViWXU2yzGn5VMJvrsHMKpINMhPmVg", + * "card_type": "CREDIT", + * "prepaid_type": "NOT_PREPAID", + * "bin": "540988" + * }, + * "entry_method": "KEYED", + * "cvv_status": "CVV_ACCEPTED", + * "avs_status": "AVS_ACCEPTED", + * "statement_description": "SQ *DEFAULT TEST ACCOUNT", + * "card_payment_timeline": { + * "authorized_at": "2020-11-22T21:16:51.198Z" + * + */ public function handle() { nlog("Square Webhook"); @@ -48,6 +100,25 @@ class SquareWebhook implements ShouldQueue $this->company_gateway = CompanyGateway::withTrashed()->find($this->company_gateway_id); + $status = $this->webhook_array['data']['object']['payment']['status'] ?? false; + $payment_id = $this->webhook_array['data']['object']['payment']['id'] ?? null; + + $payment = $this->retrieveOrCreatePayment($payment_id); + + // APPROVED, PENDING, COMPLETED, CANCELED, or FAILED + if(in_array($status, ['APPROVED', 'COMPLETED'])){ + + } + elseif(in_array($status, ['PENDING'])){ + + } + elseif(in_array($status, ['CANCELED', 'FAILED'])){ + + } + else{ + nlog("Square Webhook status not handled: $status"); + } + // if(!isset($this->webhook_array['type'])) // nlog("Checkout Webhook type not set"); @@ -56,4 +127,122 @@ class SquareWebhook implements ShouldQueue // }; } + + private function retrieveOrCreatePayment(?string $payment_reference): \App\Models\Payment + { + + $payment = Payment::withTrashed()->where('transaction_reference', $payment_reference)->first(); + + if($payment) + return $payment; + + $square = $this->company_gateway->driver()->init(); + $apiResponse = $square->getPaymentsApi()->getPayment($payment_reference); + +// { +// "payment": { +// "id": "bP9mAsEMYPUGjjGNaNO5ZDVyLhSZY", +// "created_at": "2021-10-13T19:34:33.524Z", +// "updated_at": "2021-10-13T19:34:34.339Z", +// "amount_money": { +// "amount": 555, +// "currency": "USD" +// }, +// "status": "COMPLETED", +// "delay_duration": "PT168H", +// "source_type": "CARD", +// "card_details": { +// "status": "CAPTURED", +// "card": { +// "card_brand": "VISA", +// "last_4": "1111", +// "exp_month": 11, +// "exp_year": 2022, +// "fingerprint": "sq-1-Hxim77tbdcbGejOejnoAklBVJed2YFLTmirfl8Q5XZzObTc8qY_U8RkwzoNL8dCEcQ", +// "card_type": "DEBIT", +// "prepaid_type": "NOT_PREPAID", +// "bin": "411111" +// }, +// "entry_method": "KEYED", +// "cvv_status": "CVV_ACCEPTED", +// "avs_status": "AVS_ACCEPTED", +// "auth_result_code": "2Nkw7q", +// "statement_description": "SQ *EXAMPLE TEST GOSQ.C", +// "card_payment_timeline": { +// "authorized_at": "2021-10-13T19:34:33.680Z", +// "captured_at": "2021-10-13T19:34:34.340Z" +// } +// }, +// "location_id": "L88917AVBK2S5", +// "order_id": "d7eKah653Z579f3gVtjlxpSlmUcZY", +// "processing_fee": [ +// { +// "effective_at": "2021-10-13T21:34:35.000Z", +// "type": "INITIAL", +// "amount_money": { +// "amount": 34, +// "currency": "USD" +// } +// } +// ], +// "note": "Test Note", +// "total_money": { +// "amount": 555, +// "currency": "USD" +// }, +// "approved_money": { +// "amount": 555, +// "currency": "USD" +// }, +// "employee_id": "TMoK_ogh6rH1o4dV", +// "receipt_number": "bP9m", +// "receipt_url": "https://squareup.com/receipt/preview/bP9mAsEMYPUGjjGNaNO5ZDVyLhSZY", +// "delay_action": "CANCEL", +// "delayed_until": "2021-10-20T19:34:33.524Z", +// "team_member_id": "TMoK_ogh6rH1o4dV", +// "application_details": { +// "square_product": "VIRTUAL_TERMINAL", +// "application_id": "sq0ids-Pw67AZAlLVB7hsRmwlJPuA" +// }, +// "version_token": "56pRkL3slrzet2iQrTp9n0bdJVYTB9YEWdTNjQfZOPV6o" +// } +// } + + if($apiResponse->isSuccess()){ + + $payment_hash_id = $apiResponse->getPayment()->getReferenceId() ?? false; + $square_payment = $apiResponse->getPayment()->jsonSerialize(); + $payment_hash = PaymentHash::where('hash',$payment_hash_id)->first(); + + $payment_hash->data = array_merge((array) $payment_hash->data, (array)$square_payment); + $payment_hash->save(); + + $square->setPaymentHash($payment_hash); + $square->setClient($payment_hash->fee_invoice->client); + + $data = [ + 'payment_type' => $this->source_type[$square_payment->source_type], + 'amount' => $payment_hash->amount_with_fee, + 'transaction_reference' => $square_payment->id, + 'gateway_type_id' => GatewayType::BANK_TRANSFER, + ]; + + $payment = $square->createPayment($data, Payment::STATUS_COMPLETED); + + SystemLogger::dispatch( + ['response' => $this->webhook_array, 'data' => $square_payment], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_SQUARE, + $square->client, + $square->client->company, + ); + + return $payment; + + } + else{ + nlog("Square Webhook Payment not found: $payment_reference"); + } + } } \ No newline at end of file diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 0ec08988c6b7..f25635fff855 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -20,8 +20,12 @@ use App\Models\PaymentType; use Square\Http\ApiResponse; use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; +use Square\Utils\WebhooksHelper; use App\Models\ClientGatewayToken; +use Square\Models\WebhookSubscription; use App\PaymentDrivers\Square\CreditCard; +use App\PaymentDrivers\Square\SquareWebhook; +use Square\Models\CreateWebhookSubscriptionRequest; use App\Http\Requests\Payments\PaymentWebhookRequest; use Square\Models\Builders\RefundPaymentRequestBuilder; @@ -230,6 +234,8 @@ class SquarePaymentDriver extends BaseDriver $body = new \Square\Models\CreatePaymentRequest($cgt->token, \Illuminate\Support\Str::random(32), $amount_money); $body->setCustomerId($cgt->gateway_customer_reference); $body->setAmountMoney($amount_money); + $body->setReferenceId($payment_hash->hash); + $body->setNote(substr($description,0,500)); /** @var ApiResponse */ $response = $this->square->getPaymentsApi()->createPayment($body); @@ -284,17 +290,35 @@ class SquarePaymentDriver extends BaseDriver $this->init(); $event_types = ['payment.created', 'payment.updated']; - $subscription = new \Square\Models\WebhookSubscription(); - $subscription->setName('Invoice Ninja Webhook Subscription'); + $subscription = new WebhookSubscription(); + $subscription->setName('Invoice_Ninja_Webhook_Subscription'); $subscription->setEventTypes($event_types); $subscription->setNotificationUrl($this->company_gateway->webhookUrl()); // $subscription->setApiVersion('2021-12-15'); - $body = new \Square\Models\CreateWebhookSubscriptionRequest($subscription); + $body = new CreateWebhookSubscriptionRequest($subscription); $body->setIdempotencyKey(\Illuminate\Support\Str::uuid()); $api_response = $this->square->getWebhookSubscriptionsApi()->createWebhookSubscription($body); +// { +// "subscription": { +// "id": "wbhk_b35f6b3145074cf9ad513610786c19d5", +// "name": "Example Webhook Subscription", +// "enabled": true, +// "event_types": [ +// "payment.created", +// "order.updated", +// "invoice.created" +// ], +// "notification_url": "https://example-webhook-url.com", +// "api_version": "2021-12-15", +// "signature_key": "1k9bIJKCeTmSQwyagtNRLg", +// "created_at": "2022-08-17 23:29:48 +0000 UTC", +// "updated_at": "2022-08-17 23:29:48 +0000 UTC" +// } +// } + if ($api_response->isSuccess()) { $result = $api_response->getResult(); } else { @@ -303,21 +327,29 @@ class SquarePaymentDriver extends BaseDriver } - public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) + public function processWebhookRequest(PaymentWebhookRequest $request) { + $signature_key = $this->company_gateway->getConfigField('signatureKey'); + $notification_url = $this->company_gateway->webhookUrl(); + // header('Content-Type: text/plain'); // $webhook_payload = file_get_contents('php://input'); - // if($request->header('cko-signature') == hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)) { - // CheckoutWebhook::dispatch($request->all(), $request->company_key, $this->company_gateway->id)->delay(10); - // } else { - // nlog("Hash Mismatch = {$request->header('cko-signature')} ".hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)); - // nlog($request->all()); - // } + $body = ''; + $handle = fopen('php://input', 'r'); + while(!feof($handle)) { + $body .= fread($handle, 1024); + } - // return response()->json(['success' => true]); + if (WebhooksHelper::isValidWebhookEventSignature($body, $request->header('x-square-hmacsha256-signature'), $signature_key, $notification_url)) { + SquareWebhook::dispatch($request->all(), $request->company_key, $this->company_gateway->id)->delay(5); + } else { + nlog("Square Hash Mismatch"); + nlog($request->all()); + } + return response()->json(['success' => true]); } diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php index 4a096febef58..6803c4bf5f54 100644 --- a/database/seeders/PaymentLibrariesSeeder.php +++ b/database/seeders/PaymentLibrariesSeeder.php @@ -80,7 +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}'], + ['id' => 57, 'name' => 'Square', 'provider' => 'Square', 'is_offsite' => false, 'sort_order' => 21, 'key' => '65faab2ab6e3223dbe848b1686490baz', 'fields' => '{"accessToken":"","applicationId":"","locationId":"","signatureKey":"","testMode":false}'], ['id' => 58, 'name' => 'Razorpay', 'provider' => 'Razorpay', 'is_offsite' => false, 'sort_order' => 21, 'key' => 'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9', 'fields' => '{"apiKey":"","apiSecret":""}'], ['id' => 59, 'name' => 'Forte', 'provider' => 'Forte', 'is_offsite' => false, 'sort_order' => 21, 'key' => 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs', 'fields' => '{"testMode":false,"apiLoginId":"","apiAccessId":"","secureKey":"","authOrganizationId":"","organizationId":"","locationId":""}'], ['id' => 60, 'name' => 'PayPal REST', 'provider' => 'PayPal_Rest', 'key' => '80af24a6a691230bbec33e930ab40665', 'fields' => '{"clientId":"","secret":"","signature":"","testMode":false}'], From b35b8210ac0763563c6fdf426b284f7e519aeb3f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 12:31:35 +1000 Subject: [PATCH 05/10] Handling square events --- app/PaymentDrivers/Square/SquareWebhook.php | 35 +++++++-------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index 3e35aa1211c0..a94a0a959f9a 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -103,32 +103,21 @@ class SquareWebhook implements ShouldQueue $status = $this->webhook_array['data']['object']['payment']['status'] ?? false; $payment_id = $this->webhook_array['data']['object']['payment']['id'] ?? null; - $payment = $this->retrieveOrCreatePayment($payment_id); + match($status){ + 'APPROVED' => $payment_status = Payment::STATUS_COMPLETED, + 'COMPLETED' => $payment_status = Payment::STATUS_COMPLETED, + 'PENDING' => $payment_status = Payment::STATUS_PENDING, + 'CANCELED' => $payment_status = Payment::STATUS_CANCELLED, + 'FAILED' => $payment_status = Payment::STATUS_FAILED, + default => $payment_status = Payment::STATUS_FAILED, + }; - // APPROVED, PENDING, COMPLETED, CANCELED, or FAILED - if(in_array($status, ['APPROVED', 'COMPLETED'])){ - - } - elseif(in_array($status, ['PENDING'])){ - - } - elseif(in_array($status, ['CANCELED', 'FAILED'])){ - - } - else{ - nlog("Square Webhook status not handled: $status"); - } - - // if(!isset($this->webhook_array['type'])) - // nlog("Checkout Webhook type not set"); - - // match($this->webhook_array['type']){ - // 'payment_approved' => $this->paymentApproved(), - // }; + $payment = $this->retrieveOrCreatePayment($payment_id, $payment_status); + //toggle pending to completed. } - private function retrieveOrCreatePayment(?string $payment_reference): \App\Models\Payment + private function retrieveOrCreatePayment(?string $payment_reference, int $payment_status): \App\Models\Payment { $payment = Payment::withTrashed()->where('transaction_reference', $payment_reference)->first(); @@ -227,7 +216,7 @@ class SquareWebhook implements ShouldQueue 'gateway_type_id' => GatewayType::BANK_TRANSFER, ]; - $payment = $square->createPayment($data, Payment::STATUS_COMPLETED); + $payment = $square->createPayment($data, $payment_status); SystemLogger::dispatch( ['response' => $this->webhook_array, 'data' => $square_payment], From 5fb51656a6ac2c9f8b284292d91033380cc81858 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 15:52:37 +1000 Subject: [PATCH 06/10] Handling square webhook events --- app/Models/CompanyGateway.php | 16 ++++ app/PaymentDrivers/Square/SquareWebhook.php | 65 +++++++++++++--- app/PaymentDrivers/SquarePaymentDriver.php | 85 +++++++++++++++------ 3 files changed, 130 insertions(+), 36 deletions(-) diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index 446d3022c8fe..d40c569ba05b 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -221,6 +221,22 @@ class CompanyGateway extends BaseModel { $this->config = encrypt(json_encode($config)); } + + /** + * setConfigField + * + * @param mixed $field + * @param mixed $value + * @return void + */ + public function setConfigField($field, $value): void + { + $config = $this->getConfig(); + $config->{$field} = $value; + + $this->setConfig($config); + $this->save(); + } /** * @return mixed diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index a94a0a959f9a..6b6a1841095d 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -20,9 +20,11 @@ use App\Models\PaymentType; use Illuminate\Bus\Queueable; use App\Models\CompanyGateway; use App\Jobs\Util\SystemLogger; +use App\Jobs\Mail\PaymentFailedMailer; use Illuminate\Queue\SerializesModels; use App\PaymentDrivers\Stripe\Utilities; use Illuminate\Queue\InteractsWithQueue; +use App\PaymentDrivers\SquarePaymentDriver; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -36,6 +38,10 @@ class SquareWebhook implements ShouldQueue public CompanyGateway $company_gateway; + public SquarePaymentDriver $driver; + + public \Square\SquareClient $square; + private array $source_type = [ 'CARD' => PaymentType::CREDIT_CARD_OTHER, 'BANK_ACCOUNT' => PaymentType::ACH, @@ -98,22 +104,56 @@ class SquareWebhook implements ShouldQueue MultiDB::findAndSetDbByCompanyKey($this->company_key); - $this->company_gateway = CompanyGateway::withTrashed()->find($this->company_gateway_id); + $this->company_gateway = CompanyGateway::queyr()->withTrashed()->find($this->company_gateway_id); + $this->driver = $this->company_gateway->driver()->init(); + $this->square = $this->driver->square; $status = $this->webhook_array['data']['object']['payment']['status'] ?? false; $payment_id = $this->webhook_array['data']['object']['payment']['id'] ?? null; match($status){ - 'APPROVED' => $payment_status = Payment::STATUS_COMPLETED, + 'APPROVED' => $payment_status = false, 'COMPLETED' => $payment_status = Payment::STATUS_COMPLETED, 'PENDING' => $payment_status = Payment::STATUS_PENDING, 'CANCELED' => $payment_status = Payment::STATUS_CANCELLED, 'FAILED' => $payment_status = Payment::STATUS_FAILED, - default => $payment_status = Payment::STATUS_FAILED, + default => $payment_status = false, }; + if(!$payment_status){ + nlog("Square Webhook - Payment Status Not Found or not worthy of processing"); + nlog($this->webhook_array); + } + $payment = $this->retrieveOrCreatePayment($payment_id, $payment_status); + /** If the status was pending and now is reporting as Failed / Cancelled - process failure path */ + if($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_CANCELLED, Payment::STATUS_FAILED])){ + $payment->service()->deletePayment(); + + + if ($this->driver->payment_hash) { + $error = ctrans('texts.client_payment_failure_body', [ + 'invoice' => implode(',', $payment->invoices->pluck('number')->toArray()), + 'amount' => array_sum(array_column($this->driver->payment_hash->invoices(), 'amount')) + $this->driver->payment_hash->fee_total, + ]); + } else { + $error = 'Payment for '.$payment->client->present()->name()." for {$payment->amount} failed"; + } + + PaymentFailedMailer::dispatch( + $this->driver->payment_hash, + $this->driver->client->company, + $this->driver->client, + $error + ); + + } + elseif($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_COMPLETED, Payment::STATUS_COMPLETED])){ + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->save(); + } + //toggle pending to completed. } @@ -125,8 +165,8 @@ class SquareWebhook implements ShouldQueue if($payment) return $payment; - $square = $this->company_gateway->driver()->init(); - $apiResponse = $square->getPaymentsApi()->getPayment($payment_reference); + /** Handles the edge case where for some reason the payment has not yet been recorded in Invoice Ninja */ + $apiResponse = $this->square->getPaymentsApi()->getPayment($payment_reference); // { // "payment": { @@ -201,13 +241,13 @@ class SquareWebhook implements ShouldQueue $payment_hash_id = $apiResponse->getPayment()->getReferenceId() ?? false; $square_payment = $apiResponse->getPayment()->jsonSerialize(); - $payment_hash = PaymentHash::where('hash',$payment_hash_id)->first(); + $payment_hash = PaymentHash::where('hash', $payment_hash_id)->firstOrFail(); $payment_hash->data = array_merge((array) $payment_hash->data, (array)$square_payment); $payment_hash->save(); - $square->setPaymentHash($payment_hash); - $square->setClient($payment_hash->fee_invoice->client); + $this->driver->setPaymentHash($payment_hash); + $this->driver->setClient($payment_hash->fee_invoice->client); $data = [ 'payment_type' => $this->source_type[$square_payment->source_type], @@ -216,22 +256,23 @@ class SquareWebhook implements ShouldQueue 'gateway_type_id' => GatewayType::BANK_TRANSFER, ]; - $payment = $square->createPayment($data, $payment_status); + $payment = $this->driver->createPayment($data, $payment_status); SystemLogger::dispatch( ['response' => $this->webhook_array, 'data' => $square_payment], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_SQUARE, - $square->client, - $square->client->company, + $this->driver->client, + $this->driver->client->company, ); return $payment; } else{ - nlog("Square Webhook Payment not found: $payment_reference"); + nlog("Square Webhook - Payment not found: $payment_reference"); + nlog($apiResponse->getErrors()); } } } \ No newline at end of file diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index f25635fff855..8538bfe70ce7 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -25,6 +25,7 @@ use App\Models\ClientGatewayToken; use Square\Models\WebhookSubscription; use App\PaymentDrivers\Square\CreditCard; use App\PaymentDrivers\Square\SquareWebhook; +use Square\Models\ListWebhookSubscriptionsRequest; use Square\Models\CreateWebhookSubscriptionRequest; use App\Http\Requests\Payments\PaymentWebhookRequest; use Square\Models\Builders\RefundPaymentRequestBuilder; @@ -197,9 +198,9 @@ class SquarePaymentDriver extends BaseDriver SystemLogger::dispatch( [ - 'server_response' => $data, - 'data' => request()->all() - ], + 'server_response' => $data, + 'data' => request()->all() + ], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_SQUARE, @@ -285,15 +286,64 @@ class SquarePaymentDriver extends BaseDriver return false; } - public function createWebhooks() + public function checkWebhooks(): bool { $this->init(); + + $api_response = $this->square->getWebhookSubscriptionsApi()->listWebhookSubscriptions(); + + if ($api_response->isSuccess()) { + + //array of WebhookSubscription objects + foreach($api_response->getResult()->getSubscriptions() as $subscription) + { + if($subscription->getName() == 'Invoice_Ninja_Webhook_Subscription') + return true; + } + + } else { + $errors = $api_response->getErrors(); + nlog($errors); + + } + + return false; + } + + + // { + // "subscription": { + // "id": "wbhk_b35f6b3145074cf9ad513610786c19d5", + // "name": "Example Webhook Subscription", + // "enabled": true, + // "event_types": [ + // "payment.created", + // "order.updated", + // "invoice.created" + // ], + // "notification_url": "https://example-webhook-url.com", + // "api_version": "2021-12-15", + // "signature_key": "1k9bIJKCeTmSQwyagtNRLg", + // "created_at": "2022-08-17 23:29:48 +0000 UTC", + // "updated_at": "2022-08-17 23:29:48 +0000 UTC" + // } + // } + public function createWebhooks(): void + { + + if($this->checkWebhooks()) + return; + + $this->init(); $event_types = ['payment.created', 'payment.updated']; $subscription = new WebhookSubscription(); $subscription->setName('Invoice_Ninja_Webhook_Subscription'); $subscription->setEventTypes($event_types); - $subscription->setNotificationUrl($this->company_gateway->webhookUrl()); + +$subscription->setNotificationUrl('https://invoicing.co'); + +// $subscription->setNotificationUrl($this->company_gateway->webhookUrl()); // $subscription->setApiVersion('2021-12-15'); $body = new CreateWebhookSubscriptionRequest($subscription); @@ -301,28 +351,15 @@ class SquarePaymentDriver extends BaseDriver $api_response = $this->square->getWebhookSubscriptionsApi()->createWebhookSubscription($body); -// { -// "subscription": { -// "id": "wbhk_b35f6b3145074cf9ad513610786c19d5", -// "name": "Example Webhook Subscription", -// "enabled": true, -// "event_types": [ -// "payment.created", -// "order.updated", -// "invoice.created" -// ], -// "notification_url": "https://example-webhook-url.com", -// "api_version": "2021-12-15", -// "signature_key": "1k9bIJKCeTmSQwyagtNRLg", -// "created_at": "2022-08-17 23:29:48 +0000 UTC", -// "updated_at": "2022-08-17 23:29:48 +0000 UTC" -// } -// } - if ($api_response->isSuccess()) { - $result = $api_response->getResult(); + $subscription = $api_response->getResult()->getSubscription(); + $signatureKey = $subscription->getSignatureKey(); + + $this->company_gateway->setConfigField('signatureKey', $signatureKey); + } else { $errors = $api_response->getErrors(); + nlog($errors); } } From d4aeca5ad1d998260761d0054be00f0f32341169 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 15:55:00 +1000 Subject: [PATCH 07/10] Tests for setting config fields --- tests/Feature/CompanyGatewayTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Feature/CompanyGatewayTest.php b/tests/Feature/CompanyGatewayTest.php index 4547ffbbc9af..cefb2baeaf97 100644 --- a/tests/Feature/CompanyGatewayTest.php +++ b/tests/Feature/CompanyGatewayTest.php @@ -47,6 +47,22 @@ class CompanyGatewayTest extends TestCase $this->assertNotNull($company_gateway); } + public function testSetConfigFields() + { + $company_gateway = CompanyGateway::first(); + + $this->assertNotNull($company_gateway->getConfig()); + + $company_gateway->setConfigField('test', 'test'); + + $this->assertEquals('test', $company_gateway->getConfigField('test')); + + $company_gateway->setConfigField('signatureKey', 'hero'); + + $this->assertEquals('hero', $company_gateway->getConfigField('signatureKey')); + + } + public function testFeesAndLimitsExists() { $data = []; From 87d14dd052ec3bb238fe1ce0424f316e0b0af53f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 16:32:24 +1000 Subject: [PATCH 08/10] Upgrades for Square --- app/PaymentDrivers/Square/CreditCard.php | 10 ++-------- app/PaymentDrivers/SquarePaymentDriver.php | 10 +++------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 16e22bc1dc45..616bbbbff046 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -34,11 +34,8 @@ class CreditCard implements MethodInterface { use MakesHash; - public $square_driver; - - public function __construct(SquarePaymentDriver $square_driver) + public function __construct(public SquarePaymentDriver $square_driver) { - $this->square_driver = $square_driver; $this->square_driver->init(); } @@ -103,8 +100,7 @@ class CreditCard implements MethodInterface ); if ($request->shouldUseToken()) { - /** @var \App\Models\ClientGatewayToken $cgt **/ - $cgt = ClientGatewayToken::where('token', $request->token)->first(); + $cgt = ClientGatewayToken::query()->where('token', $request->token)->first(); $token = $cgt->token; } @@ -122,7 +118,6 @@ class CreditCard implements MethodInterface $body = new \Square\Models\CreatePaymentRequest($token, $request->idempotencyKey, $amount_money); $body->setAmountMoney($amount_money); - $body->setAutocomplete(true); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); $body->setReferenceId($this->square_driver->payment_hash->hash); @@ -134,7 +129,6 @@ class CreditCard implements MethodInterface $body->setVerificationToken($request->input('verificationToken')); } - /** @var ApiResponse */ $response = $this->square_driver->square->getPaymentsApi()->createPayment($body); if ($response->isSuccess()) { diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 8538bfe70ce7..5c9b5515821a 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -17,7 +17,6 @@ use App\Models\SystemLog; use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use Square\Http\ApiResponse; use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; use Square\Utils\WebhooksHelper; @@ -25,7 +24,6 @@ use App\Models\ClientGatewayToken; use Square\Models\WebhookSubscription; use App\PaymentDrivers\Square\CreditCard; use App\PaymentDrivers\Square\SquareWebhook; -use Square\Models\ListWebhookSubscriptionsRequest; use Square\Models\CreateWebhookSubscriptionRequest; use App\Http\Requests\Payments\PaymentWebhookRequest; use Square\Models\Builders\RefundPaymentRequestBuilder; @@ -238,7 +236,6 @@ class SquarePaymentDriver extends BaseDriver $body->setReferenceId($payment_hash->hash); $body->setNote(substr($description,0,500)); - /** @var ApiResponse */ $response = $this->square->getPaymentsApi()->createPayment($body); $body = json_decode($response->getBody()); @@ -310,7 +307,6 @@ class SquarePaymentDriver extends BaseDriver return false; } - // { // "subscription": { // "id": "wbhk_b35f6b3145074cf9ad513610786c19d5", @@ -341,9 +337,9 @@ class SquarePaymentDriver extends BaseDriver $subscription->setName('Invoice_Ninja_Webhook_Subscription'); $subscription->setEventTypes($event_types); -$subscription->setNotificationUrl('https://invoicing.co'); + // $subscription->setNotificationUrl('https://invoicing.co'); -// $subscription->setNotificationUrl($this->company_gateway->webhookUrl()); + $subscription->setNotificationUrl($this->company_gateway->webhookUrl()); // $subscription->setApiVersion('2021-12-15'); $body = new CreateWebhookSubscriptionRequest($subscription); @@ -354,7 +350,7 @@ $subscription->setNotificationUrl('https://invoicing.co'); if ($api_response->isSuccess()) { $subscription = $api_response->getResult()->getSubscription(); $signatureKey = $subscription->getSignatureKey(); - + $this->company_gateway->setConfigField('signatureKey', $signatureKey); } else { From 85b5398718943b3dbd0c622de7fd5f65d1d6f36b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 16:44:17 +1000 Subject: [PATCH 09/10] Fixes for mailers --- app/Jobs/Mail/NinjaMailerJob.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index ee38fbb77181..eed243b27d91 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -51,7 +51,7 @@ class NinjaMailerJob implements ShouldQueue public $override; - /** @var \App\Models\Company $company | null **/ + /** @var null|\App\Models\Company $company **/ public ?Company $company; private $mailer; @@ -143,8 +143,6 @@ class NinjaMailerJob implements ShouldQueue LightLogs::create(new EmailSuccess($this->nmo->company->company_key)) ->send(); - $this->nmo = null; - $this->company = null; } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); @@ -195,6 +193,9 @@ class NinjaMailerJob implements ShouldQueue $this->release($this->backoff()[$this->attempts()-1]); } + $this->nmo = null; + $this->company = null; + /*Clean up mailers*/ $this->cleanUpMailers(); } From 787400ff3974b1195aa004f8f2ebfc6701353532 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Aug 2023 19:17:21 +1000 Subject: [PATCH 10/10] Fixes for squar --- app/PaymentDrivers/Square/CreditCard.php | 2 +- app/PaymentDrivers/Square/SquareWebhook.php | 8 +++++--- app/PaymentDrivers/SquarePaymentDriver.php | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 616bbbbff046..59139361a946 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -116,7 +116,7 @@ class CreditCard implements MethodInterface $amount_money->setAmount($amount); $amount_money->setCurrency($this->square_driver->client->currency()->code); - $body = new \Square\Models\CreatePaymentRequest($token, $request->idempotencyKey, $amount_money); + $body = new \Square\Models\CreatePaymentRequest($token, $request->idempotencyKey); $body->setAmountMoney($amount_money); $body->setAutocomplete(true); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index 6b6a1841095d..8e99e160e10a 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -104,13 +104,15 @@ class SquareWebhook implements ShouldQueue MultiDB::findAndSetDbByCompanyKey($this->company_key); - $this->company_gateway = CompanyGateway::queyr()->withTrashed()->find($this->company_gateway_id); + $this->company_gateway = CompanyGateway::query()->withTrashed()->find($this->company_gateway_id); $this->driver = $this->company_gateway->driver()->init(); $this->square = $this->driver->square; $status = $this->webhook_array['data']['object']['payment']['status'] ?? false; $payment_id = $this->webhook_array['data']['object']['payment']['id'] ?? null; + $payment_status = false; + match($status){ 'APPROVED' => $payment_status = false, 'COMPLETED' => $payment_status = Payment::STATUS_COMPLETED, @@ -131,7 +133,6 @@ class SquareWebhook implements ShouldQueue if($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_CANCELLED, Payment::STATUS_FAILED])){ $payment->service()->deletePayment(); - if ($this->driver->payment_hash) { $error = ctrans('texts.client_payment_failure_body', [ 'invoice' => implode(',', $payment->invoices->pluck('number')->toArray()), @@ -157,7 +158,7 @@ class SquareWebhook implements ShouldQueue //toggle pending to completed. } - private function retrieveOrCreatePayment(?string $payment_reference, int $payment_status): \App\Models\Payment + private function retrieveOrCreatePayment(?string $payment_reference, int $payment_status): ?\App\Models\Payment { $payment = Payment::withTrashed()->where('transaction_reference', $payment_reference)->first(); @@ -273,6 +274,7 @@ class SquareWebhook implements ShouldQueue else{ nlog("Square Webhook - Payment not found: $payment_reference"); nlog($apiResponse->getErrors()); + return null; } } } \ No newline at end of file diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 5c9b5515821a..3ff5e09121a3 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -230,7 +230,7 @@ class SquarePaymentDriver extends BaseDriver $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); + $body = new \Square\Models\CreatePaymentRequest($cgt->token, \Illuminate\Support\Str::random(32)); $body->setCustomerId($cgt->gateway_customer_reference); $body->setAmountMoney($amount_money); $body->setReferenceId($payment_hash->hash);