false, 'created' => 0, 'updated' => 0, 'linked' => 0, 'skipped' => 0, 'non_usd' => 0, 'error' => 'Missing hosted connection. Please connect your store first.', ]; } $path = '/api/v1/stores/' . rawurlencode($store_public_id) . '/products?include_files=0'; $res = HostedClient::get_json($hosted_base, $path, $api_key, $request_id); if (empty($res['ok'])) { $err = isset($res['error']) && is_array($res['error']) ? (array) $res['error'] : []; $msg = (string) ($res['message'] ?? ($err['message'] ?? 'Failed to fetch products')); return [ 'ok' => false, 'created' => 0, 'updated' => 0, 'linked' => 0, 'skipped' => 0, 'non_usd' => 0, 'error' => $msg, ]; } $data = isset($res['data']) && is_array($res['data']) ? (array) $res['data'] : []; $products = isset($data['products']) && is_array($data['products']) ? (array) $data['products'] : []; $created = 0; $updated = 0; $linked = 0; $skipped = 0; $non_usd = 0; $existing_map = self::existing_downloads_by_hosted_product_public_id($seller_user_id); $now = gmdate('c'); foreach ($products as $p) { if (!is_array($p)) { $skipped += 1; continue; } $public_id = isset($p['publicId']) ? trim((string) $p['publicId']) : ''; $external_id = isset($p['externalId']) ? trim((string) $p['externalId']) : ''; $name = isset($p['name']) ? trim((string) $p['name']) : ''; $status = isset($p['status']) ? trim((string) $p['status']) : ''; $currency = isset($p['currency']) ? strtolower(trim((string) $p['currency'])) : ''; $amount_minor = isset($p['amountMinor']) && is_int($p['amountMinor']) ? (int) $p['amountMinor'] : null; if ($public_id === '' || $name === '') { $skipped += 1; continue; } $download_id = 0; // If the hosted product was created via a WP download checkout, link it back. if ($external_id !== '' && preg_match('/^wp_download_(\d+)$/', $external_id, $m)) { $maybe_id = (int) ($m[1] ?? 0); $post = $maybe_id > 0 ? get_post($maybe_id) : null; if ($post instanceof \WP_Post && ($post->post_type ?? '') === DownloadPostType::POST_TYPE) { // Only link to the current seller's products. if ($seller_user_id <= 0 || (int) ($post->post_author ?? 0) === $seller_user_id) { $download_id = $maybe_id; } } } // If not linked by external id, check our stored mapping. if ($download_id <= 0 && isset($existing_map[$public_id])) { $download_id = (int) $existing_map[$public_id]; } if ($download_id > 0) { update_post_meta($download_id, self::META_HOSTED_PRODUCT_PUBLIC_ID, $public_id); if ($external_id !== '') { update_post_meta($download_id, self::META_HOSTED_PRODUCT_EXTERNAL_ID, $external_id); } update_post_meta($download_id, self::META_HOSTED_PRODUCT_LAST_SYNC_AT, $now); if ($currency === 'usd' && is_int($amount_minor) && $amount_minor > 0) { $price = number_format(((float) $amount_minor) / 100.0, 2, '.', ''); update_post_meta($download_id, '_ToDownload_price', $price); } elseif ($currency !== '' && $currency !== 'usd') { $non_usd += 1; } if (isset($existing_map[$public_id])) { $updated += 1; } else { $linked += 1; } continue; } // Import hosted-native products as new WP downloads. $post_status = ($status === 'active') ? 'publish' : 'draft'; $new_id = wp_insert_post([ 'post_type' => DownloadPostType::POST_TYPE, 'post_status' => $post_status, 'post_title' => $name, 'post_content' => '', 'post_author' => max(0, $seller_user_id), ], true); if (is_wp_error($new_id) || (int) $new_id <= 0) { $skipped += 1; continue; } $download_id = (int) $new_id; update_post_meta($download_id, self::META_HOSTED_PRODUCT_PUBLIC_ID, $public_id); if ($external_id !== '') { update_post_meta($download_id, self::META_HOSTED_PRODUCT_EXTERNAL_ID, $external_id); } update_post_meta($download_id, self::META_HOSTED_PRODUCT_LAST_SYNC_AT, $now); if ($currency === 'usd' && is_int($amount_minor) && $amount_minor > 0) { $price = number_format(((float) $amount_minor) / 100.0, 2, '.', ''); update_post_meta($download_id, '_ToDownload_price', $price); } elseif ($currency !== '' && $currency !== 'usd') { $non_usd += 1; } $created += 1; } return [ 'ok' => true, 'created' => $created, 'updated' => $updated, 'linked' => $linked, 'skipped' => $skipped, 'non_usd' => $non_usd, ]; } /** * @return array */ private static function existing_downloads_by_hosted_product_public_id(int $seller_user_id): array { $args = [ 'post_type' => DownloadPostType::POST_TYPE, 'post_status' => ['publish', 'draft', 'pending', 'private'], 'posts_per_page' => 500, 'orderby' => 'ID', 'order' => 'ASC', 'fields' => 'ids', ]; if ($seller_user_id > 0) { $args['author'] = $seller_user_id; } $ids = get_posts($args); if (!is_array($ids)) { return []; } $map = []; foreach ($ids as $id) { $pid = (int) $id; if ($pid <= 0) { continue; } $pub = trim((string) get_post_meta($pid, self::META_HOSTED_PRODUCT_PUBLIC_ID, true)); if ($pub !== '') { $map[$pub] = $pid; } } return $map; } private static function request_id(): string { try { return bin2hex(random_bytes(8)); } catch (\Throwable $e) { return (string) wp_generate_password(16, false, false); } } }