From cb9c3a0a770b91327200afbdcc960934de482135 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 19 Aug 2023 12:23:37 +1000 Subject: [PATCH] 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}'],