service_name = 'helcim'; $this->title = 'Helcim'; $this->icon = 'currency-circle-dollar'; // Helcim API endpoints $this->apiBase = [ 'production' => 'https://api.helcim.com/v2', 'sandbox' => 'https://api-sandbox.helcim.com/v2' ]; $this->apiEndpoints = [ 'commerce/invoice', 'commerce/transaction', 'commerce/customer', 'commerce/card-batch', 'commerce/terminal', 'commerce/product', 'commerce/order', 'payment/purchase', 'payment/verify', 'payment/capture', 'payment/refund', 'customer/create', 'customer/update', 'customer/get', 'inventory/product', 'inventory/batch' ]; $this->canSync = [ 'create' => true, 'update' => true, 'delete' => true ]; $this->fields = [ 'test_mode' => [ 'type' => 'select', 'label' => 'Environment', 'options' => [ '1' => 'Test Mode', '0' => 'Production' ], 'default' => '1' ], 'api_token' => [ 'type' => 'text', 'subtype' => 'password', 'required' => true, 'hint' => 'Your Helcim API Token' ], 'account_id' => [ 'type' => 'text', 'required' => true, 'label' => 'Account ID', 'hint' => 'Your Helcim Account ID', ], 'webhook_secret' => [ 'type' => 'text', 'subtype' => 'password', 'label' => 'Webhook Secret', 'hint' => 'For webhook verification', 'required' => true, ] ]; $this->advanced = [ ]; $this->instructions = [ 'Once you are set up, add this URL to your Helcim webhook settings: '.esc_html(rest_url('jvb/v1/webhooks/helcim')).'' ]; $this->defaults = [ ]; $this->handleWebhooks = true; parent::__construct($userID); $this->actions = array_merge( $this->actions, [ 'import_from_helcim' => 'handleImportFromHelcim', 'sync_to_helcim' => 'handleSyncToHelcim' ] ); // Initialize field mappings $this->initializeFieldMappings(); } /** * Initialize service-specific settings */ protected function initialize(): void { $this->api_token = $this->credentials['api_token'] ?? ''; $this->account_id = $this->credentials['account_id'] ?? ''; $this->terminal_id = $this->credentials['terminal_id'] ?? ''; $this->webhook_secret = $this->credentials['webhook_secret'] ?? ''; $this->is_test_mode = (bool)($this->credentials['test_mode'] ?? false); // Set the appropriate API base $this->apiBase = $this->is_test_mode ? $this->apiBase['sandbox'] : $this->apiBase['production']; // Load payment form settings $this->payment_form_settings = [ 'card' => $this->credentials['enable_card_payments'] ?? true, 'ach' => $this->credentials['enable_ach_payments'] ?? false, 'apple_pay' => $this->credentials['enable_apple_pay'] ?? false, 'google_pay' => $this->credentials['enable_google_pay'] ?? false, ]; } /** * Register additional WordPress hooks */ protected function registerAdditionalHooks(): void { $this->ensureInitialized(); if (!$this->isSetUp()) { return; } // User login tracking for security add_action('wp_login', [$this, 'trackUserLogin'], 10, 2); add_action('wp_footer', [$this, 'outputCheckout']); // Enqueue checkout scripts add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']); // REST API endpoints for checkout add_action('rest_api_init', [$this, 'registerRestRoutes']); } /** * Register REST API routes */ public function registerRestRoutes(): void { register_rest_route('jvb/v1', '/helcim/checkout', [ 'methods' => 'POST', 'callback' => [$this, 'handleCheckout'], 'permission_callback' => '__return_true' ]); register_rest_route('jvb/v1', '/helcim/customer', [ 'methods' => 'POST', 'callback' => [$this, 'handleCustomerLookup'], 'permission_callback' => '__return_true' ]); register_rest_route('jvb/v1', '/helcim/order-status/(?P[a-zA-Z0-9-]+)', [ 'methods' => 'GET', 'callback' => [$this, 'handleOrderStatus'], 'permission_callback' => '__return_true' ]); register_rest_route('jvb/v1', '/helcim/create-account', [ 'methods' => 'POST', 'callback' => [$this, 'handleAccountCreation'], 'permission_callback' => '__return_true' ]); } /** * Initialize field mappings for all content types */ private function initializeFieldMappings(): void { foreach (JVB_CONTENT as $key => $config) { if (isset($config['integrations']['helcim'])) { $post_type = jvbCheckBase($key); $this->field_mappings[$post_type] = $this->getFieldMapping($post_type); } } } /** * Get field mapping for a post type */ public function getFieldMapping(string $post_type): array { return apply_filters(BASE . '_helcim_field_mapping', [ 'name' => 'title', 'description' => 'content', 'price' => 'price', 'sku' => '_helcim_sku', 'product_code' => '_helcim_product_code', 'inventory' => '_helcim_inventory', 'product_type' => 'product_type', 'tax_exempt' => 'tax_exempt', 'shipping_required' => 'shipping_required' ], $post_type); } /** * Output checkout form */ public function outputCheckout(): void { if (is_singular(BASE.'dash') || is_post_type_archive(BASE.'dash')) { return; } ?> outputCheckoutTemplates(); } /** * Get account tab description based on login status */ private function getAccountTabDescription(): string { if (is_user_logged_in()) { return 'Manage your account and view order history'; } return 'Login or create an account for faster checkout'; } /** * Render account section */ private function renderAccountSection(): string { ob_start(); ?>
renderLoggedInAccount(); ?> renderGuestAccount(); ?>
ID, BASE . '_helcim_customer_id', true); ?>

Welcome back, display_name) ?>!

New Customer?

You can checkout as a guest or create an account after your order

Customer Information

Pickup/Delivery Details

Payment Information

Order Confirmed!

Order #

Order Received
Preparing
Ready
Complete
Estimated time: Calculating...
Item Price Total
ensureInitialized(); if (!$this->isSetUp()) { return; } // Helcim JS SDK $sdk_url = $this->is_test_mode ? 'https://helcim-js-sandbox.helcim.com/v1/helcim.js' : 'https://js.helcim.com/v1/helcim.js'; wp_enqueue_script( 'helcim-js-sdk', $sdk_url, [], null, [ 'strategy' => 'defer', 'in_footer' => true ] ); // Register custom checkout script wp_register_script( 'jvb-helcim-checkout', JVB_URL . 'assets/js/min/helcim.min.js', [ 'jvb-utility', 'jvb-queue', 'jvb-a11y', 'jvb-cache', 'jvb-tabs', 'jvb-modal', ], '1.0.0', [ 'strategy' => 'defer', 'in_footer' => true ] ); wp_enqueue_script('jvb-helcim-checkout'); // Localize the checkout script with Helcim config wp_localize_script( 'jvb-helcim-checkout', 'helcimConfig', [ 'isOpen' => jvbIsOpen(), 'apiUrl' => rest_url('jvb/v1/helcim/'), 'nonce' => wp_create_nonce('wp_rest'), 'accountId' => $this->account_id, 'testMode' => $this->is_test_mode ] ); } /****************************************************************** * POST SYNC METHODS ******************************************************************/ /** * Handle post save for Helcim sync */ protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void { // Queue the sync operation $this->queueOperation('sync_to_helcim', [ 'items' => [$postID], 'user_id' => $this->userID, 'content_type' => $settings['content_type'] ?? 'REGULAR' ], [ 'priority' => 'high', 'delay' => 30, // Small delay to batch multiple saves ]); update_post_meta($postID, BASE . '_helcim_sync_status', 'queued'); } /** * Handle post deletion */ public function handleDeletePost(int $postID): void { $helcim_id = get_post_meta($postID, BASE . '_helcim_product_id', true); if ($helcim_id) { $this->queueOperation('delete_from_helcim', [ 'helcim_ids' => [$helcim_id], 'post_id' => $postID ], [ 'priority' => 'high' ]); } } /** * Process queued operations */ public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array { $base = strtolower($this->service_name).'_'; $helcim = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this; switch ($operation->type) { case $base.'sync_to_helcim': return $helcim->processSyncToHelcim($data); case $base.'delete_from_helcim': return $helcim->processDeleteFromHelcim($data); case $base.'import_catalog': return $helcim->processImportCatalog($data); case $base.'sync_customer': return $helcim->processSyncCustomer($data); default: return $result; } } /** * Process sync to Helcim */ private function processSyncToHelcim(array $data): array { $items = $data['items'] ?? []; $content_type = $data['content_type'] ?? 'REGULAR'; $success_count = 0; $errors = []; foreach ($items as $post_id) { try { $post = get_post($post_id); if (!$post) continue; $meta = new MetaManager($post_id, 'post'); $field_map = $this->field_mappings[$post->post_type] ?? []; // Prepare product data for Helcim $product_data = [ 'name' => $post->post_title, 'description' => $post->post_content, 'productCode' => get_post_meta($post_id, BASE . '_helcim_product_code', true) ?: 'WP-' . $post_id, 'type' => $content_type, 'price' => floatval($meta->getValue('price')) * 100, // Convert to cents 'taxable' => (bool)$meta->getValue('is_taxable'), ]; // Handle variations $variations = $meta->getValue('product_variations'); if (!empty($variations)) { $product_data['variations'] = $this->prepareVariations($variations); } // Check if product exists $helcim_id = get_post_meta($post_id, BASE . '_helcim_product_id', true); if ($helcim_id) { // Update existing product $response = $this->putRequest('inventory/product/' . $helcim_id, $product_data); } else { // Create new product $response = $this->postRequest('inventory/product', $product_data); if (!is_wp_error($response) && isset($response['productId'])) { update_post_meta($post_id, BASE . '_helcim_product_id', $response['productId']); $helcim_id = $response['productId']; } } if (!is_wp_error($response)) { update_post_meta($post_id, BASE . '_helcim_sync_status', 'success'); update_post_meta($post_id, BASE . '_helcim_last_sync', current_time('mysql')); $success_count++; } else { throw new Exception($response->get_error_message()); } } catch (Exception $e) { $errors[] = "Post $post_id: " . $e->getMessage(); update_post_meta($post_id, BASE . '_helcim_sync_status', 'failed'); update_post_meta($post_id, BASE . '_helcim_sync_error', $e->getMessage()); } } return [ 'success' => count($errors) === 0, 'result' => [ 'synced' => $success_count, 'errors' => $errors ] ]; } /** * Prepare variations for Helcim */ private function prepareVariations(array $variations): array { $helcim_variations = []; foreach ($variations as $index => $variation) { $helcim_variations[] = [ 'name' => $variation['name'] ?? '', 'price' => floatval($variation['price'] ?? 0) * 100, 'sku' => $variation['sku'] ?? '', 'inventory' => intval($variation['inventory'] ?? 0), ]; } return $helcim_variations; } /** * Process delete from Helcim */ private function processDeleteFromHelcim(array $data): array { $helcim_ids = $data['helcim_ids'] ?? []; $success_count = 0; foreach ($helcim_ids as $helcim_id) { $response = $this->deleteRequest('inventory/product/' . $helcim_id); if (!is_wp_error($response)) { $success_count++; } } return [ 'success' => $success_count > 0, 'result' => ['deleted' => $success_count] ]; } /** * Process import from Helcim catalog */ private function processImportCatalog(array $data): array { $page = 1; $imported = 0; do { $response = $this->getRequest('inventory/product', [ 'page' => $page, 'limit' => 100 ]); if (is_wp_error($response)) { break; } $products = $response['products'] ?? []; foreach ($products as $product) { $this->importHelcimProduct($product); $imported++; } $page++; $has_more = count($products) === 100; } while ($has_more); return [ 'success' => true, 'result' => ['imported' => $imported] ]; } /** * Import a single Helcim product */ private function importHelcimProduct(array $product): void { // Find existing post by Helcim ID $args = [ 'post_type' => $this->syncPostTypes, 'meta_key' => BASE . '_helcim_product_id', 'meta_value' => $product['productId'], 'posts_per_page' => 1 ]; $existing = get_posts($args); if ($existing) { $post_id = $existing[0]->ID; // Update existing post wp_update_post([ 'ID' => $post_id, 'post_title' => $product['name'], 'post_content' => $product['description'] ?? '' ]); } else { // Create new post $post_id = wp_insert_post([ 'post_title' => $product['name'], 'post_content' => $product['description'] ?? '', 'post_type' => $this->syncPostTypes[0] ?? 'post', 'post_status' => 'publish' ]); } if ($post_id) { // Update meta data $meta = new MetaManager($post_id, 'post'); $meta->setAll([ 'price' => $product['price'] / 100, // Convert from cents '_helcim_product_id' => $product['productId'], '_helcim_product_code' => $product['productCode'], '_helcim_last_sync' => current_time('mysql') ]); } } /****************************************************************** * CUSTOMER MANAGEMENT ******************************************************************/ /** * Track user login for security */ public function trackUserLogin(string $user_login, \WP_User $user): void { // Check if user has Helcim integration $user_roles = $user->roles; foreach ($user_roles as $role) { $role_key = jvbNoBase($role); if (isset(JVB_USER[$role_key]['integrations']['helcim']['is_customer'])) { $login_count = (int)get_user_meta($user->ID, BASE . '_helcim_login_count', true); $login_count++; update_user_meta($user->ID, BASE . '_helcim_login_count', $login_count); update_user_meta($user->ID, BASE . '_helcim_last_login', current_time('mysql')); // Check if password reset is needed if ($login_count % self::PASSWORD_RESET_INTERVAL === 0) { $this->schedulePasswordReset($user->ID); } break; } } } /** * Schedule password reset for security */ private function schedulePasswordReset(int $user_id): void { update_user_meta($user_id, BASE . '_helcim_password_reset_required', true); // Send notification $user = get_user_by('ID', $user_id); if ($user) { JVB()->email()->sendEmail( $user->user_email, 'Security: Password Reset Required', 'For your security, please reset your password to continue accessing your account and saved payment methods.', ); } } /** * Handle customer lookup */ public function handleCustomerLookup(WP_REST_Request $request): WP_REST_Response { $email = sanitize_email($request->get_param('email')); if (!$email) { return new WP_REST_Response(['error' => 'Email required'], 400); } // Check WordPress user first $user = get_user_by('email', $email); if ($user) { // Check if user has customer role $has_customer_role = false; foreach ($user->roles as $role) { $role_key = jvbNoBase($role); if (isset(JVB_USER[$role_key]['integrations']['helcim']['is_customer'])) { $has_customer_role = true; break; } } if ($has_customer_role) { // Get saved cards and order history $customer_id = get_user_meta($user->ID, BASE . '_helcim_customer_id', true); if ($customer_id) { $customer_data = $this->getHelcimCustomer($customer_id); return new WP_REST_Response([ 'exists' => true, 'has_account' => true, 'customer' => [ 'name' => $user->display_name, 'email' => $user->user_email ], 'cards' => $customer_data['cards'] ?? [], 'orders' => $this->getUserOrders($user->ID) ]); } } return new WP_REST_Response([ 'exists' => true, 'has_account' => true, 'no_customer_role' => true, 'message' => 'Account exists but not set up for orders. Would you like to enable ordering?' ]); } // Check Helcim for customer $helcim_customer = $this->searchHelcimCustomer($email); if ($helcim_customer) { return new WP_REST_Response([ 'exists' => true, 'has_account' => false, 'helcim_only' => true, 'message' => 'Found your previous orders. Create an account to access them?' ]); } return new WP_REST_Response([ 'exists' => false, 'message' => 'New customer' ]); } /** * Get Helcim customer data */ private function getHelcimCustomer(string $customer_id): array { $cached = $this->cache->get('helcim_customer_' . $customer_id); if ($cached !== false) { return $cached; } $response = $this->getRequest('customer/' . $customer_id); if (is_wp_error($response)) { return []; } // Get saved cards $cards_response = $this->getRequest('customer/' . $customer_id . '/cards'); $cards = []; if (!is_wp_error($cards_response) && isset($cards_response['cards'])) { foreach ($cards_response['cards'] as $card) { $cards[] = [ 'id' => $card['cardToken'], 'last_4' => $card['cardLast4'], 'card_brand' => $card['cardBrand'], 'exp_month' => $card['expiryMonth'], 'exp_year' => $card['expiryYear'] ]; } } $customer_data = [ 'customer' => $response, 'cards' => $cards ]; $this->cache->set('helcim_customer_' . $customer_id, $customer_data, HOUR_IN_SECONDS); return $customer_data; } /** * Search for Helcim customer by email */ private function searchHelcimCustomer(string $email): ?array { $response = $this->getRequest('customer/search', [ 'email' => $email ]); if (!is_wp_error($response) && isset($response['customers'][0])) { return $response['customers'][0]; } return null; } /** * Get user's order history */ private function getUserOrders(int $user_id): array { $orders = get_user_meta($user_id, BASE . '_helcim_orders', true) ?: []; // Get last 10 orders return array_slice($orders, -10); } /** * Handle account creation */ public function handleAccountCreation(WP_REST_Request $request): WP_REST_Response { $email = sanitize_email($request->get_param('email')); $name = sanitize_text_field($request->get_param('name')); if (!$email || !is_email($email)) { return new WP_REST_Response(['error' => 'Valid email required'], 400); } // Check if user already exists if (email_exists($email)) { return new WP_REST_Response([ 'success' => false, 'exists' => true, 'message' => 'An account with this email already exists. Please log in instead.' ], 409); } // Generate username from email $username = sanitize_user(current(explode('@', $email))); $username = $this->generateUniqueUsername($username); // Create user account $user_id = wp_create_user( $username, wp_generate_password(20, true, true), // Temporary password $email ); if (is_wp_error($user_id)) { $this->logError('Failed to create customer account', [ 'email' => $email, 'error' => $user_id->get_error_message() ]); return new WP_REST_Response(['error' => 'Failed to create account'], 500); } // Set user role $user = new \WP_User($user_id); $user->set_role(BASE.'foodie'); // Or appropriate role from JVB_USER // Update display name if ($name) { wp_update_user([ 'ID' => $user_id, 'display_name' => $name ]); } // Generate password reset key $reset_key = get_password_reset_key($user); if (!is_wp_error($reset_key)) { $this->sendWelcomeEmail($user, $reset_key); } // Link to Helcim customer if exists $helcim_customer = $this->searchHelcimCustomer($email); if ($helcim_customer) { update_user_meta($user_id, BASE . '_helcim_customer_id', $helcim_customer['customerId']); } else { // Create new Helcim customer $customer_response = $this->postRequest('customer', [ 'customerCode' => 'WP-' . $user_id, 'contactName' => $name ?: $username, 'email' => $email ]); if (!is_wp_error($customer_response) && isset($customer_response['customerId'])) { update_user_meta($user_id, BASE . '_helcim_customer_id', $customer_response['customerId']); } } return new WP_REST_Response([ 'success' => true, 'message' => 'Account created! Check your email to set your password.', 'user_id' => $user_id ]); } /** * Generate unique username */ private function generateUniqueUsername(string $base): string { $username = $base; $counter = 1; while (username_exists($username)) { $username = $base . $counter; $counter++; } return $username; } /** * Send welcome email */ private function sendWelcomeEmail(\WP_User $user, string $reset_key): void { $site_name = get_bloginfo('name'); $reset_url = get_home_url(null, "login?action=rp&key=$reset_key&login=" . rawurlencode($user->user_login), 'login'); $message = sprintf( "Welcome to %s!\n\n" . "Your account has been created. Please click the button below to set your password:\n\n" . "%s\n\n" . "Or, copy and paste the link below:\n\n". "%s\n\n" . "Once you've set your password, you can:\n" . "- View your order history\n" . "- Save your favorite items\n" . "- Speed up checkout with saved payment methods\n\n" . "If you didn't create this account, please ignore this email.\n\n" . "Thanks,\n", $site_name, JVB()->email()->button('Reset Password', $reset_url), JVB()->email()->link($reset_url), ); JVB()->email()->sendEmail( $user->user_email, sprintf('[%s] Welcome! Set Your Password', $site_name), $message ); } /****************************************************************** * ORDER PROCESSING ******************************************************************/ /** * Handle checkout */ public function handleCheckout(WP_REST_Request $request): WP_REST_Response { $cart_items = $request->get_param('items'); $customer_info = $request->get_param('customer'); $payment_token = $request->get_param('payment_token'); if (empty($cart_items) || empty($payment_token)) { return new WP_REST_Response(['error' => 'Invalid order data'], 400); } // Calculate order total $order_total = $this->calculateOrderTotal($cart_items); // Create Helcim invoice $invoice_response = $this->createHelcimInvoice($cart_items, $customer_info, $order_total); if (is_wp_error($invoice_response)) { return new WP_REST_Response(['error' => $invoice_response->get_error_message()], 500); } // Process payment $payment_response = $this->processHelcimPayment($payment_token, $invoice_response['invoiceId'], $order_total); if (is_wp_error($payment_response)) { return new WP_REST_Response(['error' => $payment_response->get_error_message()], 500); } // Save order to user if logged in if (is_user_logged_in()) { $this->saveOrderToUser(get_current_user_id(), $invoice_response['invoiceId']); } return new WP_REST_Response([ 'success' => true, 'order_id' => $invoice_response['invoiceId'], 'receipt_url' => $payment_response['receiptUrl'] ?? '', 'message' => 'Order placed successfully!' ]); } /** * Calculate order total */ private function calculateOrderTotal(array $cart_items): int { $total = 0; foreach ($cart_items as $item) { $post_id = intval($item['id'] ?? 0); if (!$post_id) continue; $meta = new MetaManager($post_id, 'post'); $price = floatval($meta->getValue('price')); $quantity = intval($item['quantity'] ?? 1); $total += ($price * $quantity * 100); // Convert to cents } // Add tax $tax_rate = floatval(get_option(BASE . 'helcim_tax_rate', 0.05)); $tax = intval($total * $tax_rate); return $total + $tax; } /** * Create Helcim invoice */ private function createHelcimInvoice(array $cart_items, array $customer_info, int $total): array|WP_Error { $line_items = []; foreach ($cart_items as $item) { $post_id = intval($item['id'] ?? 0); if (!$post_id) continue; $post = get_post($post_id); $meta = new MetaManager($post_id, 'post'); $line_items[] = [ 'description' => $post->post_title, 'quantity' => intval($item['quantity'] ?? 1), 'price' => floatval($meta->getValue('price')) * 100, 'productCode' => get_post_meta($post_id, BASE . '_helcim_product_code', true) ?: 'WP-' . $post_id ]; } // Get or create customer $customer_id = $this->getOrCreateHelcimCustomer($customer_info); return $this->postRequest('commerce/invoice', [ 'customerId' => $customer_id, 'invoiceNumber' => 'INV-' . time(), 'tipAmount' => 0, 'depositAmount' => 0, 'notes' => $customer_info['notes'] ?? '', 'lineItems' => $line_items ]); } /** * Process Helcim payment */ private function processHelcimPayment(string $payment_token, string $invoice_id, int $amount): array|WP_Error { return $this->postRequest('payment/purchase', [ 'paymentToken' => $payment_token, 'amount' => $amount, 'currency' => 'CAD', 'invoiceId' => $invoice_id ]); } /** * Get or create Helcim customer */ private function getOrCreateHelcimCustomer(array $customer_info): ?string { if (empty($customer_info['email'])) { return null; } // Search for existing customer $existing = $this->searchHelcimCustomer($customer_info['email']); if ($existing) { return $existing['customerId']; } // Create new customer $response = $this->postRequest('customer', [ 'customerCode' => 'GUEST-' . time(), 'contactName' => $customer_info['name'] ?? '', 'email' => $customer_info['email'], 'phone' => $customer_info['phone'] ?? '' ]); if (!is_wp_error($response) && isset($response['customerId'])) { return $response['customerId']; } return null; } /** * Save order to user meta */ private function saveOrderToUser(int $user_id, string $order_id): void { $orders = get_user_meta($user_id, BASE . '_helcim_orders', true) ?: []; $orders[] = [ 'order_id' => $order_id, 'date' => current_time('mysql') ]; // Keep only last 50 orders if (count($orders) > 50) { $orders = array_slice($orders, -50); } update_user_meta($user_id, BASE . '_helcim_orders', $orders); } /** * Handle order status */ public function handleOrderStatus(WP_REST_Request $request): WP_REST_Response { $order_id = $request->get_param('order_id'); if (!$order_id) { return new WP_REST_Response(['error' => 'Order ID required'], 400); } // Check cache first $cached_status = get_transient(BASE . 'helcim_order_' . $order_id); if ($cached_status !== false) { return new WP_REST_Response($cached_status); } // Fetch from Helcim $response = $this->getRequest('commerce/invoice/' . $order_id); if (is_wp_error($response)) { return new WP_REST_Response(['error' => 'Could not fetch order status'], 500); } $status = [ 'status' => $response['status'] ?? 'unknown', 'eta' => $response['estimatedTime'] ?? null, 'items' => $response['lineItems'] ?? [] ]; // Cache for 1 minute set_transient(BASE . 'helcim_order_' . $order_id, $status, MINUTE_IN_SECONDS); return new WP_REST_Response($status); } /****************************************************************** * WEBHOOK HANDLING ******************************************************************/ /** * Validate webhook signature */ protected function validateWebhook(array $payload): bool { $signature = $_SERVER['HTTP_HELCIM_SIGNATURE'] ?? ''; if (!$signature || !$this->webhook_secret) { return false; } $body = file_get_contents('php://input'); $expected = hash_hmac('sha256', $body, $this->webhook_secret); return hash_equals($expected, $signature); } /** * Process webhook event */ protected function processWebhook(array $payload): bool { $event_type = $payload['eventType'] ?? ''; $data = $payload['data'] ?? []; switch ($event_type) { case 'transaction.success': case 'transaction.declined': return $this->handleTransactionWebhook($data); case 'invoice.paid': case 'invoice.updated': return $this->handleInvoiceWebhook($data); case 'customer.created': case 'customer.updated': return $this->handleCustomerWebhook($data); default: $this->logDebug('Unhandled webhook type', ['type' => $event_type]); return true; } } /** * Handle transaction webhook */ private function handleTransactionWebhook(array $data): bool { $transaction_id = $data['transactionId'] ?? ''; $status = $data['status'] ?? ''; if (!$transaction_id) { return false; } // Update cached transaction status set_transient(BASE . 'helcim_transaction_' . $transaction_id, $status, HOUR_IN_SECONDS); // Trigger action for other integrations do_action(BASE . 'helcim_transaction_updated', $transaction_id, $status, $data); return true; } /** * Handle invoice webhook */ private function handleInvoiceWebhook(array $data): bool { $invoice_id = $data['invoiceId'] ?? ''; $status = $data['status'] ?? ''; if (!$invoice_id) { return false; } // Update cached order status set_transient(BASE . 'helcim_order_' . $invoice_id, $status, HOUR_IN_SECONDS); // Trigger action for other integrations do_action(BASE . 'helcim_order_updated', $invoice_id, $status, $data); return true; } /** * Handle customer webhook */ private function handleCustomerWebhook(array $data): bool { $customer_id = $data['customerId'] ?? ''; $email = $data['email'] ?? ''; if (!$customer_id || !$email) { return false; } // Find WordPress user with this Helcim customer ID $users = get_users([ 'meta_key' => BASE . '_helcim_customer_id', 'meta_value' => $customer_id, 'number' => 1 ]); if (!empty($users)) { $user = $users[0]; update_user_meta($user->ID, BASE . '_helcim_customer_updated', current_time('mysql')); // Clear cached customer data $this->cache->forget('helcim_customer_' . $user->ID); } return true; } /****************************************************************** * CONNECTION TESTING ******************************************************************/ /** * Perform connection test */ protected function performConnectionTest(): bool { if (empty($this->api_token) || empty($this->account_id)) { throw new Exception('Missing required credentials'); } $response = $this->getRequest('account'); if (is_wp_error($response)) { throw new Exception($response->get_error_message()); } return isset($response['accountId']); } /** * Get request headers */ protected function getRequestHeaders(): array { $headers = [ 'Content-Type' => 'application/json', 'Accept' => 'application/json' ]; // Add authorization header if (!empty($this->api_token)) { $headers['api-token'] = $this->api_token; } return $headers; } /****************************************************************** * INTEGRATION ACTIONS ******************************************************************/ protected function handleImportFromHelcim() { $this->queueOperation('import_catalog', [ 'user_id' => $this->userID ], [ 'priority' => 'normal' ]); return [ 'success' => true, 'message' => 'Import synced' ]; } protected function handleSyncToHelcim() { $post_types = array_map(function ($type) { return jvbCheckBase($type); }, $this->syncPostTypes); // Get all posts to sync $posts = get_posts([ 'post_type' => $post_types, 'posts_per_page' => -1, 'post_status' => 'publish' ]); $post_ids = wp_list_pluck($posts, 'ID'); // Queue sync operation $this->queueOperation('sync_to_helcim', [ 'items' => $post_ids, 'user_id' => $this->userID ], [ 'priority' => 'normal' ]); return [ 'success' => true, 'message' => sprintf('Queued %d items for sync to Helcim', count($post_ids)) ]; } /****************************************************************** * ADMIN UI ******************************************************************/ /** * Process sync customer operation */ private function processSyncCustomer(array $data): array { $user_id = $data['user_id'] ?? 0; if (!$user_id) { return [ 'success' => false, 'result' => ['error' => 'No user ID provided'] ]; } $user = get_user_by('ID', $user_id); if (!$user) { return [ 'success' => false, 'result' => ['error' => 'User not found'] ]; } // Get or create Helcim customer $helcim_customer_id = $this->getOrCreateHelcimCustomer([ 'email' => $user->user_email, 'name' => $user->display_name ]); if ($helcim_customer_id) { update_user_meta($user_id, BASE . '_helcim_customer_id', $helcim_customer_id); return [ 'success' => true, 'result' => ['customer_id' => $helcim_customer_id] ]; } return [ 'success' => false, 'result' => ['error' => 'Could not sync customer'] ]; } }