From 5fb51656a6ac2c9f8b284292d91033380cc81858 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 15:52:37 +1000 Subject: [PATCH] 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); } }