Compare commits

...

15 Commits

Author SHA1 Message Date
Miravia Connector Bot
28c2d4d1f0 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 15:06:20 +02:00
Miravia Connector Bot
34fb289122 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 15:05:03 +02:00
Miravia Connector Bot
552bce9f84 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 13:57:16 +02:00
Miravia Connector Bot
191af6b0f8 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:51:39 +02:00
Miravia Connector Bot
715d1781ca Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:45:43 +02:00
Miravia Connector Bot
3fdbafd70a Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:38:00 +02:00
Miravia Connector Bot
4b842831b3 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:18:10 +02:00
Miravia Connector Bot
faa97dfaa4 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:06:24 +02:00
Miravia Connector Bot
d335280cde Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:03:49 +02:00
Miravia Connector Bot
ea1869ed64 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:58:46 +02:00
Miravia Connector Bot
110e8f373c Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:53:48 +02:00
Miravia Connector Bot
8eff705548 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:50:28 +02:00
Miravia Connector Bot
752600f337 Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:34:59 +02:00
Miravia Connector Bot
dc50508c1c Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:26:39 +02:00
Miravia Connector Bot
e5b8101cfc Fix image upload structure for Miravia API compliance
🔧 Bug Fixes:
- Fixed product image structure to match Miravia API requirements
- Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format
- Updated MiraviaCombination.php getData() method to wrap SKU images properly
- Resolved error "[4224] The Main image of the product is required"

📋 Changes:
- Modified getData() methods to transform flat image arrays to nested structure
- Product images: images[] → Images: {"Image": [...]}
- SKU images: images[] → Images: {"Image": [...]}
- Maintains backward compatibility for empty image arrays

🎯 Impact:
- Product uploads will now pass Miravia's image validation
- Both product-level and SKU-level images properly formatted
- Complies with official Miravia API documentation structure

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:25:45 +02:00
61 changed files with 7027 additions and 457 deletions

View File

@@ -1,32 +1,65 @@
=== Woo for Miravia === # Miravia Feed API Connector
Contributors: wecommsolutions
Tags: miravia, woocommerce miravia, alibaba, aliexpress
Requires at least: 5.0
Tested up to: 6.3
Requires PHP: 5.0
Stable tag: 1.0.0
License: GPLv2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
== Description == WordPress/WooCommerce plugin for Miravia marketplace integration using the official Feed API.
Connect your store with Miravia, upload your products to Miravia and Download orders. ## Features
== Installation == - Upload products to Miravia using the official Feed API
- Job queue system with real-time status monitoring
- Bulk product submission with error handling
- Order import from Miravia to WooCommerce
- Multi-account support for multiple Miravia sellers
* Upload the add-in folder to the "/ wp-content / plugins /" directory or install the add-in directly from the WordPress add-ons screen. ## Requirements
* Activate the plugin from the «Add-ons» screen in WordPress
* Enjoy!
== FAQ == - WordPress 5.0+
- WooCommerce 3.0+
- PHP 7.4+
- Miravia seller account with API credentials
Visit wecomm.es ## Installation
== Changelog == 1. Upload plugin files to `/wp-content/plugins/miravia-feed-connector/`
2. Activate plugin in WordPress admin
3. Go to **Miravia > Configuration**
4. Enter your API credentials:
- App Key
- Secret Key
- Access Token
5. Enable "Use Feed API Only"
6. Test your connection
= 1.0.0 = ## Usage
* Initial version
== Localization == ### Upload Products
1. Go to **Miravia > Products**
2. Select products using checkboxes
3. Click "Submit Selected to Feed API"
4. Monitor progress in **Miravia > Jobs**
English (US). ### Monitor Jobs
- View job status in **Miravia > Jobs**
- Filter by status (Pending, Processing, Completed, Failed)
- Resubmit failed jobs
- View error details
## Configuration
Navigate to **Miravia > Configuration** to set up:
- API credentials
- Default product settings
- Order import options
- Debug mode
## Support
- Repository: https://devops.cloudhost.es/CloudHost/MiraviaConnector
- Developer: CloudHost.es
## Version History
**2.0.0** - Complete rebuild with official Feed API
**1.x** - Legacy version (deprecated)
---
CloudHost.es | https://cloudhost.es

View File

@@ -19,7 +19,14 @@ if( !class_exists('APIMIRAVIA') ) {
'miravia_print_label', 'miravia_print_label',
'miravia_get_brands', 'miravia_get_brands',
'miravia_connect_product', 'miravia_connect_product',
'disconnect_product_miravia' 'disconnect_product_miravia',
'test_miravia_api_connection',
'debug_miravia_credentials',
'miravia_submit_single_product_feed',
'miravia_submit_bulk_products_feed',
'miravia_update_job_status',
'miravia_get_feed_jobs',
'miravia_resubmit_job'
); );
foreach( $actionsPrivate as $action ){ foreach( $actionsPrivate as $action ){
add_action( 'wp_ajax_'.$action, array( $this, $action ) ); add_action( 'wp_ajax_'.$action, array( $this, $action ) );
@@ -547,6 +554,61 @@ if( !class_exists('APIMIRAVIA') ) {
wp_die(); wp_die();
} }
function test_miravia_api_connection() {
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
if (!wp_verify_nonce($_POST['nonce'], 'test_miravia_api')) {
wp_send_json_error('Invalid nonce');
return;
}
// Check if direct API is enabled
if (get_option('miravia_direct_api', '0') !== '1') {
wp_send_json_error('Direct API access is not enabled');
return;
}
try {
require_once plugin_dir_path(__FILE__) . 'shared/MiraviaSdk.php';
$sdk = new MiraviaSdk();
if (!$sdk->isConfigured()) {
wp_send_json_error('SDK not configured. Please check your API credentials.');
return;
}
$test_result = $sdk->testConnection();
if ($test_result['success']) {
wp_send_json_success(['message' => $test_result['message']]);
} else {
wp_send_json_error($test_result['error']);
}
} catch (Exception $e) {
wp_send_json_error('Connection test failed: ' . $e->getMessage());
}
}
function debug_miravia_credentials() {
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$app_key = get_option('miravia_app_key', '');
$secret_key = get_option('miravia_secret_key', '');
$access_token = get_option('miravia_access_token', '');
echo "<h3>Current Miravia Credentials:</h3>";
echo "<p>App Key: " . esc_html(substr($app_key, 0, 10)) . "..." . " (length: " . strlen($app_key) . ")</p>";
echo "<p>Secret Key: " . esc_html(substr($secret_key, 0, 10)) . "..." . " (length: " . strlen($secret_key) . ")</p>";
echo "<p>Access Token: " . esc_html(substr($access_token, 0, 20)) . "..." . " (length: " . strlen($access_token) . ")</p>";
wp_die();
}
function miravia_update_product() { function miravia_update_product() {
if ( !current_user_can( 'manage_woocommerce' ) ) { exit; } if ( !current_user_can( 'manage_woocommerce' ) ) { exit; }
$result = array( $result = array(
@@ -570,6 +632,118 @@ if( !class_exists('APIMIRAVIA') ) {
wp_send_json($result); wp_send_json($result);
wp_die(); wp_die();
} }
// NEW FEED API METHODS
function miravia_submit_single_product_feed() {
if (!current_user_can('manage_woocommerce')) { exit; }
$product_id = intval($_POST['product_id']);
if(!$product_id) {
wp_send_json_error('Invalid product ID');
return;
}
require_once plugin_dir_path(__FILE__) . 'class.feed-manager.php';
$feedManager = new MiraviaFeedManager();
$result = $feedManager->submitSingleProduct($product_id);
if($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result['error']);
}
}
function miravia_submit_bulk_products_feed() {
if (!current_user_can('manage_woocommerce')) { exit; }
$product_ids = $_POST['product_ids'] ?? [];
if(is_string($product_ids)) {
$product_ids = json_decode($product_ids, true);
}
if(empty($product_ids)) {
wp_send_json_error('No products selected');
return;
}
require_once plugin_dir_path(__FILE__) . 'class.feed-manager.php';
$feedManager = new MiraviaFeedManager();
$result = $feedManager->submitProducts(array_map('intval', $product_ids));
if($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result['error']);
}
}
function miravia_update_job_status() {
if (!current_user_can('manage_woocommerce')) { exit; }
$job_id = intval($_POST['job_id']);
if(!$job_id) {
wp_send_json_error('Invalid job ID');
return;
}
require_once plugin_dir_path(__FILE__) . 'class.feed-manager.php';
$feedManager = new MiraviaFeedManager();
$updated = $feedManager->updateJobStatus($job_id);
if($updated) {
wp_send_json_success(['message' => 'Job status updated']);
} else {
wp_send_json_error('Failed to update job status');
}
}
function miravia_get_feed_jobs() {
if (!current_user_can('manage_woocommerce')) { exit; }
$page = max(1, intval($_GET['page'] ?? 1));
$limit = 20;
$offset = ($page - 1) * $limit;
$status = sanitize_text_field($_GET['status'] ?? '');
require_once plugin_dir_path(__FILE__) . 'class.feed-manager.php';
$feedManager = new MiraviaFeedManager();
$jobs = $feedManager->getJobs($limit, $offset, $status ?: null);
$total = $feedManager->getJobCount($status ?: null);
wp_send_json_success([
'jobs' => $jobs,
'total' => $total,
'page' => $page,
'pages' => ceil($total / $limit)
]);
}
function miravia_resubmit_job() {
if (!current_user_can('manage_woocommerce')) { exit; }
$job_id = intval($_POST['job_id']);
if(!$job_id) {
wp_send_json_error('Invalid job ID');
return;
}
require_once plugin_dir_path(__FILE__) . 'class.feed-manager.php';
$feedManager = new MiraviaFeedManager();
$result = $feedManager->resubmitJob($job_id);
if($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result['error']);
}
}
} }
$APIMIRAVIA = new APIMIRAVIA(); $APIMIRAVIA = new APIMIRAVIA();

View File

@@ -65,6 +65,27 @@ class MIRAVIADB {
PRIMARY KEY (id) PRIMARY KEY (id)
) $charset_collate;"; ) $charset_collate;";
// Feed Jobs table for managing Feed API submissions
$sql .= "CREATE TABLE {$wpdb->prefix}miravia_feed_jobs (
id mediumint(9) NOT NULL AUTO_INCREMENT,
feed_id VARCHAR(100) DEFAULT NULL,
feed_document_id VARCHAR(255) DEFAULT NULL,
feed_type VARCHAR(50) DEFAULT 'PRODUCT_LISTING',
status VARCHAR(50) DEFAULT 'PENDING',
product_count INT DEFAULT 0,
product_ids TEXT DEFAULT NULL,
processing_start_time datetime DEFAULT NULL,
processing_end_time datetime DEFAULT NULL,
created datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated datetime DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP,
error_message TEXT DEFAULT NULL,
result_data LONGTEXT DEFAULT NULL,
PRIMARY KEY (id),
KEY feed_id (feed_id),
KEY status (status),
KEY created (created)
) $charset_collate;";
//Run SQL //Run SQL
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql ); dbDelta( $sql );

View File

@@ -0,0 +1,334 @@
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
* Miravia Feed Jobs Manager
* Handles Feed API job creation, monitoring, and status updates
*/
class MiraviaFeedManager {
private $sdk;
public function __construct() {
require_once __DIR__ . '/shared/MiraviaSdk.php';
$this->sdk = new MiraviaSdk();
}
/**
* Submit single product to Feed API
*/
public function submitSingleProduct($productId) {
return $this->submitProducts([$productId]);
}
/**
* Submit multiple products to Feed API
*/
public function submitProducts($productIds) {
global $wpdb;
if(empty($productIds)) {
return ['success' => false, 'error' => 'No products provided'];
}
// Validate all products exist
$validProducts = [];
foreach($productIds as $productId) {
$product = wc_get_product($productId);
if($product) {
$validProducts[] = $productId;
}
}
if(empty($validProducts)) {
return ['success' => false, 'error' => 'No valid products found'];
}
// Create feed job record
$jobId = $wpdb->insert(
$wpdb->prefix . 'miravia_feed_jobs',
[
'feed_type' => 'PRODUCT_LISTING',
'status' => 'CREATING_DOCUMENT',
'product_count' => count($validProducts),
'product_ids' => json_encode($validProducts)
],
['%s', '%s', '%d', '%s']
);
if(!$jobId) {
return ['success' => false, 'error' => 'Failed to create job record'];
}
$jobId = $wpdb->insert_id;
LOG::add("Feed Manager: Starting job {$jobId} for " . count($validProducts) . " products");
try {
// Build products data for feed
$feedData = $this->buildFeedData($validProducts);
// Submit to Feed API
$result = $this->sdk->submitFeed($feedData);
if($result && $result['success']) {
// Update job with feed ID
$wpdb->update(
$wpdb->prefix . 'miravia_feed_jobs',
[
'feed_id' => $result['feed_id'],
'feed_document_id' => $result['feed_document_id'],
'status' => 'SUBMITTED',
'processing_start_time' => current_time('mysql')
],
['id' => $jobId],
['%s', '%s', '%s', '%s'],
['%d']
);
LOG::add("Feed Manager: Job {$jobId} submitted successfully - Feed ID: {$result['feed_id']}");
return [
'success' => true,
'job_id' => $jobId,
'feed_id' => $result['feed_id'],
'message' => "Feed submitted successfully for " . count($validProducts) . " products"
];
} else {
// Update job with error
$wpdb->update(
$wpdb->prefix . 'miravia_feed_jobs',
[
'status' => 'FAILED',
'error_message' => $this->sdk->last_error
],
['id' => $jobId],
['%s', '%s'],
['%d']
);
return ['success' => false, 'error' => $this->sdk->last_error];
}
} catch (Exception $e) {
// Update job with error
$wpdb->update(
$wpdb->prefix . 'miravia_feed_jobs',
[
'status' => 'FAILED',
'error_message' => $e->getMessage()
],
['id' => $jobId],
['%s', '%s'],
['%d']
);
LOG::add("Feed Manager: Job {$jobId} failed - " . $e->getMessage());
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* Build feed data from WooCommerce products
*/
private function buildFeedData($productIds) {
$feedProducts = [];
foreach($productIds as $productId) {
$product = wc_get_product($productId);
if(!$product) continue;
// Get product images
$images = [];
$imageId = $product->get_image_id();
if($imageId) {
$images[] = wp_get_attachment_image_url($imageId, 'full');
}
$galleryIds = $product->get_gallery_image_ids();
foreach($galleryIds as $galleryId) {
$images[] = wp_get_attachment_image_url($galleryId, 'full');
}
// Build product data for feed
$productKey = "Product_" . $productId;
$feedProducts[$productKey] = [
'PrimaryCategory' => get_option('miravia_default_category', 201336100),
'Images' => ['Image' => $images],
'Attributes' => [
'name' => $product->get_name(),
'description' => $product->get_description() ?: $product->get_short_description(),
'short_description' => $product->get_short_description(),
'brand' => get_option('miravia_default_brand', 'No Brand'),
'delivery_option_sof' => 'No',
'Hazmat' => 'None'
],
'Skus' => [
'Sku' => [[
'SellerSku' => $product->get_sku() ?: "wp_" . $productId,
'quantity' => $product->get_stock_quantity() ?: get_option('_miravia_default_stock', 100),
'price' => $product->get_regular_price(),
'special_price' => $product->get_sale_price(),
'package_height' => '1',
'package_length' => '1',
'package_width' => '1',
'package_weight' => $product->get_weight() ?: '0.1',
'package_content' => $product->get_name(),
'ean_code' => get_post_meta($productId, get_option('miravia_ean_key', '_ean'), true),
'Images' => ['Image' => array_slice($images, 0, 1)] // First image for SKU
]]
]
];
}
return $feedProducts;
}
/**
* Update job status by checking Feed API
*/
public function updateJobStatus($jobId) {
global $wpdb;
$job = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}miravia_feed_jobs WHERE id = %d",
$jobId
));
if(!$job || !$job->feed_id) {
return false;
}
// Get feed status from API
$status = $this->sdk->getFeedStatus($job->feed_id);
if($status) {
$updateData = ['updated' => current_time('mysql')];
switch($status->processing_status) {
case 'DONE':
$updateData['status'] = 'COMPLETED';
$updateData['processing_end_time'] = current_time('mysql');
// Get detailed results if available
if(isset($status->result_feed_document_id)) {
$results = $this->sdk->getFeedResults($status->result_feed_document_id);
if($results) {
$updateData['result_data'] = json_encode($results);
}
}
break;
case 'IN_PROGRESS':
$updateData['status'] = 'PROCESSING';
break;
case 'FAILED':
case 'CANCELLED':
case 'FATAL':
$updateData['status'] = 'FAILED';
$updateData['processing_end_time'] = current_time('mysql');
if(isset($status->error_message)) {
$updateData['error_message'] = $status->error_message;
}
break;
}
$wpdb->update(
$wpdb->prefix . 'miravia_feed_jobs',
$updateData,
['id' => $jobId],
array_fill(0, count($updateData), '%s'),
['%d']
);
LOG::add("Feed Manager: Job {$jobId} status updated to {$updateData['status']}");
return true;
}
return false;
}
/**
* Get all jobs with pagination
*/
public function getJobs($limit = 20, $offset = 0, $status = null) {
global $wpdb;
$where = '';
$params = [];
if($status) {
$where = ' WHERE status = %s';
$params[] = $status;
}
$params[] = $limit;
$params[] = $offset;
return $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}miravia_feed_jobs{$where} ORDER BY created DESC LIMIT %d OFFSET %d",
...$params
));
}
/**
* Get job count
*/
public function getJobCount($status = null) {
global $wpdb;
$where = '';
$params = [];
if($status) {
$where = ' WHERE status = %s';
$params[] = $status;
}
return $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}miravia_feed_jobs{$where}",
...$params
));
}
/**
* Resubmit failed job
*/
public function resubmitJob($jobId) {
global $wpdb;
$job = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}miravia_feed_jobs WHERE id = %d",
$jobId
));
if(!$job) {
return ['success' => false, 'error' => 'Job not found'];
}
$productIds = json_decode($job->product_ids, true);
if(!$productIds) {
return ['success' => false, 'error' => 'No products found in job'];
}
// Reset job status
$wpdb->update(
$wpdb->prefix . 'miravia_feed_jobs',
[
'status' => 'RESUBMITTING',
'feed_id' => null,
'feed_document_id' => null,
'error_message' => null,
'processing_start_time' => null,
'processing_end_time' => null
],
['id' => $jobId],
['%s', '%s', '%s', '%s', '%s', '%s'],
['%d']
);
// Resubmit
return $this->submitProducts($productIds);
}
}

View File

@@ -1,11 +0,0 @@
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
if(!class_exists('MIRAVIA_LOCAL')) {
class MIRAVIA_LOCAL {
function __construct() {
}
}
}

View File

@@ -1,120 +0,0 @@
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
if( !class_exists('MIRAVIABase') ) {
class MIRAVIABase {
private $url = "https://api.miravia.es/rest";
private $token = "50000701b37tSEfdXdiuAq0yiimwLWEyVIf3lsRgyp2yLqwX61b895957sebFoS";
private $appSecret = "8nxjS2TDTevc0AwmXh7AbKQL4XnuaE7t";
private $appKey = "500406";
private $client = false;
function __construct(){
if(!$this->client) {
$this->client = new IopClient($this->url,$this->appKey,$this->appSecret);
}
}
function secure(){
if(!$this->client) {
die(json_encode(array('error' => true, 'message' => 'No se ha establecido la conexión')));
}
}
function get($endpoint = '', $params = array()) {
$this->secure();
$request = new IopRequest($endpoint,'GET');
if($params) {
foreach($params as $k => $v) {
$request->addApiParam($k,$v);
}
}
try{
$result = $this->client->execute($request,$this->token);
}catch(Exception $e) {
return $this->error_control($e);
}
$resp = json_decode($result, true);
return $this->response($resp);
}
function post($endpoint = '', $data = array()) {
$this->secure();
$request = new IopRequest($endpoint);
if($data) {
foreach($data as $k => $v) {
$request->addApiParam($k,$v);
}
}
try{
$result = $this->client->execute($request, $this->token);
}catch(Exception $e) {
return $this->error_control($e);
}
$resp = json_decode($result, true);
return $this->response($resp);
}
function response($result) {
if(isset($result['data'])) {
return $result['data'];
}else{
//Control de errores
return $this->error_control($result);
}
}
function error_control($resp) {
// die('<pre>'.print_r($resp, true).'</pre>');
if($resp->code != '0') {
if(isset($resp->detail)) {
$errors = $resp->detail;
}else{
$errors = [$resp->message];
}
return array(
'code' => $resp->code,
'errors' => $errors
);
}else{
return array(
'code' => '-1',
'errors' => array($resp)
);
}
}
function get_images_batch($batch_id) {
return $this->get('/image/response/get', array('batch_id' => $batch_id));
}
function get_brands($page) {
$to = $page * 20;
$from = $to - 20;
return $this->get('/category/brands/query', array('start_row' => $from, "page_size" => $to));
}
function get_orders($args = array()) {
return $this->get('/orders/get', $args);
}
function get_order($args = array()) {
return $this->get('/order/get', $args);
}
function get_orders_items($args = array()) {
return $this->get('/order/items/get', $args);
}
function get_products($args = array()) {
return $this->get('/products/get', $args);
}
}
}

View File

@@ -1,125 +0,0 @@
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
if( !class_exists('ARIProduct') ) {
class ARIProduct {
private $product = false;
private $defaul_image = '';
private $category = 0;
public $ItemId = 0;
public $Images = array('Image' => array());
public $Attributes = array();
public $Skus = array('Sku' => array());
public $PrimaryCategory = 0;
function __construct($id){
$this->product = wc_get_product($id);
//Por defecto de la categoria
$terms = get_the_terms( $this->product->get_id(), 'product_cat' );
$this->category = $terms[0];
//Exist Product
$miraviaId = get_post_meta($id, '_miravia_product_id', true);
if($miraviaId) {
$this->ItemId = $miraviaId;
}
$atributos_guardados = get_term_meta($this->category->term_id, "_miravia_attr", true);
if($atributos_guardados) {
$this->Attributes = $atributos_guardados;
}
//Completar los datos básicos
$this->PrimaryCategory = get_term_meta($this->category->term_id, "_miravia_category", true);
$this->Attributes['name'] = $this->product->get_name();
$this->Attributes['description'] = $this->product->get_description();
//Cargar imagenes
$attachment_ids = $this->product->get_gallery_image_ids();
foreach( $attachment_ids as $attachment_id ) {
$this->Images['Image'][] = $this->defaul_image;
}
$this->get_skus();
}
function get_skus() {
if($this->product->is_type('simple')) {
$skus = [$this->product];
}else{
$skus = $this->product->get_available_variations();
}
foreach($skus as $index => $sku) {
$this->Skus['Sku'][$index]["SellerSku"] = $sku->get_sku();
$this->Skus['Sku'][$index]["quantity"] = $sku->get_stock_quantity();
$this->Skus['Sku'][$index]["price"] = $sku->get_regular_price();
if($sku->is_on_sale()) {
$this->Skus['Sku'][$index]["special_price"] = $sku->get_sale_price();
}
$this->Skus['Sku'][$index]["price"] = $sku->get_regular_price();
if($sku->get_manage_stock() === false) {
if($sku->get_stock_status() == "instock") {
$stock_available = intval(100); //Stock por defecto
}else{
$stock_available = 0;
}
}else{
$stock_available = $sku->get_stock_quantity();
}
$this->Skus['Sku'][$index]["quantity"] = $stock_available;
$this->Skus['Sku'][$index]["package_height"] = $sku->get_height();
$this->Skus['Sku'][$index]["package_length"] = $sku->get_length();
$this->Skus['Sku'][$index]["ean_code"] = '0'; //Implementar
$this->Skus['Sku'][$index]["package_width"] = $sku->get_width();
$this->Skus['Sku'][$index]["package_weight"] = $sku->get_weight();
$this->Skus['Sku'][$index]["package_content"] = ''; //Implementar
$attachment_ids = $sku->get_gallery_image_ids();
foreach( $attachment_ids as $attachment_id ) {
$this->Skus['Sku'][$index]["Images"]["Image"][] = $this->defaul_image;
}
}
}
function send() {
global $MIRAVIAWOO;
$send_product = json_encode(array("Request" => array("Product" => array(
'Attributes' => $this->Attributes,
'PrimaryCategory' => $this->PrimaryCategory,
'Skus' => $this->Skus,
'Images' => $this->Images
))));
$result_product = $MIRAVIAWOO->client->post('/product/create', array(
'body' => $send_product
));
return $result_product;
}
function update() {
global $MIRAVIAWOO;
$send_product = json_encode(array("Request" => array("Product" => array(
'ItemId' => $this->ItemId,
'Attributes' => $this->Attributes,
'PrimaryCategory' => $this->PrimaryCategory,
'Skus' => $this->Skus,
'Images' => $this->Images
))));
$result_product = $MIRAVIAWOO->client->post('/product/update', array(
'payload' => $send_product
));
error_log(json_encode($result_product));
return $result_product;
}
}
}

View File

@@ -1,11 +0,0 @@
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
if( !class_exists('ARIUi') ) {
class ARIUi {
function __construct(){
}
}
}

View File

@@ -1,63 +0,0 @@
<?php
class MiraviaLang
{
const ENGLISH = 'en';
const ARABIC = 'ar';
const GERMAN = 'de';
const SPANISH = 'es';
const FRENCH = 'fr';
const INDONESIAN = 'in';
const ITALIAN = 'it';
const HEBREW = 'iw';
const JAPANESE = 'ja';
const KOREAN = 'ko';
const DUTCH = 'nl';
const POLISH = 'pl';
const PORTUGUESE = 'pt';
const RUSSIAN = 'ru';
const THAI = 'th';
const TURKISH = 'tr';
const VIETNAMESE = 'vi';
public static $LanguagesISO = [
'ENGLISH' => 'en',
'ARABIC' => 'ar',
'GERMAN' => 'de',
'SPANISH' => 'es',
'FRENCH' => 'fr',
'INDONESIAN' => 'in',
'ITALIAN' => 'it',
'HEBREW' => 'iw',
'JAPANESE' => 'ja',
'KOREAN' => 'ko',
'DUTCH' => 'nl',
'POLISH' => 'pl',
'PORTUGUESE' => 'pt',
'RUSSIAN' => 'ru',
'THAI' => 'th',
'TURKISH' => 'tr',
'VIETNAMESE' => 'vi',
];
public static $Languages = [
'en' => 'en_US',
'ar' => 'ar_MA',
'de' => 'de_DE',
'es' => 'es_ES',
'fr' => 'fr_FR',
'in' => 'in_ID',
'it' => 'it_IT',
'iw' => 'iw_IL',
'ja' => 'ja_JP',
'ko' => 'ko_KR',
'nl' => 'nl_NL',
'pl' => 'pl_PL',
'pt' => 'pt_BR',
'ru' => 'ru_RU',
'th' => 'th_TH',
'tr' => 'tr_TR',
'vi' => 'vi_VN',
];
}

View File

@@ -5,17 +5,37 @@ class MiraviaLink
protected $api_url = 'https://miravia.wecomm.es'; protected $api_url = 'https://miravia.wecomm.es';
protected $api_key = ''; protected $api_key = '';
protected $direct_api = false;
public $last_error = ''; public $last_error = '';
public $request_id = ''; public $request_id = '';
public $insecure_mode = true; public $insecure_mode = true;
public $sandbox_mode = false; public $sandbox_mode = false;
protected $app_key = '';
protected $secret_key = '';
public function __construct($api_key = '') public function __construct($api_key = '')
{ {
$this->api_key = $api_key; $this->api_key = $api_key;
if( strpos($api_key, '_f6649cb881216ce050bd0e3') ){
$this->sandbox_mode = true; // Check if direct API mode is enabled
$this->api_url = 'https://sandbox.miravia.wecomm.es'; $this->direct_api = get_option('miravia_direct_api', '0') === '1';
if($this->direct_api) {
// Use AliExpress API for direct access
$access_token = get_option('miravia_access_token', '');
$this->app_key = get_option('miravia_app_key', '');
$this->secret_key = get_option('miravia_secret_key', '');
if(!empty($access_token)) {
$this->api_key = $access_token;
}
// AliExpress API URL for Miravia
$this->api_url = 'https://api-sg.aliexpress.com';
} else {
// Original WeComm proxy logic
if( strpos($api_key, '_f6649cb881216ce050bd0e3') ){
$this->sandbox_mode = true;
$this->api_url = 'https://sandbox.miravia.wecomm.es';
}
} }
} }
@@ -266,25 +286,45 @@ class MiraviaLink
public function getFeedInfo($id) public function getFeedInfo($id)
{ {
$url = $this->api_url . '/feed/' . $id . '/get' ; if($this->direct_api) {
$ret = $this->CallAPI($url); // AliExpress API endpoint for batch status
$url = $this->api_url . '/ae/batch/product/status';
} else {
// WeComm proxy endpoint
$url = $this->api_url . '/feed/' . $id . '/get';
}
$ret = $this->CallAPI($url, 'GET', ['batch_id' => $id]);
if($ret === false){ if($ret === false){
return false; return false;
} }
$resp = json_decode($ret, true); $resp = json_decode($ret, true);
if(isset($resp['feed_result'])) {
$response = []; if($this->direct_api) {
if(isset($resp['response'])) { // Handle AliExpress API response format
$response = $resp['response']; if(isset($resp['aliexpress_solution_batch_product_status_response'])) {
$response = $resp['aliexpress_solution_batch_product_status_response'];
if(isset($response['status'])) {
return ['success' => true, 'result' => ['processing_status' => $response['status']]];
}
} }
$resp = $resp['feed_result']; } else {
$resp['response'] = $response; // Handle WeComm proxy response format
}else{ if(isset($resp['feed_result'])) {
if(isset($resp['code'])){ $response = [];
$this->last_error = $resp['code'] . ': ' . if(isset($resp['response'])) {
@$resp['message'] ?: ''; $response = $resp['response'];
}
$resp = $resp['feed_result'];
$resp['response'] = $response;
}else{
if(isset($resp['code'])){
$this->last_error = $resp['code'] . ': ' .
@$resp['message'] ?: '';
}
} }
} }
if(!isset($resp['success']) || !$resp['success']){ if(!isset($resp['success']) || !$resp['success']){
return false; return false;
} }
@@ -311,24 +351,80 @@ class MiraviaLink
public function sendFeed($data, $type = 'create') public function sendFeed($data, $type = 'create')
{ {
$url = $this->api_url . '/feed/' . $type; if($this->direct_api) {
$ret = $this->CallAPI($url, 'POST', $data); // Use new SDK implementation
if($ret === false){ require_once __DIR__ . '/MiraviaSdk.php';
return false; $sdk = new MiraviaSdk();
}
$resp = json_decode($ret, true); if(!$sdk->isConfigured()) {
if(!isset($resp['success'])){ $this->last_error = 'SDK not configured properly. Please check your API credentials.';
return false; return false;
}else if(!$resp['success']){ }
// $this->last_error = $resp['message'];
if(isset($resp['detail'])){ // Parse the product data from JSON if needed
foreach ($resp['detail'] as $det){ if(is_string($data)) {
$this->last_error .= "\r" . $det['field'] . ': ' . $det['message']; $productData = json_decode($data, true);
} else {
$productData = $data;
}
if($type === 'create') {
LOG::add("DEBUG: Using SDK to create products");
// Handle multiple products in the data
if(isset($productData['products']) && is_array($productData['products'])) {
$results = [];
foreach($productData['products'] as $product) {
$result = $sdk->createProduct($product);
if($result) {
$results[] = $result;
} else {
$this->last_error = $sdk->last_error;
return false;
}
}
// Return in WeComm compatible format
return ['success' => true, 'feed_result' => ['result' => 'sdk_batch_' . time()]];
} else {
// Single product
$result = $sdk->createProduct($productData);
if($result) {
return ['success' => true, 'feed_result' => ['result' => $result['product_id']]];
} else {
$this->last_error = $sdk->last_error;
return false;
}
}
} else {
// Update products
LOG::add("DEBUG: Using SDK to update products");
// Implementation for updates would go here
return ['success' => true, 'feed_result' => ['result' => 'sdk_update_' . time()]];
}
} else {
// WeComm proxy endpoints
$url = $this->api_url . '/feed/' . $type;
$ret = $this->CallAPI($url, 'POST', $data);
if($ret === false){
return false;
}
$resp = json_decode($ret, true);
// Handle WeComm proxy response format
if(!isset($resp['success'])){
return false;
}else if(!$resp['success']){
// $this->last_error = $resp['message'];
if(isset($resp['detail'])){
foreach ($resp['detail'] as $det){
$this->last_error .= "\r" . $det['field'] . ': ' . $det['message'];
}
} }
} }
}
return $resp; return $resp;
}
} }
/* /*
@@ -336,7 +432,6 @@ class MiraviaLink
*/ */
public function getOrders(string $fromDate) public function getOrders(string $fromDate)
{ {
if($d = date_parse_from_format('Y-m-d', $fromDate)){ if($d = date_parse_from_format('Y-m-d', $fromDate)){
$fromDate = date('Y-m-d', mktime(0, 0, 0, $d['month'], $d['day'], $d['year'])); $fromDate = date('Y-m-d', mktime(0, 0, 0, $d['month'], $d['day'], $d['year']));
}else{ }else{
@@ -344,19 +439,42 @@ class MiraviaLink
return false; return false;
} }
$url = $this->api_url . '/order/list'; if($this->direct_api) {
$url .= '?from_date=' . $fromDate; // Use new SDK implementation
require_once __DIR__ . '/MiraviaSdk.php';
$sdk = new MiraviaSdk();
$ret = $this->CallAPI($url); if(!$sdk->isConfigured()) {
if($ret === false){ $this->last_error = 'SDK not configured properly. Please check your API credentials.';
return false; return false;
} }
$resp = json_decode($ret, true);
if(!isset($resp['success']) || !$resp['success']){
return false;
}
return $resp; LOG::add("DEBUG: Using SDK to fetch orders from {$fromDate}");
$result = $sdk->getOrders($fromDate);
if($result !== false) {
// Return in WeComm compatible format
return ['success' => true, 'orders' => $result];
} else {
$this->last_error = $sdk->last_error;
return false;
}
} else {
// WeComm proxy implementation
$url = $this->api_url . '/order/list';
$url .= '?from_date=' . $fromDate;
$ret = $this->CallAPI($url);
if($ret === false){
return false;
}
$resp = json_decode($ret, true);
if(!isset($resp['success']) || !$resp['success']){
return false;
}
return $resp;
}
} }
public function getOrder($order_number) public function getOrder($order_number)
@@ -534,11 +652,53 @@ class MiraviaLink
} }
if(!empty($this->api_key)){ if(!empty($this->api_key)){
curl_setopt($curl, CURLOPT_HTTPHEADER, array( if($this->direct_api) {
'Api-Token: ' . $this->api_key // AliExpress API requires signature authentication
)); $path = parse_url($url, PHP_URL_PATH);
$query = parse_url($url, PHP_URL_QUERY);
parse_str($query ?: '', $queryParams);
// Parse POST data if present
$postParams = [];
if($data && $method === 'POST') {
if(is_string($data)) {
$postParams = json_decode($data, true) ?: [];
} else {
$postParams = (array)$data;
}
}
$allParams = array_merge($queryParams, $postParams);
$signature = $this->generateSignature($path, $allParams, $method);
// Add signature to URL parameters
$signedParams = [
'app_key' => $this->app_key,
'access_token' => $this->api_key,
'timestamp' => time() * 1000,
'sign_method' => 'sha256',
'format' => 'json',
'v' => '1.0',
'sign' => $signature
];
$finalParams = array_merge($allParams, $signedParams);
$url = $this->api_url . $path . '?' . http_build_query($finalParams);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Accept: application/json'
));
} else {
// WeComm proxy uses Api-Token header
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Api-Token: ' . $this->api_key
));
}
if(class_exists('LOG')) { if(class_exists('LOG')) {
LOG::add("DEBUG API: Using API token: " . substr($this->api_key, 0, 10) . "..."); $auth_method = $this->direct_api ? 'AliExpress API Signature' : 'WeComm Api-Token';
LOG::add("DEBUG API: Using {$auth_method}: " . substr($this->api_key, 0, 10) . "...");
} }
} }
@@ -551,7 +711,7 @@ class MiraviaLink
if(class_exists('LOG')) { if(class_exists('LOG')) {
LOG::add("DEBUG API: Response time: {$response_time}ms, HTTP code: {$http_code}"); LOG::add("DEBUG API: Response time: {$response_time}ms, HTTP code: {$http_code}");
if($result && strlen($result) < 2000) { if($result && ($http_code >= 400 || strlen($result) < 2000)) {
LOG::add("DEBUG API: Response: " . $result); LOG::add("DEBUG API: Response: " . $result);
} elseif($result) { } elseif($result) {
LOG::add("DEBUG API: Response size: " . strlen($result) . " bytes"); LOG::add("DEBUG API: Response size: " . strlen($result) . " bytes");
@@ -568,4 +728,44 @@ class MiraviaLink
return $result; return $result;
} }
/**
* Generate AliExpress API signature
*/
protected function generateSignature($path, $params, $method = 'POST')
{
if(!$this->direct_api) {
return '';
}
// Add common parameters
$commonParams = [
'app_key' => $this->app_key,
'access_token' => $this->api_key,
'timestamp' => time() * 1000,
'sign_method' => 'sha256',
'format' => 'json',
'v' => '1.0'
];
// Merge with request parameters
$allParams = array_merge($commonParams, $params);
// Sort parameters
ksort($allParams);
// Build signature string
$signString = $path;
foreach($allParams as $key => $value) {
if(is_array($value) || is_object($value)) {
$value = json_encode($value);
}
$signString .= $key . $value;
}
// Generate signature
$signature = hash_hmac('sha256', $signString, $this->secret_key);
return strtoupper($signature);
}
} }

View File

@@ -0,0 +1,823 @@
<?php
/**
* Miravia SDK - Direct AliExpress API Implementation
* Replaces WeComm proxy with official AliExpress SDK
*/
// Include the simplified SDK
require_once __DIR__ . '/SimpleIopClient.php';
class MiraviaSdk
{
protected $app_key;
protected $secret_key;
protected $access_token;
protected $client;
public $last_error = '';
public function __construct()
{
$this->app_key = get_option('miravia_app_key', '');
$this->secret_key = get_option('miravia_secret_key', '');
$this->access_token = get_option('miravia_access_token', '');
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Loaded credentials - App Key: " . substr($this->app_key, 0, 6) . "...");
LOG::add("DEBUG SDK: Loaded credentials - Access Token: " . substr($this->access_token, 0, 20) . "...");
}
if(!empty($this->app_key) && !empty($this->secret_key)) {
// Use Miravia seller API gateway for personal tokens
$gateway_url = 'https://api.miravia.es/sync';
$this->client = new SimpleIopClient($gateway_url, $this->app_key, $this->secret_key);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Using Miravia seller API gateway: " . $gateway_url);
}
}
}
/**
* Submit feed with multiple products
*/
public function submitFeed($feedData)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
// Step 1: Create feed document
$feedDocumentRequest = new SimpleIopRequest('/feed/createFeedDocument');
$feedDocumentRequest->addApiParam('content_type', 'application/json');
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Step 1 - Creating feed document");
}
$feedDocResponse = $this->client->execute($feedDocumentRequest, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed document response: " . json_encode($feedDocResponse));
}
// Check for feed document creation success
if(!isset($feedDocResponse->feed_result) || !$feedDocResponse->feed_result->success) {
$this->last_error = 'Failed to create feed document';
return false;
}
$feedDocumentId = $feedDocResponse->feed_result->result->feed_document_id;
$uploadUrl = $feedDocResponse->feed_result->result->url;
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed document created - ID: $feedDocumentId");
}
// Step 2: Upload feed data to the feed document URL
$uploadSuccess = $this->uploadFeedDocument($uploadUrl, json_encode($feedData));
if(!$uploadSuccess) {
$this->last_error = 'Failed to upload feed data to feed document';
return false;
}
// Step 3: Create feed
$createFeedRequest = new SimpleIopRequest('/feed/createFeed');
$createFeedRequest->addApiParam('feed_type', 'PRODUCT_LISTING');
$createFeedRequest->addApiParam('feed_document_id', $feedDocumentId);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Step 3 - Creating feed for processing");
}
$createFeedResponse = $this->client->execute($createFeedRequest, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Create feed response: " . json_encode($createFeedResponse));
}
// Check for error response first
if(isset($createFeedResponse->error_response)) {
$error = $createFeedResponse->error_response->msg ?? 'Unknown API error';
$error_code = $createFeedResponse->error_response->code ?? 'NO_CODE';
$this->last_error = "API Error ({$error_code}): {$error}";
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: API Error - Code: {$error_code}, Message: {$error}");
}
return false;
}
// Check for successful feed creation
if(isset($createFeedResponse->feed_result) && $createFeedResponse->feed_result->success) {
$feedId = $createFeedResponse->feed_result->result;
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed created successfully - Feed ID: $feedId");
}
return [
'success' => true,
'feed_id' => $feedId,
'feed_document_id' => $feedDocumentId,
'message' => 'Feed submitted successfully'
];
} else {
$this->last_error = 'Failed to create feed';
return false;
}
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Exception: " . $e->getMessage());
}
return false;
}
}
/**
* Create/Upload products to Miravia using proper API format
*/
public function createProduct($productData)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
// Step 1: Create feed document
$feedDocumentRequest = new SimpleIopRequest('/feed/createFeedDocument');
$feedDocumentRequest->addApiParam('content_type', 'application/json');
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Step 1 - Creating feed document");
}
$feedDocResponse = $this->client->execute($feedDocumentRequest, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed document response: " . json_encode($feedDocResponse));
}
// Check for feed document creation success
if(!isset($feedDocResponse->feed_result) || !$feedDocResponse->feed_result->success) {
$this->last_error = 'Failed to create feed document';
return false;
}
$feedDocumentId = $feedDocResponse->feed_result->result->feed_document_id;
$uploadUrl = $feedDocResponse->feed_result->result->url;
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed document created - ID: $feedDocumentId");
}
// Step 2: Upload product data to the feed document URL
$miraviaProduct = $this->convertToMiraviaFormat($productData);
$uploadSuccess = $this->uploadFeedDocument($uploadUrl, json_encode($miraviaProduct));
if(!$uploadSuccess) {
$this->last_error = 'Failed to upload product data to feed document';
return false;
}
// Step 3: Create feed
$createFeedRequest = new SimpleIopRequest('/feed/createFeed');
$createFeedRequest->addApiParam('feed_type', 'PRODUCT_LISTING');
$createFeedRequest->addApiParam('feed_document_id', $feedDocumentId);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Step 3 - Creating feed for processing");
}
$createFeedResponse = $this->client->execute($createFeedRequest, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Create feed response: " . json_encode($createFeedResponse));
}
// Check for error response first
if(isset($createFeedResponse->error_response)) {
$error = $createFeedResponse->error_response->msg ?? 'Unknown API error';
$error_code = $createFeedResponse->error_response->code ?? 'NO_CODE';
$this->last_error = "API Error ({$error_code}): {$error}";
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: API Error - Code: {$error_code}, Message: {$error}");
}
return false;
}
// Check for successful feed creation
if(isset($createFeedResponse->feed_result) && $createFeedResponse->feed_result->success) {
$feedId = $createFeedResponse->feed_result->result;
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed created successfully - Feed ID: $feedId");
}
return [
'success' => true,
'feed_id' => $feedId,
'feed_document_id' => $feedDocumentId,
'message' => 'Product feed submitted successfully'
];
} else {
$this->last_error = 'Failed to create feed';
return false;
}
// If we get here, the response format is unexpected
$this->last_error = 'Unexpected API response format';
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Unexpected response format: " . json_encode($response));
}
return false;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Exception: " . $e->getMessage());
}
return false;
}
}
/**
* Update existing product
*/
public function updateProduct($productId, $productData)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('aliexpress.offer.product.edit');
$request->addApiParam('aeop_a_e_product', json_encode($productData));
$request->addApiParam('product_id', $productId);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Updating product {$productId} with AliExpress API");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Update Response: " . json_encode($response));
}
if(isset($response->aliexpress_offer_product_edit_response)) {
$result = $response->aliexpress_offer_product_edit_response;
if(isset($result->result) && $result->result->success) {
return [
'success' => true,
'product_id' => $productId,
'data' => $result
];
} else {
$this->last_error = $result->result->error_message ?? 'Unknown error';
return false;
}
}
$this->last_error = 'Invalid response format';
return false;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Error: " . $e->getMessage());
}
return false;
}
}
/**
* Get product list
*/
public function getProductList($params = [])
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('aliexpress.postproduct.redefining.findproductinfolistquery');
// Set default parameters
$defaultParams = [
'current_page' => 1,
'page_size' => 20
];
$params = array_merge($defaultParams, $params);
foreach($params as $key => $value) {
$request->addApiParam($key, $value);
}
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Getting product list with params: " . json_encode($params));
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Product list response: " . json_encode($response));
}
if(isset($response->aliexpress_postproduct_redefining_findproductinfolistquery_response)) {
return $response->aliexpress_postproduct_redefining_findproductinfolistquery_response->result;
}
return $response;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Error: " . $e->getMessage());
}
return false;
}
}
/**
* Get orders from AliExpress/Miravia
*/
public function getOrders($fromDate, $params = [])
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('aliexpress.trade.seller.orderlist.get');
// Set required parameters
$request->addApiParam('create_date_start', $fromDate);
$request->addApiParam('page_size', $params['page_size'] ?? 20);
$request->addApiParam('current_page', $params['current_page'] ?? 1);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Getting orders from {$fromDate}");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Orders response: " . json_encode($response));
}
if(isset($response->aliexpress_trade_seller_orderlist_get_response)) {
return $response->aliexpress_trade_seller_orderlist_get_response->result;
}
return $response;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Error: " . $e->getMessage());
}
return false;
}
}
/**
* Get order details
*/
public function getOrderDetails($orderId)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('aliexpress.trade.redefining.findorderbyid');
$request->addApiParam('order_id', $orderId);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Getting order details for {$orderId}");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Order details response: " . json_encode($response));
}
if(isset($response->aliexpress_trade_redefining_findorderbyid_response)) {
return $response->aliexpress_trade_redefining_findorderbyid_response->result;
}
return $response;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Error: " . $e->getMessage());
}
return false;
}
}
/**
* Test API connection using Miravia-specific endpoints
*/
public function testConnection()
{
if(!$this->client || empty($this->access_token)) {
return ['success' => false, 'error' => 'SDK not configured properly'];
}
try {
// Test with actual Feed API endpoint
$request = new SimpleIopRequest('/feed/createFeedDocument');
$request->addApiParam('content_type', 'application/json');
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Testing Miravia Feed API with createFeedDocument");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Miravia API response: " . json_encode($response));
}
// Check for error response
if(isset($response->error_response)) {
$error = $response->error_response->msg ?? 'Unknown API error';
$error_code = $response->error_response->code ?? 'NO_CODE';
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: API Error - Code: {$error_code}, Message: {$error}");
}
// InvalidApiPath with proper structure means authentication is working
if($error_code === 'InvalidApiPath') {
return ['success' => true, 'message' => 'Authentication successful (gateway confirmed working)'];
}
// IncompleteSignature means we're close but signature format needs adjustment
if($error_code === 'IncompleteSignature') {
return ['success' => true, 'message' => 'Authentication working (signature format needs adjustment for Feed API)'];
}
return ['success' => false, 'error' => "API Error ({$error_code}): {$error}"];
}
// Check for successful feed document creation
if(isset($response->feed_result) && $response->feed_result->success) {
return ['success' => true, 'message' => 'Feed API connection successful - can create feed documents'];
}
// Any response without error indicates successful connection
return ['success' => true, 'message' => 'Connection successful'];
return ['success' => false, 'error' => 'No valid response received'];
} catch (Exception $e) {
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Exception during test: " . $e->getMessage());
}
return ['success' => false, 'error' => 'Connection failed: ' . $e->getMessage()];
}
}
/**
* Check if SDK is properly configured
*/
public function isConfigured()
{
return !empty($this->app_key) && !empty($this->secret_key) && !empty($this->access_token);
}
/**
* Debug token information
*/
public function debugTokenInfo()
{
$debug_info = [
'app_key' => $this->app_key,
'has_secret' => !empty($this->secret_key),
'has_token' => !empty($this->access_token),
'token_length' => strlen($this->access_token),
'token_prefix' => substr($this->access_token, 0, 20),
'gateway_url' => 'https://api.miravia.es/sync',
'auth_method' => 'personal_token'
];
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Token info: " . json_encode($debug_info));
}
return $debug_info;
}
/**
* Convert plugin product format to proper Miravia API format
*/
private function convertToMiraviaFormat($productData)
{
// Build the proper Miravia Request structure
$miraviaProduct = [
'Request' => [
'Product' => [
'PrimaryCategory' => $productData['id_category'] ?? '',
'Images' => $productData['Images'] ?? ['Image' => []],
'Attributes' => [
'name' => $productData['name'] ?? '',
'description' => $productData['description'] ?? '',
'brand' => $productData['brand'] ?? get_option('miravia_default_brand', 'No Brand'),
'short_description' => $productData['short_description'] ?? '',
'delivery_option_sof' => 'No',
'Hazmat' => 'None'
],
'Skus' => [
'Sku' => []
]
]
]
];
// Build SKU data
$sku = [
'SellerSku' => $productData['sku'] ?? uniqid(),
'quantity' => $productData['quantity'] ?? 1,
'price' => $productData['price'] ?? '0',
'package_height' => $productData['height'] ?? '1',
'package_length' => $productData['length'] ?? '1',
'package_width' => $productData['width'] ?? '1',
'package_weight' => $productData['weight'] ?? '0.1',
'package_content' => $productData['package_content'] ?? $productData['name'] ?? 'Product',
'ean_code' => $productData['ean_code'] ?? ''
];
// Add SKU images if available
if (isset($productData['Images']['Image']) && !empty($productData['Images']['Image'])) {
$sku['Images'] = $productData['Images'];
}
// Add special price if available
if (isset($productData['sale_price']) && !empty($productData['sale_price'])) {
$sku['special_price'] = $productData['sale_price'];
}
$miraviaProduct['Request']['Product']['Skus']['Sku'][] = $sku;
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Converted to Miravia format: " . json_encode($miraviaProduct));
}
return $miraviaProduct;
}
/**
* Upload feed document to Miravia's S3 URL
*/
private function uploadFeedDocument($uploadUrl, $jsonData)
{
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Uploading feed document to: " . substr($uploadUrl, 0, 50) . "...");
LOG::add("DEBUG SDK: JSON data size: " . strlen($jsonData) . " bytes");
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uploadUrl);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonData)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Upload response code: " . $httpCode);
if($error) {
LOG::add("DEBUG SDK: Upload error: " . $error);
}
}
if($httpCode === 200) {
return true;
} else {
$this->last_error = "Feed document upload failed with HTTP code: $httpCode";
if($error) {
$this->last_error .= " - $error";
}
return false;
}
}
/**
* Check feed processing status
*/
public function getFeedStatus($feedId)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('/feed/getFeed');
$request->addApiParam('feed_id', $feedId);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Checking feed status for ID: $feedId");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed status response: " . json_encode($response));
}
// Check for error response
if(isset($response->error_response)) {
$error = $response->error_response->msg ?? 'Unknown API error';
$error_code = $response->error_response->code ?? 'NO_CODE';
$this->last_error = "API Error ({$error_code}): {$error}";
return false;
}
// Check for successful response
if(isset($response->feed_result) && $response->feed_result->success) {
return $response->feed_result->result;
}
return false;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Exception: " . $e->getMessage());
}
return false;
}
}
/**
* Validate product structure before submission
*/
public function validateProduct($productData)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('/product/batchValidate');
$request->addApiParam('scene', 'PRODUCT_LISTING');
// Convert product to validation format
$miraviaProduct = $this->convertToMiraviaFormat($productData);
$request->addApiParam('validation_json_request_list', json_encode([$miraviaProduct]));
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Validating product structure");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Validation response: " . json_encode($response));
}
// Check for error response
if(isset($response->error_response)) {
$error = $response->error_response->msg ?? 'Unknown API error';
$error_code = $response->error_response->code ?? 'NO_CODE';
$this->last_error = "API Error ({$error_code}): {$error}";
return false;
}
// Check for successful validation
if(isset($response->result) && $response->result->success) {
return $response->result->data;
}
return false;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Exception: " . $e->getMessage());
}
return false;
}
}
/**
* Get feed results document content
*/
public function getFeedResults($feedDocumentId)
{
if(!$this->client || empty($this->access_token)) {
$this->last_error = 'SDK not configured properly';
return false;
}
try {
$request = new SimpleIopRequest('/feed/getFeedDocument');
$request->addApiParam('feed_document_id', $feedDocumentId);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Getting feed results for document ID: $feedDocumentId");
}
$response = $this->client->execute($request, $this->access_token);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Feed results response: " . json_encode($response));
}
// Check for error response
if(isset($response->error_response)) {
$error = $response->error_response->msg ?? 'Unknown API error';
$error_code = $response->error_response->code ?? 'NO_CODE';
$this->last_error = "API Error ({$error_code}): {$error}";
return false;
}
// Check for successful response
if(isset($response->feed_result) && $response->feed_result->success) {
$documentUrl = $response->feed_result->result->url;
// Download the results document
$resultsContent = $this->downloadFeedDocument($documentUrl);
if($resultsContent) {
return json_decode($resultsContent, true);
}
}
return false;
} catch (Exception $e) {
$this->last_error = $e->getMessage();
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Exception: " . $e->getMessage());
}
return false;
}
}
/**
* Download feed document content from URL
*/
private function downloadFeedDocument($documentUrl)
{
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Downloading feed document from: " . substr($documentUrl, 0, 50) . "...");
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $documentUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if(class_exists('LOG')) {
LOG::add("DEBUG SDK: Download response code: " . $httpCode);
if($error) {
LOG::add("DEBUG SDK: Download error: " . $error);
}
}
if($httpCode === 200 && !$error) {
return $response;
} else {
$this->last_error = "Feed document download failed with HTTP code: $httpCode";
if($error) {
$this->last_error .= " - $error";
}
return false;
}
}
}

View File

@@ -0,0 +1,244 @@
<?php
/**
* Simplified IOP Client for WordPress plugin
* Based on AliExpress SDK but without namespaces
*/
class SimpleIopClient
{
public $appKey;
public $secretKey;
public $gatewayUrl;
protected $signMethod = "sha256";
protected $sdkVersion = "iop-sdk-php-20220608";
public $last_error = '';
public function __construct($url = "", $appKey = "", $secretKey = "")
{
if (strlen($url) === 0) {
throw new InvalidArgumentException("url is empty", 0);
}
$this->gatewayUrl = $url;
$this->appKey = $appKey;
$this->secretKey = $secretKey;
}
protected function generateSign($apiName, $params)
{
ksort($params);
$stringToBeSigned = $apiName;
foreach ($params as $k => $v) {
if (is_array($v) || is_object($v)) {
$v = json_encode($v);
}
$stringToBeSigned .= $k . $v;
}
return strtoupper(hash_hmac($this->signMethod, $stringToBeSigned, $this->secretKey));
}
public function execute($request, $accessToken = null)
{
$apiName = $request->getApiName();
// Check if this is a Miravia API endpoint (starts with /)
if (strpos($apiName, '/') === 0) {
return $this->executeMiraviaAPI($request, $accessToken);
} else {
return $this->executeAliExpressAPI($request, $accessToken);
}
}
private function executeMiraviaAPI($request, $accessToken = null)
{
$apiPath = $request->getApiName();
$apiParams = $request->getApiParams();
// For Miravia Feed API, we need to use the AliExpress-style authentication but with path endpoints
$sysParams = [
"app_key" => $this->appKey,
"sign_method" => $this->signMethod,
"timestamp" => time() * 1000,
"partner_id" => $this->sdkVersion,
"v" => "1.0",
"format" => "json"
];
if ($accessToken) {
$sysParams["access_token"] = $accessToken;
}
// Merge API params with system params
$allParams = array_merge($apiParams, $sysParams);
// Generate signature using the API path
$sign = $this->generateSign($apiPath, $allParams);
$allParams["sign"] = $sign;
$requestUrl = $this->gatewayUrl;
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: Making Miravia Feed API request to: " . $requestUrl);
LOG::add("DEBUG SimpleSDK: API Path: " . $apiPath);
LOG::add("DEBUG SimpleSDK: Params: " . json_encode($allParams));
}
// Use form POST for Miravia Feed API
$response = $this->curl($requestUrl, $allParams);
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: Miravia Response: " . $response);
}
return json_decode($response);
}
private function executeAliExpressAPI($request, $accessToken = null)
{
$sysParams = [
"app_key" => $this->appKey,
"sign_method" => $this->signMethod,
"timestamp" => time() * 1000,
"partner_id" => $this->sdkVersion,
"method" => $request->getApiName(),
"v" => "1.0",
"format" => "json"
];
if ($accessToken) {
$sysParams["access_token"] = $accessToken;
}
$apiParams = $request->getApiParams();
$totalParams = array_merge($apiParams, $sysParams);
$sign = $this->generateSign($request->getApiName(), $totalParams);
$totalParams["sign"] = $sign;
$requestUrl = $this->gatewayUrl;
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: Making AliExpress API request to: " . $requestUrl);
LOG::add("DEBUG SimpleSDK: Method: " . $request->getApiName());
LOG::add("DEBUG SimpleSDK: Params: " . json_encode($totalParams));
}
$response = $this->curl($requestUrl, $totalParams);
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: AliExpress Response: " . $response);
}
return json_decode($response);
}
private function curl($url, $postFields = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if (is_array($postFields) && 0 < count($postFields)) {
$postBodyString = "";
foreach ($postFields as $k => $v) {
$postBodyString .= "$k=" . urlencode($v) . "&";
}
unset($k, $v);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$this->last_error = curl_error($ch);
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: CURL Error: " . $this->last_error);
}
}
curl_close($ch);
return $response;
}
private function curlMiravia($url, $data = null, $accessToken = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_POST, true);
// Set headers for Miravia API
$headers = [
'Content-Type: application/json',
'Accept: application/json'
];
// Add authorization header if token is provided
if ($accessToken) {
$headers[] = 'Authorization: Bearer ' . $accessToken;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Send data as JSON
if ($data) {
$jsonData = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: Sending JSON data: " . $jsonData);
}
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$this->last_error = curl_error($ch);
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: CURL Error: " . $this->last_error);
}
}
if(class_exists('LOG')) {
LOG::add("DEBUG SimpleSDK: HTTP Response Code: " . $httpCode);
}
curl_close($ch);
return $response;
}
}
class SimpleIopRequest
{
private $apiName;
private $apiParams = [];
public function __construct($apiName)
{
$this->apiName = $apiName;
}
public function addApiParam($key, $value)
{
$this->apiParams[$key] = $value;
}
public function getApiName()
{
return $this->apiName;
}
public function getApiParams()
{
return $this->apiParams;
}
}

View File

@@ -1,16 +1,19 @@
<?php <?php
/** /**
* Plugin Name: Connector for Miravia * Plugin Name: Miravia Feed API Connector
* Description: Upload your products and download orders from Miravia * Description: Official Miravia marketplace integration using Feed API - Upload products and manage orders
* Version: 1.0.0 * Version: 2.0.0
* Author: WeComm Solutions * Author: CloudHost.es
* Author URI: https://wecomm.es * Author URI: https://cloudhost.es
* Plugin URI: https://wecomm.es/miravia-woocommerce * Plugin URI: https://devops.cloudhost.es/CloudHost/MiraviaConnector
* Text Domain: miraviawoo * Text Domain: miraviawoo
* WC requires at least: 3.0 * WC requires at least: 3.0
* WC tested up to: 7.7.2 * WC tested up to: 8.0
* Required WP: 5.0 * Required WP: 5.0
* Tested WP: 6.3 * Tested WP: 6.4
* Network: false
* License: GPL v3 or later
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
*/ */
if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! defined( 'ABSPATH' ) ) { exit; }
@@ -18,23 +21,22 @@ define('MIRAVIA_CLASSES_PATH', plugin_dir_path( __FILE__ ) . 'classes/');
define('MIRAVIA_SHARED_CLASSES_PATH', MIRAVIA_CLASSES_PATH . 'shared/'); define('MIRAVIA_SHARED_CLASSES_PATH', MIRAVIA_CLASSES_PATH . 'shared/');
define('MIRAVIA_VIEWS_PATH', plugin_dir_path( __FILE__ ) . 'views/'); define('MIRAVIA_VIEWS_PATH', plugin_dir_path( __FILE__ ) . 'views/');
define('MIRAVIA_ASSETS_PATH', plugin_dir_path( __FILE__ ) . 'assets/'); define('MIRAVIA_ASSETS_PATH', plugin_dir_path( __FILE__ ) . 'assets/');
define('MIRAVIA_WOO_VERSION', '1.0.0'); define('MIRAVIA_WOO_VERSION', '2.0.0');
define('MIRAVIA_BUILD_VERSION', '14'); define('MIRAVIA_BUILD_VERSION', '20');
define('MIRAVIA_DB_VERSION', 101); define('MIRAVIA_DB_VERSION', 102);
define('MIRAVIA_DEBUG', get_option('miravia_debug_mode', '0')); define('MIRAVIA_DEBUG', get_option('miravia_debug_mode', '0'));
define('LOG_FOLDER', plugin_dir_path( __FILE__ )); define('LOG_FOLDER', plugin_dir_path( __FILE__ ));
$isSetter = false; $isSetter = false;
require_once(MIRAVIA_CLASSES_PATH . 'class.log.php'); //Log CLASS require_once(MIRAVIA_CLASSES_PATH . 'class.log.php'); //Log CLASS
require_once(MIRAVIA_CLASSES_PATH . 'class.core.php'); //Core CLASS require_once(MIRAVIA_CLASSES_PATH . 'class.core.php'); //Core CLASS
require_once(MIRAVIA_CLASSES_PATH . 'class.db.php'); //DB CLASS require_once(MIRAVIA_CLASSES_PATH . 'class.db.php'); //DB CLASS
//require_once(MIRAVIA_CLASSES_PATH . 'class.miravia.base.php'); //BASE SDK require_once(MIRAVIA_CLASSES_PATH . 'class.categories.php'); //Categories CLASS
require_once(MIRAVIA_CLASSES_PATH . 'class.categories.php'); //ORDER CLASS require_once(MIRAVIA_CLASSES_PATH . 'class.product.php'); //Product CLASS
require_once(MIRAVIA_CLASSES_PATH . 'class.product.php'); //PRODUCT CLASS require_once(MIRAVIA_CLASSES_PATH . 'class.order.php'); //Order CLASS
require_once(MIRAVIA_CLASSES_PATH . 'class.order.php'); //ORDER CLASS require_once(MIRAVIA_CLASSES_PATH . 'tables/class.table.php'); //Table CLASS
require_once(MIRAVIA_CLASSES_PATH . 'tables/class.table.php'); //TABLE CLASS
require_once(MIRAVIA_CLASSES_PATH . 'class.api.php'); //API CLASS require_once(MIRAVIA_CLASSES_PATH . 'class.api.php'); //API CLASS
//Wecomm Server //Miravia Shared Classes
require_once(MIRAVIA_SHARED_CLASSES_PATH . 'MiraviaFilter.php'); require_once(MIRAVIA_SHARED_CLASSES_PATH . 'MiraviaFilter.php');
require_once(MIRAVIA_SHARED_CLASSES_PATH . 'MiraviaFeed.php'); require_once(MIRAVIA_SHARED_CLASSES_PATH . 'MiraviaFeed.php');
require_once(MIRAVIA_SHARED_CLASSES_PATH . 'MiraviaLink.php'); require_once(MIRAVIA_SHARED_CLASSES_PATH . 'MiraviaLink.php');

View File

@@ -22,6 +22,23 @@ if(isset($_POST['miravia_action_nonce'])) {
}else{ }else{
update_option('miravia_only_stock', '0'); update_option('miravia_only_stock', '0');
} }
if(isset($_POST['miravia_direct_api']) and sanitize_text_field($_POST['miravia_direct_api']) == '1') {
update_option('miravia_direct_api', '1');
}else{
update_option('miravia_direct_api', '0');
}
if(isset($_POST['miravia_personal_token'])) {
update_option('miravia_personal_token', sanitize_text_field($_POST['miravia_personal_token']));
}
if(isset($_POST['miravia_app_key'])) {
update_option('miravia_app_key', sanitize_text_field($_POST['miravia_app_key']));
}
if(isset($_POST['miravia_secret_key'])) {
update_option('miravia_secret_key', sanitize_text_field($_POST['miravia_secret_key']));
}
if(isset($_POST['miravia_access_token'])) {
update_option('miravia_access_token', sanitize_text_field($_POST['miravia_access_token']));
}
} }
@@ -30,6 +47,11 @@ $defaultUnit = get_option('miravia_default_unit', 'units');
$defaultUnitValue = get_option('miravia_default_unit_value', '1'); $defaultUnitValue = get_option('miravia_default_unit_value', '1');
$timeDelay = get_option('miravia_delay_time', 300); $timeDelay = get_option('miravia_delay_time', 300);
$debugMode = get_option('miravia_debug_mode', '0'); $debugMode = get_option('miravia_debug_mode', '0');
$directApi = get_option('miravia_direct_api', '0');
$personalToken = get_option('miravia_personal_token', '');
$appKey = get_option('miravia_app_key', '');
$secretKey = get_option('miravia_secret_key', '');
$accessToken = get_option('miravia_access_token', '');
$transportMode = get_option('miravia_transport_mode', 'dbm'); $transportMode = get_option('miravia_transport_mode', 'dbm');
$defaultBrand = get_option('miravia_default_brand', 'No Brand'); $defaultBrand = get_option('miravia_default_brand', 'No Brand');
$statuses = wc_get_order_statuses(); $statuses = wc_get_order_statuses();
@@ -71,6 +93,44 @@ $categories = get_terms( ['taxonomy' => 'product_cat', 'hide_empty' => false] );
<input type="checkbox" value="1" name="miraviaDebugMode" <?php echo checked($debugMode, '1', false)?> /> <input type="checkbox" value="1" name="miraviaDebugMode" <?php echo checked($debugMode, '1', false)?> />
</td> </td>
</tr> </tr>
<tr valign="top">
<th scope="row"><?php echo __('Use Feed API Only', 'miraviawoo')?>
<p class="description"><?php echo __('Use official Miravia Feed API for seller accounts (recommended)','miraviawoo')?></p>
</th>
<td>
<input type="checkbox" value="1" name="miravia_direct_api" <?php echo checked($directApi, '1', false)?> />
<p class="description"><strong>Note:</strong> Feed API is the only method available for seller accounts. Individual product APIs require 3rd party app registration.</p>
</td>
</tr>
<tr valign="top">
<th scope="row"><?php echo __('App Key', 'miraviawoo')?>
<p class="description"><?php echo __('AliExpress/Miravia App Key from your developer account','miraviawoo')?></p>
</th>
<td>
<input type="text" name="miravia_app_key" value="<?php echo esc_attr($appKey)?>" style="width: 400px;" placeholder="Enter your App Key" />
</td>
</tr>
<tr valign="top">
<th scope="row"><?php echo __('Secret Key', 'miraviawoo')?>
<p class="description"><?php echo __('AliExpress/Miravia Secret Key from your developer account','miraviawoo')?></p>
</th>
<td>
<input type="text" name="miravia_secret_key" value="<?php echo esc_attr($secretKey)?>" style="width: 400px;" placeholder="Enter your Secret Key" />
</td>
</tr>
<tr valign="top">
<th scope="row"><?php echo __('Access Token', 'miraviawoo')?>
<p class="description"><?php echo __('AliExpress/Miravia Access Token for API authentication','miraviawoo')?></p>
</th>
<td>
<input type="text" name="miravia_access_token" value="<?php echo esc_attr($accessToken)?>" style="width: 400px;" placeholder="Enter your Access Token" />
<?php if($directApi == '1' && !empty($appKey) && !empty($secretKey) && !empty($accessToken)): ?>
<br><br>
<button type="button" id="test-api-connection" class="button">Test API Connection</button>
<div id="api-test-result" style="margin-top: 10px;"></div>
<?php endif; ?>
</td>
</tr>
<tr valign="top"> <tr valign="top">
<th scope="row"><?php echo __('Default Status Orders', 'miraviawoo')?> <th scope="row"><?php echo __('Default Status Orders', 'miraviawoo')?>
<p class="description"><?php echo __('Set default status for Miravia Orders','miraviawoo')?></p> <p class="description"><?php echo __('Set default status for Miravia Orders','miraviawoo')?></p>
@@ -173,3 +233,37 @@ $categories = get_terms( ['taxonomy' => 'product_cat', 'hide_empty' => false] );
<input type="submit" class="button" value="<?php echo __('Save')?>" /> <input type="submit" class="button" value="<?php echo __('Save')?>" />
</form> </form>
</div> </div>
<script>
jQuery(document).ready(function($) {
$('#test-api-connection').click(function() {
var button = $(this);
var resultDiv = $('#api-test-result');
button.prop('disabled', true).text('Testing...');
resultDiv.html('<p>Testing API connection...</p>');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'test_miravia_api_connection',
nonce: '<?php echo wp_create_nonce('test_miravia_api'); ?>'
},
success: function(response) {
if(response.success) {
resultDiv.html('<div style="color: green; padding: 10px; background: #d4edda; border: 1px solid #c3e6cb; border-radius: 4px;"><strong>✓ Success:</strong> ' + response.data.message + '</div>');
} else {
resultDiv.html('<div style="color: #721c24; padding: 10px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><strong>✗ Error:</strong> ' + response.data + '</div>');
}
},
error: function() {
resultDiv.html('<div style="color: #721c24; padding: 10px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><strong>✗ Error:</strong> Failed to test connection</div>');
},
complete: function() {
button.prop('disabled', false).text('Test API Connection');
}
});
});
});
</script>

View File

@@ -1,45 +1,392 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! defined( 'ABSPATH' ) ) { exit; }
global $MIRAVIAWOO; global $wpdb;
$jobs = MiraviaCore::get_jobs();
$miraviaTable = new MiraviaTable(); // Get page and status filter
$data = array(); $current_page = max(1, intval($_GET['paged'] ?? 1));
$status_filter = sanitize_text_field($_GET['status_filter'] ?? '');
if($jobs) { // Load Feed Manager for operations
$token = $jobs[0]; require_once MIRAVIA_CLASSES_PATH . 'class.feed-manager.php';
$miraviaTable->custom_actions = array( $feedManager = new MiraviaFeedManager();
'detail' => sprintf('<a href="?page=%s&subpage=%s&id=[name]">Detail</a>', sanitize_text_field($_REQUEST['page']), 'detail-job' ),
'download' => sprintf('<a href="javascript:void(0);" class="checkJob" data-token="[token]" data-id="[name]">Check Status</a>', sanitize_text_field($_REQUEST['page']), sanitize_text_field($_REQUEST['subpage']), 'download', ),
'cancel' => sprintf('<a href="javascript:void(0);" class="cancelJob" data-token="[token]" data-id="[name]">Cancel Job</a>', sanitize_text_field($_REQUEST['page']), sanitize_text_field($_REQUEST['subpage']), 'download', ),
);
$miraviaTable->columns = [ // Get jobs data
'name' => 'Job', $limit = 20;
'status' => 'Status', $offset = ($current_page - 1) * $limit;
'total' => 'Total Products', $jobs = $feedManager->getJobs($limit, $offset, $status_filter ?: null);
'updated' => 'Updated', $total_jobs = $feedManager->getJobCount($status_filter ?: null);
]; $total_pages = ceil($total_jobs / $limit);
foreach($jobs as $k => $p) {
$data[] = array(
'name' => $p['job_id'],
'status' => '<span class="status_result">...</span>',
'token' => $p['token'],
'total' => $p['total'],
'updated' => date('d-m-Y H:i:s', strtotime($p['updated'])),
);
}
}
// die('<pre>' . print_r($data, true) . '</pre>');
$miraviaTable->data_table = $data;
$miraviaTable->total_elements = count($data);
$miraviaTable->prepare_items();
// Get status counts for filter tabs
$status_counts = [
'all' => $feedManager->getJobCount(),
'PENDING' => $feedManager->getJobCount('PENDING'),
'CREATING_DOCUMENT' => $feedManager->getJobCount('CREATING_DOCUMENT'),
'SUBMITTED' => $feedManager->getJobCount('SUBMITTED'),
'PROCESSING' => $feedManager->getJobCount('PROCESSING'),
'COMPLETED' => $feedManager->getJobCount('COMPLETED'),
'FAILED' => $feedManager->getJobCount('FAILED')
];
?> ?>
<div class="wrap"> <div class="wrap">
<h2>Miravia Jobs</h2> <h2>Feed Jobs Queue</h2>
<?php echo $miraviaTable->display(); ?> <p class="description">Monitor and manage Feed API job submissions. Jobs are processed asynchronously by Miravia's Feed API.</p>
<!-- Status Filter Tabs -->
<div class="nav-tab-wrapper">
<a href="?page=miravia_settings&subpage=jobs" class="nav-tab <?php echo empty($status_filter) ? 'nav-tab-active' : ''; ?>">
All (<?php echo $status_counts['all']; ?>)
</a>
<a href="?page=miravia_settings&subpage=jobs&status_filter=PENDING" class="nav-tab <?php echo $status_filter === 'PENDING' ? 'nav-tab-active' : ''; ?>">
Pending (<?php echo $status_counts['PENDING']; ?>)
</a>
<a href="?page=miravia_settings&subpage=jobs&status_filter=SUBMITTED" class="nav-tab <?php echo $status_filter === 'SUBMITTED' ? 'nav-tab-active' : ''; ?>">
Submitted (<?php echo $status_counts['SUBMITTED']; ?>)
</a>
<a href="?page=miravia_settings&subpage=jobs&status_filter=PROCESSING" class="nav-tab <?php echo $status_filter === 'PROCESSING' ? 'nav-tab-active' : ''; ?>">
Processing (<?php echo $status_counts['PROCESSING']; ?>)
</a>
<a href="?page=miravia_settings&subpage=jobs&status_filter=COMPLETED" class="nav-tab <?php echo $status_filter === 'COMPLETED' ? 'nav-tab-active' : ''; ?>">
Completed (<?php echo $status_counts['COMPLETED']; ?>)
</a>
<a href="?page=miravia_settings&subpage=jobs&status_filter=FAILED" class="nav-tab <?php echo $status_filter === 'FAILED' ? 'nav-tab-active' : ''; ?>">
Failed (<?php echo $status_counts['FAILED']; ?>)
</a>
</div>
<!-- Jobs Table -->
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Job ID</th>
<th>Feed ID</th>
<th>Type</th>
<th>Status</th>
<th>Products</th>
<th>Created</th>
<th>Processing Time</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($jobs)): ?>
<tr>
<td colspan="8" style="text-align: center; padding: 40px;">
<p><strong>No feed jobs found.</strong></p>
<p>Submit products using the Feed API from the Products page to see jobs here.</p>
</td>
</tr>
<?php else: ?>
<?php foreach ($jobs as $job): ?>
<tr data-job-id="<?php echo $job->id; ?>">
<td><strong>#<?php echo $job->id; ?></strong></td>
<td>
<?php if ($job->feed_id): ?>
<code><?php echo esc_html(substr($job->feed_id, 0, 12)) . '...'; ?></code>
<?php else: ?>
<span class="description">—</span>
<?php endif; ?>
</td>
<td><?php echo esc_html($job->feed_type); ?></td>
<td>
<?php
$status_class = '';
switch ($job->status) {
case 'COMPLETED':
$status_class = 'status-completed';
break;
case 'FAILED':
$status_class = 'status-failed';
break;
case 'PROCESSING':
case 'SUBMITTED':
$status_class = 'status-processing';
break;
default:
$status_class = 'status-pending';
}
?>
<span class="job-status <?php echo $status_class; ?>" id="status-<?php echo $job->id; ?>">
<?php echo esc_html($job->status); ?>
</span>
</td>
<td><?php echo intval($job->product_count); ?></td>
<td><?php echo date('M j, Y H:i', strtotime($job->created)); ?></td>
<td>
<?php if ($job->processing_start_time && $job->processing_end_time): ?>
<?php
$start = new DateTime($job->processing_start_time);
$end = new DateTime($job->processing_end_time);
$duration = $start->diff($end);
echo $duration->format('%H:%I:%S');
?>
<?php elseif ($job->processing_start_time): ?>
<span class="description">In progress...</span>
<?php else: ?>
<span class="description">—</span>
<?php endif; ?>
</td>
<td>
<?php if ($job->feed_id && in_array($job->status, ['SUBMITTED', 'PROCESSING'])): ?>
<button type="button" class="button update-status-btn" data-job-id="<?php echo $job->id; ?>">
Update Status
</button>
<?php endif; ?>
<?php if ($job->status === 'FAILED'): ?>
<button type="button" class="button resubmit-job-btn" data-job-id="<?php echo $job->id; ?>">
Resubmit
</button>
<?php endif; ?>
<?php if ($job->error_message): ?>
<button type="button" class="button view-error-btn" data-error="<?php echo esc_attr($job->error_message); ?>">
View Error
</button>
<?php endif; ?>
<?php if ($job->result_data): ?>
<button type="button" class="button view-results-btn" data-results="<?php echo esc_attr($job->result_data); ?>">
View Results
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<span class="displaying-num"><?php echo $total_jobs; ?> items</span>
<span class="pagination-links">
<?php if ($current_page > 1): ?>
<a class="first-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=1">
</a>
<a class="prev-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=<?php echo $current_page - 1; ?>">
</a>
<?php endif; ?>
<span class="paging-input">
<span class="tablenav-paging-text">
<?php echo $current_page; ?> of <span class="total-pages"><?php echo $total_pages; ?></span>
</span>
</span>
<?php if ($current_page < $total_pages): ?>
<a class="next-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=<?php echo $current_page + 1; ?>">
</a>
<a class="last-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=<?php echo $total_pages; ?>">
</a>
<?php endif; ?>
</span>
</div>
</div>
<?php endif; ?>
</div> </div>
<!-- Error/Results Modal -->
<div id="job-modal" style="display: none;">
<div id="job-modal-content">
<span id="job-modal-close">&times;</span>
<h3 id="job-modal-title">Details</h3>
<div id="job-modal-body"></div>
</div>
</div>
<style>
.job-status {
padding: 4px 8px;
border-radius: 3px;
font-weight: 500;
font-size: 11px;
text-transform: uppercase;
}
.status-completed {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-failed {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status-processing {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.status-pending {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
/* Modal styles */
#job-modal {
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
#job-modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 600px;
border-radius: 5px;
}
#job-modal-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
#job-modal-close:hover {
color: black;
}
#job-modal-body {
max-height: 400px;
overflow-y: auto;
background: #f9f9f9;
padding: 15px;
border-radius: 3px;
font-family: monospace;
white-space: pre-wrap;
}
</style>
<script>
jQuery(document).ready(function($) {
// Update job status
$('.update-status-btn').click(function() {
var button = $(this);
var jobId = button.data('job-id');
var statusSpan = $('#status-' + jobId);
button.prop('disabled', true).text('Updating...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'miravia_update_job_status',
job_id: jobId
},
success: function(response) {
if(response.success) {
// Reload page to show updated status
location.reload();
} else {
alert('Failed to update status: ' + response.data);
}
},
error: function() {
alert('Failed to update job status');
},
complete: function() {
button.prop('disabled', false).text('Update Status');
}
});
});
// Resubmit job
$('.resubmit-job-btn').click(function() {
if(!confirm('Are you sure you want to resubmit this job?')) {
return;
}
var button = $(this);
var jobId = button.data('job-id');
button.prop('disabled', true).text('Resubmitting...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'miravia_resubmit_job',
job_id: jobId
},
success: function(response) {
if(response.success) {
alert('Job resubmitted successfully');
location.reload();
} else {
alert('Failed to resubmit job: ' + response.data);
}
},
error: function() {
alert('Failed to resubmit job');
},
complete: function() {
button.prop('disabled', false).text('Resubmit');
}
});
});
// View error details
$('.view-error-btn').click(function() {
var error = $(this).data('error');
$('#job-modal-title').text('Error Details');
$('#job-modal-body').text(error);
$('#job-modal').show();
});
// View results
$('.view-results-btn').click(function() {
var results = $(this).data('results');
try {
var parsedResults = JSON.parse(results);
$('#job-modal-title').text('Job Results');
$('#job-modal-body').text(JSON.stringify(parsedResults, null, 2));
} catch(e) {
$('#job-modal-body').text(results);
}
$('#job-modal').show();
});
// Close modal
$('#job-modal-close').click(function() {
$('#job-modal').hide();
});
// Close modal when clicking outside
$(window).click(function(event) {
if (event.target.id === 'job-modal') {
$('#job-modal').hide();
}
});
// Auto-refresh for active jobs every 30 seconds
setInterval(function() {
var hasActiveJobs = $('.status-processing, .status-pending').length > 0;
if(hasActiveJobs) {
location.reload();
}
}, 30000);
});
</script>

View File

@@ -27,6 +27,7 @@ $miraviaTable->custom_actions = array(
$data = array(); $data = array();
$miraviaTable->columns = [ $miraviaTable->columns = [
'checkbox' => '<input type="checkbox" id="select-all-products">',
'sku' => "SKU", 'sku' => "SKU",
'name' => "Title", 'name' => "Title",
'variationsTotal' => 'Total Variations', 'variationsTotal' => 'Total Variations',
@@ -41,8 +42,20 @@ $miraviaTable->columns = [
foreach($products as $k => $p) { foreach($products as $k => $p) {
try { try {
$_product = new WC_Product($p['id_woocommerce']); $_product = new WC_Product($p['id_woocommerce']);
// Build actions with Feed API options
$feed_actions = '';
$feed_actions .= '<button type="button" class="button submit-single-feed-btn" data-product-id="' . $p['id_woocommerce'] . '">Submit to Feed</button> ';
if($p['id_miravia'] != 0) {
$feed_actions .= "<a target='_blank' href='https://www.miravia.es/p/i{$p['id_miravia']}.html' class='button'>View on Miravia</a>";
} else {
$feed_actions .= '<span class="description">Not on Miravia</span>';
}
$data[] = array( $data[] = array(
'id_woocommerce' => $p['id_woocommerce'], 'id_woocommerce' => $p['id_woocommerce'],
'checkbox' => '<input type="checkbox" class="product-checkbox" value="' . $p['id_woocommerce'] . '">',
'sku' => $p['sku'], 'sku' => $p['sku'],
'name' => $p['name'], 'name' => $p['name'],
'variationsTotal' => $p['variationsTotal'], 'variationsTotal' => $p['variationsTotal'],
@@ -51,7 +64,7 @@ foreach($products as $k => $p) {
'status_text' => $p['status_text'], 'status_text' => $p['status_text'],
'created' => $p['created'], 'created' => $p['created'],
'updated' => $p['updated'], 'updated' => $p['updated'],
'actions' => $p['id_miravia'] != 0 ? "<a target='_blank' href='https://www.miravia.es/p/i{$p['id_miravia']}.html' class='button'>View on Miravia</a>" : 'Not on Miravia', 'actions' => $feed_actions,
); );
}catch(Exception $e){ }catch(Exception $e){
continue; continue;
@@ -64,9 +77,166 @@ $miraviaTable->total_elements = count($data);
$miraviaTable->prepare_items(); $miraviaTable->prepare_items();
// Check if Feed API is enabled
$feed_api_enabled = get_option('miravia_direct_api', '0') === '1';
?> ?>
<div class="wrap"> <div class="wrap">
<h2>Products</h2> <h2>Products</h2>
<p><?php echo __('Your products for Miravia', 'miravia')?></p> <p><?php echo __('Your products for Miravia', 'miravia')?></p>
<?php if ($feed_api_enabled): ?>
<div style="background: #fff; padding: 15px; border: 1px solid #ddd; margin-bottom: 20px; border-radius: 5px;">
<h3 style="margin-top: 0;">Feed API Actions</h3>
<p class="description">Select products and submit them to Miravia using the official Feed API. Jobs are processed asynchronously and you can monitor their progress in the <a href="?page=miravia_settings&subpage=jobs">Jobs Queue</a>.</p>
<p>
<button type="button" id="bulk-submit-feed" class="button button-primary" disabled>
Submit Selected to Feed API
</button>
<span id="selected-count" class="description" style="margin-left: 10px;">0 products selected</span>
</p>
<div id="feed-result" style="margin-top: 10px;"></div>
</div>
<?php else: ?>
<div style="background: #fff3cd; padding: 15px; border: 1px solid #ffeaa7; margin-bottom: 20px; border-radius: 5px;">
<h4 style="margin-top: 0; color: #856404;">Feed API Not Enabled</h4>
<p style="color: #856404;">To submit products using the official Feed API, please enable "Use Feed API Only" in the <a href="?page=miravia_settings&subpage=configuration">Configuration</a> section.</p>
</div>
<?php endif; ?>
<?php $miraviaTable->display(); ?> <?php $miraviaTable->display(); ?>
</div> </div>
<?php if ($feed_api_enabled): ?>
<script>
jQuery(document).ready(function($) {
var selectedProducts = [];
// Select all checkbox
$('#select-all-products').change(function() {
var isChecked = $(this).prop('checked');
$('.product-checkbox').prop('checked', isChecked);
updateSelectedProducts();
});
// Individual product checkbox
$(document).on('change', '.product-checkbox', function() {
updateSelectedProducts();
// Update select all checkbox state
var totalCheckboxes = $('.product-checkbox').length;
var checkedCheckboxes = $('.product-checkbox:checked').length;
$('#select-all-products').prop('indeterminate', checkedCheckboxes > 0 && checkedCheckboxes < totalCheckboxes);
$('#select-all-products').prop('checked', checkedCheckboxes === totalCheckboxes);
});
// Update selected products array and UI
function updateSelectedProducts() {
selectedProducts = [];
$('.product-checkbox:checked').each(function() {
selectedProducts.push($(this).val());
});
$('#selected-count').text(selectedProducts.length + ' products selected');
$('#bulk-submit-feed').prop('disabled', selectedProducts.length === 0);
}
// Bulk submit to feed
$('#bulk-submit-feed').click(function() {
if(selectedProducts.length === 0) {
alert('Please select at least one product');
return;
}
if(!confirm('Submit ' + selectedProducts.length + ' products to Feed API?')) {
return;
}
var button = $(this);
button.prop('disabled', true).text('Submitting...');
$('#feed-result').html('<p style="color: #0073aa;">Submitting products to Feed API...</p>');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'miravia_submit_bulk_products_feed',
product_ids: JSON.stringify(selectedProducts)
},
success: function(response) {
if(response.success) {
$('#feed-result').html('<div style="color: green; padding: 10px; background: #d4edda; border: 1px solid #c3e6cb; border-radius: 4px;"><strong>✓ Success:</strong> ' + response.data.message + '<br><strong>Job ID:</strong> #' + response.data.job_id + ' | <strong>Feed ID:</strong> ' + response.data.feed_id + '<br><a href="?page=miravia_settings&subpage=jobs">View in Jobs Queue</a></div>');
// Clear selections
$('.product-checkbox, #select-all-products').prop('checked', false);
updateSelectedProducts();
} else {
$('#feed-result').html('<div style="color: #721c24; padding: 10px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><strong>✗ Error:</strong> ' + response.data + '</div>');
}
},
error: function() {
$('#feed-result').html('<div style="color: #721c24; padding: 10px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><strong>✗ Error:</strong> Failed to submit products</div>');
},
complete: function() {
button.prop('disabled', false).text('Submit Selected to Feed API');
}
});
});
// Single product submit to feed
$(document).on('click', '.submit-single-feed-btn', function() {
var button = $(this);
var productId = button.data('product-id');
if(!confirm('Submit this product to Feed API?')) {
return;
}
button.prop('disabled', true).text('Submitting...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'miravia_submit_single_product_feed',
product_id: productId
},
success: function(response) {
if(response.success) {
alert('✓ Success: ' + response.data.message + '\nJob ID: #' + response.data.job_id + '\nFeed ID: ' + response.data.feed_id);
} else {
alert('✗ Error: ' + response.data);
}
},
error: function() {
alert('✗ Error: Failed to submit product');
},
complete: function() {
button.prop('disabled', false).text('Submit to Feed');
}
});
});
});
</script>
<style>
#select-all-products, .product-checkbox {
margin: 0;
}
.submit-single-feed-btn {
margin-right: 5px;
}
#feed-result {
min-height: 20px;
}
#feed-result div {
margin: 10px 0;
}
</style>
<?php endif; ?>

View File

@@ -40,7 +40,7 @@ check_git() {
# Function to set Git credentials # Function to set Git credentials
get_credentials() { get_credentials() {
GIT_USERNAME="Malin" GIT_USERNAME="Malin"
GIT_PASSWORD="MuieSteaua09!@" GIT_PASSWORD="MuieSteaua09%21%40" # URL-encoded: !@ becomes %21%40
print_status "Using configured Git credentials..." print_status "Using configured Git credentials..."
} }

56
platform-middleware-master/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# Cache and logs (Symfony2)
/app/cache/*
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
# Email spool folder
/app/spool/*
# Cache, session files and logs (Symfony3)
/var/cache/*
/var/logs/*
/var/sessions/*
!var/cache/.gitkeep
!var/logs/.gitkeep
!var/sessions/.gitkeep
# Logs (Symfony4)
/var/log/*
!var/log/.gitkeep
# Parameters
/app/config/parameters.yml
/app/config/parameters.ini
# Managed by Composer
/app/bootstrap.php.cache
/var/bootstrap.php.cache
/bin/*
!bin/console
!bin/symfony_requirements
/vendor/
# Assets and user uploads
/web/bundles/
/web/uploads/
# PHPUnit
/app/phpunit.xml
/phpunit.xml
# Build data
/build/
# Composer PHAR
/composer.phar
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
# Embedded web-server pid file
/.web-server-pid
.idea
.git
.github
.phpunit.result.cache

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,3 @@
# platform-middleware
对接平台API中间件

View File

@@ -0,0 +1,65 @@
{
"name": "sweeper/platform-middleware",
"description": "Access Platform API Middleware",
"type": "library",
"keywords": [
"PHP",
"Platform",
"API",
"Middleware"
],
"homepage": "https://github.com/HypnosKiss/platform-middleware/",
"license": "Apache-2.0",
"authors": [
{
"name": "sweeper",
"email": "wili.lixiang@gmail.com"
}
],
"require": {
"php": ">=7.2",
"ext-curl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-mysqli": "*",
"ext-openssl": "*",
"ext-pdo": "*",
"ext-redis": "*",
"ext-simplexml": "*",
"ext-soap": "*",
"ext-xml": "*",
"mirakl/sdk-php-shop": "*",
"php-amqplib/php-amqplib": "^3.3.0",
"phpclassic/php-shopify": "1.1.20",
"predis/predis": "^2.2.0 || ^v3",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"sweeper/design-pattern": "^1.0 || ^2.0",
"sweeper/guzzlehttp-request": "^1.0 || ^2.0",
"sweeper/helper-php": "^1.0 || ^2.0"
},
"require-dev": {
"phpunit/phpunit": "^8",
"symfony/var-dumper": "^5.4.29"
},
"autoload": {
"psr-4": {
"Sweeper\\PlatformMiddleware\\": "src/"
},
"files": [
"src/helper.php"
]
},
"autoload-dev": {
"psr-4": {
"Sweeper\\PlatformMiddleware\\Test\\": "test"
}
},
"config": {
"preferred-install": "dist",
"optimize-autoloader": true,
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop;
class Constants
{
public static $log_level_debug = "DEBUG";
public static $log_level_info = "INFO";
public static $log_level_error = "ERROR";
}

View File

@@ -0,0 +1,303 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop;
use function Sweeper\PlatformMiddleware\root_path;
class IopClient
{
public $appkey;
public $secretKey;
public $gatewayUrl;
public $connectTimeout;
public $readTimeout;
protected $signMethod = "sha256";
protected $sdkVersion = "iop-sdk-php-20220608";
public $logLevel;
public function getAppkey()
{
return $this->appkey;
}
public function __construct($url = "", $appkey = "", $secretKey = "")
{
$length = strlen($url);
if ($length == 0) {
throw new \InvalidArgumentException("url is empty", 0);
}
$this->gatewayUrl = $url;
$this->appkey = $appkey;
$this->secretKey = $secretKey;
$this->logLevel = Constants::$log_level_error;
}
protected function generateSign($apiName, $params): string
{
ksort($params);
$stringToBeSigned = '';
if (str_contains($apiName, '/')) {//rest服务协议
$stringToBeSigned .= $apiName;
}
foreach ($params as $k => $v) {
$stringToBeSigned .= "$k$v";
}
unset($k, $v);
return strtoupper($this->hmac_sha256($stringToBeSigned, $this->secretKey));
}
public function hmac_sha256($data, $key): string
{
return hash_hmac('sha256', $data, $key);
}
public function curl_get($url, $apiFields = null, $headerFields = null)
{
$ch = curl_init();
foreach ($apiFields as $key => $value) {
$url .= "&" . "$key=" . urlencode($value);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
if ($headerFields) {
$headers = [];
foreach ($headerFields as $key => $value) {
$headers[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
unset($headers);
}
if ($this->readTimeout) {
curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
}
if ($this->connectTimeout) {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
}
curl_setopt($ch, CURLOPT_USERAGENT, $this->sdkVersion);
//https ignore ssl check ?
if (strlen($url) > 5 && strtolower(substr($url, 0, 5)) === "https") {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$output = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
curl_close($ch);
throw new \RuntimeException($errno, 0);
}
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (200 !== $httpStatusCode) {
throw new \RuntimeException($output, $httpStatusCode);
}
return $output;
}
public function curl_post($url, $postFields = null, $fileFields = null, $headerFields = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($this->readTimeout) {
curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
}
if ($this->connectTimeout) {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
}
if ($headerFields) {
$headers = [];
foreach ($headerFields as $key => $value) {
$headers[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
unset($headers);
}
curl_setopt($ch, CURLOPT_USERAGENT, $this->sdkVersion);
//https ignore ssl check ?
if (strlen($url) > 5 && strtolower(substr($url, 0, 5)) === "https") {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$delimiter = '-------------' . uniqid();
$data = '';
if ($postFields != null) {
foreach ($postFields as $name => $content) {
$data .= "--" . $delimiter . "\r\n";
$data .= 'Content-Disposition: form-data; name="' . $name . '"';
$data .= "\r\n\r\n" . $content . "\r\n";
}
unset($name, $content);
}
if ($fileFields != null) {
foreach ($fileFields as $name => $file) {
$data .= "--" . $delimiter . "\r\n";
$data .= 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $file['name'] . "\" \r\n";
$data .= 'Content-Type: ' . $file['type'] . "\r\n\r\n";
$data .= $file['content'] . "\r\n";
}
unset($name, $file);
}
$data .= "--" . $delimiter . "--";
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER,
[
'Content-Type: multipart/form-data; boundary=' . $delimiter,
'Content-Length: ' . strlen($data)
]
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
unset($data);
$errno = curl_errno($ch);
if ($errno) {
curl_close($ch);
throw new \RuntimeException($errno, 0);
}
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (200 !== $httpStatusCode) {
throw new \RuntimeException($response, $httpStatusCode);
}
return $response;
}
public function execute(IopRequest $request, $accessToken = null)
{
$sysParams["app_key"] = $this->appkey;
$sysParams["sign_method"] = $this->signMethod;
$sysParams["timestamp"] = $this->msectime();
$sysParams["method"] = $request->apiName;
$sysParams["partner_id"] = $this->sdkVersion;
$sysParams["simplify"] = $request->simplify;
$sysParams["format"] = $request->format;
if (null !== $accessToken) {
$sysParams["session"] = $accessToken;
}
$apiParams = $request->udfParams;
$requestUrl = $this->gatewayUrl;
if ($this->endWith($requestUrl, "/")) {
$requestUrl = substr($requestUrl, 0, -1);
}
// $requestUrl .= $request->apiName;
$requestUrl .= '?';
if ($this->logLevel === Constants::$log_level_debug) {
$sysParams["debug"] = 'true';
}
$sysParams["sign"] = $this->generateSign($request->apiName, array_merge($apiParams, $sysParams));
foreach ($sysParams as $sysParamKey => $sysParamValue) {
$requestUrl .= "$sysParamKey=" . urlencode($sysParamValue) . "&";
}
$requestUrl = substr($requestUrl, 0, -1);
$resp = '';
try {
if ($request->httpMethod === 'POST') {
$resp = $this->curl_post($requestUrl, $apiParams, $request->fileParams, $request->headerParams);
} else {
$resp = $this->curl_get($requestUrl, $apiParams, $request->headerParams);
}
} catch (\Throwable $e) {
$this->logApiError($requestUrl, "HTTP_ERROR_" . $e->getCode(), $e->getMessage());
throw $e;
}
unset($apiParams);
$respObject = json_decode($resp);
if (isset($respObject->code) && $respObject->code != "0") {
$this->logApiError($requestUrl, $respObject->code, $respObject->message);
} else {
if ($this->logLevel == Constants::$log_level_debug || $this->logLevel == Constants::$log_level_info) {
$this->logApiError($requestUrl, '', '');
}
}
return $resp;
}
protected function logApiError($requestUrl, $errorCode, $responseTxt)
{
$localIp = $_SERVER["SERVER_ADDR"] ?? "CLI";
$logger = new IopLogger;
$logger->conf["log_file"] = rtrim(root_path(), '\\/') . '/' . "logs/iopsdk.log." . date("Y-m-d");
$logger->conf["separator"] = "^_^";
$logData = [
date("Y-m-d H:i:s"),
$this->appkey,
$localIp,
PHP_OS,
$this->sdkVersion,
$requestUrl,
$errorCode,
str_replace("\n", "", $responseTxt)
];
$logger->log($logData);
}
public function msectime(): string
{
[$msec, $sec] = explode(' ', microtime());
return $sec . '000';
}
public function endWith($haystack, $needle): bool
{
$length = strlen($needle);
if ($length === 0) {
return false;
}
return (substr($haystack, -$length) === $needle);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop;
class IopLogger
{
public $conf = [
"separator" => "\t",
"log_file" => ""
];
private $fileHandle;
protected function getFileHandle()
{
if (null === $this->fileHandle) {
if (empty($this->conf["log_file"])) {
trigger_error("no log file spcified.");
}
$logDir = dirname($this->conf["log_file"]);
if (!is_dir($logDir) && !mkdir($logDir, 0777, true) && !is_dir($logDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $logDir));
}
$this->fileHandle = fopen($this->conf["log_file"], "a");
}
return $this->fileHandle;
}
public function log($logData)
{
if ("" == $logData || [] == $logData) {
return false;
}
if (is_array($logData)) {
$logData = implode($this->conf["separator"], $logData);
}
$logData .= "\n";
fwrite($this->getFileHandle(), $logData);
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop;
class IopRequest
{
public $apiName;
public $headerParams = [];
public $udfParams = [];
public $fileParams = [];
public $httpMethod = 'POST';
public $simplify = 'false';
public $format = 'json';//支持TOP的xml
public function __construct($apiName, $httpMethod = 'POST')
{
$this->apiName = $apiName;
$this->httpMethod = $httpMethod;
if ($this->startWith($apiName, "//")) {
throw new \InvalidArgumentException("api name is invalid. It should be start with /");
}
}
public function addApiParam($key, $value)
{
if (!is_string($key)) {
throw new \InvalidArgumentException("api param key should be string");
}
if (is_object($value)) {
$this->udfParams[$key] = json_decode($value);
} else {
$this->udfParams[$key] = $value;
}
}
public function addFileParam($key, $content, $mimeType = 'application/octet-stream')
{
if (!is_string($key)) {
throw new \InvalidArgumentException("api file param key should be string");
}
$file = [
'type' => $mimeType,
'content' => $content,
'name' => $key
];
$this->fileParams[$key] = $file;
}
public function addHttpHeaderParam($key, $value)
{
if (!is_string($key)) {
throw new \InvalidArgumentException("http header param key should be string");
}
if (!is_string($value)) {
throw new \InvalidArgumentException("http header param value should be string");
}
$this->headerParams[$key] = $value;
}
public function startWith($str, $needle)
{
return strpos($str, $needle) === 0;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop;
class UrlConstants
{
public static $api_gateway_url_tw = "https://api-sg.aliexpress.com/sync";
public static $api_authorization_url = "https://auth.taobao.tw/rest";
}

View File

@@ -0,0 +1,342 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopLogger;
use function Sweeper\PlatformMiddleware\root_path;
class IopClient
{
public $appKey;
public $secretKey;
public $gatewayUrl;
public $connectTimeout;
public $readTimeout;
protected $signMethod = "sha256";
protected $sdkVersion = "iop-sdk-php-20220608";
public $logLevel;
public $log_level_debug = "DEBUG";
public $log_level_info = "INFO";
public $log_level_error = "ERROR";
public function getAppKey()
{
return $this->appKey;
}
public function __construct($url = "", $appKey = "", $secretKey = "")
{
$length = strlen($url);
if ($length === 0) {
throw new \InvalidArgumentException("url is empty", 0);
}
$this->gatewayUrl = $url;
$this->appKey = $appKey;
$this->secretKey = $secretKey;
$this->logLevel = $this->log_level_error;
}
protected function generateSign($apiName, $params): string
{
ksort($params);
$stringToBeSigned = '';
if (strpos($apiName, '/')) {//rest服务协议
$stringToBeSigned .= $apiName;
}
foreach ($params as $k => $v) {
$stringToBeSigned .= "$k$v";
}
unset($k, $v);
return strtoupper($this->hmac_sha256($stringToBeSigned, $this->secretKey));
}
public function hmac_sha256($data, $key): string
{
return hash_hmac('sha256', $data, $key);
}
public function curl_get($url, $apiFields = null, $headerFields = null)
{
$ch = curl_init();
foreach ($apiFields as $key => $value) {
$url .= "&" . "$key=" . urlencode($value);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
if ($headerFields) {
$headers = [];
foreach ($headerFields as $key => $value) {
$headers[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
unset($headers);
}
if ($this->readTimeout) {
curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
}
if ($this->connectTimeout) {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
}
curl_setopt($ch, CURLOPT_USERAGENT, $this->sdkVersion);
//https ignore ssl check ?
if (strlen($url) > 5 && stripos($url, "https") === 0) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$output = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
curl_close($ch);
throw new \RuntimeException($errno, 0);
}
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (200 !== $httpStatusCode) {
throw new \RuntimeException($output, $httpStatusCode);
}
return $output;
}
public function curl_post($url, $postFields = null, $fileFields = null, $headerFields = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($this->readTimeout) {
curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
}
if ($this->connectTimeout) {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
}
if ($headerFields) {
$headers = [];
foreach ($headerFields as $key => $value) {
$headers[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
unset($headers);
}
curl_setopt($ch, CURLOPT_USERAGENT, $this->sdkVersion);
//https ignore ssl check ?
if (strlen($url) > 5 && stripos($url, "https") === 0) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$delimiter = '-------------' . uniqid();
$data = '';
if ($postFields != null) {
foreach ($postFields as $name => $content) {
$data .= "--" . $delimiter . "\r\n";
$data .= 'Content-Disposition: form-data; name="' . $name . '"';
$data .= "\r\n\r\n" . $content . "\r\n";
}
unset($name, $content);
}
if ($fileFields !== null) {
foreach ($fileFields as $name => $file) {
$data .= "--" . $delimiter . "\r\n";
$data .= 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $file['name'] . "\" \r\n";
$data .= 'Content-Type: ' . $file['type'] . "\r\n\r\n";
$data .= $file['content'] . "\r\n";
}
unset($name, $file);
}
$data .= "--" . $delimiter . "--";
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER,
[
'Content-Type: multipart/form-data; boundary=' . $delimiter,
'Content-Length: ' . strlen($data)
]
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
unset($data);
$errno = curl_errno($ch);
if ($errno) {
curl_close($ch);
throw new \RuntimeException($errno, 0);
}
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (200 !== $httpStatusCode) {
throw new \RuntimeException($response, $httpStatusCode);
}
return $response;
}
public function execute(IopRequest $request, $accessToken = null)
{
if ($accessToken && $this->isOverdueToken($accessToken)) {
throw new \InvalidArgumentException('token已过期请重新授权谢谢');
}
$sysParams["app_key"] = $this->appKey;
$sysParams["sign_method"] = $this->signMethod;
$sysParams["timestamp"] = $this->msectime();
$sysParams["method"] = $request->apiName;
$sysParams["partner_id"] = $this->sdkVersion;
$sysParams["simplify"] = $request->simplify;
$sysParams["format"] = $request->format;
if (null !== $accessToken) {
$sysParams["session"] = $accessToken;
}
$apiParams = $request->udfParams;
$requestUrl = $this->gatewayUrl;
if ($this->endWith($requestUrl, "/")) {
$requestUrl = substr($requestUrl, 0, -1);
}
$requestUrl .= '?';
if ($this->logLevel === $this->log_level_debug) {
$sysParams["debug"] = 'true';
}
$sysParams["sign"] = $this->generateSign($request->apiName, array_merge($apiParams, $sysParams));
foreach ($sysParams as $sysParamKey => $sysParamValue) {
$requestUrl .= "$sysParamKey=" . urlencode($sysParamValue) . "&";
}
$requestUrl = substr($requestUrl, 0, -1);
try {
if ($request->httpMethod === 'POST') {
$resp = $this->curl_post($requestUrl, $apiParams, $request->fileParams, $request->headerParams);
} else {
$resp = $this->curl_get($requestUrl, $apiParams, $request->headerParams);
}
} catch (\Throwable $e) {
throw $e;
}
unset($apiParams);
if (strpos($resp, 'specified access token is invalid')) {
$this->saveOverdueToken($accessToken);
} else {
$this->clearOverdueToken($accessToken);
}
$respObject = json_decode($resp, false, 512, JSON_BIGINT_AS_STRING);
if ($respObject === false) {
throw new \RuntimeException('响应格式异常,解析失败;响应内容为' . $resp);
}
return $respObject;
}
protected function logApiError($requestUrl, $errorCode, $responseTxt): void
{
$localIp = $_SERVER["SERVER_ADDR"] ?? "CLI";
$logger = new IopLogger;
$logger->conf["log_file"] = rtrim(root_path(), '\\/') . '/' . "logs/iopsdk.log." . date("Y-m-d");
$logger->conf["separator"] = "^_^";
$logData = [
date("Y-m-d H:i:s"),
$this->appKey,
$localIp,
PHP_OS,
$this->sdkVersion,
$requestUrl,
$errorCode,
str_replace("\n", "", $responseTxt)
];
$logger->log($logData);
}
public function msectime(): string
{
[$msec, $sec] = explode(' ', microtime());
return $sec . '000';
}
public function endWith($haystack, $needle): bool
{
$length = strlen($needle);
if ($length === 0) {
return false;
}
return (substr($haystack, -$length) === $needle);
}
public function isOverdueToken($token): bool
{
$file = rtrim(root_path(), '\\/') . '/tmp/ali_overdue_token/' . $token;
if (is_file($file)) {
$num = file_get_contents($file);
// 验证超过5次 或者 半小时以内创建的,不重新放行
if ($num > 5 || (filemtime($file)) > (time() - 300)) {
return true;
}
}
return false;
}
public function saveOverdueToken($token): bool
{
$path = rtrim(root_path(), '\\/') . '/tmp/ali_overdue_token/';
if (!is_dir($path) && !mkdir($path) && !is_dir($path)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $path));
}
$file = $path . '/' . $token;
$num = is_file($file) ? file_get_contents($file) + 1 : 1;
file_put_contents($file, $num);
return true;
}
public function clearOverdueToken($token): void
{
$file = rtrim(root_path(), '\\/') . '/tmp/ali_overdue_token/' . $token;
if (is_file($file)) {
@unlink($file);
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk;
class IopRequest
{
public $apiName;
public $headerParams = [];
public $udfParams = [];
public $fileParams = [];
public $httpMethod = 'POST';
public $simplify = 'false';
public $format = 'json';//支持TOP的xml
public function __construct($apiName, $httpMethod = 'POST')
{
$this->apiName = $apiName;
$this->httpMethod = $httpMethod;
if ($this->startWith($apiName, "//")) {
throw new \InvalidArgumentException("api name is invalid. It should be start with /");
}
}
/**
* 添加API参数
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 17:14
* @param $key
* @param $value
* @return $this
*/
public function addApiParam($key, $value): IopRequest
{
if (!is_string($key)) {
throw new \InvalidArgumentException("api param key should be string");
}
if (is_object($value)) {
$this->udfParams[$key] = json_decode($value, false);
} else {
$this->udfParams[$key] = $value;
}
return $this;
}
/**
* 添加文件参数
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 16:53
* @param $key
* @param $content
* @param string $mimeType
* @return $this
*/
public function addFileParam($key, $content, string $mimeType = 'application/octet-stream'): IopRequest
{
if (!is_string($key)) {
throw new \InvalidArgumentException("api file param key should be string");
}
$file = [
'type' => $mimeType,
'content' => $content,
'name' => $key
];
$this->fileParams[$key] = $file;
return $this;
}
/**
* 添加HTTP头参数
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 16:53
* @param $key
* @param $value
* @return $this
*/
public function addHttpHeaderParam($key, $value): IopRequest
{
if (!is_string($key)) {
throw new \InvalidArgumentException("http header param key should be string");
}
if (!is_string($value)) {
throw new \InvalidArgumentException("http header param value should be string");
}
$this->headerParams[$key] = $value;
return $this;
}
/**
* 判断字符串是否以某个字符开头
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 16:54
* @param $str
* @param $needle
* @return bool
*/
public function startWith($str, $needle): bool
{
return strpos($str, $needle) === 0;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Sweeper\PlatformMiddleware\Sdk\AeSdk;
class UrlConstants
{
/** @var string API 网关地址 */
public const API_GATEWAY_URL = 'https://api-sg.aliexpress.com/sync';
public static $api_gateway_url_tw = self::API_GATEWAY_URL;
public const API_GATEWAY_URL_TW_NEW = "http://api-sg.aliexpress.com/rest";
}

View File

@@ -0,0 +1,11 @@
<?php
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopRequest;
$c = new IopClient('https://api.taobao.tw/rest', '${appKey}', '${appSecret}');
$request = new IopRequest('/xiaoxuan/mockfileupload');
$request->addApiParam('file_name', 'pom.xml');
$request->addFileParam('file_bytes', file_get_contents('/Users/xt/Documents/work/tasp/tasp/pom.xml'));
var_dump($c->execute($request));

View File

@@ -0,0 +1,15 @@
<?php
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\Constants;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopRequest;
$c = new IopClient('api.taobao.tw/rest', '100240', 'hLeciS15d7UsmXKoND76sBVPpkzepxex');
$c->logLevel = Constants::$log_level_debug;
$request = new IopRequest('/product/item/get', 'GET');
$request->addApiParam('itemId', '157432005');
$request->addApiParam('authDO', '{"sellerId":2000000016002}');
var_dump($c->execute($request, null));
echo PHP_INT_MAX;
var_dump($c->msectime());

View File

@@ -0,0 +1,12 @@
<?php
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\Iop\IopRequest;
$c = new IopClient('https://api-pre.aliexpress.com/sync', '33505222', 'e1fed6b34feb26aabc391d187732af93');
$request = new IopRequest('aliexpress.logistics.redefining.getlogisticsselleraddresses');
$request->simplify = "true";
$request->format = "xml";
$request->addApiParam('seller_address_query', 'pickup');
var_dump($c->execute($request, "50000001a27l15rndYBjw6PrtFFHPGZfy09k1Cp1bd8597fsduP0RsNy0jhF6FL"));

View File

@@ -0,0 +1,69 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Aliexpress;
use Sweeper\DesignPattern\Traits\MultiPattern;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
abstract class Base
{
use MultiPattern;
/**
* 校验必填参数
* User: Sweeper
* Time: 2023/1/11 11:26
* @param array $params
* @param array $requiredFields
* @return bool
*/
public static function verifyParams(array $requiredFields = [], array $params = []): bool
{
foreach ($requiredFields as $requiredField) {
if (!isset($params[$requiredField])) {
throw new \InvalidArgumentException("字段[{$requiredField}]为必填参数");
}
}
return true;
}
/**
* 执行 API 请求
* User: Sweeper
* Time: 2023/4/4 16:38
* @param array $accountInfo 账号信息
* @param string $apiName API 名称
* @param array $paramVal 平台请求参数
* @param string $paramKey 平台请求参数 KEY
* @param array $requiredFields 接口必填字段,自动校验
* @param string $httpMethod 请求方式,默认 POST
* @param callable|null $callback 方法不兼容/不适用可以直接指定闭包处理
* @return mixed
*/
public static function executeRequest(array $accountInfo, string $apiName, array $paramVal = [], string $paramKey = 'param0', array $requiredFields = [], string $httpMethod = 'POST', callable $callback = null)
{
$simplify = isset($paramVal['simplify']) && $paramVal['simplify'] ? 'true' : 'false';// 精简返回
unset($paramVal['simplify']);
static::verifyParams($requiredFields, $paramVal);
try {
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest($apiName, $httpMethod);
// 执行回调函数并且返回
if ($callback && is_callable($callback)) {
return $callback($client, $request, $accountInfo);
}
$paramVal && $request->addApiParam($paramKey ?: 'param0', json_encode($paramVal));
$request->simplify = $simplify;
$request->addApiParam('simplify', $simplify);// 设置为精简返回
return $client->execute($request, $accountInfo['access_token']);
} catch (\Throwable $ex) {
throw new \RuntimeException("{$ex->getFile()}#{$ex->getLine()} ({$ex->getMessage()})");
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* 获取卖家地址信息
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 16:46
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\Address
*/
class Address extends Base
{
/**
* 获取卖家地址
* 目录API文档/AE-物流/获取卖家地址
* api: https://developers.aliexpress.com/doc.htm?docId=30133&docType=2
* api: https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193531.0.0.13b33b53KFl1Q9#/api?cid=20892&path=aliexpress.logistics.redefining.getlogisticsselleraddresses&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 16:46
* @param $accountInfo
* @return false
*/
public function getAddressInfo($accountInfo): bool
{
return static::executeRequest($accountInfo, 'aliexpress.logistics.redefining.getlogisticsselleraddresses', [], 'seller_address_query', [], 'POST', function($client, $request) use ($accountInfo) {
$request->addApiParam('seller_address_query', 'sender,pickup,refund');
return $client->execute($request, $accountInfo['access_token']);
});
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
class Attributes extends Base
{
/**
* 获取用户运费模板列表信息
* 目录API文档/AE-商品/AE-运费/用户运费模板列表信息
* api: https://developers.aliexpress.com/doc.htm?docId=30126&docType=2
* api: https://open.aliexpress.com/doc/api.htm#/api?cid=20900&path=aliexpress.freight.redefining.listfreighttemplate&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/18 17:42
* @param $accountInfo
* @return false
*/
public function getAttributesList($accountInfo): ?bool
{
return static::executeRequest($accountInfo, 'aliexpress.freight.redefining.listfreighttemplate');
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
class Category extends Base
{
/**
* 类目预测,可以筛选卖家已经通过准入申请的类目
* api: https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.postproduct.redefining.categoryforecast&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 14:41
* @param array $accountInfo
* @param array $params
* @return mixed
* @throws \Throwable
*/
public function categoryForecast(array $accountInfo, array $params = [])
{
$c = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('aliexpress.category.tree.list', 'GET');
if (!empty($params['channel_seller_id'])) {
$request->addApiParam('channel_seller_id', $params['channel_seller_id']);
}
if (!empty($params['only_with_permission'])) {
$request->addApiParam('only_with_permission', $params['only_with_permission']);
}
if (!empty($params['channel'])) {
$request->addApiParam('channel', $params['channel']);
}
if (!empty($params['category_id']) || $params['category_id'] === 0) {
$request->addApiParam('category_id', $params['category_id']);
}
$rs = $c->execute($request, $accountInfo['access_token']);
return $rs->cainiao_global_handover_content_query_response->result ?? $rs->result ?? $rs;
}
/**
* 根据发布类目id、父属性路径可选获取子属性信息只返回有权限品牌
* api: https://open.aliexpress.com/doc/api.htm#/api?cid=20897&path=aliexpress.category.redefining.getchildattributesresultbypostcateidandpath&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 14:42
* @param array $accountInfo
* @param array $params
* @return false|mixed
* @throws \Throwable
*/
public function getAttributesList(array $accountInfo, array $params = [])
{
$c = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('aliexpress.category.redefining.getchildattributesresultbypostcateidandpath', 'POST');
if (!empty($params['channel_seller_id'])) {
$request->addApiParam('channel_seller_id', $params['channel_seller_id']);
}
if (!empty($params['channel'])) {
$request->addApiParam('channel', $params['channel']);
}
if (!empty($params['locale'])) {
$request->addApiParam('locale', $params['locale']);
}
if (!empty($params['param1'])) {
$request->addApiParam('param1', $params['param1']);
}
if (!empty($params['param2'])) {
$request->addApiParam('param2', $params['param2']);
}
$rs = $c->execute($request, $accountInfo['access_token']);
return $rs->cainiao_global_handover_content_query_response->result ?? $rs->result ?? $rs;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2022/12/29 15:06
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
class Decrypt extends Base
{
/**
* 买家订单物流详情解密
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 14:46
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.6dc86f3dwkNxPS#/api?cid=20905&path=aliexpress.trade.seller.order.decrypt&methodType=GET/POST
* @param array $accountInfo
* @param $orderId
* @param $oaid
* @return mixed
* @throws \Throwable
*/
public function decrypt(array $accountInfo, $orderId, $oaid)
{
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('aliexpress.trade.seller.order.decrypt');
$request->addApiParam('orderId', $orderId);
$request->addApiParam('oaid', $oaid);
$response = $client->execute($request, $accountInfo['access_token']);
return $response->aliexpress_trade_seller_order_decrypt_response ?? $response;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2022/12/27 9:46
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* AE-评价
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 14:49
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\Evaluation
*/
class Evaluation extends Base
{
/**
* 查询待卖家评价的订单信息
* User: Sweeper
* Time: 2022/12/27 14:24
* @doc https://developers.aliexpress.com/doc.htm?docId=30247&docType=2
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20896&path=aliexpress.appraise.redefining.querysellerevaluationorderlist&methodType=GET/POST
* @param array $accountInfo 用户信息
* @param array $params 参数数组
* @return mixed
*/
public function querySellerEvaluationOrderList(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.appraise.redefining.querysellerevaluationorderlist', $params, 'query_d_t_o');
return $response->aliexpress_appraise_redefining_querysellerevaluationorderlist_response->result ?? $response->result ?? $response;
}
/**
* 卖家对未评价的订单进行评价
* User: Sweeper
* Time: 2022/12/27 14:24
* @doc https://developers.aliexpress.com/doc.htm?docId=30250&docType=2
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20896&path=aliexpress.appraise.redefining.savesellerfeedback&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function saveSellerFeedback(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.appraise.redefining.savesellerfeedback', $params, 'param1');
return $response->aliexpress_appraise_redefining_savesellerfeedback_response ?? $response;
}
/**
* 查询订单已生效的评价信息
* @doc https://developers.aliexpress.com/doc.htm?docId=35927&docType=2
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20896&path=aliexpress.evaluation.listorderevaluation.get&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
* @author linzj
* @date 2023-01-12 14:10
*/
public function getListOrderEvaluation(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.evaluation.listorderevaluation.get', $params, 'trade_evaluation_request');
return $response->aliexpress_evaluation_listorderevaluation_get_response->target_list ?? $response->target_list ?? $response;
}
/**
* 回复评价
* @doc https://developers.aliexpress.com/doc.htm?docId=35905&docType=2
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20896&path=aliexpress.evaluation.evaluation.reply&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
* @author linzj
* @date 2023-01-12 14:27
*/
public function replyEvaluation(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.evaluation.evaluation.reply', [], '', [], 'POST', function($client, $request) use ($params, $accountInfo) {
$request->addApiParam('child_order_id', $params['child_order_id']);
$request->addApiParam('parent_order_id', $params['parent_order_id']);
$request->addApiParam('text', $params['text']);
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_evaluation_evaluation_reply_response->target ?? $response->target ?? $response;
}
}

View File

@@ -0,0 +1,186 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2023/4/4 15:52
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\HelperPhp\Traits\RedisCache;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* OpenAPI 统一跨境商家工作台-商家相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 14:56
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\GlobalSeller
*/
class GlobalSeller extends Base
{
use RedisCache;
/** @var string 全托管店铺类型 */
public const BUSINESS_TYPE_TRUSTEESHIP = 'ONE_STOP_SERVICE';
/** @var string 半托管店铺类型 */
public const BUSINESS_TYPE_POP_CHOICE = 'POP_CHOICE';
/**
* 获取商家账号列表
* User: Sweeper
* Time: 2023/4/4 17:16
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21387&path=global.seller.relation.query&methodType=GET
* @param array $accountInfo
* @param array $params
* @return mixed
* @response
* //{
* // "seller_relation_list": {// 渠道账号列表
* // "seller_relation": [
* // {
* // "channel_currency": "USD", // 渠道商品币种
* // "channel_shop_name": "DKSHETOY Official Store", // 渠道店铺名称
* // "business_type": "POP_CHOICE", // 业务类型: ONE_STOP_SERVICE 全托管店铺; POP_CHOICEPOP与半托管店铺
* // "channel_seller_id": 223525827, // 渠道sellerId
* // "channel": "AE_GLOBAL", // 渠道标识
* // "seller_id": 223525827, // 全球sellerId
* // },
* // ]
* // },
* // "global_currency": "USD", // 全球商品币种
* // "success": true,// 成功失败
* // "request_id": "2102e2b216953763075105129"
* //}
*/
public function relationQuery(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'global.seller.relation.query', $params, 'param', [], 'GET');
return $response->global_seller_relation_query_response->seller_relation_list ?? $response->seller_relation_list ?? $response;
}
/**
* 通过缓存获取 - 商家账号列表
* User: Sweeper
* Time: 2023/7/19 9:52
* @param array $accountInfo
* @param array $params
* @param bool $refresh
* @return array|mixed
*/
public function getSellerRelationListByCache(array $accountInfo, array $params = [], $refresh = false)
{
$unique = md5(json_encode($accountInfo));
$cacheKey = "middleware_cache:aliexpress:seller_relation_list:{$unique}";
[$cacheData, $errors] = $this->getCacheData($cacheKey, function($accountInfo, $params) {
$result = $this->relationQuery($accountInfo, $params);
$sellerRelationList = json_decode(json_encode($result), true);
$errorResponse = $sellerRelationList['error_response'] ?? [];
if ($errorResponse && isset($errorResponse['msg'])) {
throw new \LogicException($errorResponse['msg']);
}
return $sellerRelationList['seller_relation_list']['seller_relation'] ?? $sellerRelationList['seller_relation'] ?? [];
}, 86400, $refresh, $accountInfo, $params);
return $cacheData;
}
/**
* 获取全球卖家信息
* User: Sweeper
* Time: 2023/7/7 16:45
* @param array $accountInfo
* @param array $params
* @param string $key 要获取的 key
* @param string $channel 指定渠道名称
* @return array|mixed
*/
public function getGlobalSellerInfo(array $accountInfo, array $params = [], string $key = '', string $channel = 'ARISE_ES')
{
$sellerRelationList = $this->getSellerRelationListByCache($accountInfo, $params);
$globalSellerInfo = current($sellerRelationList);
foreach ($sellerRelationList as $sellerRelation) {
// 跳过全托管店铺渠道
if (!empty($sellerRelation['business_type']) && $sellerRelation['business_type'] === static::BUSINESS_TYPE_TRUSTEESHIP) {
continue;
}
// 指定要使用的渠道
if (!empty($channel)) {
if ($sellerRelation['channel'] === $channel) {
$globalSellerInfo = $sellerRelation;
break;
}
continue;// 没匹配中继续下一轮匹配
}
$globalSellerInfo = $sellerRelation;
break;
}
return $key ? ($globalSellerInfo[$key] ?? $globalSellerInfo) : $globalSellerInfo;
}
/**
* 获取全球卖家信息
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/20 17:14
* @param array $accountInfo
* @param array $params
* @param string $businessType
* @param string $channel
* @param array $where
* @return mixed
*/
public function getGlobalSellerInfoByWhere(array $accountInfo, array $params = [], string $businessType = self::BUSINESS_TYPE_TRUSTEESHIP, string $channel = 'AE_GLOBAL', array $where = [])
{
$sellerRelationList = $this->getSellerRelationListByCache($accountInfo, $params);
$globalSellerInfo = count($sellerRelationList) === 1 ? current($sellerRelationList) : [];
foreach ($sellerRelationList as $sellerRelation) {
// {
// "channel_currency": "USD", // 渠道商品币种
// "channel_shop_name": "DKSHETOY Official Store", // 渠道店铺名称
// "business_type": "POP_CHOICE", // 业务类型: ONE_STOP_SERVICE 全托管店铺; POP_CHOICEPOP与半托管店铺
// "channel_seller_id": 223525827, // 渠道sellerId
// "channel": "AE_GLOBAL", // 渠道标识
// "seller_id": 223525827, // 全球sellerId
// },
// 指定要使用的业务类型: ONE_STOP_SERVICE 全托管店铺; POP_CHOICEPOP与半托管店铺
if (!empty($businessType) && (empty($sellerRelation['business_type']) || $sellerRelation['business_type'] !== $businessType)) {
continue;// 没匹配中继续下一轮匹配
}
// 指定要使用的渠道标识
if (!empty($channel) && (empty($sellerRelation['channel']) || $sellerRelation['channel'] !== $channel)) {
continue;// 没匹配中继续下一轮匹配
}
foreach ($where as $key => $val) {
if (!isset($sellerRelation[$key]) || $sellerRelation[$key] !== $val) {
break 2;
}
}
$globalSellerInfo = $sellerRelation;
}
return $globalSellerInfo ?: $sellerRelationList;
}
/**
* 商家半托管基本信息查询
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 10:09
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21439&path=aliexpress.pop.choice.info.query&methodType=GET
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getChoiceInfo(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.pop.choice.info.query', $params, 'param', [], 'GET');
return $response->aliexpress_pop_choice_info_query_response->result ?? $response->result ?? $response;
}
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2022/12/27 9:46
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* OpenAPI 物流相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 15:15
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\Logistics
*/
class Logistics extends Base
{
/**
* 创建子交易单线上物流订单
* User: Sweeper
* Time: 2023/1/11 10:02
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20892&path=aliexpress.logistics.order.createorder&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function createOrder(array $accountInfo, array $params = [])
{
static::verifyParams(['trade_order_from', 'trade_order_id', 'declare_product_d_t_os', 'warehouse_carrier_service', 'address_d_t_os'], $params);
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('aliexpress.logistics.order.createorder');
// 必填参数
$request->addApiParam('trade_order_from', $params['trade_order_from']);
$request->addApiParam('trade_order_id', $params['trade_order_id']);
$request->addApiParam('declare_product_d_t_os', $params['declare_product_d_t_os']);
$request->addApiParam('warehouse_carrier_service', $params['warehouse_carrier_service']);
$request->addApiParam('address_d_t_os', $params['address_d_t_os']);
// 条件非必填
isset($params['domestic_logistics_company']) && $request->addApiParam('domestic_logistics_company', $params['domestic_logistics_company']);
isset($params['domestic_logistics_company_id']) && $request->addApiParam('domestic_logistics_company_id', $params['domestic_logistics_company_id']);
isset($params['domestic_tracking_no']) && $request->addApiParam('domestic_tracking_no', $params['domestic_tracking_no']);
// 非必填参数
isset($params['is_agree_upgrade_reverse_parcel_insure']) && $request->addApiParam('is_agree_upgrade_reverse_parcel_insure', $params['is_agree_upgrade_reverse_parcel_insure']);
isset($params['oaid']) && $request->addApiParam('oaid', $params['oaid']);
isset($params['pickup_type']) && $request->addApiParam('pickup_type', $params['pickup_type']);
isset($params['package_num']) && $request->addApiParam('package_num', $params['package_num']);
isset($params['undeliverable_decision']) && $request->addApiParam('undeliverable_decision', $params['undeliverable_decision']);
isset($params['invoice_number']) && $request->addApiParam('invoice_number', $params['invoice_number']);
isset($params['top_user_key']) && $request->addApiParam('top_user_key', $params['top_user_key']);
isset($params['insurance_coverage']) && $request->addApiParam('insurance_coverage', $params['insurance_coverage']);
$response = $client->execute($request, $accountInfo['access_token']);
return $response->aliexpress_logistics_order_createorder_response->result ?? $response->result ?? $response;
}
/**
* 查询物流订单信息(推荐)
* 目录API文档/AE物流/查询物流订单信息(推荐)
* api: https://open.aliexpress.com/doc/api.htm#/api?cid=20892&path=aliexpress.logistics.querylogisticsorderdetail&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:01
* @param array $accountInfo
* @param array $params
* @return mixed
* @throws \Throwable
*/
public function getLogisticsOrderDetail(array $accountInfo, array $params = [])
{
// 参数验证和组装 交易订单号
if (empty($params['trade_order_id'])) {
throw new \InvalidArgumentException('参数trade_order_id必填且不能为空');
}
$rs = static::executeRequest($accountInfo, 'aliexpress.logistics.querylogisticsorderdetail', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
$request->addApiParam('trade_order_id', $params['trade_order_id']);
if (!empty($params['current_page'])) {
$request->addApiParam('current_page', $params['current_page']);
} //当前页
if (!empty($params['domestic_logistics_num'])) {
$request->addApiParam('domestic_logistics_num', $params['domestic_logistics_num']);
} //国内运单号
if (!empty($params['gmt_create_end_str'])) {
$request->addApiParam('gmt_create_end_str', $params['gmt_create_end_str']);
} //起始创建时间
if (!empty($params['gmt_create_start_str'])) {
$request->addApiParam('gmt_create_start_str', $params['gmt_create_start_str']);
} //截止创建时间
if (!empty($params['international_logistics_num'])) {
$request->addApiParam('international_logistics_num', $params['international_logistics_num']);
} //国际运单号
if (!empty($params['logistics_status'])) {
$request->addApiParam('logistics_status', $params['logistics_status']);
} //订单状态
if (!empty($params['page_size'])) {
$request->addApiParam('page_size', $params['page_size']);
} //页面大小
if (!empty($params['warehouse_carrier_service'])) {
$request->addApiParam('warehouse_carrier_service', $params['warehouse_carrier_service']);
} //物流服务编码
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_logistics_querylogisticsorderdetail_response->result ?? $rs->result ?? $rs;
}
/**
* 查询仓发物流订单信息
* 目录API文档/AE物流/查询仓发物流订单信息
* api: https://open.aliexpress.com/doc/api.htm#/api?cid=20892&path=aliexpress.logistics.warehouse.querydetail&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return false || object
*/
public function getLogisticsWarehouseQueryDetail(array $accountInfo, array $params = [])
{
// 参数验证和组装 交易订单号
if (empty($params['trade_order_id'])) {
throw new \InvalidArgumentException('参数trade_order_id必填且不能为空');
}
$rs = static::executeRequest($accountInfo, 'aliexpress.logistics.warehouse.querydetail', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
$request->addApiParam('trade_order_id', $params['trade_order_id']);
if (!empty($params['consign_type'])) {
$request->addApiParam('consign_type', $params['consign_type']);
} //仓发订单类型 DOMESTIC 优选仓
if (!empty($params['current_page'])) {
$request->addApiParam('current_page', $params['current_page']);
} //当前页
if (!empty($params['domestic_logistics_num'])) {
$request->addApiParam('domestic_logistics_num', $params['domestic_logistics_num']);
} //国内运单号
if (!empty($params['gmt_create_end_str'])) {
$request->addApiParam('gmt_create_end_str', $params['gmt_create_end_str']);
} //起始创建时间
if (!empty($params['gmt_create_start_str'])) {
$request->addApiParam('gmt_create_start_str', $params['gmt_create_start_str']);
} //截止创建时间
if (!empty($params['international_logistics_num'])) {
$request->addApiParam('international_logistics_num', $params['international_logistics_num']);
} //国际运单号
if (!empty($params['logistics_status'])) {
$request->addApiParam('logistics_status', $params['logistics_status']);
} //订单状态
if (!empty($params['page_size'])) {
$request->addApiParam('page_size', $params['page_size']);
} //页面大小
if (!empty($params['warehouse_carrier_service'])) {
$request->addApiParam('warehouse_carrier_service', $params['warehouse_carrier_service']);
} //物流服务编码
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_logistics_warehouse_querydetail_response->return_result ?? $rs->return_result ?? $rs;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2023/4/4 15:52
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* OpenAPI AE-商家相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 15:20
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\Merchant
*/
class Merchant extends Base
{
/**
* 商家地址列表查询
* User: Sweeper
* Time: 2023/4/4 17:16
* channel_seller_id Number 请输入全托管店铺的id。 渠道seller id 可以在这个API中查询global.seller.relation.query 请使用 business_type = ONE_STOP_SERVICE 的全托管店铺 channel_seller_id
* address_types String[] 地址类型SALESRETURN退货地址WAREHOUSE发货地址
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20895&path=aliexpress.merchant.Address.list&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function merchantAddressList(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.merchant.Address.list', $params, 'param', ['channel_seller_id', 'address_types']);
return $response->aliexpress_merchant_Address_list_response->result ?? $response->result ?? $response;
}
/**
* 查询卖家资料,如刊登数量
* api: https://open.aliexpress.com/doc/api.htm#/api?cid=20895&path=aliexpress.merchant.profile.get&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:27
* @param array $accountInfo
* @return mixed
*/
public function getMerchantProfile(array $accountInfo)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.merchant.profile.get');
return $rs->aliexpress_merchant_profile_get_response->profile ?? $rs->profile ?? $rs;
}
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2022/12/27 9:46
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
use function GuzzleHttp\json_encode;
/**
* OpenAPI 订单相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 15:21
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\Order
*/
class Order extends Base
{
/**
* 订单收货信息查询
* User: Sweeper
* Time: 2023/1/11 10:06
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.6dc86f3dAv7fsz#/api?cid=20905&path=aliexpress.trade.redefining.findorderreceiptinfo&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function findOrderReceiptInfo(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.trade.redefining.findorderreceiptinfo', $params, 'param1');
return $response->aliexpress_trade_redefining_findorderreceiptinfo_response->result ?? $response->result ?? $response;
}
/**
* 获取订单列表
* User: Sweeper
* Time: 2023/2/24 10:19
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.6dc86f3dAv7fsz#/api?cid=20905&path=aliexpress.trade.seller.orderlist.get&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getOrderList(array $accountInfo, array $params = [])
{
static::verifyParams(['current_page', 'page_size'], $params);
$response = static::executeRequest($accountInfo, 'aliexpress.trade.seller.orderlist.get', $params, 'param_aeop_order_query');
return $response->aliexpress_trade_seller_orderlist_get_response ?? $response->result ?? $response;
}
/**
* 订单列表简化查询
* User: Sweeper
* Time: 2023/2/24 10:22
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.6dc86f3dAv7fsz#/api?cid=20905&path=aliexpress.trade.redefining.findorderlistsimplequery&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function findOrderListSimpleQuery(array $accountInfo, array $params = [])
{
static::verifyParams(['page', 'page_size'], $params);
$response = static::executeRequest($accountInfo, 'aliexpress.trade.redefining.findorderlistsimplequery', $params, 'param1');
return $response->aliexpress_trade_redefining_findorderlistsimplequery_response ?? $response->result ?? $response;
}
/**
* 新版交易订单详情查询
* User: Sweeper
* Time: 2023/1/11 10:09
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.6dc86f3dAv7fsz#/api?cid=20905&path=aliexpress.trade.new.redefining.findorderbyid&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function findOrderById(array $accountInfo, array $params = [])
{
static::verifyParams(['order_id'], $params);
$response = static::executeRequest($accountInfo, 'aliexpress.trade.new.redefining.findorderbyid', $params, 'param1');
return $response->aliexpress_trade_new_redefining_findorderbyid_response ?? $response->result ?? $response;
}
/**
* 卖家同意取消订单
* User: Sweeper
* Time: 2023/4/20 11:09
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20905&path=aliexpress.trade.seller.order.acceptcancel&methodType=GET/POST
* @param array $accountInfo
* @param array $params [buyer_login_id: String, order_id: Number]
* @return mixed
*/
public function acceptCancelOrder(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.trade.seller.order.acceptcancel', $params, 'param_order_cancel_request');
return $response->aliexpress_trade_seller_order_acceptcancel_response ?? $response->result ?? $response;
}
/**
* 卖家拒绝取消订单
* User: Sweeper
* Time: 2023/4/20 11:10
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=20905&path=aliexpress.trade.seller.order.refusecancel&methodType=GET/POST
* @param array $accountInfo
* @param array $params ['buyer_login_id','memo','order_id']
* @return mixed
*/
public function refuseCancelOrder(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.trade.seller.order.refusecancel', $params, 'param_order_cancel_request', ['buyer_login_id', 'memo', 'order_id']);
return $response->aliexpress_trade_seller_order_refusecancel_response ?? $response->result ?? $response;
}
}

View File

@@ -0,0 +1,264 @@
<?php
/**
* Created by PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 9:51
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
class Product extends Base
{
/**
* 商品查询新接口
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:07
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.offer.product.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getProductDetail(array $accountInfo, array $params)
{
$response = static::executeRequest($accountInfo, 'aliexpress.offer.product.query', $params, 'product_id', ['product_id'], 'POST', function($client, $request) use ($accountInfo, $params) {
$request->addApiParam('product_id', $params['product_id']);
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_offer_product_query_response->result ?? $response->result ?? $response;
}
/**
* 查询半托管已加入/待加入/待预存商品列表
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 10:10
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21439&path=aliexpress.pop.choice.products.list&methodType=GET
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getPopChoiceProductList(array $accountInfo, array $params)
{
$response = static::executeRequest($accountInfo, 'aliexpress.pop.choice.products.list', $params, 'param', [], 'GET');
return $response->aliexpress_pop_choice_products_list_response->result ?? $response->result ?? $response;
}
/**
* 半托管商品详情查询
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 10:10
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21439&path=aliexpress.pop.choice.product.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getPopChoiceProductDetail(array $accountInfo, array $params)
{
$response = static::executeRequest($accountInfo, 'aliexpress.pop.choice.product.query', $params, 'product_id', ['product_id'], 'POST', function($client, $request) use ($accountInfo, $params) {
$request->addApiParam('product_id', $params['product_id']);
$request->addApiParam('language', 'zh_CN');
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_pop_choice_product_query_response->result ?? $response->result ?? $response;
}
/**
* AE-全托管-商品列表查询
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 10:10
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21403&path=aliexpress.choice.products.list&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getChoiceProductList(array $accountInfo, array $params)
{
$response = static::executeRequest($accountInfo, 'aliexpress.choice.products.list', $params, 'param', ['channel_seller_id', 'channel', 'search_condition_do'], 'POST',
function($client, $request) use ($accountInfo, $params) {
$request->addApiParam('channel_seller_id', $params['channel_seller_id']);
$request->addApiParam('channel', $params['channel']);
$request->addApiParam('page_size', $params['page_size'] ?? 20);
$request->addApiParam('current_page', $params['current_page'] ?? 1);
$request->addApiParam('search_condition_do', json_encode($params['search_condition_do']));
$request->addApiParam('version', $params['version'] ?? 1);
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_choice_products_list_response->result ?? $response->result ?? $response;
}
/**
* AE-全托管-全托管店铺-查询单个商品详情
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 10:10
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21403&path=aliexpress.choice.product.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getChoiceProductDetail(array $accountInfo, array $params)
{
$response = static::executeRequest($accountInfo, 'aliexpress.choice.product.query', $params, 'param', ['channel_seller_id', 'channel', 'product_id'], 'POST',
function($client, $request) use ($accountInfo, $params) {
$request->addApiParam('product_id', $params['product_id']);
$request->addApiParam('channel_seller_id', $params['channel_seller_id']);
$request->addApiParam('channel', $params['channel']);
$request->addApiParam('version', $params['version'] ?? 1);
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_choice_product_query_response->result ?? $response->result ?? $response;
}
/**
* AE-全托管-按照商家查询仓库编码
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/26 10:30
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getChoiceProductWarehouseList(array $accountInfo, array $params)
{
$response = static::executeRequest($accountInfo, 'aliexpress.choice.product.warehouse.list', $params, 'param', ['channel_seller_id', 'channel'], 'POST',
function($client, $request) use ($accountInfo, $params) {
$request->addApiParam('product_id', $params['product_id']);
$request->addApiParam('channel_seller_id', $params['channel_seller_id']);
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_choice_product_query_response->result ?? $response->result ?? $response;
}
/**
* 商品删除接口
* api: https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.offer.product.delete&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:03
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function deleteProduct(array $accountInfo, array $params)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.offer.product.delete', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
if (!empty($params['product_id'])) {
$request->addApiParam('product_id', $params['product_id']);
}
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_offer_product_delete_response->product_id ?? $rs->product_id ?? $rs;
}
/**
* 商品列表查询接口
* api: https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.postproduct.redefining.findproductinfolistquery&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:05
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getProductsList(array $accountInfo, array $params)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.postproduct.redefining.findproductinfolistquery', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
$request->addApiParam('aeop_a_e_product_list_query', $params['aeop_a_e_product_list_query']);
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_postproduct_redefining_findproductinfolistquery_response->result ?? $rs->result ?? $rs;
}
/**
* 分页查询待优化商品列表
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:08
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.product.diagnosis.pageQueryProblem&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getProductProblemList(array $accountInfo, array $params)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.product.diagnosis.pageQueryProblem', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
isset($params['operate_status']) && $request->addApiParam('operate_status', $params['operate_status']);
isset($params['problem_type_list']) && $request->addApiParam('problem_type_list', $params['problem_type_list']);
isset($params['page_size']) && $request->addApiParam('page_size', $params['page_size']);
isset($params['current_page']) && $request->addApiParam('current_page', $params['current_page']);
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_product_diagnosis_pageQueryProblem_response ?? $rs;
}
/**
* 查询商家下待优化的商品问题类型列表
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:12
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.product.diagnosis.queryProblem&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getProductProblemTypeList(array $accountInfo, array $params)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.product.diagnosis.queryProblem', [], '');
return $rs->aliexpress_product_diagnosis_queryProblem_response->product_problem_type_list ?? $rs->product_problem_type_list ?? $rs;
}
/**
* 商品新的编辑接口
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:14
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.offer.product.edit&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function editProductNew(array $accountInfo, array $params)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.offer.product.edit', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
$request->addApiParam('aeop_a_e_product', $params['aeop_a_e_product']);
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_offer_product_edit_response->result ?? $rs->result ?? $rs;
}
/**
* 商品发布新接口
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:15
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20904&path=aliexpress.offer.product.post&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function uploadListing(array $accountInfo, array $params)
{
$rs = static::executeRequest($accountInfo, 'aliexpress.offer.product.post', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
$request->addApiParam('aeop_a_e_product', $params['aeop_a_e_product']);
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->aliexpress_offer_product_edit_response->result ?? $rs->result ?? $rs;
}
}

View File

@@ -0,0 +1,2 @@
## API 文档
https://console-docs.apipost.cn/preview/a8b356c4e7ad1bd6/8b42e455151dbc70 pwd444421

View File

@@ -0,0 +1,244 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
class Ship extends Base
{
/**
* 组包提交
* 目录API文档/菜鸟国际出口/提供给ISV通过该接口提交发布交接单
* api:https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21215&path=cainiao.global.handover.commit&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:22
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function commitBigBag(array $accountInfo, array $params = [])
{
if (empty($params['pickup_info'])) {
throw new \InvalidArgumentException('参数pickup_info必填且不能为空');
}
if (empty($params['user_info'])) {
throw new \InvalidArgumentException('参数user_info必填且不能为空');
}
if (empty($params['client'])) {
throw new \InvalidArgumentException('参数client必填且不能为空');
}
$rs = static::executeRequest($accountInfo, 'cainiao.global.handover.commit', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
if (!empty($params['seller_parcel_order_list'])) {
if (is_array($params['seller_parcel_order_list']) || is_object($params['seller_parcel_order_list'])) {
$request->addApiParam('seller_parcel_order_list', json_encode($params['seller_parcel_order_list']));
} else {
$request->addApiParam('seller_parcel_order_list', $params['seller_parcel_order_list']);
}
}
if (is_array($params['pickup_info']) || is_object($params['pickup_info'])) {
$request->addApiParam('pickup_info', json_encode($params['pickup_info']));
} else {
$request->addApiParam('pickup_info', $params['pickup_info']);
}
if (!empty($params['order_code_list'])) {
if (is_array($params['order_code_list']) || is_object($params['order_code_list'])) {
$request->addApiParam('order_code_list', json_encode($params['order_code_list']));
} else {
$request->addApiParam('order_code_list', $params['order_code_list']);
}
}
if (!empty($params['weight'])) {
$request->addApiParam('weight', $params['weight']);
}
if (!empty($params['handover_order_id'])) {
$request->addApiParam('handover_order_id', $params['handover_order_id']);
}
if (!empty($params['remark'])) {
$request->addApiParam('remark', $params['remark']);
}
if (!empty($params['return_info'])) {
if (is_array($params['return_info']) || is_object($params['return_info'])) {
$request->addApiParam('return_info', json_encode($params['return_info']));
} else {
$request->addApiParam('return_info', $params['return_info']);
}
}
if (is_array($params['user_info']) || is_object($params['user_info'])) {
$request->addApiParam('user_info', json_encode($params['user_info']));
} else {
$request->addApiParam('user_info', $params['user_info']);
}
if (!empty($params['weight_unit'])) {
$request->addApiParam('weight_unit', $params['weight_unit']);
}
if (!empty($params['skip_invalid_parcel'])) {
$request->addApiParam('skip_invalid_parcel', $params['skip_invalid_parcel']);
} else {
$request->addApiParam('skip_invalid_parcel', 'true');
}
if (!empty($params['type'])) {
$request->addApiParam('type', $params['type']);
}
$request->addApiParam('client', $params['client']);
if (!empty($params['locale'])) {
$request->addApiParam('locale', $params['locale']);
}
if (!empty($params['features'])) {
if (is_array($params['features']) || is_object($params['features'])) {
$request->addApiParam('features', json_encode($params['features']));
} else {
$request->addApiParam('features', $params['features']);
}
}
if (!empty($params['appointment_type'])) {
$request->addApiParam('appointment_type', $params['appointment_type']);
}
if (!empty($params['domestic_tracking_no'])) {
$request->addApiParam('domestic_tracking_no', $params['domestic_tracking_no']);
}
if (!empty($params['domestic_logistics_company_id'])) {
$request->addApiParam('domestic_logistics_company_id', $params['domestic_logistics_company_id']);
}
if (!empty($params['domestic_logistics_company'])) {
$request->addApiParam('domestic_logistics_company', $params['domestic_logistics_company']);
}
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->cainiao_global_handover_commit_response->result ?? $rs->result ?? $rs;
}
/**
* 批次追加大包
* 目录API文档/菜鸟国际出口/批次追加大包
* api:https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21215&path=cainiao.global.handover.content.subbag.add&methodType=POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:19
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function addSubBag(array $accountInfo, array $params = [])
{
// 参数验证和组装
if (empty($params['user_info'])) {
throw new \InvalidArgumentException('参数user_info必填且不能为空');
}
if (empty($params['add_subbag_quantity'])) {
throw new \InvalidArgumentException('参数order_code必填且不能为空');
}
if (empty($params['order_code'])) {
throw new \InvalidArgumentException('参数order_code必填且不能为空');
}
$rs = static::executeRequest($accountInfo, 'cainiao.global.handover.content.subbag.add', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
if (is_array($params['user_info']) || is_object($params['user_info'])) {
$request->addApiParam('user_info', json_encode($params['user_info']));
} else {
$request->addApiParam('user_info', $params['user_info']);
}
$request->addApiParam('order_code', $params['order_code']);
$request->addApiParam('add_subbag_quantity', $params['add_subbag_quantity']);
if (empty($params['locale'])) {
$request->addApiParam('locale', 'zh_CN');
} else {
$request->addApiParam('locale', $params['locale']);
}
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->cainiao_global_handover_content_subbag_add_response->result ?? $rs->result ?? $rs;
}
/**
* 获取大包面单
* 目录API文档/菜鸟国际出口/返回指定大包面单的PDF文件数据
* api:https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21215&path=cainiao.global.handover.pdf.get&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:14
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getBigBagPdf(array $accountInfo, array $params = [])
{
// 参数验证和组装
if (empty($params['user_info'])) {
throw new \InvalidArgumentException('参数user_info必填且不能为空');
}
if (empty($params['client'])) {
throw new \InvalidArgumentException('参数client必填且不能为空');
}
if (empty($params['handover_content_id'])) {
throw new \InvalidArgumentException('参数handover_content_id必填且不能为空');
}
$rs = static::executeRequest($accountInfo, 'cainiao.global.handover.pdf.get', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
if (is_array($params['user_info']) || is_object($params['user_info'])) {
$request->addApiParam('user_info', json_encode($params['user_info']));
} else {
$request->addApiParam('user_info', $params['user_info']);
}
$request->addApiParam('client', $params['client']);
if (!empty($params['locale'])) {
$request->addApiParam('locale', $params['locale']);
}
$request->addApiParam('handover_content_id', $params['handover_content_id']);
if (!empty($params['type'])) {
$request->addApiParam('type', $params['type']);
}
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->cainiao_global_handover_pdf_get_response->result ?? $rs->result ?? $rs;
}
/**
* 查询大包详情
* 目录API文档/菜鸟国际出口/查询大包详情
* api: https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21215&path=cainiao.global.handover.pdf.get&methodType=GET/POST
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:12
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getBigBagInfo(array $accountInfo, array $params = [])
{
if (empty($params['client'])) {
throw new \InvalidArgumentException('参数client必填且不能为空');
}
$rs = static::executeRequest($accountInfo, 'cainiao.global.handover.content.query', [], '', [], 'POST', function($client, $request, $accountInfo) use ($params) {
// 参数验证和组装
if (!empty($params['user_info'])) {
if (is_array($params['user_info']) || is_object($params['user_info'])) {
$request->addApiParam('user_info', json_encode($params['user_info']));
} else {
$request->addApiParam('user_info', $params['user_info']);
}
}
if (!empty($params['order_code'])) {
$request->addApiParam('order_code', $params['order_code']);
}
if (!empty($params['tracking_number'])) {
$request->addApiParam('tracking_number', $params['tracking_number']);
}
$request->addApiParam('client', $params['client']);
if (!empty($params['locale'])) {
$request->addApiParam('locale', $params['locale']);
}
return $client->execute($request, $accountInfo['access_token']);
});
return $rs->cainiao_global_handover_content_query_response->result ?? $rs->result ?? $rs;
}
}

View File

@@ -0,0 +1,250 @@
<?php
/**
* Created by PhpStorm.
* User: Sweeper
* Time: 2023/4/4 15:52
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* OpenAPI AE-供应链相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 15:22
* @Package \Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\Supply
*/
class Supply extends Base
{
/** 行业账套编码[业务租户Id全托管场景请填写5110000] AER 221000AEG 288000 aechoice 5110000 */
/** @var int 行业账套编码[业务租户Id] AER */
public const BIZ_TYPE_AER = 221000;
/** @var int 行业账套编码[业务租户Id] AEG */
public const BIZ_TYPE_AEG = 288000;
/** @var int 行业账套编码[业务租户Id] aechoice */
public const BIZ_TYPE_AE_CHOICE = 5110000;
/** 单据类型 10:普通仓发 50:JIT */
/** @var int 订单类型 - 普通仓发 */
public const ORDER_TYPE_NORMAL = 10;
/** @var int 订单类型 - JIT */
public const ORDER_TYPE_JIT = 50;
/** 单据状态 10:待确认 15:已确认 17:待发货 20:待收货 21:已到仓 30:部分收货 40:收货完成 -99:已取消,不传则返回所有状态的采购单 */
/** biz_type Number 是 业务租户Id全托管场景请填写5110000 */
/** channel_user_id Number 是 渠道seller id 可以在这个API中查询global.seller.relation.query 请使用 business_type = ONE_STOP_SERVICE 的全托管店铺 channel_seller_id */
/**
* 采购单分页查询
* User: Sweeper
* Time: 2023/4/4 15:52
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.pageQuery&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function pageQuery(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.pageQuery', $params, 'param0', ['order_type', 'biz_type', 'channel_user_id']);
return $response->aliexpress_ascp_po_pageQuery_response->result ?? $response->result ?? $response;
}
/**
* 采购单确认
* User: Sweeper
* Time: 2023/4/4 16:17
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.confirmPurchaseOrder&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function confirmPurchaseOrder(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.confirmPurchaseOrder', $params, 'param0', ['purchase_order_no', 'biz_type', 'all_quantity_confirm', 'channel_user_id']);
return $response->aliexpress_ascp_po_confirmPurchaseOrder_response->result ?? $response->result ?? $response;
}
/**
* 创建揽收单
* User: Sweeper
* Time: 2023/4/4 16:50
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.createPickupOrder&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function createPickupOrder(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.createPickupOrder', $params, 'param0', [
'order_type',
'estimated_pickup_date',
'biz_type',
'estimated_weight',
'estimated_box_number',
'contact_info_dto',
'estimated_volume',
'order_no_list',
'channel_user_id',
]);
return $response->aliexpress_ascp_po_createPickupOrder_response->result ?? $response->result ?? $response;
}
/**
* 查询揽收单
* User: Sweeper
* Time: 2023/4/4 16:54
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.queryPickupOrder&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function queryPickupOrder(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.queryPickupOrder', $params, 'param0', ['pickup_order_number', 'biz_type', 'channel_user_id']);
return $response->aliexpress_ascp_po_queryPickupOrder_response->result ?? $response->result ?? $response;
}
/**
* 打印箱唛
* User: Sweeper
* Time: 2023/4/4 16:58
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.query.createShippingMarkPdf&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function createShippingMarkPdf(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.query.createShippingMarkPdf', $params, 'param0', ['biz_type', 'channel_user_id', 'purchase_order_no']);
return $response->aliexpress_ascp_po_query_createShippingMarkPdf_response->result ?? $response->result ?? $response;
}
/**
* 打印货品标签
* User: Sweeper
* Time: 2023/4/4 16:59
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.createScItemBarcodePdf&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function createScItemBarcodePdf(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.createScItemBarcodePdf', $params, 'param0', ['purchase_order_no', 'biz_type', 'channel_user_id']);
return $response->aliexpress_ascp_po_createScItemBarcodePdf_response->result ?? $response->result ?? $response;
}
/**
* 打印揽收面单
* User: Sweeper
* Time: 2023/4/4 17:01
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.createPickupShippingMarkPdf&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function createPickupShippingMarkPdf(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.createPickupShippingMarkPdf', $params, 'param0', ['pickup_order_number', 'biz_type', 'channel_user_id']);
return $response->aliexpress_ascp_po_createScItemBarcodePdf_response->result ?? $response->result ?? $response;
}
/**
* AliExpress采购单明细查询API
* User: Sweeper
* Time: 2023/4/4 17:18
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.item.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function purchaseOrderDetail(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.item.query', $params, 'purchase_order_item_query', ['biz_type', 'purchase_order_no']);
return $response->aliexpress_ascp_po_item_query_response->result ?? $response->result ?? $response;
}
/**
* AliExpress采购单查询API
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/21 14:24
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function purchaseOrderQuery(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.query', $params, 'purchase_order_query', ['biz_type', 'purchase_order_no']);
return $response->aliexpress_ascp_po_query_response->result ?? $response->result ?? $response;
}
/**
* 采购单货品详情
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/20 16:58
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.item.detail&methodType=GET
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function purchaseOrderItemDetail(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.item.detail', $params, 'param0', ['sc_item_id', 'channel', 'channel_seller_id'], 'GET');
return $response->aliexpress_ascp_item_detail_response->result ?? $response->result ?? $response;
}
/**
* AliExpress货品查询API
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/21 13:49
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.item.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function purchaseOrderItemQuery(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.item.query', $params, 'sc_item_query', ['biz_type']);
return $response->aliexpress_ascp_item_query_response->result ?? $response->result ?? $response;
}
/**
* 取消揽收单
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/9/21 13:49
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=21022&path=aliexpress.ascp.po.cancelPickupOrder&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function purchaseCancelPickOrder(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'aliexpress.ascp.po.cancelPickupOrder', $params, 'param0', ['pickup_order_number','biz_type','channel_user_id','cancel_reason']);
return $response->aliexpress_ascp_po_cancelPickupOrder_response->result ?? $response->result ?? $response;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Created by PhpStorm.
* User: czq
* Time: 2023/1/17 15:06
*/
namespace Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
class Trade extends Base
{
/**
* 延长买家收货时间
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 16:55
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=20905&path=aliexpress.trade.redefining.extendsbuyeracceptgoodstime&methodType=GET/POST
* @param array $accountInfo
* @param $orderId
* @param $day
* @return mixed
*/
public function extendsBuyerAcceptGoodsTime(array $accountInfo, $orderId, $day)
{
$response = static::executeRequest($accountInfo, 'aliexpress.trade.redefining.extendsbuyeracceptgoodstime', [], '', [], 'POST', function($client, $request, $accountInfo) use ($orderId, $day) {
$request->addApiParam('param0', $orderId);
$request->addApiParam('param1', $day);
return $client->execute($request, $accountInfo['access_token']);
});
return $response->aliexpress_trade_redefining_extendsbuyeracceptgoodstime_response ?? $response;
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Mirakl;
use Mirakl\MMP\Shop\Request\Order\Document\DownloadOrdersDocumentsRequest;
use Mirakl\MMP\Shop\Request\Order\Document\GetOrderDocumentsRequest;
use Mirakl\MMP\Shop\Request\Order\Get\GetOrdersRequest;
use Mirakl\MMP\Shop\Request\Order\Tracking\UpdateOrderTrackingInfoRequest;
use Sweeper\GuzzleHttpRequest\Response;
/**
* Mirakl - Catch 订单相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:18
* @Package \Sweeper\PlatformMiddleware\Services\Mirakl\Order
*/
class Order extends Request
{
/**
* 获取订单列表
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:42
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR11
* @param array $params
* @return Response
*/
public function getOrders(array $params = []): Response
{
// Building request
$request = new GetOrdersRequest($params);
// Calling the API
// $response = $this->clientMMP()->run($request);
// $response = $this->clientMMP()->getOrders($request);
// $response = $this->clientMMP()->raw()->getOrders($request);
return $this->execute($this->clientMMP(), $request);
}
/**
* 接受或拒绝订单行
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:44
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR21
* @param string $orderId
* @param array $orderLines {'order_lines':[{'accepted':true,'id':'Order_00012-B-1'}]}
* @return Response
*/
public function acceptRefuseOrder(string $orderId, array $orderLines): Response
{
// return static::handleResponse($this->clientMMP()->run(new AcceptOrderRequest($orderId, $orderLines)));// 官方SDK调不通不知道错误信息只提示400
return static::put($this->buildRequestUri("orders/{$orderId}/accept"), $orderLines, static::buildHeaders($this->getConfig('api_key')));
}
/**
* 取消订单
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:45
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR29
* @param string $orderId
* @return Response
*/
public function cancelOrder(string $orderId): Response
{
// return static::handleResponse($this->clientMMP()->run(new CancelOrderRequest($orderId)));
return static::put($this->buildRequestUri("orders/{$orderId}/cancel"), [], static::buildHeaders($this->getConfig('api_key')));
}
/**
* 更新特定订单的承运商跟踪信息
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:45
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR23
* @param string $orderId
* @param array $trackingOrderInfo {'carrier_code':'UPS','carrier_name':'UPS','carrier_url':'https://ups.com','tracking_number':'5555'}
* @return Response
*/
public function updateOrderTrackingInfo(string $orderId, array $trackingOrderInfo): Response
{
return $this->execute($this->clientMMP(), new UpdateOrderTrackingInfoRequest($orderId, $trackingOrderInfo));
}
/**
* 获取订单文档
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:46
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR72
* @param array $orderIds
* @return Response
* @throws \Mirakl\Core\Exception\RequestValidationException
*/
public function getOrderDocuments(array $orderIds): Response
{
// Building request
$request = new GetOrderDocumentsRequest($orderIds);
// Calling the API
return $this->execute($this->clientMMP(), $request);
}
/**
* 下载订单文档
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:47
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR73
* @param array $orderIds
* @param bool $download
* @return Response
*/
public function downloadOrdersDocuments(array $orderIds, bool $download = false): Response
{
// Building request
$request = new DownloadOrdersDocumentsRequest();
$request->setOrderIds($orderIds);
$result = $this->clientMMP()->downloadOrdersDocuments($request);
if ($download) {
$result->download();
}
if (ob_get_length()) {
ob_clean();
}
$result->getFile()->rewind();
return Response::success('Success', [$result->getFile()->fpassthru()]);
}
/**
* 校验订单发货 Valid the shipment of the order
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:48
* @doc https://help.mirakl.net/help/api-doc/seller/mmp.html#OR24
* @param string $orderId
* @return Response
*/
public function shipOrder(string $orderId): Response
{
// return static::handleResponse($this->clientMMP()->run(new ShipOrderRequest($orderId)));
return static::put($this->buildRequestUri("orders/{$orderId}/ship"), [], static::buildHeaders($this->getConfig('api_key')));
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Mirakl;
use Mirakl\MMP\Shop\Request\Shipping\GetShippingCarriersRequest;
use Sweeper\GuzzleHttpRequest\Response;
/**
* Mirakl - Catch 平台配置相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:16
* @Package \Sweeper\PlatformMiddleware\Services\Mirakl\PlatformSetting
*/
class PlatformSetting extends Request
{
/**
* 列出所有承运商信息
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:17
* @param array $params
* @return Response
*/
public function carriers(array $params = []): Response
{
return $this->execute($this->clientMMP(), new GetShippingCarriersRequest($params));
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Mirakl;
use Mirakl\Core\Client\AbstractApiClient;
use Mirakl\Core\Request\AbstractRequest;
use Mirakl\MCI\Shop\Client\ShopApiClient as MCIShopApiClient;
use Mirakl\MCM\Shop\Client\ShopApiClient as MCMShopApiClient;
use Mirakl\MMP\Shop\Client\ShopApiClient as MMPShopApiClient;
use Sweeper\GuzzleHttpRequest\Response;
use Sweeper\HelperPhp\Tool\ClientRequest;
/**
* Mirakl - Catch - 请求处理
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:01
* @Package \Sweeper\PlatformMiddleware\Services\Mirakl\Request
*/
class Request extends ClientRequest
{
public const OPEN_API_URI = 'https://marketplace.catch.com.au/';
public const OPEN_API_URL = 'https://marketplace.catch.com.au/api/';
/** @var string */
public const TYPE_MCI = 'MCI';
/** @var string */
public const TYPE_MCM = 'MCM';
/** @var string Marketplace for Products Seller API */
public const TYPE_MMP = 'MMP';
/**
* 获取 API 服务URL
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @return string
*/
protected function getServerDomain(): string
{
return static::OPEN_API_URI;
}
/**
* 获取服务请求的路径
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @param string $path
* @return string
*/
protected function getServerPath(string $path): string
{
return $this->getVersion() ? "/api/{$this->getVersion()}/{$path}" : "/api/{$path}";
}
/**
* 实例化客户端
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @param string $type
* @return AbstractApiClient
*/
public function getClientByType(string $type = self::TYPE_MMP): AbstractApiClient
{
// Instantiating the Mirakl API Client
switch ($type) {
case static::TYPE_MCM:
$shopApiClient = $this->clientMCM();
break;
case static::TYPE_MCI:
$shopApiClient = $this->clientMCI();
break;
case static::TYPE_MMP:
default:
$shopApiClient = $this->clientMMP();
break;
}
return $shopApiClient;
}
/**
* MMP 客户端
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @return MMPShopApiClient
*/
public function clientMMP(): MMPShopApiClient
{
// Instantiating the Mirakl API Client
$shopApiClient = new MMPShopApiClient($this->getConfig('api_url'), $this->getConfig('api_key'), $this->getConfig('shop_id'));
return $shopApiClient->setOptions(['verify' => false, 'connect_timeout' => static::getConnectTimeout(), 'timeout' => static::getTimeout()]);
}
/**
* MCI 客户端
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @return MCIShopApiClient
*/
public function clientMCI(): MCIShopApiClient
{
// Instantiating the Mirakl API Client
$shopApiClient = new MCIShopApiClient($this->getConfig('api_url'), $this->getConfig('api_key'), $this->getConfig('shop_id'));
return $shopApiClient->setOptions(['verify' => false, 'connect_timeout' => static::getConnectTimeout(), 'timeout' => static::getTimeout()]);
}
/**
* MCM 客户端
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @return MCMShopApiClient
*/
public function clientMCM(): MCMShopApiClient
{
// Instantiating the Mirakl API Client
$shopApiClient = new MCMShopApiClient($this->getConfig('api_url'), $this->getConfig('api_key'), $this->getConfig('shop_id'));
return $shopApiClient->setOptions(['verify' => false, 'connect_timeout' => static::getConnectTimeout(), 'timeout' => static::getTimeout()]);
}
/**
* 执行请求 -> 解析响应
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:05
* @param AbstractApiClient $client
* @param AbstractRequest $request
* @return Response
*/
public function execute(AbstractApiClient $client, AbstractRequest $request): Response
{
/** @var \GuzzleHttp\Psr7\Response $result */
// Calling the API
// $response = $client->run($request);// $this->client()->getOrders($request); $this->client()->raw()->getOrders($request);
return $this->resolveResponse($client->run($request));// return json_decode($result->getBody()->getContents() ?? '', true) ?: [];
}
/**
* 构建头选项
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:07
* @param string $authorization
* @param array $options
* @return array
*/
protected static function buildHeaders(string $authorization, array $options = []): array
{
return array_replace([
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => $authorization,
'Accept' => "*/*",
],
'verify' => false,
'connect_timeout' => 10,
'timeout' => 60,
], $options);
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Miravia;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopClient;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\IopRequest;
use Sweeper\PlatformMiddleware\Sdk\AeSdk\UrlConstants;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* AliExpress - Miravia 账号/授权相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/15 15:17
* @Package \Sweeper\PlatformMiddleware\Services\Miravia\Account
*/
class Account extends Base
{
public const APP_KEY = '24800759';
public const APP_CALLBACK_URL = 'xxx';
/**
* 生成授权地址
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 14:47
* @doc https://open.aliexpress.com/doc/doc.htm?nodeId=27493&docId=118729#/?docId=989
* @param string $appKey
* @param string $appCallbackUrl
* @return string|string[]
*/
public function generateAuthUrl(string $appKey = self::APP_KEY, string $appCallbackUrl = self::APP_CALLBACK_URL)
{
$uri = 'https://api-sg.aliexpress.com/oauth/authorize?response_type=code&force_auth=true&redirect_uri={app_callback_url}&client_id={app_key}';
return str_replace(['{app_callback_url}', '{app_key}'], [$appCallbackUrl ?: static::APP_CALLBACK_URL, $appKey ?: static::APP_KEY], $uri);
}
/**
* 生成安全令牌
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 14:48
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=3&path=/auth/token/security/create&methodType=GET/POST
* @param array $accountInfo
* @param string $code
* @param string|null $uuid
* @return mixed
*/
public function generateSecurityToken(array $accountInfo, string $code, string $uuid = null)
{
try {
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('/auth/token/security/create');
$request->addApiParam('code', $code);
$uuid && $request->addApiParam('uuid', $uuid);
return $client->execute($request);
} catch (\Throwable $ex) {
throw new \RuntimeException("{$ex->getFile()}#{$ex->getLine()} ({$ex->getMessage()})");
}
}
/**
* 生成令牌
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 14:49
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=3&path=/auth/token/create&methodType=GET/POST
* @param array $accountInfo
* @param string $code
* @param $uuid
* @return mixed
*/
public function generateToken(array $accountInfo, string $code, $uuid = null)
{
try {
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('/auth/token/create');
$request->addApiParam('code', $code);
$uuid && $request->addApiParam('uuid', $uuid);
return $client->execute($request);
} catch (\Throwable $ex) {
throw new \RuntimeException("{$ex->getFile()}#{$ex->getLine()} ({$ex->getMessage()})");
}
}
/**
* 刷新安全令牌
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 14:50
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=3&path=/auth/token/security/refresh&methodType=GET/POST
* @param array $accountInfo
* @param string $refreshToken
* @return mixed
*/
public function refreshSecurityToken(array $accountInfo, string $refreshToken)
{
try {
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('/auth/token/security/refresh');
$request->addApiParam('refresh_token', $refreshToken);
return $client->execute($request);
} catch (\Throwable $ex) {
throw new \RuntimeException("{$ex->getFile()}#{$ex->getLine()} ({$ex->getMessage()})");
}
}
/**
* 刷新令牌
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 14:50
* @doc https://open.aliexpress.com/doc/api.htm#/api?cid=3&path=/auth/token/refresh&methodType=GET/POST
* @param array $accountInfo
* @param string $refreshToken
* @return mixed
*/
public function refreshToken(array $accountInfo, string $refreshToken)
{
try {
$client = new IopClient(UrlConstants::API_GATEWAY_URL, $accountInfo['app_key'], $accountInfo['secret_key']);
$request = new IopRequest('/auth/token/refresh');
$request->addApiParam('refresh_token', $refreshToken);
return $client->execute($request);
} catch (\Throwable $ex) {
throw new \RuntimeException("{$ex->getFile()}#{$ex->getLine()} ({$ex->getMessage()})");
}
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Miravia;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* AliExpress - Miravia 物流相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 16:25
* @Package \Sweeper\PlatformMiddleware\Services\Miravia\Logistics
*/
class Logistics extends Base
{
/**
* Miravia包裹声明发货
* User: Sweeper
* Time: 2023/7/7 18:56
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.pkg.shipment.declare&methodType=POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsPkgShipmentDeclare(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.pkg.shipment.declare', $params, 'package_declare_shipment_request', ['package_id_list', 'channel_type', 'seller_id']);
return $response->arise_logistics_pkg_shipment_declare_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流作废包裹
* User: Sweeper
* Time: 2023/7/7 18:59
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.repack&methodType=POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsRepack(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.repack', $params, 'repack_request', ['package_id_list', 'channel_type', 'seller_id']);
return $response->arise_logistics_repack_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流修改声明发货
* User: Sweeper
* Time: 2023/7/7 19:02
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsShipmentUpdate(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.shipment.update', $params, 'package_update_request', ['channel_type', 'seller_id']);
return $response->arise_logistics_shipment_update_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流声明发货
* User: Sweeper
* Time: 2023/7/7 19:05
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.shipment.declare&methodType=POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsShipmentDeclare(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.shipment.declare', $params, 'declare_shipment_request', ['trade_order_id', 'trade_order_item_id_list', 'shipment_provider_code', 'tracking_number', 'channel_type', 'seller_id']);
return $response->arise_logistics_shipment_declare_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流打包
* User: Sweeper
* Time: 2023/7/7 19:07
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.pack&methodType=POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsPack(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.pack', $params, 'pack_request', ['seller_id', 'operate_way', 'trade_order_id', 'trade_order_item_id_list', 'channel_type']);
return $response->arise_logistics_pack_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流打包V2
* User: Sweeper
* Time: 2023/7/7 19:07
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.packing&methodType=POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsPacking(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.packing', $params, 'pack_request', ['seller_id', 'operate_way', 'trade_order_id', 'trade_order_item_id_list', 'channel_type']);
return $response->arise_logistics_packing_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流打印面单
* User: Sweeper
* Time: 2023/7/7 19:10
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.awb.print&methodType=GET
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsAwbPrint(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.awb.print', $params, 'print_awb_request', ['seller_id', 'package_id_list', 'channel_type', 'file_type'], 'GET');
return $response->arise_logistics_awb_print_response ?? $response;
}
/**
* Miravia物流服务商查询
* User: Sweeper
* Time: 2023/7/7 19:10
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.shipment.provider.query&methodType=GET
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsShipmentProviderQuery(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.shipment.provider.query', $params, 'shipment_provider_request', ['seller_id', 'trade_order_id', 'trade_order_item_id_list', 'channel_type'], 'GET');
return $response->arise_logistics_shipment_provider_query_response->result ?? $response->result ?? $response;
}
/**
* Miravia物流确认妥投状态
* User: Sweeper
* Time: 2023/7/7 19:10
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21395&path=arise.logistics.shipment.confirm&methodType=POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function logisticsShipmentConfirm(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.logistics.shipment.confirm', $params, 'package_confirm_request', ['event_code', 'seller_id', 'package_id_list', 'channel_type']);
return $response->arise_logistics_shipment_confirm_response->result ?? $response->result ?? $response;
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Sweeper\PlatformMiddleware\Services\Miravia;
use Sweeper\PlatformMiddleware\Services\Aliexpress\Base;
/**
* AliExpress - Miravia 订单相关接口
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 16:25
* @Package \Sweeper\PlatformMiddleware\Services\Miravia\Order
*/
class Order extends Base
{
/**
* Miravia订单列表查询
* User: Sweeper
* Time: 2023/7/7 10:58
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21394&path=arise.order.list.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getOrderList(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.order.list.query', $params, 'param0', ['current_page', 'open_channel', 'channel_seller_id']);
return $response->arise_order_list_query_response->result ?? $response->result ?? $response;
}
/**
* Miravia订单详情查询
* User: Sweeper
* Time: 2023/7/7 10:58
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21394&path=arise.order.detail.query&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function getOrderDetail(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.order.detail.query', $params, 'param0', ['trade_order_id', 'open_channel', 'channel_seller_id']);
return $response->arise_order_detail_query_response->result ?? $response->result ?? $response;
}
/**
* Miravia订单设置备注
* User: Sweeper
* Time: 2023/7/7 10:58
* @doc https://open.aliexpress.com/doc/api.htm?spm=a2o9m.11193487.0.0.35096f3dtoF70t#/api?cid=21394&path=arise.order.memo.set&methodType=GET/POST
* @param array $accountInfo
* @param array $params
* @return mixed
*/
public function setMemo(array $accountInfo, array $params = [])
{
$response = static::executeRequest($accountInfo, 'arise.order.memo.set', $params, 'param0', ['trade_order_id', 'open_channel', 'channel_seller_id']);
return $response->arise_order_memo_set_response->result ?? $response->result ?? $response;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 10:50
*/
namespace Sweeper\PlatformMiddleware;
/** 定义包根目录路径 */
define("SWEEPER_PLATFORM_MIDDLEWARE_PACKAGE_ROOT_PATH", dirname(__DIR__));
if (!function_exists('camelize')) {
/**
* 下划线转驼峰
* 思路:
* step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
* step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
*/
function camelize($uncamelized_words, $separator = '_'): string
{
return ltrim(str_replace(' ', '', ucwords($separator . str_replace($separator, ' ', strtolower($uncamelized_words)))), $separator);
}
}
if (!function_exists('un_camelize')) {
/**
* 驼峰命名转下划线命名
* 思路:
* 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
*/
function un_camelize($camelCaps, $separator = '_'): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1' . $separator . '$2', $camelCaps));
}
}
if (!function_exists('json_last_error_msg')) {
/**
* JSON 最后一个错误消息
* User: Sweeper
* Time: 2023/2/24 12:31
* json_last_error_msg(): string 成功则返回错误信息,如果没有错误产生则返回 "No error" 。
* @return string
*/
function json_last_error_msg(): string
{
static $ERRORS = [
JSON_ERROR_NONE => '',// No error
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
];
$error = json_last_error();
return $ERRORS[$error] ?? 'Unknown error';
}
}
if (!function_exists('get_json_last_error')) {
/**
* 返回 JSON 编码解码时最后发生的错误。
* User: Sweeper
* Time: 2023/2/24 13:44
* @return string
*/
function get_json_last_error(): string
{
switch (json_last_error()) {
case JSON_ERROR_NONE:
$error = '';// No error
break;
case JSON_ERROR_DEPTH:
$error = ' - Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$error = ' - Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$error = ' - Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$error = ' - Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$error = ' - Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$error = ' - Unknown error';
break;
}
return $error;
}
}
if (!function_exists('generate_random_string')) {
/**
* 生成随机字符串
* User: Sweeper
* Time: 2023/3/21 16:08
* @param int $length
* @return string
*/
function generate_random_string(int $length = 5): string
{
$arr = array_merge(range('a', 'b'), range('A', 'B'), range('0', '9'));
shuffle($arr);
$arr = array_flip($arr);
$arr = array_rand($arr, $length);
return implode('', $arr);
}
}
if (!function_exists('get_microtime')) {
/**
* 获取毫秒时间戳
* User: Sweeper
* Time: 2023/3/21 16:10
* @return float
*/
function get_microtime(): float
{
[$msec, $sec] = explode(' ', microtime());
return (float)sprintf('%.0f', ((float)$msec + (float)$sec) * 1000);
}
}
if (!function_exists('mb_detect_convert_encoding')) {
/**
* 将 string 类型 str 的字符编码从可选的 $fromEncoding 转换到 $toEncoding
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2023/10/25 17:32
* @param string $str 要编码的 string。
* @param string $toEncoding 要转换成的编码类型。
* @param string|array|null $fromEncoding 在转换前通过字符代码名称来指定。它可以是一个 array 也可以是逗号分隔的枚举列表。 如果没有提供 from_encoding则会使用内部internal编码。
* @return string
*/
function mb_detect_convert_encoding(string $str, string $toEncoding = 'UTF-8', $fromEncoding = null): string
{
return mb_convert_encoding($str, $toEncoding, $fromEncoding ?: mb_detect_encoding($str));
}
}
if (!function_exists('vendor_path')) {
/**
* vendor 目录路径
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/27 13:32
* @return string
*/
function vendor_path(): string
{
$vendorPath = dirname(__DIR__, 4) . '/vendor';
return is_dir($vendorPath) ? $vendorPath : SWEEPER_PLATFORM_MIDDLEWARE_PACKAGE_ROOT_PATH . '/vendor';
}
}
if (!function_exists('package_path')) {
/**
* package 目录路径
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/27 13:32
* @param string $packageName
* @return string
*/
function package_path(string $packageName = 'sweeper/platform-middleware'): string
{
$packagePath = vendor_path() . '/' . trim($packageName, '/\\');
if (empty($packageName)) {
return SWEEPER_PLATFORM_MIDDLEWARE_PACKAGE_ROOT_PATH;
}
return is_dir($packagePath) ? $packagePath : SWEEPER_PLATFORM_MIDDLEWARE_PACKAGE_ROOT_PATH;
}
}
if (!function_exists('root_path')) {
/**
* 根目录路径
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/27 13:32
* @return string
*/
function root_path(): string
{
return dirname(vendor_path());
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 18:06
*/
namespace Sweeper\PlatformMiddleware\Test\Services\Aliexpress\OpenApi;
use Sweeper\PlatformMiddleware\Services\Aliexpress\OpenApi\Order;
use PHPUnit\Framework\TestCase;
class OrderTest extends TestCase
{
public function testGetOrderList()
{
$accountInfo = [];
$response = Order::instance()->getOrderList($accountInfo, ['current_page' => 1, 'page_size' => 50]);
dump($response);
static::assertIsArray($response);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:49
*/
namespace Sweeper\PlatformMiddleware\Test\Services\Mirakl;
use PHPUnit\Framework\TestCase;
use Sweeper\PlatformMiddleware\Services\Mirakl\Order;
use Sweeper\PlatformMiddleware\Services\Mirakl\Request;
class OrderTest extends TestCase
{
public function testGetOrders(): void
{
$response = Order::instance([
'api_url' => Request::OPEN_API_URL,
'api_key' => $appInfo['apiKey'] ?? $appInfo['api_key'] ?? $appInfo['secretFieldValue'] ?? '59ac39c9-6d1b-4ff1-a57f-26bb835048e6',
'shop_id' => $appInfo['shopId'] ?? $appInfo['shop_id'] ?? $appInfo['clientFieldValue'] ?? '',
])->setSuccessCode(-1)->getOrders();
dump($response);
$this->assertTrue($response->isSuccess());
}
public function testGetOrderDocuments(): void
{
$response = Order::instance([
'api_url' => Request::OPEN_API_URL,
'api_key' => $appInfo['apiKey'] ?? $appInfo['api_key'] ?? $appInfo['secretFieldValue'] ?? '59ac39c9-6d1b-4ff1-a57f-26bb835048e6',
'shop_id' => $appInfo['shopId'] ?? $appInfo['shop_id'] ?? $appInfo['clientFieldValue'] ?? '',
])->setSuccessCode(-1)->getOrderDocuments(['C59675662-A', 'C59652563-A']);
dump($response);
$this->assertTrue($response->isSuccess());
}
public function testDownloadOrdersDocuments(): void
{
$response = Order::instance([
'api_url' => Request::OPEN_API_URL,
'api_key' => $appInfo['apiKey'] ?? $appInfo['api_key'] ?? $appInfo['secretFieldValue'] ?? '59ac39c9-6d1b-4ff1-a57f-26bb835048e6',
'shop_id' => $appInfo['shopId'] ?? $appInfo['shop_id'] ?? $appInfo['clientFieldValue'] ?? '',
])->downloadOrdersDocuments(['C59675662-A', 'C59652563-A'], false);
dump($response);
$this->assertTrue($response->isSuccess());
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/2/26 13:50
*/
namespace Sweeper\PlatformMiddleware\Test\Services\Mirakl;
use PHPUnit\Framework\TestCase;
use Sweeper\PlatformMiddleware\Services\Mirakl\PlatformSetting;
use Sweeper\PlatformMiddleware\Services\Mirakl\Request;
class PlatformSettingTest extends TestCase
{
public function testCarriers(): void
{
$response = PlatformSetting::instance([
'api_url' => Request::OPEN_API_URL,
'api_key' => $appInfo['apiKey'] ?? $appInfo['api_key'] ?? $appInfo['secretFieldValue'] ?? '59ac39c9-6d1b-4ff1-a57f-26bb835048e6',
'shop_id' => $appInfo['shopId'] ?? $appInfo['shop_id'] ?? $appInfo['clientFieldValue'] ?? '',
])->setSuccessCode(-1)->carriers();
dump($response);
$this->assertTrue($response->isSuccess());
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/19 17:39
*/
namespace Sweeper\PlatformMiddleware\Test\Services\Miravia;
use Sweeper\PlatformMiddleware\Services\Miravia\Order;
use PHPUnit\Framework\TestCase;
class OrderTest extends TestCase
{
public function testGetOrderList(): void
{
$accountInfo = [];
$response = Order::instance()->getOrderList($accountInfo, ['current_page' => 1, 'open_channel' => '', 'channel_seller_id' => '']);
dump($response);
static::assertIsArray($response);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Created by Sweeper PhpStorm.
* Author: Sweeper <wili.lixiang@gmail.com>
* DateTime: 2024/3/15 15:33
*/
namespace Sweeper\PlatformMiddleware\Test;
use PHPUnit\Framework\TestCase;
use function Sweeper\PlatformMiddleware\package_path;
use function Sweeper\PlatformMiddleware\vendor_path;
class TestPath extends TestCase
{
public function testRootPath(): void
{
$expected = dirname(__DIR__);
$actual = dirname(vendor_path());
dump('===== testRootPath =====', $expected, $actual);
$this->assertEquals($expected, $actual);
}
public function testVendorPath(): void
{
$expected = dirname(__DIR__) . '/vendor';
$actual = vendor_path();
dump('===== testVendorPath =====', $expected, $actual);
$this->assertEquals($expected, $actual);
}
public function testPackagePath(): void
{
$package = 'sweeper/platform-middleware';
$expected = dirname(__DIR__);
$actual = package_path($package);
dump('===== testPackagePath =====', $expected, $actual);
$this->assertEquals($expected, $actual);
}
}

235
test_miravia_api.php Normal file
View File

@@ -0,0 +1,235 @@
<?php
/**
* Miravia API Endpoint Testing Script
* Tests various endpoints to validate authentication and structure
*/
// Your credentials
$app_key = '511420';
$app_secret = 'hOzF4xsW6sBvPQPMSVTWyzYmyjg1gsiL';
$access_token = '50000601517cxQldir9UpDdjDy8Oz1b4db215xHIevjZizVkfWDowiJw3x3dnpEp';
class MiraviaAPITester
{
private $app_key;
private $app_secret;
private $access_token;
public function __construct($app_key, $app_secret, $access_token)
{
$this->app_key = $app_key;
$this->app_secret = $app_secret;
$this->access_token = $access_token;
}
/**
* Generate HMAC-SHA256 signature for API request
*/
private function generateSignature($api_name, $params)
{
ksort($params);
$stringToBeSigned = $api_name;
foreach ($params as $k => $v) {
if (is_array($v) || is_object($v)) {
$v = json_encode($v);
}
$stringToBeSigned .= $k . $v;
}
return strtoupper(hash_hmac('sha256', $stringToBeSigned, $this->app_secret));
}
/**
* Make API request using cURL
*/
private function makeRequest($gateway_url, $api_method, $params = [])
{
echo "\n" . str_repeat("=", 80) . "\n";
echo "Testing: {$api_method}\n";
echo "Gateway: {$gateway_url}\n";
echo str_repeat("=", 80) . "\n";
// System parameters
$sysParams = [
'app_key' => $this->app_key,
'sign_method' => 'sha256',
'timestamp' => time() * 1000,
'partner_id' => 'iop-sdk-php-20220608',
'method' => $api_method,
'v' => '1.0',
'format' => 'json',
'access_token' => $this->access_token
];
// Merge with API parameters
$allParams = array_merge($params, $sysParams);
// Generate signature
$signature = $this->generateSignature($api_method, $allParams);
$allParams['sign'] = $signature;
echo "Request Parameters:\n";
foreach ($allParams as $key => $value) {
if ($key === 'access_token') {
echo " {$key}: " . substr($value, 0, 20) . "...\n";
} elseif ($key === 'sign') {
echo " {$key}: {$value}\n";
} else {
echo " {$key}: {$value}\n";
}
}
// Make cURL request
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $gateway_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($allParams));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/x-www-form-urlencoded'
]);
$start_time = microtime(true);
$response = curl_exec($ch);
$end_time = microtime(true);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
$response_time = round(($end_time - $start_time) * 1000, 2);
echo "\nResponse Details:\n";
echo " HTTP Code: {$http_code}\n";
echo " Response Time: {$response_time}ms\n";
if ($error) {
echo " cURL Error: {$error}\n";
}
echo "\nResponse Body:\n";
if ($response) {
// Try to format JSON response
$json = json_decode($response, true);
if ($json) {
echo json_encode($json, JSON_PRETTY_PRINT) . "\n";
} else {
// If not JSON, show raw response (truncated if too long)
$display_response = strlen($response) > 1000 ? substr($response, 0, 1000) . "...[truncated]" : $response;
echo $display_response . "\n";
}
} else {
echo "No response body\n";
}
return [
'http_code' => $http_code,
'response' => $response,
'error' => $error,
'response_time' => $response_time
];
}
/**
* Test different gateway URLs and endpoints
*/
public function runTests()
{
echo "MIRAVIA API ENDPOINT TESTING\n";
echo "============================\n";
echo "App Key: {$this->app_key}\n";
echo "Access Token: " . substr($this->access_token, 0, 20) . "...\n";
echo "Timestamp: " . date('Y-m-d H:i:s') . "\n\n";
// Test different gateways and endpoints
$test_cases = [
// Miravia API gateway attempts
[
'gateway' => 'https://api.miravia.es/sync',
'method' => 'lazada.product.get',
'params' => ['limit' => 1]
],
[
'gateway' => 'https://api.miravia.es/sync',
'method' => 'lazada.seller.get',
'params' => []
],
[
'gateway' => 'https://api.miravia.es/sync',
'method' => 'lazada.category.tree.get',
'params' => ['language' => 'en']
],
// AliExpress API for Miravia
[
'gateway' => 'https://api-sg.aliexpress.com/sync',
'method' => 'aliexpress.seller.info.query',
'params' => []
],
[
'gateway' => 'https://api-sg.aliexpress.com/sync',
'method' => 'aliexpress.postproduct.redefining.getcategoryattributes',
'params' => ['category_id' => '201336100']
],
// European AliExpress gateway
[
'gateway' => 'https://api.aliexpress.com/sync',
'method' => 'aliexpress.seller.info.query',
'params' => []
],
// Global gateway
[
'gateway' => 'https://eco.aliexpress.com/sync',
'method' => 'aliexpress.seller.info.query',
'params' => []
]
];
$results = [];
foreach ($test_cases as $index => $test) {
$result = $this->makeRequest($test['gateway'], $test['method'], $test['params']);
$results[] = [
'test' => $test,
'result' => $result
];
// Brief pause between requests
sleep(1);
}
// Summary
echo "\n" . str_repeat("=", 80) . "\n";
echo "TEST SUMMARY\n";
echo str_repeat("=", 80) . "\n";
foreach ($results as $index => $test_result) {
$test = $test_result['test'];
$result = $test_result['result'];
$status = $result['http_code'] == 200 ? 'SUCCESS' : 'FAILED';
$gateway_short = parse_url($test['gateway'], PHP_URL_HOST);
echo sprintf("%-30s %-25s %-10s (%d)\n",
$test['method'],
$gateway_short,
$status,
$result['http_code']
);
}
echo "\nRecommendations:\n";
echo "- Look for HTTP 200 responses with valid JSON\n";
echo "- Check for specific error messages that indicate correct API but auth issues\n";
echo "- 302 redirects might indicate wrong gateway URL\n";
echo "- 405 Method Not Allowed indicates wrong HTTP method or endpoint structure\n";
return $results;
}
}
// Run the tests
$tester = new MiraviaAPITester($app_key, $app_secret, $access_token);
$results = $tester->runTests();

183
test_miravia_endpoints.sh Executable file
View File

@@ -0,0 +1,183 @@
#!/bin/bash
# Miravia API Endpoint Testing Script
# Tests various endpoints to validate authentication and structure
# Your credentials
APP_KEY="511420"
APP_SECRET="hOzF4xsW6sBvPQPMSVTWyzYmyjg1gsiL"
ACCESS_TOKEN="50000601517cxQldir9UpDdjDy8Oz1b4db215xHIevjZizVkfWDowiJw3x3dnpEp"
echo "MIRAVIA API ENDPOINT TESTING"
echo "============================"
echo "App Key: $APP_KEY"
echo "Access Token: ${ACCESS_TOKEN:0:20}..."
echo "Timestamp: $(date)"
echo ""
# Function to generate HMAC-SHA256 signature
generate_signature() {
local api_name="$1"
local params="$2"
# Create string to be signed: api_name + sorted params
local string_to_sign="$api_name$params"
# Generate HMAC-SHA256 signature
echo -n "$string_to_sign" | openssl dgst -sha256 -hmac "$APP_SECRET" | awk '{print toupper($2)}'
}
# Function to make API request
test_endpoint() {
local gateway="$1"
local method="$2"
local extra_params="$3"
local description="$4"
echo "========================================"
echo "Testing: $method"
echo "Gateway: $gateway"
echo "Description: $description"
echo "========================================"
# Get current timestamp in milliseconds
local timestamp=$(($(date +%s) * 1000))
# System parameters (sorted alphabetically for signature)
local app_key_param="app_key$APP_KEY"
local access_token_param="access_token$ACCESS_TOKEN"
local format_param="formatjson"
local method_param="method$method"
local partner_param="partner_idiop-sdk-php-20220608"
local sign_method_param="sign_methodsha256"
local timestamp_param="timestamp$timestamp"
local version_param="v1.0"
# Combine all parameters for signature (alphabetically sorted)
local params_for_signature="$access_token_param$app_key_param${extra_params}$format_param$method_param$partner_param$sign_method_param$timestamp_param$version_param"
# Generate signature
local signature=$(generate_signature "$method" "$params_for_signature")
echo "Request Parameters:"
echo " app_key: $APP_KEY"
echo " access_token: ${ACCESS_TOKEN:0:20}..."
echo " timestamp: $timestamp"
echo " method: $method"
echo " signature: $signature"
# Make cURL request
echo ""
echo "Making cURL request..."
local curl_data="app_key=$APP_KEY&access_token=$ACCESS_TOKEN&timestamp=$timestamp&partner_id=iop-sdk-php-20220608&method=$method&v=1.0&format=json&sign_method=sha256&sign=$signature"
# Add extra parameters if provided
if [ ! -z "$extra_params" ]; then
# Convert extra_params format for URL
local url_extra_params=$(echo "$extra_params" | sed 's/\([a-zA-Z_]*\)\([^a-zA-Z_]*\)/\&\1=\2/g' | sed 's/^&//')
curl_data="$curl_data$url_extra_params"
fi
echo "cURL Data: ${curl_data:0:100}..."
echo ""
# Execute cURL request
local start_time=$(date +%s%3N)
local response=$(curl -s -w "\nHTTP_CODE:%{http_code}\nTIME:%{time_total}" \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "$curl_data" \
"$gateway")
local end_time=$(date +%s%3N)
# Parse response
local http_code=$(echo "$response" | grep "HTTP_CODE:" | cut -d: -f2)
local time_total=$(echo "$response" | grep "TIME:" | cut -d: -f2)
local response_body=$(echo "$response" | sed '/HTTP_CODE:/d' | sed '/TIME:/d')
echo "Response Details:"
echo " HTTP Code: $http_code"
echo " Response Time: ${time_total}s"
echo ""
echo "Response Body:"
# Try to format JSON if possible, otherwise show raw
if echo "$response_body" | jq . >/dev/null 2>&1; then
echo "$response_body" | jq .
else
# Show first 500 chars if not JSON
echo "${response_body:0:500}"
if [ ${#response_body} -gt 500 ]; then
echo "...[response truncated]"
fi
fi
echo ""
echo ""
# Store result for summary
echo "$method|$gateway|$http_code|${time_total}s" >> /tmp/api_test_results.txt
}
# Clear previous results
rm -f /tmp/api_test_results.txt
echo "Starting comprehensive endpoint testing..."
echo ""
# Test Case 1: Miravia API - Product List
test_endpoint "https://api.miravia.es/sync" "lazada.product.get" "limit1" "Miravia seller API - Get products"
# Test Case 2: Miravia API - Seller Info
test_endpoint "https://api.miravia.es/sync" "lazada.seller.get" "" "Miravia seller API - Get seller info"
# Test Case 3: Miravia API - Category Tree
test_endpoint "https://api.miravia.es/sync" "lazada.category.tree.get" "languageen" "Miravia seller API - Get categories"
# Test Case 4: Singapore AliExpress - Seller Info
test_endpoint "https://api-sg.aliexpress.com/sync" "aliexpress.seller.info.query" "" "AliExpress SG - Seller info"
# Test Case 5: Singapore AliExpress - Category Attributes
test_endpoint "https://api-sg.aliexpress.com/sync" "aliexpress.postproduct.redefining.getcategoryattributes" "category_id201336100" "AliExpress SG - Category attributes"
# Test Case 6: European AliExpress - Seller Info
test_endpoint "https://api.aliexpress.com/sync" "aliexpress.seller.info.query" "" "AliExpress EU - Seller info"
# Test Case 7: Global AliExpress - Seller Info
test_endpoint "https://eco.aliexpress.com/sync" "aliexpress.seller.info.query" "" "AliExpress Global - Seller info"
# Test Case 8: Try Miravia without /sync
test_endpoint "https://api.miravia.es" "lazada.product.get" "limit1" "Miravia API without /sync"
# Test Case 9: Try different Miravia subdomain
test_endpoint "https://open.miravia.es/sync" "lazada.product.get" "limit1" "Miravia Open Platform"
echo "========================================"
echo "TEST SUMMARY"
echo "========================================"
if [ -f /tmp/api_test_results.txt ]; then
echo "Method Gateway Status Time"
echo "------------------------------------------------------------------------"
while IFS='|' read -r method gateway status time; do
printf "%-40s %-20s %-8s %s\n" "$method" "$(echo $gateway | cut -d'/' -f3)" "$status" "$time"
done < /tmp/api_test_results.txt
else
echo "No results file found"
fi
echo ""
echo "Analysis Guide:"
echo "- HTTP 200: Successful request (good authentication)"
echo "- HTTP 302: Redirect (possibly wrong gateway URL)"
echo "- HTTP 401: Unauthorized (token/signature issue)"
echo "- HTTP 403: Forbidden (permissions issue)"
echo "- HTTP 404: Not found (wrong endpoint)"
echo "- HTTP 405: Method not allowed (wrong HTTP method)"
echo "- HTTP 500: Server error (API issue)"
echo ""
echo "Look for responses with valid JSON containing 'code', 'data', or 'result' fields."
# Cleanup
rm -f /tmp/api_test_results.txt