| | |
| | | <?php |
| | | namespace JVBase\integrations; |
| | | |
| | | use JVBase\meta\MetaForm; |
| | | use JVBase\meta\MetaManager; |
| | | use JVBase\meta\Form; |
| | | use JVBase\meta\Meta; |
| | | use Exception; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\registry\PostTypeRegistrar; |
| | | use WP_Error; |
| | | use JVBase\ui\Checkout; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\managers\queue\executors\IntegrationExecutor; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | */ |
| | | protected function exchangeOAuthCode(string $code): ?array |
| | | { |
| | | error_log('Exchanging tokens with credentials: '.print_r($this->credentials, true)); |
| | | |
| | | $this->ensureInitialized(); |
| | | |
| | | // Prepare the request body as an array |
| | |
| | | } |
| | | |
| | | $data = json_decode(wp_remote_retrieve_body($response), true); |
| | | error_log('OAuth Response: '.print_r($data, true)); |
| | | if (isset($data['access_token'])) { |
| | | return [ |
| | | 'access_token' => $data['access_token'], |
| | |
| | | |
| | | $data = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | error_log('RefreshAccessToken Response: '.print_r($data, true)); |
| | | if (isset($data['access_token'])) { |
| | | $this->credentials['access_token'] = $data['access_token']; |
| | | $this->credentials['expires_at'] = time() + ($data['expires_in'] ?? 2592000); // 30 days |
| | |
| | | { |
| | | // Skip if we don't have credentials yet (during OAuth flow) |
| | | if (empty($this->credentials['access_token'])) { |
| | | error_log('[Square] Skipping loadLocations - no access token yet'); |
| | | return; |
| | | } |
| | | try { |
| | |
| | | if (!$this->isSetUp()) { |
| | | return; |
| | | } |
| | | // User login tracking for security |
| | | add_action('wp_login', [$this, 'trackUserLogin'], 10, 2); |
| | | |
| | | // Enqueue checkout scripts |
| | | add_action('wp_login', [$this, 'trackUserLogin'], 10, 2); |
| | | add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']); |
| | | |
| | | add_filter('jvbAdditionalActions', [$this, 'outputCheckout']); |
| | | } |
| | | // Shared checkout UI (replaces outputCheckout) |
| | | add_filter('jvbAdditionalActions', [Checkout::class, 'render']); |
| | | |
| | | |
| | | public function outputCheckout(array $actions):array { |
| | | if (is_singular(BASE.'dash') || is_post_type_archive(BASE.'dash')) { |
| | | return $actions; |
| | | } |
| | | $meta = new MetaForm(); |
| | | $form = '<aside id="cart" class="right main"> |
| | | <form id="checkout" data-form-id="checkout" data-save="checkout">'; |
| | | |
| | | $tabs = [ |
| | | 'cartItems' => [ |
| | | 'title' => 'Your Order', |
| | | 'icon' => 'cart', |
| | | 'description' => 'Here\'s your order. You can change quantities, remove items, or clear your cart.', |
| | | 'content' => $this->cartContent() |
| | | ], |
| | | 'checkout' => [ |
| | | 'title' => 'Checkout', |
| | | 'icon' => 'checkout', |
| | | 'description' => 'Securely checkout with your name, email, and payments processed by Square.', |
| | | 'content' => '<div class="checkout-section"> |
| | | <h3>Customer Information</h3> |
| | | '.$meta->return('cart_name', null, [ |
| | | 'type' => 'text', |
| | | 'label' => 'Your Name', |
| | | 'required' => true, |
| | | 'autocomplete' => 'name' |
| | | ]). |
| | | $meta->return('cart_email', null, [ |
| | | 'type' => 'email', |
| | | 'label' => 'Your Email', |
| | | 'required' => true, |
| | | 'autocomplete'=> 'email', |
| | | ]). |
| | | $meta->return('cart_phone', null, [ |
| | | 'type' => 'tel', |
| | | 'label' => 'Your Phone', |
| | | 'required' => true, |
| | | 'autocomplete'=> 'phone' |
| | | ]).' |
| | | <h3>Pickup Details</h3>'. |
| | | $meta->return('pickup_time', null, [ |
| | | 'type' => 'datetime', |
| | | 'label' => 'Pickup Type', |
| | | 'min' => '11:00', |
| | | 'max' => '20:00', |
| | | 'required' => true, |
| | | ]). |
| | | $meta->return('special_instructions', null, [ |
| | | 'type' => 'textarea', |
| | | 'label' => 'Special Instructions', |
| | | 'quill' => true, |
| | | ]).' |
| | | <textarea name="special_instructions" placeholder="Special instructions or dietary notes"></textarea> |
| | | </div> |
| | | |
| | | <div class="checkout-section"> |
| | | <h3>Payment Information</h3> |
| | | <div id="saved-cards"></div> |
| | | <div id="square-card-container"></div> |
| | | </div>' |
| | | ], |
| | | 'order' => [ |
| | | 'title' => 'Your Order', |
| | | 'icon' => 'truck', |
| | | 'hidden' => true, |
| | | 'description' => '', |
| | | 'content' => $this->renderOrderStatus() |
| | | ] |
| | | ]; |
| | | $form .= jvbRenderTabs($tabs, true); |
| | | |
| | | $form .= '<div class="cart-total row end"><p class="tax">Tax: <span></span></p><p class="total">GRAND TOTAL: <span></span></p></div> |
| | | </form> |
| | | </aside> |
| | | <template class="restoredCart"> |
| | | <div class="restored"> |
| | | <h3>Looks like we left things hanging</h3> |
| | | <p>We\'ve restored your cart from your last session below.</p> |
| | | <p>If you\'d rather start over, click the button below.</p> |
| | | <div class="row btw"> |
| | | <button type="button" onclick="window.squareCheckout.clearCart();this.closest(\'.restored\').remove()">'.jvbIcon('trash').'Clear Cart</button> |
| | | <button type="button" onclick="this.closest(\'.restored\').remove()">'.jvbIcon('x').'Dismiss</button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <template class="cartItem"> |
| | | <tr class="item"> |
| | | <td class="item"> |
| | | <label for="quantity"></label> |
| | | <div class="quantity field" data-min="0" data-max="50" data-step="1" data-price="17" data-id=""> |
| | | |
| | | <button type="button" class="decrease"aria-label="Decrease Add to Order">'.jvbIcon('minus-square').'</button> |
| | | |
| | | <input type="number" id="quantity" name="quantity" value="0" min="0" max="50" step="1" class="quantity-input"> |
| | | |
| | | <button type="button" class="increase" aria-label="Increase Add to Order">'.jvbIcon('plus-square').'</button> |
| | | </div> |
| | | </td> |
| | | <td class="price"> |
| | | <span class="price"></span> |
| | | </td> |
| | | <td class="total"> |
| | | <span class="total"></span> |
| | | </td> |
| | | <td> |
| | | <button type="button" data-remove-from-cart>'.jvbIcon('trash').'</button> |
| | | </td> |
| | | </tr> |
| | | </template> |
| | | <template class="emptyCart"> |
| | | <div class="empty"> |
| | | <p><i><b>No items in cart.</b></i></p> |
| | | <p>You can <a href="'.get_post_type_archive_link(BASE.'menu_item').'" title="Browse our menu">browse our menu</a> to order.</p> |
| | | </div> |
| | | </template>'; |
| | | |
| | | |
| | | $actions[] = [ |
| | | 'button' => '<button type="button" class="toggle-cart row" title="Your Cart" data-action="toggle-cart" aria-label="Open Cart" aria-controls="checkout" aria-expanded="false"> |
| | | '.jvbIcon('shopping-cart').'<span class="abs"></span><span class="abs count"></span> |
| | | </button>', |
| | | 'content' => $form |
| | | ]; |
| | | return $actions; |
| | | } |
| | | |
| | | private function cartContent():string |
| | | { |
| | | ob_start(); |
| | | ?> |
| | | <div class="cart-items"> |
| | | <table> |
| | | <thead> |
| | | <tr> |
| | | <th scope="col">Item</th> |
| | | <th scope="col">Price</th> |
| | | <th scope="col">Total</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | |
| | | <details class="account"> |
| | | <summary> |
| | | <?php |
| | | if (is_user_logged_in()) { |
| | | echo 'Your Favourites and Order History'; |
| | | } else { |
| | | echo '<a href="'.wp_login_url(get_the_permalink()).'">Log in</a> to save your favourites and view order history.'; |
| | | } |
| | | ?> |
| | | </summary> |
| | | <?php |
| | | if (is_user_logged_in()) { |
| | | $tabs = [ |
| | | 'history' => [ |
| | | 'title' => 'Order History', |
| | | 'icon' => 'checkout', |
| | | 'description' => 'View your past orders and quickly reorder', |
| | | 'content' => $this->renderOrderHistory() |
| | | ], |
| | | 'favourites' => [ |
| | | 'title' => 'Favourites', |
| | | 'icon' => 'heart', |
| | | 'description' => 'View your favourites from our menu', |
| | | 'content' => $this->renderFavourites() |
| | | ] |
| | | ]; |
| | | jvbRenderTabs($tabs); |
| | | // Square-specific checkout description |
| | | add_filter('jvb_checkout_description', function (string $desc, string $provider) { |
| | | if ($provider === 'square') { |
| | | return 'Securely checkout with your name, email, and payments processed by Square.'; |
| | | } |
| | | return $desc; |
| | | }, 10, 2); |
| | | |
| | | ?> |
| | | </details> |
| | | // Square-specific pickup fields (extracted from old outputCheckout) |
| | | add_filter('jvb_checkout_fields', [$this, 'addPickupFields'], 10, 2); |
| | | |
| | | <?php |
| | | return ob_get_clean(); |
| | | // Browse URL for this client (restaurant menu) |
| | | add_filter('jvb_checkout_browse_url', function () { |
| | | return get_post_type_archive_link(BASE . 'menu_item'); |
| | | }); |
| | | add_filter('jvb_checkout_browse_text', function () { |
| | | return 'browse our menu'; |
| | | }); |
| | | |
| | | // Register queue executor types |
| | | $this->registerQueueTypes(); |
| | | } |
| | | |
| | | private function renderOrderHistory():string |
| | | /** |
| | | * Pickup/ordering fields for the shared checkout form. |
| | | * Specific to this Square client's food ordering use case. |
| | | */ |
| | | public function addPickupFields(string $html, string $provider): string |
| | | { |
| | | ob_start(); |
| | | //TODO: getRequest, cache for 1 day |
| | | return ob_get_clean(); |
| | | } |
| | | private function renderFavourites():string |
| | | { |
| | | ob_start(); |
| | | //TODO: get user's favourites and list them |
| | | return ob_get_clean(); |
| | | if ($provider !== 'square') { |
| | | return $html; |
| | | } |
| | | |
| | | return $html |
| | | . '<h3>Pickup Details</h3>' |
| | | . Form::render('pickup_time', null, [ |
| | | 'type' => 'datetime', |
| | | 'label' => 'Pickup Time', |
| | | 'min' => '11:00', |
| | | 'max' => '20:00', |
| | | 'required' => true, |
| | | ]) |
| | | . Form::render('special_instructions', null, [ |
| | | 'type' => 'textarea', |
| | | 'label' => 'Special Instructions', |
| | | 'quill' => true, |
| | | ]); |
| | | } |
| | | |
| | | protected function renderOrderStatus():string |
| | | protected function registerQueueTypes(): void |
| | | { |
| | | ob_start(); |
| | | ?> |
| | | <div class="order-confirmation"> |
| | | <h2>Order Confirmed!</h2> |
| | | <div id="order-status" data-order=""> |
| | | <p>Order #<span class="order-num"></span></p> |
| | | <div class="status-timeline"> |
| | | <div class="status-item active" data-status="received">Order Received</div> |
| | | <div class="status-item" data-status="preparing">Preparing</div> |
| | | <div class="status-item" data-status="ready">Ready for Pickup</div> |
| | | </div> |
| | | <div class="pickup-time"> |
| | | Estimated pickup: <span id="eta">Calculating...</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <?php |
| | | return ob_get_clean(); |
| | | $queue = JVB()->queue(); |
| | | $executor = new IntegrationExecutor(); |
| | | |
| | | $queue->registry()->register('square_sync_to', new TypeConfig( |
| | | executor: $executor, |
| | | chunkKey: 'items', |
| | | chunkSize: 50, |
| | | maxRetries: 3 |
| | | )); |
| | | |
| | | $queue->registry()->register('square_delete_from', new TypeConfig( |
| | | executor: $executor, |
| | | chunkKey: 'external_ids', |
| | | chunkSize: 200, |
| | | maxRetries: 2 |
| | | )); |
| | | |
| | | $queue->registry()->register('square_sync_from', new TypeConfig( |
| | | executor: $executor, |
| | | maxRetries: 3 |
| | | )); |
| | | |
| | | $queue->registry()->register('square_sync_customer', new TypeConfig( |
| | | executor: $executor, |
| | | maxRetries: 2 |
| | | )); |
| | | |
| | | $queue->registry()->register('square_import', new TypeConfig( |
| | | executor: $executor, |
| | | maxRetries: 3 |
| | | )); |
| | | } |
| | | |
| | | /****************************************************************** |
| | |
| | | */ |
| | | protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void |
| | | { |
| | | error_log('Queuing Sync to Square'); |
| | | // Queue the sync operation |
| | | $this->queueOperation('sync_to_square', [ |
| | | 'items' => [$postID], |
| | | 'user_id' => $this->userID |
| | | $this->queueOperation('sync_to', [ |
| | | 'items' => [$postID], |
| | | 'user_id' => $this->userID, |
| | | ], [ |
| | | 'priority' => 'high', |
| | | 'delay' => 30, // Small delay to batch multiple saves |
| | | 'delay' => 30, |
| | | ]); |
| | | |
| | | update_post_meta($postID, BASE . '_square_sync_status', 'queued'); |
| | |
| | | $square_id = get_post_meta($postID, BASE . '_square_catalog_id', true); |
| | | |
| | | if ($square_id) { |
| | | $this->queueOperation('delete_from_square', [ |
| | | 'square_ids' => [$square_id], |
| | | 'post_id' => $postID |
| | | $this->queueOperation('delete_from', [ |
| | | 'external_ids' => [$square_id], |
| | | 'post_id' => $postID, |
| | | ], [ |
| | | 'priority' => 'high' |
| | | 'priority' => 'high', |
| | | ]); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Process queued operations |
| | | * @deprecated IntegrationExecutor handles new operations via registerQueueTypes(). |
| | | * Kept for legacy-typed operations ('square_sync_to_square') already queued. |
| | | * Safe to remove once all legacy operations have been processed. |
| | | */ |
| | | public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array |
| | | { |
| | | $base = strtolower($this->service_name).'_'; |
| | | $square = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this; |
| | | switch ($operation->type) { |
| | | case $base.'sync_to_square': |
| | | return $square->processSyncToSquare($data); |
| | | $base = strtolower($this->service_name) . '_'; |
| | | $square = array_key_exists('user', $data) ? new self((int) $data['user']) : $this; |
| | | |
| | | case $base.'delete_from_square': |
| | | return $square->processDeleteFromSquare($data); |
| | | |
| | | case $base.'sync_from_square': |
| | | return $square->processSyncFromSquare($data); |
| | | |
| | | case $base.'sync_customer': |
| | | return $square->processSyncCustomer($data); |
| | | |
| | | default: |
| | | return $result; |
| | | } |
| | | return match ($operation->type) { |
| | | $base . 'sync_to_square' => $square->processSyncToSquare($data), |
| | | $base . 'delete_from_square' => $square->processDeleteFromSquare($data), |
| | | $base . 'sync_from_square' => $square->processSyncFromSquare($data), |
| | | $base . 'sync_customer' => $square->processSyncCustomer($data), |
| | | default => $result, |
| | | }; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Process sync to Square |
| | | */ |
| | |
| | | return new WP_Error('post_not_found', "Post $postID not found"); |
| | | } |
| | | |
| | | $meta = new MetaManager($postID, 'post'); |
| | | $meta = Meta::forPost($postID); |
| | | $post_type = get_post_type($postID); |
| | | |
| | | // Get existing Square catalog ID if it exists |
| | |
| | | } |
| | | |
| | | // Add variations |
| | | $variations = $meta->getValue('product_variations'); |
| | | $variations = $meta->get('product_variations'); |
| | | if (empty($variations)) { |
| | | // Create default variation if none exist |
| | | $price = floatval($meta->getValue('price') ?: 0); |
| | | $price = floatval($meta->get('price') ?: 0); |
| | | $catalog_object['item_data']['variations'][] = [ |
| | | 'type' => 'ITEM_VARIATION', |
| | | 'id' => $existing_square_id ? null : '#'.BASE.'menu_item_' . $postID . '_var_default', |
| | |
| | | } |
| | | |
| | | // Add modifiers if they exist |
| | | $modifiers = $meta->getValue('modifiers'); |
| | | $modifiers = $meta->get('modifiers'); |
| | | if (!empty($modifiers)) { |
| | | $modifier_ids = []; |
| | | foreach ($modifiers as $modifier) { |
| | |
| | | } |
| | | |
| | | // Add tax settings |
| | | $tax_ids = $meta->getValue('tax_ids'); |
| | | $tax_ids = $meta->get('tax_ids'); |
| | | if (!empty($tax_ids)) { |
| | | $catalog_object['item_data']['tax_ids'] = $tax_ids; |
| | | } |
| | |
| | | */ |
| | | protected function getVariationMapping(string $post_type): array |
| | | { |
| | | $product_type = JVB_CONTENT[jvbNoBase($post_type)]['integrations']['square']['content_type'] ?? 'REGULAR'; |
| | | $registrar = Registrar::getInstance($post_type); |
| | | if (!$registrar) { |
| | | return []; |
| | | } |
| | | $config = $registrar->getIntegrationConfig($this->service_name); |
| | | $product_type = $config['content_type']??'REGULAR'; |
| | | $valid_fields = $this->getValidFieldsForProductType($product_type); |
| | | |
| | | $defaults = [ |
| | |
| | | */ |
| | | protected function getFieldMapping(string $post_type): array |
| | | { |
| | | $product_type = JVB_CONTENT[jvbNoBase($post_type)]['integrations']['square']['content_type'] ?? 'REGULAR'; |
| | | $registrar = Registrar::getInstance($post_type); |
| | | if (!$registrar) { |
| | | return []; |
| | | } |
| | | $config = $registrar->getIntegrationConfig($this->service_name); |
| | | $product_type = $config['content_type']??'REGULAR'; |
| | | $valid_fields = $this->getValidFieldsForProductType($product_type); |
| | | |
| | | $defaults = [ |
| | |
| | | |
| | | // Set user role (assuming you have a customer role defined) |
| | | $user = new \WP_User($user_id); |
| | | $user->set_role(BASE.'foodie'); // Or whatever role from JVB_USER |
| | | $user->set_role(BASE.'foodie'); // Or whatever role |
| | | |
| | | // Generate password reset key |
| | | $reset_key = get_password_reset_key($user); |
| | |
| | | public function trackUserLogin(string $user_login, \WP_User $user): void |
| | | { |
| | | // Check if user has Square integration |
| | | $roles = array_keys(JVB_USER); |
| | | $user_roles = $user->roles; |
| | | |
| | | foreach ($user_roles as $role) { |
| | | if (isset(JVB_USER[$role]['integrations']['square']['is_customer'])) { |
| | | $role = jvbUserRole($user->ID); |
| | | $registrar = Registrar::getInstance($role); |
| | | if ($registrar) { |
| | | $config = $registrar->getIntegration($this->service_name); |
| | | if ($config->isCustomer()) { |
| | | $login_count = (int)get_user_meta($user->ID, BASE . '_square_login_count', true); |
| | | $login_count++; |
| | | |
| | |
| | | if ($login_count % self::PASSWORD_RESET_INTERVAL === 0) { |
| | | $this->schedulePasswordReset($user->ID); |
| | | } |
| | | |
| | | break; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | if ($wp_order_id) { |
| | | // Update the post meta |
| | | $meta = new MetaManager($wp_order_id, 'post'); |
| | | $meta = Meta::forPost($wp_order_id); |
| | | $updates = [ |
| | | 'status' => $state, |
| | | 'updated_at' => current_time('mysql') |
| | |
| | | /** |
| | | * Enqueue checkout scripts with Square configuration |
| | | */ |
| | | public function enqueueScripts():void |
| | | public function enqueueScripts(): void |
| | | { |
| | | $this->loadCredentials(); |
| | | $sdk_url = $this->environment === 'production' |
| | |
| | | $sdk_url, |
| | | [], |
| | | null, |
| | | [ |
| | | 'strategy' => 'defer', |
| | | 'in_footer' => true |
| | | ] |
| | | ['strategy' => 'defer', 'in_footer' => true] |
| | | ); |
| | | |
| | | // Register your custom checkout script |
| | | // Shared cart checkout base class |
| | | wp_register_script( |
| | | 'jvb-checkout', |
| | | JVB_URL . 'assets/js/min/checkout.min.js', |
| | | ['jvb-utility', 'jvb-queue', 'jvb-a11y', 'jvb-cache', 'jvb-tabs', 'jvb-popup'], |
| | | '1.1.31', |
| | | ['strategy' => 'defer', 'in_footer' => true] |
| | | ); |
| | | |
| | | // Square checkout extends CartCheckout |
| | | wp_register_script( |
| | | 'jvb-square-checkout', |
| | | JVB_URL . 'assets/js/min/square.min.js', |
| | | [ |
| | | // 'square-payments-sdk', |
| | | 'jvb-utility', |
| | | 'jvb-queue', |
| | | 'jvb-a11y', |
| | | 'jvb-cache', |
| | | 'jvb-tabs', |
| | | 'jvb-popup' |
| | | ], |
| | | '1.0.0', |
| | | [ |
| | | 'strategy' => 'defer', |
| | | 'in_footer' => true |
| | | ] |
| | | ['jvb-checkout', 'square-payments-sdk'], |
| | | '1.1.31', |
| | | ['strategy' => 'defer', 'in_footer' => true] |
| | | ); |
| | | |
| | | wp_enqueue_script('jvb-square-checkout'); |
| | | |
| | | // Localize the checkout script with Square config |
| | | wp_localize_script( |
| | | 'jvb-square-checkout', |
| | | 'squareConfig', |
| | | [ |
| | | 'isOpen' => jvbIsOpen(), |
| | | 'application_id' => $this->credentials['client_id'] ?? '', |
| | | 'location_id' => $this->locationId, |
| | | 'environment' => $this->environment, |
| | | 'api_url' => rest_url('jvb/v1/square/'), |
| | | 'nonce' => wp_create_nonce('wp_rest'), |
| | | 'currency' => get_option(BASE . 'currency', 'CAD'), |
| | | 'is_logged_in' => is_user_logged_in(), |
| | | 'user_email' => is_user_logged_in() ? wp_get_current_user()->user_email : '' // NEW |
| | | ] |
| | | ); |
| | | wp_localize_script('jvb-square-checkout', 'squareConfig', [ |
| | | //TODO 'isOpen' => jvbIsOpen(), |
| | | 'application_id' => $this->credentials['client_id'] ?? '', |
| | | 'location_id' => $this->locationId, |
| | | 'environment' => $this->environment, |
| | | 'api_url' => rest_url('jvb/v1/square/'), |
| | | 'nonce' => wp_create_nonce('wp_rest'), |
| | | 'currency' => get_option(BASE . 'currency', 'CAD'), |
| | | 'is_logged_in' => is_user_logged_in(), |
| | | 'user_email' => is_user_logged_in() ? wp_get_current_user()->user_email : '', |
| | | ]); |
| | | } |
| | | |
| | | /****************************************************************** |
| | |
| | | */ |
| | | private function importSquareItem(array $item): bool|int |
| | | { |
| | | //TODO: We need to add the post type to custom meta for Square, this is not good if we have multiple post types with the same product type |
| | | // Find matching content type |
| | | $product_type = $item['item_data']['product_type'] ?? 'REGULAR'; |
| | | $post_type = null; |
| | | |
| | | foreach (JVB_CONTENT as $key => $config) { |
| | | if (isset($config['integrations']['square']['content_type']) && |
| | | $config['integrations']['square']['content_type'] === $product_type) { |
| | | $post_type = jvbCheckBase($key); |
| | | foreach (Registrar::getRegistered() as $registrar) { |
| | | if (!$registrar->hasIntegration($this->service_name)) { |
| | | continue; |
| | | } |
| | | $config = $registrar->getIntegration($this->service_name); |
| | | if ($config->getContent_type() && $config->getContent_type() === $product_type) { |
| | | $post_type = jvbCheckBase($registrar->getSlug()); |
| | | break; |
| | | } |
| | | |
| | | } |
| | | |
| | | if (!$post_type) { |
| | |
| | | */ |
| | | private function mapSquareFieldsToWordPress(int $post_id, array $item): void |
| | | { |
| | | $meta = new MetaManager($post_id, 'post'); |
| | | $meta = Meta::forPost($post_id); |
| | | $field_map = $this->getFieldMapping(get_post_type($post_id)); |
| | | |
| | | $values_to_save = []; |
| | |
| | | update_user_meta($user->ID, BASE . '_square_customer_updated', current_time('mysql')); |
| | | |
| | | // Clear cached customer data |
| | | $this->cache->delete('square_customer_' . $user->ID); |
| | | $this->cache->forget('square_customer_' . $user->ID); |
| | | } |
| | | |
| | | return true; |
| | |
| | | |
| | | // Validate environment setting |
| | | if (isset($credentials['environment'])) { |
| | | error_log('Environment: '.print_r($credentials['environment'], true)); |
| | | $validEnvironments = ['sandbox', 'production']; |
| | | if (!in_array($credentials['environment'], $validEnvironments)) { |
| | | $this->logError('Invalid environment setting', [ |
| | |
| | | 'GIFT_CARD' => array_merge($this->setGiftCardFields()) |
| | | ]; |
| | | } |
| | | public function getAdditionalFields(?string $content_type = null):array { |
| | | if ($content_type && array_key_exists($content_type, $this->contentTypes)){ |
| | | $array = $this->contentTypes[$content_type]; |
| | | return array_combine( |
| | | array_map(fn($k) => 'sq_' . $k, array_keys($array)), |
| | | $array |
| | | ); |
| | | } else if ($content_type && !array_key_exists($content_type, $this->contentTypes)) { |
| | | error_log('Could not get default fields for '.$this->service_name.' content type: '.$content_type); |
| | | return []; |
| | | } |
| | | $array = $this->setBaseFields(); |
| | | return array_combine( |
| | | array_map(fn($k) => 'sq_' . $k, array_keys($array)), |
| | | $array |
| | | ); |
| | | } |
| | | protected function setBaseFields():array |
| | | { |
| | | return [ |
| | |
| | | } |
| | | |
| | | // Save all order meta |
| | | $meta = new MetaManager($order_post_id, 'post'); |
| | | $meta = Meta::forPost($order_post_id); |
| | | $fields = $this->getSquarePostConfig('_sq_orders')['fields']; |
| | | unset($fields['post_title']); |
| | | $meta->setFieldConfig($fields); |
| | | |
| | | $meta->setAll([ |
| | | 'square_order_id' => $order_data['square_order_id'], |
| | |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Single-item sync. Called by IntegrationExecutor::processSyncTo(). |
| | | * Delegates to syncBatchToService since Square uses batch-upsert. |
| | | */ |
| | | public function syncPostToService(int $postID): array|WP_Error |
| | | { |
| | | return $this->syncBatchToService(['items' => [$postID]]); |
| | | } |
| | | |
| | | /** |
| | | * Batch sync — preferred by IntegrationExecutor when available. |
| | | * Wraps existing processSyncToSquare which already handles batches. |
| | | */ |
| | | public function syncBatchToService(array $data): array|WP_Error |
| | | { |
| | | $result = $this->processSyncToSquare($data); |
| | | |
| | | if (empty($result['success'])) { |
| | | $errors = implode(', ', $result['result']['errors'] ?? ['Sync failed']); |
| | | return new WP_Error('square_sync_failed', $errors); |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | /** |
| | | * Delete catalog object from Square. |
| | | * Called by IntegrationExecutor::processDeleteFrom(). |
| | | */ |
| | | public function deleteFromService(string $externalId): array|WP_Error |
| | | { |
| | | $result = $this->processDeleteFromSquare(['square_ids' => [$externalId]]); |
| | | |
| | | if (empty($result['success'])) { |
| | | return new WP_Error('square_delete_failed', $result['result']['error'] ?? 'Delete failed'); |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | /** |
| | | * Import from Square catalog → WordPress. |
| | | * Called by IntegrationExecutor::processImport(). |
| | | */ |
| | | public function importFromService(array $data): array|WP_Error |
| | | { |
| | | $result = $this->processSyncFromSquare($data); |
| | | |
| | | if (empty($result['success'])) { |
| | | return new WP_Error('square_import_failed', $result['result']['error'] ?? 'Import failed'); |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | /** |
| | | * Sync customer to Square. |
| | | * Called by IntegrationExecutor::processSyncCustomer(). |
| | | */ |
| | | public function syncCustomer(array $data): array|WP_Error |
| | | { |
| | | $result = $this->processSyncCustomer($data); |
| | | |
| | | if (empty($result['success'])) { |
| | | return new WP_Error('square_customer_sync_failed', $result['result']['error'] ?? 'Customer sync failed'); |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | } |