Jake Vanderwerf
5 days ago 0dfe1d8afafc59c4a5559c498342668d5a58d6ef
inc/integrations/services/Square.php
@@ -3,6 +3,7 @@
namespace JVBase\integrations\services;
use Exception;
use JVBase\integrations\Admin;
use JVBase\integrations\APIEndpoint;
use JVBase\integrations\Instance;
use JVBase\integrations\Integrations;
use JVBase\integrations\OAuth;
@@ -35,7 +36,13 @@
      Webhooks;
   protected string $orderPostType = '_square_order';
   protected string $apiVersion = '2025-09-24';
   protected string $apiVersion = '2026-07-15';
   public bool $hasBatchCreate = true;
   public bool $hasBatchUpdate = true;
   public bool $hasBatchDelete = true;
   public array $apiEndpoints = [
   ];
   protected function __construct()
   {
@@ -157,8 +164,58 @@
   protected function initialize(): void
   {
      // TODO: Implement initialize() method.
      $this->defineEndpoints();
   }
      protected function defineEndpoints():void
      {
         $this->defineCustomerEndpoints();
         $this->defineProductEndpoints();
         $this->defineOrderEndpoints();
         $this->definePaymentEndpoints();
      }
         protected function defineCustomerEndpoints():void
         {
            $customer = new APIEndpoint();
            $customer->setCreate('/v2/customers');
            $customer->setBatchCreate('/v2/customers/bulk-create');
            $customer->setDelete('/v2/customers/');//add the item_id to the end
            $customer->setBatchDelete('/v2/customers/bulk-delete');
            $customer->setUpdate('/v2/customers/');//add the item_id to the end
            $customer->setBatchUpdate('/v2/customers/bulk-update');
            $customer->setSearch('/v2/customers/search');
            $customer->setImport('/v2/customers');
            $this->apiEndpoints['customer'] = $customer;
         }
         protected function defineProductEndpoints():void
         {
            $product = new APIEndpoint();
            $product->setSearch('/v2/catalog/search-catalog-items');
            $product->setImport('/v2/catalog/list');
            $product->setImage('/v2/catalog/images');
            $product->setCreate('/v2/catalog/object');
            $product->setBatchCreate('/v2/catalog/batch-upsert');
            $product->setUpdate('/v2/catalog/object');
            $product->setBatchUpdate('/v2/catalog/batch-upsert');
            $product->setDelete('/v2/catalog/object/');//add the item_id to the end
            $product->setBatchDelete('/v2/catalog/batch-delete');
            $this->apiEndpoints['product'] = $product;
         }
         protected function defineOrderEndpoints():void
         {
            $order = new APIEndpoint();
            $order->setCreate('/v2/orders');
            $order->setSearch('/v2/orders/search');
            $order->setUpdate('/v2/orders/'); //add order_id
            $this->apiEndpoints['order'] = $order;
         }
         protected function definePaymentEndpoints():void
         {
            $payment = new APIEndpoint();
            $payment->setCreate('/v2/payments');
            $this->apiEndpoints['payment'] = $payment;
         }
   protected function refreshAccessToken(OAuthCredentials $credentials): array
   {
@@ -227,8 +284,275 @@
   protected function setContentTypes(): void
   {
      // TODO: Implement setContentTypes() method.
      $base = $this->getBaseFields();
      $this->contentTypes = [
         'customer'           => $this->getCustomerFields(),
         'REGULAR'            => $base,
         'FOOD_AND_BEV'       => array_merge($base, $this->setFoodAndBevFields()),
         'APPOINTMENTS_SERVICE'  => array_merge($base, $this->setAppointmentServiceFields()),
         'EVENT'              => array_merge($base, $this->setEventFields()),
         'GIFT_CARD'          => array_merge($base, $this->setGiftCardFields()),
      ];
   }
      protected function getCustomerFields():array
      {
         return [
            'address'   => [
               'type'   => 'group',
               'fields' => [
                  'address_line_1'  => [
                     'type'   => 'text',
                     'label'  => 'Address Line 1',
                     'hint'   => 'ex: 6551 111 St NW',
                     'required'  => true,
                     'section'   => 'address'
                  ],
                  'address_line_2'  => [
                     'type'   => 'text',
                     'label'  => 'Address Line 2',
                     'hint'   => 'ex: Unit 2',
                     'section'   => 'address'
                  ],
                  'locality'  => [
                     'type'   => 'text',
                     'label'=> 'City',
                     'section'   => 'address',
                     'required'  => true,
                  ],
                  'administrative_district_level_1' => [
                     'type'   => 'text',
                     'label' => 'Province',
                     'hint' => 'The two-character code, example: AB',
                     'default'=> 'AB',
                     'section'   => 'address',
                     'required'  => true,
                  ],
                  'postal_code' => [
                     'type'   => 'text',
                     'label'  => 'Postal Code',
                     'section'=> 'address'
                  ],
                  'country'   => [
                     'type'   => 'text',
                     'label'  => 'Country Code',
                     'hint'   => 'The tw-character country code, example: CA',
                     'default'   => 'CA',
                     'section'   => 'address',
                     'required'  => true,
                  ],
                  'first_name'   => [
                     'type'   => 'text',
                     'label'  => 'First Name (if different)',
                     'hint'   => 'Optional field to save the address to someone that isn\'t you'
                  ],
                  'last_name' => [
                     'type'   => 'text',
                     'label'  => 'Last Name (if different)',
                     'hint'   => 'Optional field to save the address to someone that isn\'t you'
                  ]
               ]
            ]
         ];
      }
      protected function getBaseFields():array
      {
         return [
            'price' => [
               'type'        => 'number',
               'label'       => 'Price',
               'step'        => 0.01,
               'max'         => 99999,
               'description' => 'Price for this variation'
            ],
            'sku' => [
               'type'        => 'text',
               'label'       => 'SKU',
               'description' => 'Stock keeping unit'
            ],
            'cart_quantity'   => [
               'type'   => 'number',
               'label'  => 'Quantity',
               'hidden' => true,
            ],
            'available_for_pickup' => [
               'type'   => 'true_false',
               'label'  => 'Available for Pick Up',
               'section'=> 'square-advanced'
            ],
            'available_online' => [
               'type'   => 'true_false',
               'label'  => 'Available online',
               'section'=> 'square-advanced'
            ],
            'product_variations' => [
               'type'        => 'repeater',
               'label'       => 'Product Variations',
               'description' => 'Different versions of this product (sizes, colors, etc.)',
               'add_label'   => 'Add Variation',
               'section'     => 'variations',
               'fields'      => $this->setVariationFields()
            ],
         ];
      }
         protected function setVariationFields(?string $type = null):array
         {
            $fields = [
               'name' => [
                  'type'        => 'text',
                  'label'       => 'Variation Name',
                  'description' => 'e.g., "Small", "Large", "Red", etc.'
               ],
               'sku' => [
                  'type'        => 'text',
                  'label'       => 'SKU',
                  'description' => 'Stock keeping unit'
               ],
               'price' => [
                  'type'        => 'number',
                  'label'       => 'Price',
                  'step'        => 0.01,
                  'max'         => 99999,
                  'description' => 'Price for this variation'
               ],
               'track_inventory' => [
                  'type'   => 'true_false',
                  'label'  => 'Track Inventory',
               ],
               'item_id' => [
                  'type'        => 'text',
                  'label'       => 'Square Variation ID',
                  'description' => 'Square catalog ID for this variation',
                  'hidden'      => true
               ],
               'last_sync' => [
                  'type'   => 'datetime',
                  'label'  => 'Last Sync',
                  'hidden' => true
               ]
            ];
            switch ($type) {
               case 'FOOD_AND_BEV':
                  $fields['ingredients']  = [
                     'type'   => 'textarea',
                     'label'  => 'Ingredients List',
                     'description'  => 'Separate ingredients with commas',
                  ];
                  $fields['preparation_time_duration']   = [
                     'type'   => 'number',
                     'label'  => 'Preparation time (in minutes)',
                  ];
                  break;
               case 'GIFT_CARD':
                  $fields['gift_card_type']  = [
                     'type'   => 'select',
                     'label'  => 'Gift Card Type',
                     'options'   => [
                        'PHYSICAL'  => 'Physical',
                        'DIGITAL'   => 'Digital',
                     ],
                     'default'   => 'DIGITAL',
                  ];
                  break;
               case 'APPOINTMENTS_SERVICE':
                  $fields['service_duration']   = [
                     'type'   => 'number',
                     'label'  => 'Duration of Service in Minutes'
                  ];
                  $fields['available_for_booking'] = [
                     'type'   => 'true_false',
                     'label'  => 'Available for Booking'
                  ];
                  break;
            }
            return array_combine(
               array_map(fn($k) => '_square_' . $k, array_keys($fields)),
               $fields
            );
         }
         protected function setFoodAndBevFields():array
         {
            return [
               'ingredients'  => [
                  'type'   => 'textarea',
                  'label'  => 'Ingredients',
                  'hint'   => 'A comma separated list of ingredients'
               ],
               'preparation_time_duration' => [
                  'label'  => 'Preparation Time',
                  'type'   => 'number',
                  'hint'   => 'Preparation time in minutes'
               ],
               'product_variations' => [
                  'type'        => 'repeater',
                  'label'       => 'Product Variations',
                  'description' => 'Different versions of this product (sizes, colors, etc.)',
                  'add_label'   => 'Add Variation',
                  'section'     => 'variations',
                  'fields'      => $this->setVariationFields('FOOD_AND_BEV')
               ],
            ];
            /* TODO: Set definitions for:
               'dietary_preferences'   => [
               ],
               'calories_text'   => [
               ],
            */
         }
         protected function setAppointmentServiceFields():array
         {
            return [
               'product_variations' => [
                  'type'        => 'repeater',
                  'label'       => 'Product Variations',
                  'description' => 'Different versions of this product (sizes, colors, etc.)',
                  'add_label'   => 'Add Variation',
                  'section'     => 'variations',
                  'fields'      => $this->setVariationFields('APPOINTMENTS_SERVICE')
               ],
            ];
         }
         protected function setEventFields():array
         {
            return [
               'venue_details'   => [
                  'type'   => 'textarea',
                  'label'  => 'Venue Details',
               ],
               'capacity'  => [
                  'type'   => 'number',
                  'label'  => 'Capacity'
               ],
               'product_variations' => [
                  'type'        => 'repeater',
                  'label'       => 'Product Variations',
                  'description' => 'Different versions of this product (sizes, colors, etc.)',
                  'add_label'   => 'Add Variation',
                  'section'     => 'variations',
                  'fields'      => $this->setVariationFields('EVENT')
               ],
            ];
         }
         protected function setGiftCardFields():array
         {
            return [
      //       'allowed_locations'  => [
      //
      //       ],
               'product_variations' => [
                  'type'        => 'repeater',
                  'label'       => 'Product Variations',
                  'description' => 'Different versions of this product (sizes, colors, etc.)',
                  'add_label'   => 'Add Variation',
                  'section'     => 'variations',
                  'fields'      => $this->setVariationFields('GIFT_CARD')
               ],
            ];
         }
   protected function formatForService(int $itemID, string $type = 'post'): array
   {
@@ -255,21 +579,28 @@
               ]
            ]
         );
         return $response['id'];
         return $response['data'];
      } catch (Exception $e) {
         return false;
      }
   }
      protected function handleEmailSearchResponse(WP_Error|array $response, string $method):array
      {
         $data = empty($response['customers'])
            ? false
            : $response['customers'][0]??false;
         return $this->response(true, 'Successfully got results.', $data);
      }
   protected function handleCreateUser(array $data): array
   {
      try {
         return $this->postRequest(
            sprintf('%s/v2/customers',$this->getBaseURL()),
            sprintf(
               '%s%s',
               $this->getBaseURL(),
               $this->apiEndpoints['customer']['create']->getEndpoint()
            ),
            $data
         );
      } catch (Exception $e) {
@@ -278,7 +609,10 @@
   }
      protected function handleCreateUserResponse(WP_Error|array $response, string $method):array
      {
         $data = empty($response['customer'])
            ? false
            : $response['customer'];
         return $this->response(true, 'Successfully got results.', $data);
      }
   protected function validateUserFields(int $userID, array $fields):array|false
   {
@@ -616,5 +950,16 @@
      }
   }
   public function getAdditionalFields(?string $content_type = null):array
   {
      $this->setContentTypes();
      if (is_null($content_type) || !array_key_exists($content_type, $this->contentTypes)) {
         return [];
      }
      return $this->contentTypes[$content_type];
   }
}