Zezo Ai ๐ŸŒŒ KOSMOS v1.0 โ€” ุงู„ู…ูˆุงุตูุฉ ุงู„ู‡ู†ุฏุณูŠุฉ ุงู„ูƒุงู…ู„ุฉ

๐Ÿ“Š 4 ู…ุดุงู‡ุฏุฉ

KOSMOS v1.0 โ€” ุงู„ู…ูˆุงุตูุฉ ุงู„ู‡ู†ุฏุณูŠุฉ ุงู„ูƒุงู…ู„ุฉ

ู…ู† ุงู„ุชุตูˆุฑ ุฅู„ู‰ ุงู„ุชู†ููŠุฐ โ€” Blueprint ุชู†ููŠุฐูŠ ูƒุงู…ู„

ู‡ุฐู‡ ู„ูŠุณุช ู…ู‚ุงู„ุฉ. ู‡ุฐู‡ ู…ูˆุงุตูุฉ ู‡ู†ุฏุณูŠุฉ (Engineering Spec) ุฌุงู‡ุฒุฉ ู„ู„ุชุณู„ูŠู… ู„ูุฑูŠู‚ ุชุทูˆูŠุฑ. ูƒู„ ุฌุฏูˆู„ุŒ ูƒู„ classุŒ ูƒู„ endpointุŒ ูƒู„ hook โ€” ู…ูˆุซู‘ู‚ ูˆู…ุจุฑุฑ.


๐ŸŽฏ ุงู„ุฌุฒุก 0: ุฅุนุงุฏุฉ ุชุนุฑูŠู ุงู„ู…ู†ุตุฉ โ€” ุงู„ู†ุณุฎุฉ ุงู„ู†ู‡ุงุฆูŠุฉ

ุงู„ุงุณู… ุงู„ู†ู‡ุงุฆูŠ: KOSMOS Studio

ุงู„ู†ูˆุน: Subscription-as-a-Service Knowledge & Commerce Bridge

ุงู„ุฑุคูŠุฉ (ุฌู…ู„ุฉ ูˆุงุญุฏุฉ):

ุฌุณุฑ ุฑู‚ู…ูŠ ูŠุฑุจุท ุงู„ู…ุนุฑูุฉ ุจู€ ุงู„ุชุฌุงุฑุฉ: ู…ู†ุตุฉ ุงุดุชุฑุงูƒูŠุฉ ุชู…ู†ุญ ุงู„ุชุฌุงุฑ ูˆุงู„ูˆูƒุงู„ุงุช ุงู„ู‚ุฏุฑุฉ ุนู„ู‰ ุณุญุจ ู…ู†ุชุฌุงุชู‡ู… ูˆู…ุญุชูˆุงู‡ู… ูˆู…ู‡ุงุฑุงุชู‡ู… ู…ู† ู…ุตุงุฏุฑ ู…ุชุนุฏุฏุฉุŒ ูˆุถุฎู‘ู‡ุง ููŠ ู‚ู†ูˆุงุช ุงู„ุจูŠุน (ุณู„ุฉุŒ ุดูˆุจูŠูุงูŠุŒ ูˆูˆูƒูˆู…ุฑุณ) โ€” ู…ุน ู…ุฏุฑู‘ุจ AI ูˆู…ูƒุชุจุฉ ู…ุนุฑูุฉ ุญูŠุฉ ุชูุบู„ู‚ ุชู„ู‚ุงุฆูŠุงู‹ ุนู†ุฏ ุงู†ู‚ุทุงุน ุงู„ุงุดุชุฑุงูƒ.

ุงู„ู‚ูŠู…ุฉ ุงู„ูุฑูŠุฏุฉ (3 ู…ุญุงูˆุฑ):

ุงู„ู…ุญูˆุฑุงู„ูˆุตู
๐Ÿ”Œ Content Bridgeุงุณุญุจ ู…ู† ุฃูŠ ู…ุตุฏุฑ (ShopifyุŒ WooCommerceุŒ AliExpressุŒ CSVุŒ API)ุŒ ุงุฏูุน ุฅู„ู‰ ุณู„ุฉ/ุบูŠุฑู‡ุง ุจุถุบุทุฉ
๐Ÿง  Knowledge Layerูƒู„ ู…ู†ุชุฌ ูŠุชุญูˆู„ ุฅู„ู‰ ุนู‚ุฏุฉ ู…ุนุฑูุฉ + AI Tutor ูŠุดุฑุญ ุงุณุชุฎุฏุงู…ู‡
๐Ÿ’ฐ Subscription Lockุฅุฐุง ุชูˆู‚ู ุงู„ุงุดุชุฑุงูƒ โ€” ุงู„ู…ู†ุชุฌุงุช ูˆุงู„ุชุตู†ูŠูุงุช ูˆุงู„ุฎุฏู…ุงุช ุชูู‚ูู„ ููˆุฑุงู‹

ุงู„ุดุนุงุฑ ุงู„ุชุณูˆูŠู‚ูŠ:

“ู…ุนุฑูุชูƒุŒ ู…ู†ุชุฌุงุชูƒุŒ ุฃุฑุจุงุญูƒ โ€” ููŠ ู…ุฏุงุฑ ูˆุงุญุฏ. ุชูˆู‚ู ุงู„ุฏูุนุŸ ูŠุชูˆู‚ู ุงู„ูƒูˆู†.”

ุงู„ุฌู…ู‡ูˆุฑ ุงู„ู…ุณุชู‡ุฏู:

  1. ุงู„ุชุงุฌุฑ ููŠ ุณู„ุฉย ุงู„ุฐูŠ ูŠุฑูŠุฏ ุงุณุชูŠุฑุงุฏ ู…ู†ุชุฌุงุช ู…ู† ู…ู†ุตุงุช ุฃุฎุฑู‰ ุจุฐูƒุงุก
  2. ุงู„ูˆูƒุงู„ุฉ ุงู„ุฑู‚ู…ูŠุฉย ุงู„ุชูŠ ุชุฏูŠุฑ ู…ุชุงุฌุฑ ู…ุชุนุฏุฏุฉ ู„ุนู…ู„ุงุฆู‡ุง
  3. ุตุงุญุจ ุงู„ู…ุนุฑูุฉ/ุงู„ู…ุฏุฑุจย ุงู„ุฐูŠ ูŠุฑูŠุฏ ุจูŠุน ุฏูˆุฑุงุช + ู…ู†ุชุฌุงุช ุฑู‚ู…ูŠุฉ
  4. ุงู„ู…ุจุฑู…ุฌ/ุงู„ู…ุตู…ู…ย ุงู„ุฐูŠ ูŠุฑูŠุฏ ุจูŠุน ู‚ูˆุงู„ุจู‡ ูˆุฎุฏู…ุงุชู‡ ุจุงุดุชุฑุงูƒ

๐Ÿ“ ุงู„ุฌุฒุก 1: ุงู„ู…ุนู…ุงุฑูŠุฉ ุงู„ุนุงู…ุฉ (System Architecture)

ุงู„ู…ูƒูˆู†ุงุช ุงู„ุฃุณุงุณูŠุฉ (Bounded Contexts):

#ุงู„ู…ูƒูˆู†ุงู„ู…ุณุคูˆู„ูŠุฉ
1Tenant Serviceุฅุฏุงุฑุฉ ุงู„ุญุณุงุจุงุชุŒ ุงู„ู†ุทุงู‚ุงุชุŒ ุงู„ููˆุงุชูŠุฑ
2Subscription Engineุงู„ุจุงู‚ุงุชุŒ ุงู„ุชุฌุฏูŠุฏุŒ ุงู„ุฅู„ุบุงุกุŒ Dunning
3Entitlements Engine“ู‡ู„ ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… ูŠุญู‚ ู„ู‡ ุงู„ูˆุตูˆู„ุŸ” โ€” ู†ู‚ุทุฉ ุงู„ู‚ุฑุงุฑ ุงู„ู…ุฑูƒุฒูŠุฉ
4Content Bridgeุณุญุจ/ุฏูุน ุงู„ู…ู†ุชุฌุงุช ุจูŠู† ุงู„ู…ุตุงุฏุฑ
5AI Gatewayุชุฌู…ูŠุน ูƒู„ ุงุณุชุฏุนุงุกุงุช AI + Metering
6Knowledge Graphุฑุณู… ุงู„ู…ู‡ุงุฑุงุช ูˆุงู„ู…ู†ุชุฌุงุช ูˆุงู„ุนู„ุงู‚ุงุช
7Job Queueุงู„ู…ู‡ุงู… ุบูŠุฑ ุงู„ู…ุชุฒุงู…ู†ุฉ (Sync, Publish)
8Audit & ComplianceุชุณุฌูŠู„ ูƒู„ ุญุฑูƒุฉุŒ GDPRุŒ Export

ุงู„ุจุฑูˆู…ุจุช ุงู„ู‡ู†ุฏุณูŠ ุงู„ุดุงู…ู„ โ€” ู…ู†ุตุฉ “ุณู„ุฉ ุจุฑูŠุฏุฌ” (Salla Bridge Platform)

๐ŸŽฏ ุงู„ู‡ุฏู ุงู„ุนุงู… (Master Prompt)

ุฃู†ุช ู…ู‡ู†ุฏุณ ุจุฑู…ุฌูŠุงุช ุฑุฆูŠุณูŠ (Lead Software Architect) ู…ุชุฎุตุต ููŠ WordPress Plugin Development ูˆWooCommerce ูˆSaaS Multi-Tenant Systems. ู…ุทู„ูˆุจ ู…ู†ูƒ ุชุตู…ูŠู… ูˆุชู†ููŠุฐ ู…ูˆุงุตูุฉ ู‡ู†ุฏุณูŠุฉ ูƒุงู…ู„ุฉ v1.0 ู„ู…ู†ุตุฉ SaaS ูˆุณูŠุทุฉ (Middleware Platform) ุชุฑุจุท ู…ุชุฌุฑ ุณู„ุฉ (Salla) ุจู…ุตุงุฏุฑ ุฎุงุฑุฌูŠุฉ ู„ู„ู…ู†ุชุฌุงุช (AliExpress, Amazon, Noon, Shopify, Taobao, 1688, CJ Dropshipping, ูˆุบูŠุฑู‡ุง)ุŒ ู…ุน ู†ุธุงู… ุงุดุชุฑุงูƒุงุช ู…ูุญูƒู… ูŠู‚ูู„ ุงู„ู…ุญุชูˆู‰ ุชู„ู‚ุงุฆูŠู‹ุง ุนู†ุฏ ุงู†ุชู‡ุงุก ุงู„ุงุดุชุฑุงูƒ.

ุงู„ู…ุทู„ูˆุจ: ู…ู† ุงู„ุชุตูˆุฑ ุฅู„ู‰ ุงู„ุชู†ููŠุฐ โ€” ูƒุชุงุจุฉ ุงู„ู…ูˆุงุตูุฉ ูƒุงู…ู„ุฉ ุจุญูŠุซ ูŠู…ูƒู† ู„ู…ุจุฑู…ุฌ ุชู†ููŠุฐู‡ุง ุญุฑููŠู‹ุง.


๐Ÿ“ ุงู„ู‚ุณู… 1: ุชุนุฑูŠู ุงู„ู…ู†ุตุฉ (Platform Definition)

ุงู„ุงุณู… ุงู„ู…ู‚ุชุฑุญ: SallaBridge โ€” ุฌุณุฑ ุงู„ู…ู†ุชุฌุงุช ุงู„ุฐูƒูŠ.

ุงู„ูˆุตู:
ู…ู†ุตุฉ SaaS ู…ุชุนุฏุฏุฉ ุงู„ู…ุณุชุฃุฌุฑูŠู† (Multi-Tenant) ุชุนู…ู„ ูƒู€ WordPress Plugin + ุจูˆุงุจุฉ ูˆูŠุจ ุฎุงุฑุฌูŠุฉุŒ ุชูู…ูƒู‘ู† ุฃุตุญุงุจ ู…ุชุงุฌุฑ ุณู„ุฉ ู…ู†:

  1. ุณุญุจ ุงู„ู…ู†ุชุฌุงุช ูˆุงู„ุตูˆุฑ ูˆุงู„ุฃุณุนุงุฑ ู…ู† ู…ู†ุตุงุช ุนุงู„ู…ูŠุฉ (AliExpress, Amazon, Noon, Taobao, 1688, CJ, Shopifyโ€ฆ).
  2. ุฅุนุงุฏุฉ ู‡ูŠูƒู„ุชู‡ุง ูˆุชุฑุฌู…ุชู‡ุง ูˆุชุญุณูŠู†ู‡ุง ุจุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ.
  3. ุฏูุนู‡ุง ุชู„ู‚ุงุฆูŠู‹ุง ุฅู„ู‰ ู…ุชุฌุฑ ุณู„ุฉ (Products, Categories, Images, Variants).
  4. ุฅุฏุงุฑุฉ ุฐู„ูƒ ุนุจุฑ ู„ูˆุญุฉ ุชุญูƒู… ูˆุงุญุฏุฉ ู…ุน ู†ุธุงู… ุจุงู‚ุงุช ูˆุงุดุชุฑุงูƒุงุช ุดู‡ุฑูŠ/ุณู†ูˆูŠ.

ุงู„ู‚ูŠู…ุฉ ุงู„ู…ุถุงูุฉ:

  • ุฃุชู…ุชุฉ ุงู„ุงุณุชูŠุฑุงุฏ (Bulk Import) ุจู…ุนุฏู„ ุขู„ุงู ุงู„ู…ู†ุชุฌุงุช/ุณุงุนุฉ.
  • ุชุฑุฌู…ุฉ ุนุฑุจูŠุฉ/ุฅู†ุฌู„ูŠุฒูŠุฉ ุชู„ู‚ุงุฆูŠุฉ ุนุจุฑ AI Gateway.
  • ู…ุฒุงู…ู†ุฉ ุงู„ุฃุณุนุงุฑ ูˆุงู„ู…ุฎุฒูˆู†.
  • ู†ุธุงู… ู‚ูู„ (Lock): ุนู†ุฏ ุนุฏู… ุงู„ุฏูุน โ†’ ุฅุฎูุงุก/ู‚ูู„ ุงู„ู…ู†ุชุฌุงุช ูˆุงู„ุฃู‚ุณุงู… ูˆุงู„ุชุตู†ูŠูุงุช ู…ู† ู…ุชุฌุฑ ุณู„ุฉ ุชู„ู‚ุงุฆูŠู‹ุง.

๐Ÿ—„๏ธ ุงู„ู‚ุณู… 2: ุจู†ูŠุฉ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช (Database Schema v1.0)

ุงูƒุชุจ ุงู„ุฌุฏุงูˆู„ ุจุตูŠุบุฉ wp_sb_* ู…ุน dbDelta():

2.1 ุงู„ุฌุฏุงูˆู„ ุงู„ุฃุณุงุณูŠุฉ

ุงู„ุฌุฏูˆู„ุงู„ุญู‚ูˆู„ ุงู„ุฑุฆูŠุณูŠุฉุงู„ุนู„ุงู‚ุงุช
wp_sb_tenantsid, user_id, salla_store_id, salla_access_token, salla_refresh_token, plan_id, status, trial_ends_at, created_atโ†’ wp_users, โ†’ wp_sb_plans
wp_sb_plansid, name, slug, price_monthly, price_yearly, max_products, max_images, max_sources, features_json, is_activeโ†’ wp_sb_entitlements
wp_sb_subscriptionsid, tenant_id, plan_id, gateway, gateway_sub_id, status, current_period_start, current_period_end, cancel_at, created_atโ†’ tenants, plans
wp_sb_entitlementsid, plan_id, feature_key, limit_value, reset_periodโ†’ plans
wp_sb_sourcesid, tenant_id, source_type (aliexpress/amazon/noon…), api_key, api_secret, is_active, last_sync_atโ†’ tenants
wp_sb_import_jobsid, tenant_id, source_id, status, total_items, processed, failed, payload_json, started_at, finished_atโ†’ tenants, sources
wp_sb_products_mapid, tenant_id, source_product_id, salla_product_id, sku, price_source, price_salla, markup_percent, image_count, locked TINYINT, synced_atโ†’ tenants
wp_sb_imagesid, product_map_id, source_url, salla_media_id, checksum, statusโ†’ products_map
wp_sb_categories_mapid, tenant_id, source_cat_id, salla_cat_id, lockedโ†’ tenants
wp_sb_ai_jobsid, tenant_id, job_type (translate/rewrite/seo), input_hash, output_json, tokens_used, provider, statusโ†’ tenants
wp_sb_webhooks_logid, tenant_id, event, payload, signature, status, created_atโ†’ tenants
wp_sb_audit_logid, tenant_id, actor_id, action, object_type, object_id, meta_json, ip, created_atโ†’ tenants
wp_sb_lock_eventsid, tenant_id, reason (expired/cancelled/suspended), locked_items_count, unlocked_at, created_atโ†’ tenants

2.2 ุงู„ูู‡ุงุฑุณ (Indexes)

  • UNIQUE(tenant_id, source_product_id)ย ุนู„ู‰ย products_map.
  • INDEX(status, current_period_end)ย ุนู„ู‰ย subscriptions.
  • INDEX(tenant_id, locked)ย ุนู„ู‰ย products_mapย ูˆcategories_map.

๐Ÿงฉ ุงู„ู‚ุณู… 3: ู‡ูŠูƒู„ ุงู„ู€Plugin (Folder & Namespace Structure)

text

salla-bridge/
โ”œโ”€โ”€ salla-bridge.php                 (Bootstrap + Plugin Header)
โ”œโ”€โ”€ composer.json                    (PSR-4: SallaBridge\)
โ”œโ”€โ”€ uninstall.php
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ Core/
โ”‚   โ”‚   โ”œโ”€โ”€ Plugin.php
โ”‚   โ”‚   โ”œโ”€โ”€ Activator.php
โ”‚   โ”‚   โ”œโ”€โ”€ Deactivator.php
โ”‚   โ”‚   โ”œโ”€โ”€ Installer.php            (dbDelta migrations)
โ”‚   โ”‚   โ””โ”€โ”€ Container.php            (DI Container)
โ”‚   โ”œโ”€โ”€ Admin/
โ”‚   โ”‚   โ”œโ”€โ”€ Menu.php
โ”‚   โ”‚   โ”œโ”€โ”€ Dashboards/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TenantDashboard.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ AdminDashboard.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ BillingDashboard.php
โ”‚   โ”‚   โ””โ”€โ”€ Views/
โ”‚   โ”œโ”€โ”€ Api/
โ”‚   โ”‚   โ”œโ”€โ”€ RestController.php       (Base)
โ”‚   โ”‚   โ”œโ”€โ”€ AuthController.php
โ”‚   โ”‚   โ”œโ”€โ”€ ImportController.php
โ”‚   โ”‚   โ”œโ”€โ”€ SyncController.php
โ”‚   โ”‚   โ”œโ”€โ”€ BillingController.php
โ”‚   โ”‚   โ””โ”€โ”€ LockController.php
โ”‚   โ”œโ”€โ”€ Salla/
โ”‚   โ”‚   โ”œโ”€โ”€ Client.php               (OAuth2 + REST)
โ”‚   โ”‚   โ”œโ”€โ”€ ProductPusher.php
โ”‚   โ”‚   โ”œโ”€โ”€ CategoryPusher.php
โ”‚   โ”‚   โ”œโ”€โ”€ MediaUploader.php
โ”‚   โ”‚   โ””โ”€โ”€ Webhooks.php
โ”‚   โ”œโ”€โ”€ Sources/
โ”‚   โ”‚   โ”œโ”€โ”€ SourceInterface.php
โ”‚   โ”‚   โ”œโ”€โ”€ AliExpressSource.php
โ”‚   โ”‚   โ”œโ”€โ”€ AmazonSource.php
โ”‚   โ”‚   โ”œโ”€โ”€ NoonSource.php
โ”‚   โ”‚   โ”œโ”€โ”€ ShopifySource.php
โ”‚   โ”‚   โ””โ”€โ”€ SourceFactory.php
โ”‚   โ”œโ”€โ”€ AI/
โ”‚   โ”‚   โ”œโ”€โ”€ Gateway.php
โ”‚   โ”‚   โ”œโ”€โ”€ Providers/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ OpenAIProvider.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ AnthropicProvider.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ GeminiProvider.php
โ”‚   โ”‚   โ”œโ”€โ”€ TranslateJob.php
โ”‚   โ”‚   โ””โ”€โ”€ SeoRewriteJob.php
โ”‚   โ”œโ”€โ”€ Billing/
โ”‚   โ”‚   โ”œโ”€โ”€ SubscriptionManager.php
โ”‚   โ”‚   โ”œโ”€โ”€ EntitlementChecker.php
โ”‚   โ”‚   โ”œโ”€โ”€ WooCommerceHooks.php
โ”‚   โ”‚   โ”œโ”€โ”€ Gateways/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ StripeGateway.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PaypalGateway.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ MoyasarGateway.php    (ุณุนูˆุฏูŠ)
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ TapGateway.php        (ุฎู„ูŠุฌูŠ)
โ”‚   โ”‚   โ””โ”€โ”€ WebhookHandler.php
โ”‚   โ”œโ”€โ”€ Locking/
โ”‚   โ”‚   โ”œโ”€โ”€ LockManager.php
โ”‚   โ”‚   โ”œโ”€โ”€ SallaLocker.php           (unpublish products/cats on Salla)
โ”‚   โ”‚   โ””โ”€โ”€ CronGuard.php
โ”‚   โ”œโ”€โ”€ Roles/
โ”‚   โ”‚   โ”œโ”€โ”€ RoleManager.php
โ”‚   โ”‚   โ””โ”€โ”€ Capabilities.php
โ”‚   โ”œโ”€โ”€ Cron/
โ”‚   โ”‚   โ”œโ”€โ”€ SyncPrices.php
โ”‚   โ”‚   โ”œโ”€โ”€ CheckSubscriptions.php    (hourly)
โ”‚   โ”‚   โ””โ”€โ”€ RetryFailedJobs.php
โ”‚   โ””โ”€โ”€ Support/
โ”‚       โ”œโ”€โ”€ Logger.php
โ”‚       โ”œโ”€โ”€ Crypto.php                (encrypt tokens at rest)
โ”‚       โ””โ”€โ”€ Helpers.php
โ”œโ”€โ”€ assets/  (js, css, react build)
โ”œโ”€โ”€ languages/
โ””โ”€โ”€ tests/

Namespace root: SallaBridge\
Text Domain: salla-bridge


๐Ÿ”Œ ุงู„ู‚ุณู… 4: REST Endpoints (v1)

Base: /wp-json/sb/v1/
Auth: Bearer JWT + HMAC signature for Salla webhooks.

MethodRoutePurposeCapability
POST/auth/salla/callbackุงุณุชู‚ุจุงู„ OAuth codepublic
POST/import/startุจุฏุก ู…ู‡ู…ุฉ ุงุณุชูŠุฑุงุฏsb_start_import
GET/import/{job_id}/statusุญุงู„ุฉ ุงู„ู…ู‡ู…ุฉsb_view_jobs
POST/sync/productsู…ุฒุงู…ู†ุฉ ู…ู†ุชุฌุงุชsb_sync_products
POST/sync/pricesุชุญุฏูŠุซ ุงู„ุฃุณุนุงุฑsb_sync_prices
POST/billing/subscribeุฅู†ุดุงุก ุงุดุชุฑุงูƒsb_manage_billing
POST/billing/cancelุฅู„ุบุงุก ุงุดุชุฑุงูƒsb_manage_billing
GET/billing/statusุญุงู„ุฉ ุงู„ุงุดุชุฑุงูƒ ูˆุงู„ู€entitlementssb_view_billing
POST/lock/enforceุชุดุบูŠู„ ุงู„ู‚ูู„ ูŠุฏูˆูŠู‹ุง (admin)sb_manage_lock
POST/lock/releaseุฑูุน ุงู„ู‚ูู„ ุจุนุฏ ุงู„ุฏูุนsb_manage_lock
POST/ai/translateุชุฑุฌู…ุฉ ุฏูุนุฉsb_use_ai
POST/ai/seoุชุญุณูŠู† SEOsb_use_ai
GET/dashboard/statsุฅุญุตุงุฆูŠุงุชsb_view_dashboard
POST/webhooks/sallaWebhooks ู…ู† ุณู„ุฉpublic+HMAC
POST/webhooks/wooWebhooks ู…ู† WooCommercepublic+HMAC

ูƒู„ endpoint ูŠู…ุฑ ุนุจุฑ RestController::permission_callback() ูŠุชุญู‚ู‚ ู…ู† tenant_id + EntitlementChecker.


๐Ÿ›’ ุงู„ู‚ุณู… 5: ุชูƒุงู…ู„ ุณู„ุฉ (Salla Integration)

  • OAuth2 Flow:ย https://accounts.salla.sa/oauth2/authย โ†’ย tokenย โ†’ ุชุฎุฒูŠู† ู…ูุดูู‘ุฑ ููŠย wp_sb_tenants.
  • Endpoints ุงู„ู…ูุณุชุฎุฏู…ุฉ ู…ู† Salla API:
    • POST /productsย โ€” ุฅู†ุดุงุก ู…ู†ุชุฌ
    • PUT /products/{id}ย โ€” ุชุญุฏูŠุซ
    • POST /products/{id}/images
    • POST /categories
    • PUT /products/{id}ย ู…ุนย status: hiddenย โ†’ย ู„ู„ู‚ูู„
  • Mapping ุงู„ุญู‚ูˆู„:
    • source.title โ†’ salla.name
    • source.description โ†’ salla.description
    • source.price * (1 + markup/100) โ†’ salla.price
    • source.stock โ†’ salla.quantity
  • Rate Limiting:ย 60 req/min โ†’ Backoff + Queue.
  • Retry:ย 3 ู…ุญุงูˆู„ุงุช (exponential: 5s, 30s, 5m).

๐Ÿ’ณ ุงู„ู‚ุณู… 6: ู†ุธุงู… ุงู„ุจุงู‚ุงุช ูˆุงู„ุงุดุชุฑุงูƒุงุช (Plans & Entitlements)

6.1 ุงู„ุจุงู‚ุงุช ุงู„ู…ู‚ุชุฑุญุฉ

ุงู„ุจุงู‚ุฉุดู‡ุฑูŠุณู†ูˆูŠู…ู†ุชุฌุงุชุตูˆุฑู…ุตุงุฏุฑAI tokens
Starter99 SAR990 SAR5002,0001100K
Growth299 SAR2,990 SAR5,00020,00031M
Pro799 SAR7,990 SAR25,000100,00085M
EnterpriseุชุณุนูŠุฑ ู…ุฎุตุตโ€”UnlimitedUnlimitedUnlimitedCustom

6.2 Entitlements Logic

php

EntitlementChecker::can($tenant_id, 'import_products', $qty)
  • ูŠูุญุต:ย subscription.status = activeย +ย now <= current_period_endย +ย used + qty <= limit.
  • ุนู†ุฏ ุงู„ุชุฌุงูˆุฒ โ†’ ูŠุฑุฌุนย WP_Error('quota_exceeded').

6.3 WooCommerce Hooks (ู„ุฃู† ุงู„ู…ู†ุตุฉ ู†ูุณู‡ุง ุชูุจู†ู‰ ุนู„ู‰ Woo)

  • woocommerce_checkout_order_processedย โ†’ ุชูุนูŠู„ ุงุดุชุฑุงูƒ.
  • woocommerce_order_status_completedย โ†’ ุชู…ุฏูŠุฏย current_period_end.
  • woocommerce_order_status_refundedย โ†’ ุฅู„ุบุงุก ููˆุฑูŠ.
  • woocommerce_subscription_status_updatedย (ุฅู† ูˆูุฌุฏ WCS).
  • woocommerce_payment_completeย โ†’ ุฑุจุทย gateway_sub_id.

6.4 ุฏูˆุฑุฉ ุญูŠุงุฉ ุงู„ุงุดุชุฑุงูƒ

trialing โ†’ active โ†’ past_due (grace 3 ุฃูŠุงู…) โ†’ locked โ†’ cancelled


๐Ÿ”’ ุงู„ู‚ุณู… 7: ู†ุธุงู… ุงู„ู‚ูู„ ุงู„ุชู„ู‚ุงุฆูŠ (Auto-Lock System) โ€” ุงู„ู‚ู„ุจ ุงู„ู†ุงุจุถ

ุงู„ุณูŠู†ุงุฑูŠูˆ:

ุงู„ุนู…ูŠู„ ุชูˆู‚ู ุนู† ุงู„ุฏูุน โ†’ ุจุนุฏ ุงู†ุชู‡ุงุก current_period_end + grace period โ†’ ุชูู‚ูู„ ุฌู…ูŠุน ุงู„ู…ู†ุชุฌุงุช ูˆุงู„ุฃู‚ุณุงู… ูˆุงู„ุชุตู†ูŠูุงุช ุงู„ุชูŠ ุงุณุชูˆุฑุฏู‡ุง ุนุจุฑ ุงู„ู…ู†ุตุฉ ููŠ ู…ุชุฌุฑ ุณู„ุฉุŒ ูˆุชุฎุชููŠ ู…ู† ุงู„ูˆุงุฌู‡ุฉ.

7.1 Cron: sb_check_subscriptions (ูƒู„ ุณุงุนุฉ)

php

foreach (subscriptions where status IN ('active','past_due')) {
    if (now > current_period_end + grace_days) {
        LockManager::lock($tenant_id, 'expired');
    }
}

7.2 LockManager::lock()

  1. ุฌู„ุจ ูƒู„ย products_map WHERE tenant_id AND locked = 0.
  2. ู„ูƒู„ ู…ู†ุชุฌ: ุงุณุชุฏุนุงุกย Salla API PUT /products/{id} {status: 'hidden'}.
  3. ู†ูุณ ุงู„ุดูŠุก ู„ู„ุชุตู†ูŠูุงุชย categories_map.
  4. ุชุณุฌูŠู„ ููŠย lock_events.
  5. ุชุญุฏูŠุซย locked = 1.
  6. ุฅุฑุณุงู„ ุฅูŠู…ูŠู„ + ุฅุดุนุงุฑ Dashboard.
  7. ุชุณุฌูŠู„ webhookย subscription.locked.

7.3 LockManager::release()

  • ุนูƒุณ ุงู„ุนู…ู„ูŠุฉย status: 'sale'.
  • ูู‚ุท ุจุนุฏ ุชุฃูƒูŠุฏ ุงู„ุฏูุน ู…ู† Gateway webhook.

7.4 Cron Guard

  • ูŠู…ู†ุน ุฃูŠย import/syncย ุฌุฏูŠุฏ ุฅุฐุงย locked = 1.
  • REST endpoints ุชุฑุฌุนย 403 subscription_locked.

๐Ÿค– ุงู„ู‚ุณู… 8: AI Gateway

Interface ู…ูˆุญู‘ุฏ:

php

interface AiProviderInterface {
    public function translate(string $text, string $to, string $from = 'auto'): string;
    public function rewrite(string $text, string $tone): string;
    public function seo(string $title, string $desc): array;
}
  • Routing:ย ูŠุฎุชุงุฑ Provider ุญุณุจ ุงู„ุชูƒู„ูุฉ ูˆุงู„ู„ุบุฉ (OpenAI ู„ู„ุนุฑุจูŠุฉุŒ Anthropic ู„ู„ู€SEOุŒ Gemini ู„ู„ุชุฑุฌู…ุฉ ุงู„ุฌู…ุงุนูŠุฉ).
  • Caching:ย input_hashย โ†’ ูŠู…ู†ุน ุฅุนุงุฏุฉ ุงู„ุฏูุน ู„ู†ูุณ ุงู„ู†ุต.
  • Rate Limit per tenant:ย ุญุณุจ ุงู„ุจุงู‚ุฉ.
  • Fallback:ย ุฅุฐุง ูุดู„ OpenAI โ†’ Anthropic โ†’ Gemini.

๐Ÿ‘ฅ ุงู„ู‚ุณู… 9: Roles & Capabilities

RoleCapabilities
sb_tenant_ownerูƒู„ ุตู„ุงุญูŠุงุช ุงู„ู…ุณุชุฃุฌุฑ
sb_tenant_managerimport, sync, view (ุจุฏูˆู† billing)
sb_tenant_viewerview ูู‚ุท
sb_supportview_all_tenants, impersonate
sb_platform_adminูƒู„ ุดูŠุก + manage_lock, manage_plans

php

add_role('sb_tenant_owner', 'SallaBridge Tenant', [
    'sb_start_import' => true,
    'sb_sync_products' => true,
    'sb_manage_billing' => true,
    'sb_manage_lock' => false,   // admin only
    'sb_use_ai' => true,
    'sb_view_dashboard' => true,
]);

๐Ÿ“Š ุงู„ู‚ุณู… 10: Dashboards

10.1 Tenant Dashboard (React SPA)

  • ุจุทุงู‚ุงุช: ู…ู†ุชุฌุงุช ู…ุณุชูˆุฑุฏุฉ | ุตูˆุฑ | AI tokens ู…ุณุชุฎุฏู…ุฉ | ุญุงู„ุฉ ุงู„ุงุดุชุฑุงูƒ (ุนุฏู‘ุงุฏ ุชู†ุงุฒู„ูŠ).
  • ุฑุณู… ุจูŠุงู†ูŠ: imports/day.
  • ุฌุฏูˆู„: ุขุฎุฑ 20 ู…ู‡ู…ุฉ ุงุณุชูŠุฑุงุฏ + ุญุงู„ุชู‡ุง.
  • ุชุญุฐูŠุฑ ุฃุญู…ุฑ ุฅุฐุงย current_period_end - now < 7 days.

10.2 Admin Dashboard

  • ุนุฏุฏ ุงู„ู…ุณุชุฃุฌุฑูŠู†ุŒ MRRุŒ ChurnุŒ Locked Tenants.
  • ุฒุฑย Force Lock / Release.
  • ุณุฌู„ย audit_log.

10.3 Billing Dashboard

  • Upgrade/DowngradeุŒ ุงู„ููˆุงุชูŠุฑุŒ ุทุฑูŠู‚ุฉ ุงู„ุฏูุนุŒ Cancel.

โฑ๏ธ ุงู„ู‚ุณู… 11: ุฎุทุฉ ุชู†ููŠุฐ ุฃูˆู„ 60 ุฏู‚ูŠู‚ุฉ (Minute-by-Minute)

ุงู„ุฏู‚ูŠู‚ุฉุงู„ู…ู„ูุงู„ู…ุฎุฑุฌ
0โ€“5salla-bridge.phpPlugin Header + Autoload + Activation Hook
5โ€“10src/Core/Installer.phpdbDelta ู„ูƒู„ ุงู„ุฌุฏุงูˆู„
10โ€“15src/Core/Plugin.php + Container.phpBootstrap + DI
15โ€“20src/Roles/RoleManager.php + Capabilities.phpุชุณุฌูŠู„ ุงู„ุฃุฏูˆุงุฑ
20โ€“25src/Api/RestController.phpBase REST + namespace
25โ€“30src/Api/AuthController.phpOAuth callback
30โ€“35src/Salla/Client.phpOAuth2 + request wrapper
35โ€“40src/Sources/SourceInterface.php + SourceFactory.phpูˆุงุฌู‡ุฉ ุงู„ู…ุตุงุฏุฑ
40โ€“45src/Sources/AliExpressSource.phpุฃูˆู„ ู…ุตุฏุฑ ูุนู„ูŠ
45โ€“50src/Billing/SubscriptionManager.php + EntitlementChecker.phpู…ู†ุทู‚ ุงู„ุจุงู‚ุงุช
50โ€“55src/Locking/LockManager.php + SallaLocker.phpู†ุธุงู… ุงู„ู‚ูู„
55โ€“60src/Cron/CheckSubscriptions.php + ุชุณุฌูŠู„ู‡Cron ูƒู„ ุณุงุนุฉ

ุงู„ู†ุงุชุฌ ุจุนุฏ 60 ุฏู‚ูŠู‚ุฉ: Plugin ูŠููุนู‘ู„ุŒ ูŠู†ุดุฆ ุงู„ุฌุฏุงูˆู„ุŒ ูŠุณุฌู‘ู„ RESTุŒ ูŠุฑุจุท ุณู„ุฉ OAuthุŒ ูŠุณุชูˆุฑุฏ ู…ู† AliExpressุŒ ูŠุฏูŠุฑ ุงุดุชุฑุงูƒุŒ ูˆูŠู‚ูู„ ุชู„ู‚ุงุฆูŠู‹ุง ุนู†ุฏ ุงู„ุงู†ุชู‡ุงุก.


๐Ÿงช ุงู„ู‚ุณู… 12: ู…ุนุงูŠูŠุฑ ุงู„ู‚ุจูˆู„ (Acceptance Criteria)

  1. โœ… ุชูุนูŠู„ Plugin ูŠูู†ุดุฆ 13 ุฌุฏูˆู„ู‹ุง ุจุฏูˆู† ุฃุฎุทุงุก.
  2. โœ… OAuth ู…ุน ุณู„ุฉ ูŠุนู…ู„ ูˆูŠุฎุฒู‘ู† tokens ู…ูุดูู‘ุฑุฉ.
  3. โœ… ุงุณุชูŠุฑุงุฏ 100 ู…ู†ุชุฌ ู…ู† AliExpress ููŠ < 5 ุฏู‚ุงุฆู‚.
  4. โœ… ู…ู†ุชุฌุงุช ุชุธู‡ุฑ ููŠ ุณู„ุฉ ุจุงู„ุตูˆุฑ ูˆุงู„ุชุตู†ูŠูุงุช.
  5. โœ… ุงุดุชุฑุงูƒ ุดู‡ุฑูŠ ุนุจุฑ Moyasar ูŠูู†ุดุฆย subscription.active.
  6. โœ… ุนู†ุฏ ุงู†ุชู‡ุงุกย current_period_end + graceย โ†’ ูƒู„ ุงู„ู…ู†ุชุฌุงุชย hiddenย ููŠ ุณู„ุฉ ุฎู„ุงู„ ุณุงุนุฉ.
  7. โœ… ุนู†ุฏ ุชุฌุฏูŠุฏ ุงู„ุฏูุน โ†’ย release()ย ูŠุฑุฌู‘ุน ูƒู„ ุดูŠุก.
  8. โœ… Dashboard ูŠุนุฑุถ ุงู„ุนุฏู‘ุงุฏ ูˆุงู„ุฅุญุตุงุฆูŠุงุช.
  9. โœ… REST endpoints ู…ุญู…ูŠุฉ ุจู€Capabilities.
  10. โœ… Audit log ูŠุณุฌู‘ู„ ูƒู„ ุนู…ู„ูŠุฉ ุญุณุงุณุฉ.

๐Ÿ“ฆ ุงู„ู‚ุณู… 13: ู…ุฎุฑุฌุงุช ู…ุทู„ูˆุจุฉ ู…ู†ูƒ (Deliverables)

ุงูƒุชุจ ุงู„ุขู†ุŒ ุจุงู„ุชุฑุชูŠุจ:

  1. ูƒูˆุฏ SQL ูƒุงู…ู„ย ู„ูƒู„ ุงู„ุฌุฏุงูˆู„ (MySQL 8, utf8mb4).
  2. ูƒูˆุฏ PHP ูƒุงู…ู„ย ู„ู„ู…ู„ูุงุช ููŠ ุฎุทุฉ ุงู„ู€60 ุฏู‚ูŠู‚ุฉ (ู„ุง pseudo-code).
  3. ู…ุฎุทุท ERD ู†ุตูŠย ู„ู„ุนู„ุงู‚ุงุช.
  4. OpenAPI 3.0 YAMLย ู„ูƒู„ REST endpoints.
  5. Schema ู„ู„ู€Payloadsย (Salla webhook, Woo webhook, AI job).
  6. Pseudo-codeย ู„ู€Cronย CheckSubscriptionsย ูˆLockManager::lock/release.
  7. ุฎุทุฉ ุงุฎุชุจุงุฑย (Unit + Integration + E2E).
  8. Roadmapย ู„ู€v1.1 (Multi-store, WhatsApp notifications, Mobile app).

๐Ÿšซ ู‚ูŠูˆุฏ ุตุงุฑู…ุฉ

  • ู„ุง ุชุณุชุฎุฏู…ย eval, ู„ุงย base64ย ู„ู„ูƒูˆุฏ.
  • ูƒู„ ุงู„ู€SQL ุนุจุฑย $wpdb->prepare.
  • ูƒู„ ุงู„ู€Tokens ู…ูุดูู‘ุฑุฉ ุจู€Crypto::encrypt()ย (Sodium).
  • ุงู„ุงู„ุชุฒุงู… ุจู€WordPress Coding Standards + PSR-12.
  • ุงู„ู€Multi-Tenancy ุนุจุฑย tenant_idย ููŠ ูƒู„ ุฌุฏูˆู„ (Row-level isolation).
  • ู„ุง ุชุญุฐู ุจูŠุงู†ุงุช ุนู†ุฏ ุงู„ุฅู„ุบุงุก โ€” ูู‚ุทย locked = 1.

ุงุจุฏุฃ ุงู„ุขู† ุจูƒุชุงุจุฉ ุงู„ู…ูˆุงุตูุฉ ุงู„ู‡ู†ุฏุณูŠุฉ ุงู„ูƒุงู…ู„ุฉ v1.0ุŒ ู…ู„ูู‹ุง ุจู…ู„ูุŒ ุณุทุฑู‹ุง ุจุณุทุฑุŒ ุฌุงู‡ุฒุฉ ู„ู„ู†ุณุฎ ุฅู„ู‰ IDE.

๐Ÿ“š ุงู„ู…ู„ุญู‚ ุงู„ู‡ู†ุฏุณูŠ ุงู„ู…ูˆุณู‘ุน โ€” SallaBridge v1.1

ุงุณุชูƒู…ุงู„ู‹ุง ู„ู„ู…ูˆุงุตูุฉ v1.0ุŒ ู‡ุฐุง ุงู„ู…ู„ุญู‚ ูŠุบุทูŠ ุงู„ุทุจู‚ุงุช ุงู„ุนู…ูŠู‚ุฉ: ุงู„ุชุดููŠุฑุŒ ุงู„ุทูˆุงุจูŠุฑุŒ ุงู„ุฃู…ุงู†ุŒ ุงู„ุชูˆุณุนุŒ ุงู„ุงุฎุชุจุงุฑุงุชุŒ DevOpsุŒ ูˆุงู„ุชูุงุตูŠู„ ุงู„ุชู†ููŠุฐูŠุฉ ุงู„ูƒุงู…ู„ุฉ.


๐Ÿ” ุงู„ู‚ุณู… 14: ุทุจู‚ุฉ ุงู„ุชุดููŠุฑ ูˆุงู„ุฃู…ุงู† (Crypto & Security)

14.1 src/Support/Crypto.php

php

<?php
declare(strict_types=1);
namespace SallaBridge\Support;

final class Crypto {
    private const CIPHER = 'sodium_crypto_secretbox';
    private static string $key;

    public static function boot(): void {
        $raw = defined('SB_ENCRYPTION_KEY') ? SB_ENCRYPTION_KEY : get_option('sb_encryption_key');
        if (!$raw) {
            $raw = base64_encode(random_bytes(32));
            update_option('sb_encryption_key', $raw, false);
        }
        self::$key = base64_decode($raw);
    }

    public static function encrypt(string $plain): string {
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $cipher = sodium_crypto_secretbox($plain, $nonce, self::$key);
        return base64_encode($nonce . $cipher);
    }

    public static function decrypt(string $encoded): string {
        $decoded = base64_decode($encoded, true);
        if ($decoded === false) return '';
        $nonce  = substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $cipher = substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $plain  = sodium_crypto_secretbox_open($cipher, $nonce, self::$key);
        return $plain === false ? '' : $plain;
    }
}

14.2 ู‚ูˆุงุนุฏ ุงู„ุฃู…ุงู† ุงู„ุฅู„ุฒุงู…ูŠุฉ

ุงู„ุจู†ุฏุงู„ุชู†ููŠุฐ
Tokens at RestSodium secretbox (ุฃุนู„ุงู‡)
HTTPS Onlyforce_ssl_admin() + HSTS header
REST AuthJWT + HMAC-SHA256 ู„ู„ู€ webhooks
SQL Injection$wpdb->prepare ุฏุงุฆู…ู‹ุง
XSSesc_html, esc_attr, wp_kses_post
CSRFwp_nonce ู„ูƒู„ form
Rate LimitTransient-based per IP + tenant
File Uploadsูุญุต MIME + wp_check_filetype_and_ext
Secretsู„ุง ุชูุฎุฒู‘ู† ููŠ Git โ€” .env + wp-config.php
Auditูƒู„ ุนู…ู„ูŠุฉ ูƒุชุงุจุฉ โ†’ audit_log

14.3 HMAC Webhook Verification

php

public static function verify_salla_webhook(string $raw, string $signature, string $secret): bool {
    $expected = base64_encode(hash_hmac('sha256', $raw, $secret, true));
    return hash_equals($expected, $signature);
}

โš™๏ธ ุงู„ู‚ุณู… 15: ู†ุธุงู… ุงู„ุทูˆุงุจูŠุฑ (Job Queue)

15.1 ู„ู…ุงุฐุง ู„ุง ู†ุนุชู…ุฏ ุนู„ู‰ wp_cron ูู‚ุทุŸ

  • wp_cronย ูŠุนุชู…ุฏ ุนู„ู‰ ุฒูŠุงุฑุงุช ุงู„ู…ุณุชุฎุฏู… โ†’ ุบูŠุฑ ู…ูˆุซูˆู‚ ู„ู„ู€SaaS.
  • ุงู„ุญู„:ย Action Schedulerย (ู…ุฏู…ุฌ ู…ุน WooCommerce) + Queue ู…ุฎุตู‘ุต.

15.2 ุจู†ูŠุฉ Queue

php

namespace SallaBridge\Core;

final class Queue {
    public const HOOK = 'sb_process_job';

    public static function push(string $job_type, array $payload, int $priority = 10, int $delay = 0): int {
        return as_schedule_single_action(
            time() + $delay,
            self::HOOK,
            ['type' => $job_type, 'payload' => $payload],
            'salla-bridge',
            $priority
        );
    }

    public static function register(): void {
        add_action(self::HOOK, [self::class, 'handle'], 10, 2);
    }

    public static function handle(string $type, array $payload): void {
        $handler = JobRegistry::resolve($type);
        if (!$handler) return;
        try {
            $handler->handle($payload);
        } catch (\Throwable $e) {
            Logger::error("Job failed: {$type}", ['error' => $e->getMessage()]);
            $attempts = ($payload['_attempts'] ?? 0) + 1;
            if ($attempts < 3) {
                $payload['_attempts'] = $attempts;
                self::push($type, $payload, 10, 30 * $attempts);
            } else {
                self::move_to_dlq($type, $payload, $e);
            }
        }
    }
}

15.3 ุฃู†ูˆุงุน ุงู„ู€Jobs

Job TypeHandlerBatch
import.productImportProductJob1 ู…ู†ุชุฌ
import.batchImportBatchJob50 ู…ู†ุชุฌ
sync.priceSyncPriceJob100 ู…ู†ุชุฌ
upload.imageUploadImageJob1 ุตูˆุฑุฉ
ai.translateTranslateJob20 ู†ุต
lock.tenantLockTenantJobtenant ูƒุงู…ู„
release.tenantReleaseTenantJobtenant ูƒุงู…ู„
notify.emailEmailJob1

15.4 Dead Letter Queue (DLQ)

ุฌุฏูˆู„ wp_sb_dlq (id, job_type, payload, error, failed_at) + Dashboard ู„ุนุฑุถู‡ุง ูˆุฅุนุงุฏุฉ ุชุดุบูŠู„ู‡ุง.


๐Ÿ—๏ธ ุงู„ู‚ุณู… 16: Multi-Tenancy ุงู„ุนู…ูŠู‚

16.1 Row-Level Isolation

  • ู‚ุงุนุฏุฉ ุฐู‡ุจูŠุฉ:ย ูƒู„ ุฌุฏูˆู„ (ุนุฏุงย plansย ูˆentitlements) ูŠุญู…ู„ย tenant_id BIGINT UNSIGNED NOT NULL.
  • ูƒู„ Query ูŠู…ุฑ ุนุจุฑย TenantScope::apply($sql).

php

final class TenantScope {
    public static function id(): int {
        return (int) (Container::get('current_tenant')?->id ?? 0);
    }
    public static function where(string $table): string {
        global $wpdb;
        return $wpdb->prepare("{$table}.tenant_id = %d", self::id());
    }
}

16.2 Tenant Context Resolution

text

Request โ†’ Middleware:
  1. JWT decode โ†’ user_id
  2. users โ†’ tenants (1:1 ุฃูˆ 1:N ู…ุน owner)
  3. ุชุญู…ูŠู„ Tenant object + Subscription + Entitlements
  4. Container::set('current_tenant', $tenant)
  5. ุงู„ุชุญู‚ู‚ ู…ู† Lock status โ†’ ุฑูุถ ุฅู† ู…ูุนู‘ู„

16.3 Tenant Lifecycle

text

registered โ†’ trial (14 ูŠูˆู…) โ†’ active โ†’ past_due โ†’ locked โ†’ cancelled
                โ”‚                  โ”‚
                โ””โ”€โ”€ soft delete โ”€โ”€โ”€โ”˜

๐Ÿ”Œ ุงู„ู‚ุณู… 17: ู…ุญูˆู‘ู„ุงุช ุงู„ู…ุตุงุฏุฑ (Source Adapters) ุจุงู„ุชูุตูŠู„

17.1 ุงู„ูˆุงุฌู‡ุฉ ุงู„ู…ูˆุญุฏุฉ

php

interface SourceInterface {
    public function authenticate(array $credentials): bool;
    public function search(string $query, int $page = 1): array;
    public function fetch_product(string $external_id): array;   // DTO ู…ูˆุญู‘ุฏ
    public function fetch_images(string $external_id): array;
    public function fetch_categories(): array;
    public function normalize(array $raw): ProductDTO;
}

17.2 ProductDTO ุงู„ู…ูˆุญู‘ุฏ

php

final class ProductDTO {
    public string $external_id;
    public string $title;
    public string $description;
    public float  $price;
    public string $currency;
    public int    $stock;
    public string $sku;
    public array  $images = [];      // ['url' => '', 'alt' => '']
    public array  $variants = [];    // ['name'=>'', 'price'=>0, 'stock'=>0]
    public string $category_path;    // "Electronics > Phones"
    public array  $attributes = [];  // ['color'=>'red']
    public array  $raw = [];         // ุงู„ุฃุตู„ ู„ู„ุฑุฌูˆุน
}

17.3 ุฌุฏูˆู„ ุงู„ู…ุตุงุฏุฑ ุงู„ู…ุฏุนูˆู…ุฉ

ุงู„ู…ุตุฏุฑAuthRate Limitู…ู„ุงุญุธุงุช
AliExpressAppKey + AppSecret + OAuth100/minุนุจุฑ aliexpress.open.api
Amazon PA-APIAccessKey + Secret + PartnerTag1 req/sูŠุญุชุงุฌ Associate
NoonPartner API60/minูŠุญุชุงุฌ ุงุชูุงู‚ ุชุฌุงุฑูŠ
ShopifyStore URL + Admin Token2 req/sREST Admin API
TaobaoAppKey + Session60/minูŠุญุชุงุฌ ูˆุณูŠุท ุตูŠู†ูŠ
1688AppKey + Session60/minู…ู…ุงุซู„ Taobao
CJ DropshippingAPI Key120/minุงู„ุฃุณู‡ู„ ู„ู„ุจุฏุงูŠุฉ
AlibabaOAuth30/minู„ู„ุฌู…ู„ุฉ

17.4 ุฎูˆุงุฑุฒู…ูŠุฉ ุงุฎุชูŠุงุฑ ุงู„ู…ุตุฏุฑ

php

SourceFactory::make($type, $tenant) // ูŠุชุญู‚ู‚ ู…ู†:
  - ุงู„ู…ุตุฏุฑ ู…ุฏุนูˆู… ููŠ ุงู„ุจุงู‚ุฉ (max_sources)
  - credentials ุตุญูŠุญุฉ + ุบูŠุฑ ู…ู†ุชู‡ูŠุฉ
  - rate limit ู…ุชุงุญ
  - circuit breaker ู…ุบู„ู‚

๐Ÿค– ุงู„ู‚ุณู… 18: AI Gateway ุงู„ู…ุชู‚ุฏู…

18.1 Routing Strategy (Cost-Aware)

php

final class Gateway {
    public function translate(string $text, string $to): string {
        $provider = $this->pick_provider('translate', strlen($text), $to);
        return $provider->translate($text, $to);
    }

    private function pick_provider(string $task, int $len, string $lang): AiProviderInterface {
        // ู‚ูˆุงุนุฏ:
        // - ู†ุตูˆุต <500 char + ุนุฑุจูŠ โ†’ Gemini Flash (ุฑุฎูŠุต)
        // - ู†ุตูˆุต >2000 char โ†’ OpenAI GPT-4o-mini
        // - SEO/rewrite โ†’ Claude Sonnet
        // - fallback chain ุนู†ุฏ ุงู„ูุดู„
    }
}

18.2 AI Caching Layer

ุฌุฏูˆู„ wp_sb_ai_cache (hash, provider, output, tokens, created_at) โ€” ูŠู…ู†ุน ุฅุนุงุฏุฉ ุงู„ุฏูุน ู„ู†ูุณ ุงู„ู…ุฏุฎู„.

18.3 Prompt Templates

php

final class Prompts {
    public const TRANSLATE = <<<PROMPT
    Translate the following e-commerce product text to %s.
    Preserve: brand names, units, numbers, HTML tags.
    Tone: persuasive, marketing.
    Input: %s
    Output (JSON): {"title":"","description":"","tags":[]}
    PROMPT;

    public const SEO = <<<PROMPT
    Rewrite as SEO-optimized Arabic product listing.
    Include: primary keyword, 3 long-tail, meta description (155 char).
    Input: %s
    PROMPT;
}

18.4 ุญุฏูˆุฏ AI ุญุณุจ ุงู„ุจุงู‚ุฉ

ุงู„ุจุงู‚ุฉTokens/ุดู‡ุฑRate/minProviders
Starter100K10Gemini
Growth1M30+ OpenAI
Pro5M100+ Anthropic
EnterpriseCustomCustomAll + Dedicated

๐Ÿ’ฐ ุงู„ู‚ุณู… 19: ุงู„ููˆุชุฑุฉ ูˆุงู„ุงุดุชุฑุงูƒุงุช (Billing Deep Dive)

19.1 ุฏูˆุฑุฉ ุญูŠุงุฉ ุงู„ุงุดุชุฑุงูƒ (State Machine)

text

          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
          โ”‚ trialing โ”‚
          โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚ (14 ูŠูˆู…)
               โ–ผ
          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   ูุดู„ ุฏูุน   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
          โ”‚  active  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ past_due  โ”‚
          โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜             โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚ ุชุฌุฏูŠุฏ                    โ”‚ (3 ุฃูŠุงู…)
               โ”‚                          โ–ผ
               โ”‚                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
               โ”‚                    โ”‚  locked  โ”‚
               โ”‚                    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚                         โ”‚ ุฏูุน
               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚ ุฅู„ุบุงุก
                            โ–ผ
                       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                       โ”‚ cancelled โ”‚
                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

19.2 SubscriptionManager::renew()

php

public function renew(int $subscription_id, string $gateway_txn): bool {
    $sub = $this->repo->find($subscription_id);
    $plan = $this->plans->find($sub->plan_id);

    $sub->current_period_start = time();
    $sub->current_period_end   = $sub->billing_cycle === 'yearly'
        ? strtotime('+1 year')
        : strtotime('+1 month');
    $sub->status = 'active';
    $sub->gateway_sub_id = $gateway_txn;
    $this->repo->save($sub);

    $this->entitlements->reset_usage($sub->tenant_id, $sub->current_period_start);
    $this->events->dispatch('subscription.renewed', $sub);

    // ุฑูุน ุงู„ู‚ูู„ ุฅู† ูˆูุฌุฏ
    if ($this->lock->is_locked($sub->tenant_id)) {
        $this->lock->release($sub->tenant_id, 'renewal');
    }
    return true;
}

19.3 Webhook Handlers ู„ูƒู„ Gateway

Gatewayุญุฏุซ ุงู„ุฏูุนุญุฏุซ ุงู„ุฅู„ุบุงุกุงู„ุชูˆู‚ูŠุน
Stripeinvoice.paidcustomer.subscription.deletedStripe-Signature
PayPalBILLING.SUBSCRIPTION.ACTIVATED...CANCELLEDPAYPAL-TRANSMISSION-SIG
Moyasarpayment_paidsubscription_canceledHMAC-SHA256
Tapcharge.succeededsubscription.cancelledhashstring

19.4 ุงู„ููˆุงุชูŠุฑ (Invoices)

  • ุชูˆู„ูŠุฏ PDF ุนุจุฑย dompdfย ุฃูˆย TCPDF.
  • ุชุฎุฒูŠู† ููŠย wp-content/uploads/sb-invoices/{tenant_id}/.
  • ุฅุฑุณุงู„ ุชู„ู‚ุงุฆูŠ ุจุงู„ุจุฑูŠุฏ + ุฑุงุจุท ุฏุงุฆู….

๐Ÿ”’ ุงู„ู‚ุณู… 20: ู†ุธุงู… ุงู„ู‚ูู„ โ€” ุงู„ุชู†ููŠุฐ ุงู„ูƒุงู…ู„

20.1 LockManager.php (ูƒุงู…ู„)

php

<?php
declare(strict_types=1);
namespace SallaBridge\Locking;

final class LockManager {
    public function __construct(
        private \SallaBridge\Salla\SallaLocker $locker,
        private \SallaBridge\Core\Repositories\ProductsMapRepo $products,
        private \SallaBridge\Core\Repositories\CategoriesMapRepo $categories,
        private \SallaBridge\Core\Events\Dispatcher $events,
        private \SallaBridge\Support\Logger $logger
    ) {}

    public function lock(int $tenant_id, string $reason): int {
        $this->logger->info("Locking tenant {$tenant_id}", ['reason' => $reason]);

        $product_ids = $this->products->get_unlocked_ids($tenant_id);
        $cat_ids     = $this->categories->get_unlocked_ids($tenant_id);
        $total       = 0;

        foreach (array_chunk($product_ids, 50) as $chunk) {
            $total += $this->locker->hide_products($tenant_id, $chunk);
            $this->products->mark_locked($tenant_id, $chunk);
            usleep(500_000); // 0.5s ู„ู„ู€rate limit
        }

        foreach ($cat_ids as $cat_id) {
            $this->locker->hide_category($tenant_id, $cat_id);
            $this->categories->mark_locked($tenant_id, $cat_id);
        }

        $this->events->dispatch('tenant.locked', [
            'tenant_id' => $tenant_id,
            'reason'    => $reason,
            'products'  => count($product_ids),
            'categories'=> count($cat_ids),
        ]);

        $this->log_lock_event($tenant_id, $reason, $total);
        return $total;
    }

    public function release(int $tenant_id, string $trigger): int {
        $this->logger->info("Releasing tenant {$tenant_id}", ['trigger' => $trigger]);

        $product_ids = $this->products->get_locked_ids($tenant_id);
        $cat_ids     = $this->categories->get_locked_ids($tenant_id);
        $total       = 0;

        foreach (array_chunk($product_ids, 50) as $chunk) {
            $total += $this->locker->publish_products($tenant_id, $chunk);
            $this->products->mark_unlocked($tenant_id, $chunk);
            usleep(500_000);
        }

        foreach ($cat_ids as $cat_id) {
            $this->locker->publish_category($tenant_id, $cat_id);
            $this->categories->mark_unlocked($tenant_id, $cat_id);
        }

        $this->events->dispatch('tenant.released', [
            'tenant_id' => $tenant_id,
            'trigger'   => $trigger,
            'products'  => count($product_ids),
        ]);

        return $total;
    }

    public function is_locked(int $tenant_id): bool {
        return (bool) get_transient("sb_locked_{$tenant_id}");
    }
}

20.2 SallaLocker.php

php

final class SallaLocker {
    public function __construct(private \SallaBridge\Salla\Client $client) {}

    public function hide_products(int $tenant_id, array $salla_product_ids): int {
        $count = 0;
        foreach ($salla_product_ids as $pid) {
            $res = $this->client->for_tenant($tenant_id)
                ->put("/products/{$pid}", ['status' => 'hidden']);
            if (!is_wp_error($res)) $count++;
        }
        return $count;
    }

    public function publish_products(int $tenant_id, array $ids): int {
        $count = 0;
        foreach ($ids as $pid) {
            $res = $this->client->for_tenant($tenant_id)
                ->put("/products/{$pid}", ['status' => 'sale']);
            if (!is_wp_error($res)) $count++;
        }
        return $count;
    }

    public function hide_category(int $tenant_id, int $cat_id): bool {
        $res = $this->client->for_tenant($tenant_id)
            ->put("/categories/{$cat_id}", ['status' => 'hidden']);
        return !is_wp_error($res);
    }

    public function publish_category(int $tenant_id, int $cat_id): bool {
        $res = $this->client->for_tenant($tenant_id)
            ->put("/categories/{$cat_id}", ['status' => 'active']);
        return !is_wp_error($res);
    }
}

20.3 Cron: CheckSubscriptions

php

final class CheckSubscriptions {
    public const HOOK = 'sb_hourly_check';

    public static function register(): void {
        if (!wp_next_scheduled(self::HOOK)) {
            wp_schedule_event(time(), 'hourly', self::HOOK);
        }
        add_action(self::HOOK, [self::class, 'run']);
    }

    public static function run(): void {
        global $wpdb;
        $grace_days = (int) get_option('sb_grace_days', 3);
        $now        = time();
        $threshold  = $now - ($grace_days * DAY_IN_SECONDS);

        $subs = $wpdb->get_results($wpdb->prepare("
            SELECT id, tenant_id, status, current_period_end
            FROM {$wpdb->prefix}sb_subscriptions
            WHERE status IN ('active','past_due')
              AND current_period_end < %d
        ", $threshold));

        $lock = Container::get(LockManager::class);

        foreach ($subs as $sub) {
            // 1) ุญุงูˆู„ ุชุญุตูŠู„ ุงู„ุฏูุน ุฃูˆู„ุงู‹ (Retry)
            if ($sub->status === 'active') {
                $wpdb->update("{$wpdb->prefix}sb_subscriptions",
                    ['status' => 'past_due'], ['id' => $sub->id]);
                continue;
            }

            // 2) past_due + ุงู†ุชู‡ุช ุงู„ู…ู‡ู„ุฉ โ†’ ุงู‚ูู„
            if ($sub->status === 'past_due') {
                $lock->lock((int) $sub->tenant_id, 'expired');
                $wpdb->update("{$wpdb->prefix}sb_subscriptions",
                    ['status' => 'locked'], ['id' => $sub->id]);
                self::notify_tenant($sub->tenant_id);
            }
        }
    }

    private static function notify_tenant(int $tenant_id): void {
        $email = get_userdata(get_user_by('id', $tenant_id)->user_email ?? 0);
        wp_mail(
            $email?->user_email ?? get_option('admin_email'),
            'ุชู… ุฅูŠู‚ุงู ู…ุชุฌุฑูƒ ู…ุคู‚ุชู‹ุง',
            'ุงู†ุชู‡ู‰ ุงุดุชุฑุงูƒูƒ ููŠ SallaBridge. ุฌุฏู‘ุฏ ุงู„ุขู† ู„ุฅุนุงุฏุฉ ุชูุนูŠู„ ู…ู†ุชุฌุงุชูƒ.'
        );
    }
}

๐ŸŒ ุงู„ู‚ุณู… 21: ุชูƒุงู…ู„ ุณู„ุฉ (Salla) โ€” ุงู„ุทุจู‚ุฉ ุงู„ูƒุงู…ู„ุฉ

21.1 Salla\Client.php

php

final class Client {
    private const API_BASE = 'https://api.salla.dev/admin/v2';
    private const OAUTH_AUTH = 'https://accounts.salla.sa/oauth2/auth';
    private const OAUTH_TOKEN = 'https://accounts.salla.sa/oauth2/token';

    private ?int $tenant_id = null;

    public function for_tenant(int $tenant_id): self {
        $clone = clone $this;
        $clone->tenant_id = $tenant_id;
        return $clone;
    }

    private function token(): string {
        $tenant = Container::get(TenantRepo::class)->find($this->tenant_id);
        $token  = Crypto::decrypt($tenant->salla_access_token);

        // ุชุญู‚ู‚ ู…ู† ุงู„ุงู†ุชู‡ุงุก
        if ($tenant->salla_expires_at < time() + 300) {
            $token = $this->refresh($tenant);
        }
        return $token;
    }

    private function refresh(object $tenant): string {
        $res = wp_remote_post(self::OAUTH_TOKEN, [
            'body' => [
                'grant_type'    => 'refresh_token',
                'refresh_token' => Crypto::decrypt($tenant->salla_refresh_token),
                'client_id'     => SB_SALLA_CLIENT_ID,
                'client_secret' => SB_SALLA_CLIENT_SECRET,
            ],
        ]);
        $body = json_decode(wp_remote_retrieve_body($res), true);
        if (!empty($body['access_token'])) {
            Container::get(TenantRepo::class)->update_tokens($tenant->id, $body);
            return $body['access_token'];
        }
        throw new \RuntimeException('Salla token refresh failed');
    }

    public function get(string $endpoint, array $query = []): array|\WP_Error {
        return $this->request('GET', $endpoint, $query);
    }

    public function post(string $endpoint, array $body): array|\WP_Error {
        return $this->request('POST', $endpoint, $body);
    }

    public function put(string $endpoint, array $body): array|\WP_Error {
        return $this->request('PUT', $endpoint, $body);
    }

    private function request(string $method, string $endpoint, array $data): array|\WP_Error {
        $args = [
            'method'  => $method,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->token(),
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
            ],
            'timeout' => 30,
        ];
        if ($method === 'GET') {
            $url = add_query_arg($data, self::API_BASE . $endpoint);
        } else {
            $url = self::API_BASE . $endpoint;
            $args['body'] = wp_json_encode($data);
        }

        $res = wp_remote_request($url, $args);
        if (is_wp_error($res)) return $res;

        $code = wp_remote_retrieve_response_code($res);
        $body = json_decode(wp_remote_retrieve_body($res), true);

        if ($code >= 400) {
            return new \WP_Error("salla_http_{$code}", $body['message'] ?? 'Salla error', $body);
        }
        return $body ?? [];
    }
}

21.2 Rate Limiter ู„ุณู„ุฉ

php

final class SallaRateLimiter {
    private const MAX_PER_MIN = 60;

    public static function acquire(int $tenant_id): void {
        $key = "sb_salla_rl_{$tenant_id}";
        $count = (int) get_transient($key);
        if ($count >= self::MAX_PER_MIN) {
            sleep(2); // backoff
            $count = 0;
        }
        set_transient($key, $count + 1, MINUTE_IN_SECONDS);
    }
}

21.3 Webhooks ู…ู† ุณู„ุฉ

ุงู„ุญุฏุซุงู„ุงุณุชุฌุงุจุฉ
order.createdู…ุฒุงู…ู†ุฉ ุงู„ู…ุฎุฒูˆู†
product.updatedูƒุดู ุงู„ุชุถุงุฑุจ
app.store.authorizeุชุฎุฒูŠู† tokens
app.uninstalledsoft delete ู„ู„ู€tenant

๐Ÿ“Š ุงู„ู‚ุณู… 22: ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช โ€” SQL ุงู„ูƒุงู…ู„

sql

-- 1. ุงู„ุฎุทุท
CREATE TABLE {$prefix}sb_plans (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  slug VARCHAR(50) NOT NULL,
  price_monthly DECIMAL(10,2) NOT NULL DEFAULT 0,
  price_yearly DECIMAL(10,2) NOT NULL DEFAULT 0,
  currency CHAR(3) NOT NULL DEFAULT 'SAR',
  max_products INT UNSIGNED NOT NULL DEFAULT 500,
  max_images INT UNSIGNED NOT NULL DEFAULT 2000,
  max_sources TINYINT UNSIGNED NOT NULL DEFAULT 1,
  max_ai_tokens INT UNSIGNED NOT NULL DEFAULT 100000,
  features_json JSON NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  sort_order SMALLINT NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_slug (slug),
  KEY idx_active_sort (is_active, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 2. ุงู„ู…ุณุชุฃุฌุฑูˆู†
CREATE TABLE {$prefix}sb_tenants (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  store_name VARCHAR(200) NOT NULL,
  salla_store_id VARCHAR(64) NULL,
  salla_access_token TEXT NULL,
  salla_refresh_token TEXT NULL,
  salla_expires_at DATETIME NULL,
  plan_id BIGINT UNSIGNED NULL,
  status ENUM('trial','active','past_due','locked','cancelled') NOT NULL DEFAULT 'trial',
  trial_ends_at DATETIME NULL,
  locale CHAR(5) NOT NULL DEFAULT 'ar',
  timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Riyadh',
  meta_json JSON NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_user (user_id),
  KEY idx_status (status),
  KEY idx_plan (plan_id),
  CONSTRAINT fk_tenant_plan FOREIGN KEY (plan_id) REFERENCES {$prefix}sb_plans(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 3. ุงู„ุงุดุชุฑุงูƒุงุช
CREATE TABLE {$prefix}sb_subscriptions (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  plan_id BIGINT UNSIGNED NOT NULL,
  billing_cycle ENUM('monthly','yearly') NOT NULL DEFAULT 'monthly',
  gateway VARCHAR(32) NOT NULL,
  gateway_sub_id VARCHAR(128) NULL,
  gateway_customer_id VARCHAR(128) NULL,
  status ENUM('trialing','active','past_due','locked','cancelled') NOT NULL DEFAULT 'trialing',
  amount DECIMAL(10,2) NOT NULL DEFAULT 0,
  currency CHAR(3) NOT NULL DEFAULT 'SAR',
  current_period_start DATETIME NULL,
  current_period_end DATETIME NULL,
  cancel_at DATETIME NULL,
  cancelled_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_tenant_status (tenant_id, status),
  KEY idx_period_end (status, current_period_end),
  CONSTRAINT fk_sub_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE,
  CONSTRAINT fk_sub_plan FOREIGN KEY (plan_id) REFERENCES {$prefix}sb_plans(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 4. ุงู„ู…ุตุงุฏุฑ
CREATE TABLE {$prefix}sb_sources (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  source_type VARCHAR(32) NOT NULL,
  label VARCHAR(100) NOT NULL,
  credentials TEXT NULL,
  config_json JSON NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  last_sync_at DATETIME NULL,
  last_error TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_tenant_type (tenant_id, source_type),
  CONSTRAINT fk_source_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 5. ุฎุฑูŠุทุฉ ุงู„ู…ู†ุชุฌุงุช
CREATE TABLE {$prefix}sb_products_map (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  source_id BIGINT UNSIGNED NOT NULL,
  source_product_id VARCHAR(128) NOT NULL,
  salla_product_id VARCHAR(64) NULL,
  sku VARCHAR(128) NULL,
  title VARCHAR(500) NULL,
  price_source DECIMAL(12,2) NOT NULL DEFAULT 0,
  price_salla DECIMAL(12,2) NOT NULL DEFAULT 0,
  markup_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
  currency CHAR(3) NOT NULL DEFAULT 'SAR',
  image_count SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  stock INT NOT NULL DEFAULT 0,
  locked TINYINT(1) NOT NULL DEFAULT 0,
  sync_status ENUM('pending','synced','failed','outdated') NOT NULL DEFAULT 'pending',
  last_error TEXT NULL,
  synced_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_tenant_source (tenant_id, source_id, source_product_id),
  KEY idx_salla (tenant_id, salla_product_id),
  KEY idx_locked (tenant_id, locked),
  CONSTRAINT fk_pm_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE,
  CONSTRAINT fk_pm_source FOREIGN KEY (source_id) REFERENCES {$prefix}sb_sources(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 6. ุงู„ุตูˆุฑ
CREATE TABLE {$prefix}sb_images (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  product_map_id BIGINT UNSIGNED NOT NULL,
  source_url VARCHAR(1000) NOT NULL,
  salla_media_id VARCHAR(64) NULL,
  checksum CHAR(64) NULL,
  position SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  status ENUM('pending','uploaded','failed') NOT NULL DEFAULT 'pending',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_pm (product_map_id),
  KEY idx_status (status),
  CONSTRAINT fk_img_pm FOREIGN KEY (product_map_id) REFERENCES {$prefix}sb_products_map(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 7. ุฎุฑูŠุทุฉ ุงู„ุชุตู†ูŠูุงุช
CREATE TABLE {$prefix}sb_categories_map (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  source_id BIGINT UNSIGNED NOT NULL,
  source_cat_id VARCHAR(128) NOT NULL,
  salla_cat_id VARCHAR(64) NULL,
  name VARCHAR(255) NULL,
  parent_source_id VARCHAR(128) NULL,
  locked TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_tenant_source_cat (tenant_id, source_id, source_cat_id),
  KEY idx_locked (tenant_id, locked),
  CONSTRAINT fk_cm_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 8. ู…ู‡ุงู… ุงู„ุงุณุชูŠุฑุงุฏ
CREATE TABLE {$prefix}sb_import_jobs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  source_id BIGINT UNSIGNED NOT NULL,
  job_type ENUM('search','category','manual','bulk') NOT NULL DEFAULT 'manual',
  status ENUM('queued','running','paused','completed','failed','cancelled') NOT NULL DEFAULT 'queued',
  total_items INT UNSIGNED NOT NULL DEFAULT 0,
  processed_items INT UNSIGNED NOT NULL DEFAULT 0,
  failed_items INT UNSIGNED NOT NULL DEFAULT 0,
  payload_json JSON NULL,
  started_at DATETIME NULL,
  finished_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_tenant_status (tenant_id, status),
  CONSTRAINT fk_ij_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 9. ู…ู‡ุงู… AI
CREATE TABLE {$prefix}sb_ai_jobs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  job_type ENUM('translate','rewrite','seo','categorize') NOT NULL,
  provider VARCHAR(32) NOT NULL,
  input_hash CHAR(64) NOT NULL,
  output_json JSON NULL,
  tokens_used INT UNSIGNED NOT NULL DEFAULT 0,
  cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
  status ENUM('queued','running','completed','failed') NOT NULL DEFAULT 'queued',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_hash (input_hash),
  KEY idx_tenant (tenant_id),
  CONSTRAINT fk_ai_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 10. ุงุณุชุฎุฏุงู… ุงู„ู…ูŠุฒุงุช
CREATE TABLE {$prefix}sb_usage (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  feature_key VARCHAR(64) NOT NULL,
  period_start DATE NOT NULL,
  used_value BIGINT UNSIGNED NOT NULL DEFAULT 0,
  limit_value BIGINT UNSIGNED NOT NULL DEFAULT 0,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_tenant_feature_period (tenant_id, feature_key, period_start)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 11. ุณุฌู„ ุงู„ู‚ูู„
CREATE TABLE {$prefix}sb_lock_events (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NOT NULL,
  action ENUM('lock','release') NOT NULL,
  reason VARCHAR(64) NOT NULL,
  products_affected INT UNSIGNED NOT NULL DEFAULT 0,
  categories_affected INT UNSIGNED NOT NULL DEFAULT 0,
  actor_id BIGINT UNSIGNED NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_tenant (tenant_id, created_at),
  CONSTRAINT fk_le_tenant FOREIGN KEY (tenant_id) REFERENCES {$prefix}sb_tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 12. ุณุฌู„ ุงู„ุชุฏู‚ูŠู‚
CREATE TABLE {$prefix}sb_audit_log (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id BIGINT UNSIGNED NULL,
  actor_id BIGINT UNSIGNED NULL,
  action VARCHAR(64) NOT NULL,
  object_type VARCHAR(64) NULL,
  object_id BIGINT UNSIGNED NULL,
  meta_json JSON NULL,
  ip VARCHAR(45) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_tenant_time (tenant_id, created_at),
  KEY idx_action (action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 13. DLQ
CREATE TABLE {$prefix}sb_dlq (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  job_type VARCHAR(64) NOT NULL,
  payload LONGTEXT NOT NULL,
  error TEXT NULL,
  attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
  failed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  resolved_at DATETIME NULL,
  PRIMARY KEY (id),
  KEY idx_type_time (job_type, failed_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

๐Ÿงช ุงู„ู‚ุณู… 23: ุงู„ุงุฎุชุจุงุฑุงุช (Testing Strategy)

23.1 ุงู„ู‡ุฑู…

text

       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚   E2E (5%)  โ”‚  Playwright + WP test env
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
       โ”‚Integration  โ”‚  PHPUnit + wp-phpunit
       โ”‚   (25%)     โ”‚
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
       โ”‚  Unit (70%) โ”‚  PHPUnit + Brain Monkey
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

23.2 ุฃู…ุซู„ุฉ ุงุฎุชุจุงุฑุงุช ุญุฑุฌุฉ

php

// tests/Unit/EntitlementCheckerTest.php
public function test_blocks_when_subscription_expired(): void {
    $tenant = $this->factory->tenant(['status' => 'locked']);
    $checker = new EntitlementChecker();
    $this->assertFalse($checker->can($tenant->id, 'import_products', 1));
}

public function test_allows_when_within_quota(): void {
    $tenant = $this->factory->tenant(['plan' => 'growth']);
    $this->assertTrue((new EntitlementChecker())->can($tenant->id, 'import_products', 100));
}

// tests/Integration/LockManagerTest.php
public function test_lock_hides_all_products_on_salla(): void {
    $mock = $this->mock_salla_client();
    $mock->expects($this->exactly(50))->method('put')
         ->with($this->stringContains('/products/'), ['status' => 'hidden']);

    $lock = Container::get(LockManager::class);
    $count = $lock->lock($this->tenant->id, 'test');
    $this->assertEquals(50, $count);
}

23.3 E2E Scenarios

  1. ู…ุณุชุฃุฌุฑ ุฌุฏูŠุฏ โ†’ ุชุณุฌูŠู„ โ†’ ุฑุจุท ุณู„ุฉ โ†’ ุงุณุชูŠุฑุงุฏ 10 ู…ู†ุชุฌุงุช โ†’ ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุธู‡ูˆุฑ.
  2. ุงุดุชุฑุงูƒ โ†’ ุงู†ุชู‡ุงุก โ†’ ู‚ูู„ ุชู„ู‚ุงุฆูŠ โ†’ ุชุฌุฏูŠุฏ โ†’ ุฑูุน ุงู„ู‚ูู„.
  3. AI translate 100 ู†ุต โ†’ ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุชุฎุฒูŠู† ูˆุงู„ูุงุชูˆุฑุฉ.

๐Ÿš€ ุงู„ู‚ุณู… 24: DevOps ูˆุงู„ู†ุดุฑ

24.1 CI/CD Pipeline

yaml

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.2' }
      - run: composer install
      - run: composer lint       # PHPCS + PSR-12
      - run: composer test       # PHPUnit
      - run: composer phpstan    # Level 6
      - run: npm ci && npm run build

24.2 Release Process

  1. npm run buildย โ†’ย assets/dist/
  2. composer install --no-dev --optimize-autoloader
  3. wp i18n make-pot . languages/salla-bridge.pot
  4. wp dist-archive .ย โ†’ย salla-bridge-x.y.z.zip
  5. GitHub Release + ุชุญุฏูŠุซย readme.txt.

24.3 Monitoring

ุงู„ู…ู‚ูŠุงุณุงู„ุฃุฏุงุฉ
UptimeUptimeRobot
ErrorsSentry (PHP SDK)
PerformanceNew Relic / Query Monitor
BusinessMetabase ุนู„ู‰ replica DB
Logsู…ู„ูุงุช ูŠูˆู…ูŠุฉ ููŠ wp-content/uploads/sb-logs/

24.4 Backup Strategy

  • DB: ู†ุณุฎุฉ ูƒู„ 6 ุณุงุนุงุช โ†’ S3.
  • wp-content/uploads/sb-*: ู†ุณุฎุฉ ูŠูˆู…ูŠุฉ.
  • ุงู„ุงุญุชูุงุธ: 30 ูŠูˆู…ู‹ุง.

๐Ÿ“ˆ ุงู„ู‚ุณู… 25: ุงู„ุฃุฏุงุก ูˆุงู„ุชูˆุณุน (Performance & Scale)

25.1 ุงู„ู‚ูŠุงุณุงุช ุงู„ู…ุณุชู‡ุฏูุฉ

ุงู„ุนู…ู„ูŠุฉุงู„ู‡ุฏู
ุงุณุชูŠุฑุงุฏ ู…ู†ุชุฌ ูˆุงุญุฏ< 2s
ุงุณุชูŠุฑุงุฏ 1000 ู…ู†ุชุฌ< 30 ุฏู‚ูŠู‚ุฉ
Lock tenant (10K ู…ู†ุชุฌ)< 5 ุฏู‚ุงุฆู‚
REST response p95< 300ms
Dashboard load< 1.5s

25.2 ุงุณุชุฑุงุชูŠุฌูŠุงุช ุงู„ุชุญุณูŠู†

  • Indexes:ย ูƒู…ุง ููŠ SQL ุฃุนู„ุงู‡.
  • Chunking:ย ุฏุงุฆู…ู‹ุงย array_chunk(..., 50).
  • Object Cache:ย Redis/Memcached ู„ู„ู€Tenant,ย Plan,ย Entitlements.
  • DB Read Replica:ย ู„ู„ู€Dashboard queries.
  • CDN:ย ู„ู„ุตูˆุฑ ู‚ุจู„ ุฑูุนู‡ุง ู„ุณู„ุฉ.
  • HTTP/2 + Connection Poolย ู„ู…ุตุงุฏุฑ ุฎุงุฑุฌูŠุฉ.

25.3 Circuit Breaker ู„ู„ู…ุตุงุฏุฑ

php

final class CircuitBreaker {
    public static function call(string $service, callable $fn): mixed {
        if (get_transient("sb_cb_open_{$service}")) {
            throw new \RuntimeException("Circuit open for {$service}");
        }
        try {
            $res = $fn();
            delete_transient("sb_cb_fail_{$service}");
            return $res;
        } catch (\Throwable $e) {
            $fails = (int) get_transient("sb_cb_fail_{$service}") + 1;
            set_transient("sb_cb_fail_{$service}", $fails, 300);
            if ($fails >= 5) {
                set_transient("sb_cb_open_{$service}", 1, 60);
            }
            throw $e;
        }
    }
}

๐Ÿ›ก๏ธ ุงู„ู‚ุณู… 26: ู…ุนุงู„ุฌุฉ ุงู„ุฃุฎุทุงุก ูˆุงู„ู…ุฑุงู‚ุจุฉ

26.1 Error Codes ุงู„ู…ูˆุญู‘ุฏุฉ

Codeุงู„ู…ุนู†ู‰HTTP
SB_1001Entitlement exceeded402
SB_1002Subscription locked403
SB_1003Invalid source credentials401
SB_1004Salla rate limited429
SB_1005AI provider failed502
SB_1006Product not found404
SB_1007Duplicate import409
SB_2001Internal error500

26.2 Logger

php

final class Logger {
    public function info(string $msg, array $ctx = []): void { $this->write('info', $msg, $ctx); }
    public function error(string $msg, array $ctx = []): void {
        $this->write('error', $msg, $ctx);
        if (function_exists('sentry_capture_exception')) {
            \Sentry\captureMessage($msg, \Sentry\Severity::error());
        }
    }
    private function write(string $level, string $msg, array $ctx): void {
        $file = WP_CONTENT_DIR . '/uploads/sb-logs/' . date('Y-m-d') . ".log";
        wp_mkdir_p(dirname($file));
        @file_put_contents($file, sprintf(
            "[%s] %s: %s %s\n",
            date('c'), strtoupper($level), $msg, wp_json_encode($ctx)
        ), FILE_APPEND | LOCK_EX);
    }
}

๐ŸŽจ ุงู„ู‚ุณู… 27: ูˆุงุฌู‡ุฉ ุงู„ู…ุณุชุฎุฏู… (UI/UX)

27.1 ุงู„ุตูุญุงุช ุงู„ุฑุฆูŠุณูŠุฉ

ุงู„ุตูุญุฉุงู„ุนู†ุงุตุฑ
OnboardingWizard 4 ุฎุทูˆุงุช: ุญุณุงุจ โ†’ ุจุงู‚ุฉ โ†’ ุฑุจุท ุณู„ุฉ โ†’ ุฃูˆู„ ุงุณุชูŠุฑุงุฏ
Dashboard4 ุจุทุงู‚ุงุช KPI + ุฑุณู… + ุฌุฏูˆู„ ุขุฎุฑ ุงู„ู…ู‡ุงู… + ุชู†ุจูŠู‡ ุงู„ุงุดุชุฑุงูƒ
ImportุงุฎุชูŠุงุฑ ู…ุตุฏุฑุŒ ุจุญุซุŒ ูู„ุชุฑุฉุŒ ุฅุถุงูุฉ ู„ู„ุณู„ุฉุŒ ุงุณุชูŠุฑุงุฏ ุจุงู„ุฌู…ู„ุฉ
Productsุฌุฏูˆู„ 10K ุตู ุงูุชุฑุงุถูŠ (Virtual Scroll)ุŒ ูู„ุชุฑุฉุŒ bulk actions
Categoriesุดุฌุฑุฉ ู‚ุงุจู„ุฉ ู„ู„ุณุญุจุŒ mapping
AI Toolsุชุฑุฌู…ุฉุŒ SEOุŒ ุฅุนุงุฏุฉ ูƒุชุงุจุฉ
BillingุฎุทุทุŒ ููˆุงุชูŠุฑุŒ ุทุฑูŠู‚ุฉ ุฏูุนุŒ ุฅู„ุบุงุก
SettingsAPI keysุŒ webhooksุŒ timezoneุŒ ุชูุถูŠู„ุงุช
Logsaudit + lock events + DLQ

27.2 Stack ุงู„ูˆุงุฌู‡ุฉ

  • React 18 + TypeScript
  • Vite ู„ู„ุจู†ุงุก
  • Tailwind CSS
  • TanStack Query ู„ู„ู€state
  • @wordpress/dataย ู„ู„ุชูƒุงู…ู„ ู…ุน WP admin
  • RTL native (ุฏุนู… ุนุฑุจูŠ ูƒุงู…ู„)

27.3 ุงู„ู…ูƒูˆู†ุงุช ุงู„ู…ุดุชุฑูƒุฉ

<PlanCard>, <QuotaMeter>, <LockBanner>, <SourceSelector>, <ImportWizard>, <ProductTable>, <AiPanel>.


๐Ÿ“… ุงู„ู‚ุณู… 28: ุฎุงุฑุทุฉ ุงู„ุทุฑูŠู‚ (Roadmap)

ุงู„ู…ุฑุญู„ุฉุงู„ู…ุฏุฉุงู„ู…ุฎุฑุฌุงุช
v1.0 MVP8 ุฃุณุงุจูŠุนCore + Salla + 1 source (AliExpress) + Billing + Lock
v1.14 ุฃุณุงุจูŠุน3 ู…ุตุงุฏุฑ ุฅุถุงููŠุฉ + AI ู…ุชูƒุงู…ู„ + Dashboards ูƒุงู…ู„ุฉ
v1.24 ุฃุณุงุจูŠุนMulti-store + Mobile API + Webhooks ูƒุงู…ู„ุฉ
v2.08 ุฃุณุงุจูŠุนMulti-currency + Marketplace ู„ู„ู…ูˆุงุตูุงุช + Plugin SDK
v2.14 ุฃุณุงุจูŠุนWhatsApp/Twitter notifications + Auto-scaling on K8s
v3.0โ€”Mobile App (Flutter) + Public API ู„ู„ู…ุทูˆุฑูŠู†

โœ… ุงู„ู‚ุณู… 29: Definition of Done (DoD)

ู„ุฃูŠ ู…ู‡ู…ุฉ:

  • โ–กย Unit tests โ‰ฅ 80% coverage
  • โ–กย Integration test ู„ู„ู…ุณุงุฑ ุงู„ูƒุงู…ู„
  • โ–กย PHPCS + PHPStan level 6 ูŠู…ุฑู‘ุงู†
  • โ–กย ุงู„ุชูˆุซูŠู‚ ู…ุญุฏู‘ุซ ููŠย /docs
  • โ–กย CHANGELOG.mdย ูŠุฐูƒุฑ ุงู„ุชุบูŠูŠุฑ
  • โ–กย Migration ุขู…ู†ุฉ (rollback ู…ุชุงุญ)
  • โ–กย Audit log ูŠุณุฌู‘ู„ ุงู„ุนู…ู„ูŠุฉ
  • โ–กย ู…ุฑุงุฌุนุฉ ุฃู…ุงู† (checklist)
  • โ–กย i18n strings ู…ุญุถู‘ุฑุฉ
  • โ–กย Performance ู…ู‚ุงุณ

๐Ÿงญ ุงู„ู‚ุณู… 30: ุงู„ุฎุทูˆุฉ ุงู„ุชุงู„ูŠุฉ (Next Action)

ู„ู„ุจุฏุก ููˆุฑู‹ุง:

  1. composer create-projectย ุฃูˆ ุฃู†ุดุฆ ู‡ูŠูƒู„ ุงู„ู…ุฌู„ุฏุงุช.
  2. ุงู†ุณุฎ SQL ู…ู† ุงู„ู‚ุณู… 22 ุฅู„ู‰ย Installer.php.
  3. ู†ูู‘ุฐ ุฎุทุฉ ุงู„ู€60 ุฏู‚ูŠู‚ุฉ (ุงู„ู‚ุณู… 11).
  4. ุดุบู‘ู„ย CheckSubscriptionsย ูŠุฏูˆูŠู‹ุง ู„ุงุฎุชุจุงุฑ ุงู„ู‚ูู„.
  5. ุงุฑุจุท ุฃูˆู„ ู…ุตุฏุฑ (AliExpress) ุนุจุฑ Sandbox.
  6. ุงุฎุชุจุฑ ุฏูˆุฑุฉ: ุชุณุฌูŠู„ โ†’ ุงุดุชุฑุงูƒ โ†’ ุงู†ุชู‡ุงุก โ†’ ู‚ูู„ โ†’ ุชุฌุฏูŠุฏ โ†’ ุฑูุน.

ู‡ู„ ุชุฑูŠุฏ ุฃู† ุฃุจุฏุฃ ุงู„ุขู† ุจูƒุชุงุจุฉ ุงู„ูƒูˆุฏ ุงู„ูุนู„ูŠ ู„ุฃูŠ ู…ู„ู ุจุนูŠู†ู‡ุŸ (ู…ุซู„ู‹ุง: Installer.php ูƒุงู…ู„ู‹ุงุŒ LockManager.phpุŒ Client.phpุŒ ุฃูˆ React Dashboard). ุงุฎุชุฑ ูˆุงุจุฏุฃ.

๐Ÿ—„๏ธ ุงู„ุฌุฒุก 2: ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช

โ€” ุงู„ู…ูˆุงุตูุฉ ุงู„ูƒุงู…ู„ุฉ

Convention:

  • Prefix:ย kosmos_
  • Engine: InnoDB
  • Charset:ย utf8mb4_unicode_ci
  • ูƒู„ ุงู„ุฌุฏุงูˆู„ ู„ู‡ุง:ย id,ย created_at,ย updated_at,ย deleted_atย (soft delete)

๐Ÿ“‹ ุฌุฏูˆู„ 1: kosmos_tenants โ€” ุงู„ุญุณุงุจุงุช ุงู„ุฑุฆูŠุณูŠุฉ

sql

CREATE TABLE kosmos_tenants (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid            CHAR(36) NOT NULL UNIQUE,
    slug            VARCHAR(64) NOT NULL UNIQUE,
    name            VARCHAR(191) NOT NULL,
    owner_user_id   BIGINT UNSIGNED NOT NULL,
    plan_slug       VARCHAR(32) NOT NULL DEFAULT 'trial',
    status          ENUM('active','suspended','cancelled','trial') DEFAULT 'trial',
    
    -- Domain & branding
    subdomain       VARCHAR(64) NOT NULL UNIQUE,
    custom_domain   VARCHAR(191) NULL UNIQUE,
    logo_url        VARCHAR(500) NULL,
    primary_color   VARCHAR(7) DEFAULT '#7c3aed',
    
    -- Salla integration
    salla_store_id      VARCHAR(64) NULL,
    salla_access_token  TEXT NULL,
    salla_refresh_token TEXT NULL,
    salla_token_expires DATETIME NULL,
    salla_webhook_secret VARCHAR(64) NULL,
    
    -- Limits (denormalized for speed)
    max_products    INT DEFAULT 100,
    max_users       INT DEFAULT 3,
    max_sources     INT DEFAULT 2,
    ai_credits      INT DEFAULT 500,
    
    -- Trial & billing
    trial_ends_at   DATETIME NULL,
    current_period_start DATETIME NULL,
    current_period_end   DATETIME NULL,
    
    metadata        JSON NULL,
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at      DATETIME NULL,
    
    INDEX idx_status (status),
    INDEX idx_plan (plan_slug),
    INDEX idx_owner (owner_user_id),
    INDEX idx_trial_end (trial_ends_at),
    INDEX idx_period_end (current_period_end)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 2: kosmos_subscriptions โ€” ุงู„ุงุดุชุฑุงูƒุงุช

sql

CREATE TABLE kosmos_subscriptions (
    id                  BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id           BIGINT UNSIGNED NOT NULL,
    plan_slug           VARCHAR(32) NOT NULL,
    
    -- Billing cycle
    billing_cycle       ENUM('monthly','yearly','lifetime') NOT NULL,
    amount              DECIMAL(10,2) NOT NULL,
    currency            CHAR(3) DEFAULT 'SAR',
    
    -- Status
    status              ENUM('pending','active','past_due','suspended',
                             'cancelled','expired','paused') DEFAULT 'pending',
    
    -- Periods
    started_at          DATETIME NULL,
    current_period_start DATETIME NOT NULL,
    current_period_end   DATETIME NOT NULL,
    cancelled_at        DATETIME NULL,
    paused_at           DATETIME NULL,
    grace_period_ends_at DATETIME NULL, -- 3 ุฃูŠุงู… ุจุนุฏ ุงู„ูุดู„
    
    -- Payment gateway
    gateway             ENUM('stripe','paypal','moyasar','tap','manual') NOT NULL,
    gateway_subscription_id VARCHAR(191) NULL,
    gateway_customer_id     VARCHAR(191) NULL,
    
    -- Dunning
    failed_payment_count    INT DEFAULT 0,
    last_payment_attempt    DATETIME NULL,
    next_retry_at           DATETIME NULL,
    
    metadata            JSON NULL,
    created_at          DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at          DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    INDEX idx_tenant_status (tenant_id, status),
    INDEX idx_period_end (current_period_end),
    INDEX idx_grace (grace_period_ends_at),
    INDEX idx_retry (next_retry_at)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 3: kosmos_entitlements โ€” ุตู„ุงุญูŠุงุช ุงู„ูˆุตูˆู„ (ุงู„ู‚ู„ุจ)

sql

CREATE TABLE kosmos_entitlements (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    feature_key     VARCHAR(64) NOT NULL, -- 'products.sync', 'ai.tutor', 'export.salla'
    
    -- Scope
    scope_type      ENUM('global','source','target','product') DEFAULT 'global',
    scope_id        BIGINT UNSIGNED NULL,
    
    -- Limits
    limit_value     INT NULL,              -- NULL = unlimited
    used_value      INT DEFAULT 0,
    reset_period    ENUM('never','daily','monthly','yearly') DEFAULT 'monthly',
    last_reset_at   DATETIME NULL,
    
    -- Flags
    is_enabled      TINYINT(1) DEFAULT 1,
    expires_at      DATETIME NULL,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    UNIQUE KEY uk_tenant_feature_scope (tenant_id, feature_key, scope_type, scope_id),
    INDEX idx_feature (feature_key),
    INDEX idx_expires (expires_at)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 4: kosmos_plans โ€” ุชุนุฑูŠู ุงู„ุจุงู‚ุงุช

sql

CREATE TABLE kosmos_plans (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    slug            VARCHAR(32) NOT NULL UNIQUE,
    name            VARCHAR(191) NOT NULL,
    description     TEXT NULL,
    
    -- Pricing
    price_monthly   DECIMAL(10,2) NOT NULL,
    price_yearly    DECIMAL(10,2) NOT NULL,
    currency        CHAR(3) DEFAULT 'SAR',
    trial_days      INT DEFAULT 14,
    
    -- Limits (JSON ู„ู„ู‚ุฑุงุกุฉ ุงู„ุณุฑูŠุนุฉ)
    limits          JSON NOT NULL,
    /* ู…ุซุงู„:
    {
        "products": 500,
        "sources": 5,
        "targets": 3,
        "users": 10,
        "ai_credits": 5000,
        "storage_mb": 2048
    }
    */
    
    features        JSON NOT NULL,
    /* ู…ุซุงู„:
    {
        "salla_integration": true,
        "shopify_integration": true,
        "ai_tutor": true,
        "knowledge_graph": true,
        "white_label": false,
        "api_access": false,
        "priority_support": false
    }
    */
    
    is_active       TINYINT(1) DEFAULT 1,
    sort_order      INT DEFAULT 0,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 5: kosmos_sources โ€” ู…ุตุงุฏุฑ ุงู„ู…ุญุชูˆู‰

sql

CREATE TABLE kosmos_sources (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    
    source_type     ENUM('shopify','woocommerce','salla','aliexpress',
                         'amazon','csv','json','api_custom','rss') NOT NULL,
    name            VARCHAR(191) NOT NULL,
    
    -- Credentials (encrypted)
    credentials     TEXT NULL,            -- AES-256 encrypted JSON
    config          JSON NULL,            -- filters, categories, etc.
    
    -- Sync state
    status          ENUM('idle','syncing','error','disabled') DEFAULT 'idle',
    last_sync_at    DATETIME NULL,
    last_error      TEXT NULL,
    sync_frequency  ENUM('manual','hourly','daily','weekly') DEFAULT 'daily',
    next_sync_at    DATETIME NULL,
    
    -- Stats
    total_products  INT DEFAULT 0,
    synced_products INT DEFAULT 0,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at      DATETIME NULL,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    INDEX idx_tenant_type (tenant_id, source_type),
    INDEX idx_next_sync (next_sync_at)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 6: kosmos_targets โ€” ู‚ู†ูˆุงุช ุงู„ุฅุฎุฑุงุฌ (ุณู„ุฉุŒ ุดูˆุจูŠูุงูŠ…)

sql

CREATE TABLE kosmos_targets (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    
    target_type     ENUM('salla','shopify','woocommerce','custom') NOT NULL,
    name            VARCHAR(191) NOT NULL,
    
    -- Credentials
    credentials     TEXT NOT NULL,        -- encrypted
    store_url       VARCHAR(500) NULL,
    store_id        VARCHAR(191) NULL,
    
    -- Sync policy
    auto_publish    TINYINT(1) DEFAULT 0,
    auto_update     TINYINT(1) DEFAULT 1,
    auto_delete     TINYINT(1) DEFAULT 0, -- ุนู†ุฏ ูุดู„ ุงู„ุงุดุชุฑุงูƒ
    
    -- Lock state
    is_locked       TINYINT(1) DEFAULT 0,
    locked_at       DATETIME NULL,
    lock_reason     VARCHAR(64) NULL,     -- 'subscription_expired', 'manual'
    
    -- Stats
    published_count INT DEFAULT 0,
    last_publish_at DATETIME NULL,
    last_error      TEXT NULL,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at      DATETIME NULL,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    INDEX idx_tenant_type (tenant_id, target_type),
    INDEX idx_locked (is_locked)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 7: kosmos_products โ€” ูƒุชุงู„ูˆุฌ ู…ูˆุญู‘ุฏ

sql

CREATE TABLE kosmos_products (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    source_id       BIGINT UNSIGNED NULL,
    
    -- Identity
    external_id     VARCHAR(191) NULL,    -- ID ููŠ ุงู„ู…ุตุฏุฑ
    sku             VARCHAR(100) NULL,
    hash            CHAR(64) NOT NULL,    -- SHA256 ู„ู„ุชุบูŠูŠุฑุงุช
    
    -- Content
    title           VARCHAR(500) NOT NULL,
    description     LONGTEXT NULL,
    short_desc      TEXT NULL,
    
    -- Pricing
    price           DECIMAL(10,2) NULL,
    compare_price   DECIMAL(10,2) NULL,
    cost_price      DECIMAL(10,2) NULL,
    currency        CHAR(3) DEFAULT 'SAR',
    
    -- Inventory
    stock           INT NULL,
    stock_status    ENUM('in_stock','out_of_stock','backorder') DEFAULT 'in_stock',
    
    -- Media
    images          JSON NULL,            -- [{url, alt, order}]
    videos          JSON NULL,
    primary_image   VARCHAR(500) NULL,
    
    -- Categorization
    categories      JSON NULL,            -- [{id, name, path}]
    tags            JSON NULL,
    brand           VARCHAR(191) NULL,
    
    -- Type
    product_type    ENUM('simple','variable','digital','service',
                         'subscription','bundle') DEFAULT 'simple',
    variants        JSON NULL,
    
    -- AI enrichment
    ai_summary      TEXT NULL,
    ai_tags         JSON NULL,
    embedding_id    VARCHAR(64) NULL,     -- Qdrant point ID
    
    -- Sync status
    sync_status     ENUM('pending','synced','outdated','error','locked') DEFAULT 'pending',
    locked_by_subscription TINYINT(1) DEFAULT 0,
    
    metadata        JSON NULL,
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at      DATETIME NULL,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (source_id) REFERENCES kosmos_sources(id) ON DELETE SET NULL,
    UNIQUE KEY uk_tenant_source_external (tenant_id, source_id, external_id),
    INDEX idx_tenant_status (tenant_id, sync_status),
    INDEX idx_hash (hash),
    INDEX idx_locked (locked_by_subscription)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 8: kosmos_publish_log โ€” ุณุฌู„ ุงู„ู†ุดุฑ

sql

CREATE TABLE kosmos_publish_log (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    product_id      BIGINT UNSIGNED NOT NULL,
    target_id       BIGINT UNSIGNED NOT NULL,
    
    action          ENUM('create','update','delete','lock','unlock','skip') NOT NULL,
    status          ENUM('pending','success','failed','retrying') DEFAULT 'pending',
    
    external_id     VARCHAR(191) NULL,    -- ID ููŠ ุงู„ู‡ุฏู
    request_payload JSON NULL,
    response_body   TEXT NULL,
    error_message   TEXT NULL,
    
    attempts        INT DEFAULT 1,
    duration_ms     INT NULL,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    completed_at    DATETIME NULL,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES kosmos_products(id) ON DELETE CASCADE,
    FOREIGN KEY (target_id) REFERENCES kosmos_targets(id) ON DELETE CASCADE,
    INDEX idx_tenant_status (tenant_id, status),
    INDEX idx_target (target_id, created_at),
    INDEX idx_product (product_id, created_at)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 9: kosmos_ai_usage โ€” ุงุณุชู‡ู„ุงูƒ AI

sql

CREATE TABLE kosmos_ai_usage (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    user_id         BIGINT UNSIGNED NULL,
    
    feature         VARCHAR(64) NOT NULL, -- 'tutor', 'enrich', 'translate', 'generate'
    model           VARCHAR(64) NOT NULL,
    provider        ENUM('openai','anthropic','google','local') NOT NULL,
    
    tokens_in       INT UNSIGNED DEFAULT 0,
    tokens_out      INT UNSIGNED DEFAULT 0,
    credits_used    DECIMAL(10,4) DEFAULT 0,
    cost_usd        DECIMAL(10,6) DEFAULT 0,
    
    duration_ms     INT NULL,
    success         TINYINT(1) DEFAULT 1,
    error_message   TEXT NULL,
    
    context_id      VARCHAR(191) NULL,    -- ู„ุฑุจุทู‡ุง ุจู…ู†ุชุฌ ุฃูˆ ู…ุญุงุฏุซุฉ
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    INDEX idx_tenant_date (tenant_id, created_at),
    INDEX idx_feature (feature),
    INDEX idx_user (user_id)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 10: kosmos_audit_log โ€” ุงู„ุฃู…ุงู† ูˆุงู„ุงู…ุชุซุงู„

sql

CREATE TABLE kosmos_audit_log (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NULL,
    user_id         BIGINT UNSIGNED NULL,
    
    action          VARCHAR(64) NOT NULL,
    entity_type     VARCHAR(64) NULL,
    entity_id       BIGINT UNSIGNED NULL,
    
    old_value       JSON NULL,
    new_value       JSON NULL,
    
    ip_address      VARCHAR(45) NULL,
    user_agent      VARCHAR(500) NULL,
    
    severity        ENUM('info','warning','error','critical') DEFAULT 'info',
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_tenant_date (tenant_id, created_at),
    INDEX idx_entity (entity_type, entity_id),
    INDEX idx_severity (severity, created_at)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 11: kosmos_jobs โ€” ุทุงุจูˆุฑ ุงู„ู…ู‡ุงู…

sql

CREATE TABLE kosmos_jobs (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NOT NULL,
    
    job_type        VARCHAR(64) NOT NULL,  -- 'sync_source', 'publish_product', 'bulk_lock'
    payload         JSON NOT NULL,
    
    status          ENUM('queued','running','completed','failed','cancelled') DEFAULT 'queued',
    priority        TINYINT DEFAULT 5,     -- 1 = highest
    
    attempts        INT DEFAULT 0,
    max_attempts    INT DEFAULT 3,
    
    scheduled_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
    started_at      DATETIME NULL,
    completed_at    DATETIME NULL,
    
    result          JSON NULL,
    error_message   TEXT NULL,
    worker_id       VARCHAR(64) NULL,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (tenant_id) REFERENCES kosmos_tenants(id) ON DELETE CASCADE,
    INDEX idx_status_priority (status, priority, scheduled_at),
    INDEX idx_tenant (tenant_id, created_at)
) ENGINE=InnoDB;

๐Ÿ“‹ ุฌุฏูˆู„ 12: kosmos_webhook_events โ€” ุงู„ุฃุญุฏุงุซ ุงู„ูˆุงุฑุฏุฉ

sql

CREATE TABLE kosmos_webhook_events (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       BIGINT UNSIGNED NULL,
    
    source          ENUM('salla','shopify','stripe','paypal','moyasar','internal') NOT NULL,
    event_type      VARCHAR(64) NOT NULL,
    event_id        VARCHAR(191) NULL UNIQUE,
    
    payload         JSON NOT NULL,
    signature       VARCHAR(500) NULL,
    signature_valid TINYINT(1) DEFAULT 0,
    
    processed       TINYINT(1) DEFAULT 0,
    processed_at    DATETIME NULL,
    error_message   TEXT NULL,
    
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_source_event (source, event_type),
    INDEX idx_processed (processed, created_at)
) ENGINE=InnoDB;

๐Ÿ“Š ู…ุฎุทุท ุงู„ุนู„ุงู‚ุงุช (ERD)


๐Ÿงฉ ุงู„ุฌุฒุก 3: ู‡ูŠูƒู„ Plugin ุงู„ูƒุงู…ู„

text

kosmos-studio/
โ”œโ”€โ”€ kosmos-studio.php                    # Main plugin file
โ”œโ”€โ”€ composer.json
โ”œโ”€โ”€ composer.lock
โ”œโ”€โ”€ readme.txt
โ”œโ”€โ”€ uninstall.php
โ”œโ”€โ”€ .env.example
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ Core/
โ”‚   โ”‚   โ”œโ”€โ”€ Plugin.php                   # Bootstrap
โ”‚   โ”‚   โ”œโ”€โ”€ Container.php                # Simple DI container
โ”‚   โ”‚   โ”œโ”€โ”€ Config.php
โ”‚   โ”‚   โ”œโ”€โ”€ Hooks.php                    # Hook loader
โ”‚   โ”‚   โ””โ”€โ”€ Constants.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Database/
โ”‚   โ”‚   โ”œโ”€โ”€ Migrator.php
โ”‚   โ”‚   โ”œโ”€โ”€ Schema.php
โ”‚   โ”‚   โ”œโ”€โ”€ QueryBuilder.php
โ”‚   โ”‚   โ””โ”€โ”€ Migrations/
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_001_CreateTenants.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_002_CreateSubscriptions.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_003_CreateEntitlements.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_004_CreatePlans.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_005_CreateSources.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_006_CreateTargets.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_007_CreateProducts.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_008_CreatePublishLog.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_009_CreateAiUsage.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_010_CreateAuditLog.php
โ”‚   โ”‚       โ”œโ”€โ”€ Migration_011_CreateJobs.php
โ”‚   โ”‚       โ””โ”€โ”€ Migration_012_CreateWebhookEvents.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Domain/
โ”‚   โ”‚   โ”œโ”€โ”€ Tenant/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Tenant.php               # Entity
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TenantRepository.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TenantService.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ TenantProvisioner.php    # ุฅู†ุดุงุก tenant ุฌุฏูŠุฏ
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ Subscription/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Subscription.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SubscriptionRepository.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SubscriptionService.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Plan.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PlanRepository.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ DunningManager.php       # ุฅุฏุงุฑุฉ ูุดู„ ุงู„ุฏูุน
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ Entitlement/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Entitlement.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ EntitlementRepository.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ EntitlementService.php   # can(), consume(), grant()
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Feature.php              # Enum-like
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ Product/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Product.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ProductRepository.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ProductService.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ProductLockManager.php   # ๐Ÿ”’ ู‚ูู„ ุงู„ู…ู†ุชุฌุงุช
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ Source/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Source.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SourceRepository.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Adapters/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ SourceAdapterInterface.php
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ ShopifyAdapter.php
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ WooCommerceAdapter.php
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ SallaAdapter.php
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ AliExpressAdapter.php
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ CsvAdapter.php
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ CustomApiAdapter.php
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ””โ”€โ”€ Target/
โ”‚   โ”‚       โ”œโ”€โ”€ Target.php
โ”‚   โ”‚       โ”œโ”€โ”€ TargetRepository.php
โ”‚   โ”‚       โ””โ”€โ”€ Publishers/
โ”‚   โ”‚           โ”œโ”€โ”€ TargetPublisherInterface.php
โ”‚   โ”‚           โ”œโ”€โ”€ SallaPublisher.php   # โ† ุงู„ุฃู‡ู…
โ”‚   โ”‚           โ”œโ”€โ”€ ShopifyPublisher.php
โ”‚   โ”‚           โ””โ”€โ”€ WooCommercePublisher.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Application/
โ”‚   โ”‚   โ”œโ”€โ”€ Bridge/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SyncOrchestrator.php     # ูŠุฏูŠุฑ ุนู…ู„ูŠุฉ ุงู„ุณุญุจ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PublishOrchestrator.php  # ูŠุฏูŠุฑ ุนู…ู„ูŠุฉ ุงู„ุฏูุน
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ LockOrchestrator.php     # ๐Ÿ”’ ูŠุฏูŠุฑ ุงู„ู‚ูู„
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ AI/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ AIGateway.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Providers/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ OpenAIProvider.php
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ AnthropicProvider.php
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ LocalProvider.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Features/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ProductEnricher.php
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TutorAgent.php
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Translator.php
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ DescriptionGenerator.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ MeteringService.php
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ””โ”€โ”€ Jobs/
โ”‚   โ”‚       โ”œโ”€โ”€ JobInterface.php
โ”‚   โ”‚       โ”œโ”€โ”€ JobQueue.php
โ”‚   โ”‚       โ”œโ”€โ”€ Worker.php
โ”‚   โ”‚       โ””โ”€โ”€ Handlers/
โ”‚   โ”‚           โ”œโ”€โ”€ SyncSourceJob.php
โ”‚   โ”‚           โ”œโ”€โ”€ PublishProductJob.php
โ”‚   โ”‚           โ”œโ”€โ”€ BulkLockJob.php
โ”‚   โ”‚           โ””โ”€โ”€ RetryPaymentJob.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Integrations/
โ”‚   โ”‚   โ”œโ”€โ”€ Salla/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SallaClient.php          # HTTP Client
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SallaOAuth.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SallaWebhooks.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ SallaMapper.php          # Product โ†’ Salla format
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ Shopify/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ShopifyClient.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ShopifyMapper.php
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ””โ”€โ”€ Payments/
โ”‚   โ”‚       โ”œโ”€โ”€ StripeGateway.php
โ”‚   โ”‚       โ”œโ”€โ”€ MoyasarGateway.php
โ”‚   โ”‚       โ”œโ”€โ”€ TapGateway.php
โ”‚   โ”‚       โ””โ”€โ”€ PayPalGateway.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Http/
โ”‚   โ”‚   โ”œโ”€โ”€ Router.php
โ”‚   โ”‚   โ”œโ”€โ”€ Middleware/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ AuthMiddleware.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TenantMiddleware.php     # โ† ูŠุญุฏุฏ ุงู„ู€tenant ุงู„ุญุงู„ูŠ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ EntitlementMiddleware.php# โ† ูŠู…ู†ุน ุงู„ูˆุตูˆู„ ุจุฏูˆู† ุตู„ุงุญูŠุฉ
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ RateLimitMiddleware.php
โ”‚   โ”‚   โ””โ”€โ”€ Controllers/
โ”‚   โ”‚       โ”œโ”€โ”€ AuthController.php
โ”‚   โ”‚       โ”œโ”€โ”€ TenantController.php
โ”‚   โ”‚       โ”œโ”€โ”€ SubscriptionController.php
โ”‚   โ”‚       โ”œโ”€โ”€ SourceController.php
โ”‚   โ”‚       โ”œโ”€โ”€ TargetController.php
โ”‚   โ”‚       โ”œโ”€โ”€ ProductController.php
โ”‚   โ”‚       โ”œโ”€โ”€ AiController.php
โ”‚   โ”‚       โ””โ”€โ”€ WebhookController.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Admin/
โ”‚   โ”‚   โ”œโ”€โ”€ Menu.php
โ”‚   โ”‚   โ”œโ”€โ”€ Dashboard.php
โ”‚   โ”‚   โ”œโ”€โ”€ Pages/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TenantsPage.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PlansPage.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ SourcesPage.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TargetsPage.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ProductsPage.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ SettingsPage.php
โ”‚   โ”‚   โ””โ”€โ”€ Views/
โ”‚   โ”‚       โ”œโ”€โ”€ dashboard.php
โ”‚   โ”‚       โ”œโ”€โ”€ sources-list.php
โ”‚   โ”‚       โ”œโ”€โ”€ products-grid.php
โ”‚   โ”‚       โ””โ”€โ”€ lock-status.php
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Frontend/
โ”‚   โ”‚   โ”œโ”€โ”€ Shortcodes/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PricingShortcode.php
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ DashboardShortcode.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ TutorShortcode.php
โ”‚   โ”‚   โ”œโ”€โ”€ Blocks/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PricingBlock.php
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ProductsBlock.php
โ”‚   โ”‚   โ””โ”€โ”€ Assets/
โ”‚   โ”‚       โ”œโ”€โ”€ css/
โ”‚   โ”‚       โ””โ”€โ”€ js/
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ Support/
โ”‚   โ”‚   โ”œโ”€โ”€ Logger.php
โ”‚   โ”‚   โ”œโ”€โ”€ Encrypter.php               # AES-256 for credentials
โ”‚   โ”‚   โ”œโ”€โ”€ Validator.php
โ”‚   โ”‚   โ”œโ”€โ”€ Str.php
โ”‚   โ”‚   โ”œโ”€โ”€ Arr.php
โ”‚   โ”‚   โ””โ”€โ”€ Http.php
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ Exceptions/
โ”‚       โ”œโ”€โ”€ EntitlementException.php
โ”‚       โ”œโ”€โ”€ SubscriptionExpiredException.php
โ”‚       โ”œโ”€โ”€ SourceException.php
โ”‚       โ””โ”€โ”€ TargetException.php
โ”‚
โ”œโ”€โ”€ config/
โ”‚   โ”œโ”€โ”€ plans.php                        # ุชุนุฑูŠู ุงู„ุจุงู‚ุงุช
โ”‚   โ”œโ”€โ”€ features.php                     # ู‚ุงุฆู…ุฉ ุงู„ู…ูŠุฒุงุช
โ”‚   โ””โ”€โ”€ ai-models.php
โ”‚
โ”œโ”€โ”€ database/
โ”‚   โ””โ”€โ”€ seeds/
โ”‚       โ”œโ”€โ”€ PlansSeeder.php
โ”‚       โ””โ”€โ”€ FeaturesSeeder.php
โ”‚
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ Unit/
โ”‚   โ”œโ”€โ”€ Integration/
โ”‚   โ””โ”€โ”€ E2E/
โ”‚
โ”œโ”€โ”€ assets/
โ”‚   โ”œโ”€โ”€ admin/
โ”‚   โ””โ”€โ”€ public/
โ”‚
โ””โ”€โ”€ languages/
    โ”œโ”€โ”€ kosmos-ar.po
    โ””โ”€โ”€ kosmos-en_US.po

๐Ÿ’ป ุงู„ุฌุฒุก 4: PHP Namespaces & Classes

Convention:

text

KOSMOS\Studio\<Layer>\<Domain>\<Class>

ุงู„ู…ู„ู ุงู„ุฑุฆูŠุณูŠ:

php

<?php
/**
 * Plugin Name:  KOSMOS Studio
 * Description:  Subscription-as-a-Service Knowledge & Commerce Bridge
 * Version:      1.0.0
 * Author:       KOSMOS Team
 * Text Domain:  kosmos
 * Requires PHP: 8.2
 * Requires at least: 6.4
 */

declare(strict_types=1);

namespace KOSMOS\Studio;

if (!defined('ABSPATH')) exit;

// Autoload
require_once __DIR__ . '/vendor/autoload.php';

// Constants
define('KOSMOS_VERSION', '1.0.0');
define('KOSMOS_FILE', __FILE__);
define('KOSMOS_PATH', plugin_dir_path(__FILE__));
define('KOSMOS_URL', plugin_dir_url(__FILE__));
define('KOSMOS_DB_VERSION', '1.0.0');

// Boot
Core\Plugin::instance()->boot();

Core\Plugin โ€” ู†ู‚ุทุฉ ุงู„ุฏุฎูˆู„:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Core;

use KOSMOS\Studio\Database\Migrator;
use KOSMOS\Studio\Admin\Menu;
use KOSMOS\Studio\Http\Router;
use KOSMOS\Studio\Application\Jobs\Worker;

final class Plugin {
    private static ?self $instance = null;
    private Container $container;
    
    public static function instance(): self {
        return self::$instance ??= new self();
    }
    
    private function __construct() {
        $this->container = new Container();
    }
    
    public function boot(): void {
        // 1. Container bindings
        $this->registerBindings();
        
        // 2. Activation/Deactivation
        register_activation_hook(KOSMOS_FILE, [$this, 'onActivate']);
        register_deactivation_hook(KOSMOS_FILE, [$this, 'onDeactivate']);
        
        // 3. Hooks
        add_action('plugins_loaded', [$this, 'onPluginsLoaded']);
        add_action('init', [$this, 'onInit']);
        add_action('rest_api_init', [$this, 'registerRestRoutes']);
        
        // 4. Workers (background)
        if (defined('KOSMOS_ENABLE_WORKER') && KOSMOS_ENABLE_WORKER) {
            add_action('kosmos_worker_tick', [Worker::class, 'tick']);
        }
    }
    
    private function registerBindings(): void {
        // Core services
        $this->container->singleton(Config::class);
        $this->container->singleton(Hooks::class);
        
        // Domain services
        $this->container->singleton(\KOSMOS\Studio\Domain\Tenant\TenantService::class);
        $this->container->singleton(\KOSMOS\Studio\Domain\Subscription\SubscriptionService::class);
        $this->container->singleton(\KOSMOS\Studio\Domain\Entitlement\EntitlementService::class);
        $this->container->singleton(\KOSMOS\Studio\Domain\Product\ProductLockManager::class);
        
        // Application services
        $this->container->singleton(\KOSMOS\Studio\Application\Bridge\SyncOrchestrator::class);
        $this->container->singleton(\KOSMOS\Studio\Application\Bridge\PublishOrchestrator::class);
        $this->container->singleton(\KOSMOS\Studio\Application\Bridge\LockOrchestrator::class);
        $this->container->singleton(\KOSMOS\Studio\Application\AI\AIGateway::class);
    }
    
    public function onActivate(): void {
        // Run migrations
        (new Migrator())->run();
        
        // Seed plans
        (new \KOSMOS\Studio\Database\Seeds\PlansSeeder())->run();
        
        // Schedule cron
        if (!wp_next_scheduled('kosmos_worker_tick')) {
            wp_schedule_event(time(), 'every_minute', 'kosmos_worker_tick');
        }
        
        // Flush rewrite rules
        flush_rewrite_rules();
        
        // Set DB version
        update_option('kosmos_db_version', KOSMOS_DB_VERSION);
    }
    
    public function onDeactivate(): void {
        wp_clear_scheduled_hook('kosmos_worker_tick');
        flush_rewrite_rules();
    }
    
    public function onPluginsLoaded(): void {
        // Check if WooCommerce is active
        if (!class_exists('WooCommerce')) {
            add_action('admin_notices', function () {
                echo '<div class="error"><p>';
                echo 'KOSMOS Studio requires WooCommerce to be installed.';
                echo '</p></div>';
            });
            return;
        }
        
        // Load translations
        load_plugin_textdomain('kosmos', false, dirname(plugin_basename(KOSMOS_FILE)) . '/languages');
    }
    
    public function onInit(): void {
        // Register post types, taxonomies if needed
        // Register shortcodes
        add_shortcode('kosmos_pricing', [\KOSMOS\Studio\Frontend\Shortcodes\PricingShortcode::class, 'render']);
        add_shortcode('kosmos_dashboard', [\KOSMOS\Studio\Frontend\Shortcodes\DashboardShortcode::class, 'render']);
        add_shortcode('kosmos_tutor', [\KOSMOS\Studio\Frontend\Shortcodes\TutorShortcode::class, 'render']);
    }
    
    public function registerRestRoutes(): void {
        (new Router())->register();
    }
    
    public function container(): Container {
        return $this->container;
    }
}

Domain\Entitlement\EntitlementService โ€” ุฃู‡ู… class:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Domain\Entitlement;

use KOSMOS\Studio\Domain\Tenant\Tenant;
use KOSMOS\Studio\Exceptions\EntitlementException;

/**
 * ู†ู‚ุทุฉ ุงู„ู‚ุฑุงุฑ ุงู„ู…ุฑูƒุฒูŠุฉ โ€” ูƒู„ ุดูŠุก ูŠู…ุฑ ู…ู† ู‡ู†ุง
 */
final class EntitlementService {
    
    public function __construct(
        private EntitlementRepository $repo
    ) {}
    
    /**
     * ู‡ู„ ูŠู…ูƒู† ู„ู„ู€tenant ุงุณุชุฎุฏุงู… ุงู„ู…ูŠุฒุฉุŸ
     */
    public function can(int $tenantId, string $featureKey, ?int $scopeId = null): bool {
        $entitlement = $this->repo->find($tenantId, $featureKey, $scopeId);
        
        if (!$entitlement) return false;
        if (!$entitlement->isEnabled()) return false;
        if ($entitlement->isExpired()) return false;
        if ($entitlement->isExhausted()) return false;
        
        return true;
    }
    
    /**
     * ุงุณุชู‡ู„ูƒ ู…ู† ุงู„ุญุตุฉ
     */
    public function consume(int $tenantId, string $featureKey, int $amount = 1): bool {
        if (!$this->can($tenantId, $featureKey)) {
            throw new EntitlementException(
                "Entitlement exhausted: {$featureKey}"
            );
        }
        
        return $this->repo->increment($tenantId, $featureKey, $amount);
    }
    
    /**
     * ุงู…ู†ุญ/ุฌุฏู‘ุฏ
     */
    public function grant(int $tenantId, string $featureKey, int $limit, ?int $scopeId = null): void {
        $this->repo->upsert($tenantId, $featureKey, [
            'limit_value' => $limit,
            'used_value'  => 0,
            'is_enabled'  => 1,
            'last_reset_at' => current_time('mysql'),
        ], $scopeId);
    }
    
    /**
     * ุนุฒู‘ู„ ูƒู„ entitlements ุนู†ุฏ ุฅู„ุบุงุก ุงู„ุงุดุชุฑุงูƒ
     */
    public function revokeAll(int $tenantId): void {
        $this->repo->disableAll($tenantId);
    }
    
    /**
     * ุจุทู‘ู„ ุงู„ู…ูŠุฒุงุช ุงู„ู…ุฑุชุจุทุฉ ุจู…ุตุฏุฑ ู…ุนูŠู†
     */
    public function revokeForSource(int $tenantId, int $sourceId): void {
        $this->repo->disableByScope($tenantId, 'source', $sourceId);
    }
}

Domain\Product\ProductLockManager โ€” ๐Ÿ”’ ู‚ูู„ ุงู„ู…ู†ุชุฌุงุช:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Domain\Product;

/**
 * ุงู„ู…ุณุคูˆู„ ุนู† ู‚ูู„/ูุชุญ ุงู„ู…ู†ุชุฌุงุช ุนู†ุฏ ุงู„ุงุดุชุฑุงูƒ/ุงู„ุฅู„ุบุงุก
 */
final class ProductLockManager {
    
    public function __construct(
        private ProductRepository $products,
        private \KOSMOS\Studio\Integrations\Salla\SallaClient $salla,
        private \KOSMOS\Studio\Support\Logger $logger
    ) {}
    
    /**
     * ุงู‚ูู„ ูƒู„ ู…ู†ุชุฌุงุช ุงู„ู€tenant
     */
    public function lockAll(int $tenantId, string $reason = 'subscription_expired'): int {
        $this->logger->warning("Locking all products for tenant {$tenantId}: {$reason}");
        
        // 1. ุนู„ู‘ู… ููŠ DB
        $locked = $this->products->lockAll($tenantId);
        
        // 2. ุฃุถู job ู„ุฅุฎูุงุก ุงู„ู…ู†ุชุฌุงุช ู…ู† ุณู„ุฉ
        foreach ($this->products->getTargetsByTenant($tenantId) as $target) {
            \KOSMOS\Studio\Application\Jobs\JobQueue::push(
                $tenantId,
                'bulk_lock',
                [
                    'target_id' => $target['id'],
                    'action'    => 'hide',
                    'reason'    => $reason,
                ],
                priority: 1
            );
        }
        
        // 3. ุณุฌู‘ู„ ุงู„ุญุฏุซ
        do_action('kosmos/products_locked', $tenantId, $locked, $reason);
        
        return $locked;
    }
    
    /**
     * ุงูุชุญ ุงู„ู…ู†ุชุฌุงุช ุนู†ุฏ ุชุฌุฏูŠุฏ ุงู„ุงุดุชุฑุงูƒ
     */
    public function unlockAll(int $tenantId): int {
        $this->logger->info("Unlocking all products for tenant {$tenantId}");
        
        $unlocked = $this->products->unlockAll($tenantId);
        
        foreach ($this->products->getTargetsByTenant($tenantId) as $target) {
            \KOSMOS\Studio\Application\Jobs\JobQueue::push(
                $tenantId,
                'bulk_lock',
                [
                    'target_id' => $target['id'],
                    'action'    => 'show',
                ],
                priority: 1
            );
        }
        
        do_action('kosmos/products_unlocked', $tenantId, $unlocked);
        
        return $unlocked;
    }
    
    /**
     * ุงู‚ูู„ ู…ุตุฏุฑุงู‹ ู…ุนูŠู†ุงู‹ (ุนู†ุฏ ุญุฐู/ุชุนุทูŠู„ ู…ุตุฏุฑ)
     */
    public function lockSource(int $tenantId, int $sourceId): int {
        return $this->products->lockBySource($tenantId, $sourceId);
    }
}

Application\Bridge\LockOrchestrator โ€” ุงู„ู…ู†ุณู‚:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Application\Bridge;

use KOSMOS\Studio\Domain\Product\ProductLockManager;
use KOSMOS\Studio\Domain\Entitlement\EntitlementService;
use KOSMOS\Studio\Domain\Target\TargetRepository;

/**
 * ูŠู†ุณู‘ู‚ ุนู…ู„ูŠุฉ ุงู„ู‚ูู„ ุงู„ูƒุงู…ู„ุฉ ุนู†ุฏ ุงู†ุชู‡ุงุก ุงู„ุงุดุชุฑุงูƒ
 */
final class LockOrchestrator {
    
    public function __construct(
        private ProductLockManager $lockManager,
        private EntitlementService $entitlements,
        private TargetRepository $targets
    ) {}
    
    /**
     * ูŠูุณุชุฏุนู‰ ู…ู† cron ุฃูˆ webhook ุนู†ุฏ ุงู†ุชู‡ุงุก ุงู„ุงุดุชุฑุงูƒ
     */
    public function handleSubscriptionExpired(int $tenantId): void {
        // 1. ุนุฒู‘ู„ ูƒู„ ุงู„ู€entitlements
        $this->entitlements->revokeAll($tenantId);
        
        // 2. ุงู‚ูู„ ูƒู„ ุงู„ู…ู†ุชุฌุงุช
        $count = $this->lockManager->lockAll($tenantId, 'subscription_expired');
        
        // 3. ุงู‚ูู„ ูƒู„ ุงู„ู€targets
        $this->targets->lockAll($tenantId, 'subscription_expired');
        
        // 4. ุณุฌู‘ู„
        do_action('kosmos/subscription_expired_handled', $tenantId, $count);
    }
    
    /**
     * ุนู†ุฏ ุชุฌุฏูŠุฏ ุงู„ุงุดุชุฑุงูƒ โ€” ุงูุชุญ ูƒู„ ุดูŠุก
     */
    public function handleSubscriptionRenewed(int $tenantId): void {
        // 1. ุฌุฏู‘ุฏ ุงู„ู€entitlements ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ุจุงู‚ุฉ
        $this->entitlements->grantFromPlan($tenantId);
        
        // 2. ุงูุชุญ ุงู„ู…ู†ุชุฌุงุช
        $this->lockManager->unlockAll($tenantId);
        
        // 3. ุงูุชุญ ุงู„ู€targets
        $this->targets->unlockAll($tenantId);
        
        do_action('kosmos/subscription_renewed_handled', $tenantId);
    }
}

Integrations\Salla\SallaPublisher โ€” ุงู„ู†ุดุฑ ููŠ ุณู„ุฉ:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Integrations\Salla;

use KOSMOS\Studio\Domain\Product\Product;
use KOSMOS\Studio\Domain\Target\Target;

/**
 * ู…ุณุคูˆู„ ุนู† ู†ุดุฑ/ุฅุฎูุงุก/ุชุญุฏูŠุซ ุงู„ู…ู†ุชุฌุงุช ููŠ ู…ุชุฌุฑ ุณู„ุฉ
 */
final class SallaPublisher {
    
    public function __construct(
        private SallaClient $client,
        private SallaMapper $mapper
    ) {}
    
    /**
     * ุงู†ุดุฑ ู…ู†ุชุฌุงู‹ ุฌุฏูŠุฏุงู‹ ููŠ ุณู„ุฉ
     */
    public function publish(Target $target, Product $product): array {
        $payload = $this->mapper->toSalla($product);
        
        $response = $this->client
            ->forTarget($target)
            ->post('/products', $payload);
        
        if ($response['success'] ?? false) {
            return [
                'success'     => true,
                'external_id' => $response['data']['id'] ?? null,
            ];
        }
        
        return ['success' => false, 'error' => $response['message'] ?? 'unknown'];
    }
    
    /**
     * ุญุฏู‘ุซ ู…ู†ุชุฌุงู‹ ู…ูˆุฌูˆุฏุงู‹
     */
    public function update(Target $target, Product $product, string $externalId): array {
        $payload = $this->mapper->toSalla($product);
        
        $response = $this->client
            ->forTarget($target)
            ->put("/products/{$externalId}", $payload);
        
        return [
            'success' => $response['success'] ?? false,
            'error'   => $response['message'] ?? null,
        ];
    }
    
    /**
     * ๐Ÿ”’ ุฃุฎูู ู…ู†ุชุฌุงู‹ (ุนู†ุฏ ุงู†ุชู‡ุงุก ุงู„ุงุดุชุฑุงูƒ)
     */
    public function hide(Target $target, string $externalId): bool {
        $response = $this->client
            ->forTarget($target)
            ->put("/products/{$externalId}", [
                'status'     => 'hidden',
                'visibility' => 'hidden',
            ]);
        
        return $response['success'] ?? false;
    }
    
    /**
     * ุฃุธู‡ุฑ ู…ู†ุชุฌุงู‹ (ุนู†ุฏ ุงู„ุชุฌุฏูŠุฏ)
     */
    public function show(Target $target, string $externalId): bool {
        $response = $this->client
            ->forTarget($target)
            ->put("/products/{$externalId}", [
                'status'     => 'sale',
                'visibility' => 'visible',
            ]);
        
        return $response['success'] ?? false;
    }
    
    /**
     * ุงุญุฐู ู…ู†ุชุฌุงู‹
     */
    public function delete(Target $target, string $externalId): bool {
        $response = $this->client
            ->forTarget($target)
            ->delete("/products/{$externalId}");
        
        return $response['success'] ?? false;
    }
    
    /**
     * ๐Ÿ”’ ุงู‚ูู„ ุชุตู†ูŠูุงู‹ ูƒุงู…ู„ุงู‹ (categories hidden)
     */
    public function hideCategory(Target $target, string $categoryId): bool {
        $response = $this->client
            ->forTarget($target)
            ->put("/categories/{$categoryId}", [
                'status' => 'hidden',
            ]);
        
        return $response['success'] ?? false;
    }
    
    /**
     * ุงุฌู„ุจ ุงู„ู…ู†ุชุฌุงุช ู…ู† ุณู„ุฉ (ู„ู„ู€sync ุงู„ุนูƒุณูŠ)
     */
    public function listProducts(Target $target, int $page = 1): array {
        return $this->client
            ->forTarget($target)
            ->get('/products', ['page' => $page, 'per_page' => 50]);
    }
}

Integrations\Salla\SallaClient:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Integrations\Salla;

use KOSMOS\Studio\Domain\Target\Target;
use KOSMOS\Studio\Support\Http;

final class SallaClient {
    
    private const BASE_URL = 'https://api.salla.dev/admin/v2';
    
    private ?Target $target = null;
    
    public function forTarget(Target $target): self {
        $clone = clone $this;
        $clone->target = $target;
        return $clone;
    }
    
    public function get(string $path, array $query = []): array {
        return $this->request('GET', $path, $query);
    }
    
    public function post(string $path, array $body = []): array {
        return $this->request('POST', $path, [], $body);
    }
    
    public function put(string $path, array $body = []): array {
        return $this->request('PUT', $path, [], $body);
    }
    
    public function delete(string $path): array {
        return $this->request('DELETE', $path);
    }
    
    private function request(string $method, string $path, array $query = [], array $body = []): array {
        if (!$this->target) {
            throw new \RuntimeException('No target set. Use forTarget() first.');
        }
        
        $creds = json_decode(
            \KOSMOS\Studio\Support\Encrypter::decrypt($this->target->credentials),
            true
        );
        
        $token = $creds['access_token'] ?? '';
        
        // Handle token refresh if needed
        if ($this->isTokenExpired($creds)) {
            $token = $this->refreshToken($this->target, $creds);
        }
        
        $url = self::BASE_URL . $path;
        if ($query) $url .= '?' . http_build_query($query);
        
        $response = Http::request($method, $url, [
            'headers' => [
                'Authorization' => "Bearer {$token}",
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
            ],
            'body' => $body ? wp_json_encode($body) : null,
            'timeout' => 30,
        ]);
        
        // Log for audit
        \KOSMOS\Studio\Support\Logger::debug("Salla API {$method} {$path}", [
            'status' => $response['status'] ?? null,
        ]);
        
        return $response;
    }
    
    private function isTokenExpired(array $creds): bool {
        return isset($creds['expires_at']) 
            && strtotime($creds['expires_at']) < time() + 300;
    }
    
    private function refreshToken(Target $target, array $creds): string {
        $response = Http::post('https://accounts.salla.sa/oauth2/token', [
            'grant_type'    => 'refresh_token',
            'refresh_token' => $creds['refresh_token'],
            'client_id'     => get_option('kosmos_salla_client_id'),
            'client_secret' => get_option('kosmos_salla_client_secret'),
        ]);
        
        $newCreds = array_merge($creds, [
            'access_token'  => $response['access_token'],
            'refresh_token' => $response['refresh_token'] ?? $creds['refresh_token'],
            'expires_at'    => date('c', time() + ($response['expires_in'] ?? 3600)),
        ]);
        
        $target->updateCredentials(\KOSMOS\Studio\Support\Encrypter::encrypt(
            wp_json_encode($newCreds)
        ));
        
        return $newCreds['access_token'];
    }
}

๐ŸŒ ุงู„ุฌุฒุก 5: REST API Endpoints ุงู„ูƒุงู…ู„ุฉ

Base URL: /wp-json/kosmos/v1/

Convention:

  • Auth:ย Authorization: Bearer <jwt>ย ุฃูˆ WP Nonce
  • Tenant: ูŠูุญุฏุฏ ุชู„ู‚ุงุฆูŠุงู‹ ู…ู† ุงู„ู€user
  • Rate limit: 100 req/min/tenant
  • Response: JSON ู…ูˆุญุฏ

Response Envelope:

json

{
  "success": true,
  "data": { ... },
  "meta": { "page": 1, "total": 100 }
}

json

{
  "success": false,
  "error": {
    "code": "entitlement_exhausted",
    "message": "Your plan has reached the limit for products",
    "details": { "feature": "products.sync", "limit": 500, "used": 500 }
  }
}

๐Ÿ“‹ ุฌุฏูˆู„ Endpoints ุงู„ูƒุงู…ู„:

MethodEndpointDescriptionAuthEntitlement
Auth
POST/auth/registerุชุณุฌูŠู„ tenant ุฌุฏูŠุฏโŒโ€”
POST/auth/loginุชุณุฌูŠู„ ุฏุฎูˆู„โŒโ€”
POST/auth/refreshุชุญุฏูŠุซ JWTโœ…โ€”
GET/auth/meู…ุนู„ูˆู…ุงุช ุงู„ู…ุณุชุฎุฏู…โœ…โ€”
Tenants
GET/tenantุจูŠุงู†ุงุช ุงู„ู€tenant ุงู„ุญุงู„ูŠโœ…โ€”
PATCH/tenantุชุญุฏูŠุซ ุงู„ุจูŠุงู†ุงุชโœ…โ€”
GET/tenant/usageุงุณุชู‡ู„ุงูƒ ุงู„ุญุตุตโœ…โ€”
GET/tenant/limitsุงู„ุญุฏูˆุฏ ุงู„ุญุงู„ูŠุฉโœ…โ€”
Subscriptions
GET/plansู‚ุงุฆู…ุฉ ุงู„ุจุงู‚ุงุชโŒโ€”
GET/subscriptionุงุดุชุฑุงูƒูŠ ุงู„ุญุงู„ูŠโœ…โ€”
POST/subscription/checkoutุฅู†ุดุงุก ุฌู„ุณุฉ ุฏูุนโœ…โ€”
POST/subscription/cancelุฅู„ุบุงุกโœ…โ€”
POST/subscription/resumeุงุณุชุฆู†ุงูโœ…โ€”
POST/subscription/upgradeุชุฑู‚ูŠุฉโœ…โ€”
Sources
GET/sourcesู‚ุงุฆู…ุฉ ุงู„ู…ุตุงุฏุฑโœ…sources.list
POST/sourcesุฅุถุงูุฉ ู…ุตุฏุฑโœ…sources.create
GET/sources/{id}ุชูุงุตูŠู„ ู…ุตุฏุฑโœ…sources.view
PATCH/sources/{id}ุชุญุฏูŠุซโœ…sources.update
DELETE/sources/{id}ุญุฐูโœ…sources.delete
POST/sources/{id}/testุงุฎุชุจุงุฑ ุงู„ุงุชุตุงู„โœ…sources.test
POST/sources/{id}/syncุจุฏุก sync ูŠุฏูˆูŠโœ…sources.sync
GET/sources/{id}/statusุญุงู„ุฉ ุงู„ู€syncโœ…sources.view
Targets
GET/targetsู‚ุงุฆู…ุฉ ุงู„ุฃู‡ุฏุงูโœ…targets.list
POST/targetsุฅุถุงูุฉ ู‡ุฏู (ุณู„ุฉ)โœ…targets.create
POST/targets/salla/oauthOAuth ู…ุน ุณู„ุฉโœ…targets.create
GET/targets/{id}ุชูุงุตูŠู„โœ…targets.view
DELETE/targets/{id}ุญุฐูโœ…targets.delete
POST/targets/{id}/testุงุฎุชุจุงุฑโœ…targets.test
POST/targets/{id}/publish-allู†ุดุฑ ุงู„ูƒู„โœ…targets.publish
Products
GET/productsู‚ุงุฆู…ุฉ ุงู„ู…ู†ุชุฌุงุชโœ…products.list
GET/products/{id}ุชูุงุตูŠู„โœ…products.view
POST/productsุฅู†ุดุงุก ูŠุฏูˆูŠโœ…products.create
PATCH/products/{id}ุชุญุฏูŠุซโœ…products.update
DELETE/products/{id}ุญุฐูโœ…products.delete
POST/products/{id}/publishู†ุดุฑ ู„ู…ู†ุตุฉโœ…products.publish
POST/products/bulk-publishู†ุดุฑ ุฌู…ุงุนูŠโœ…products.bulk_publish
GET/products/{id}/lock-status๐Ÿ”’ ุญุงู„ุฉ ุงู„ู‚ูู„โœ…products.view
AI
POST/ai/tutorู…ุญุงุฏุซุฉ AIโœ…ai.tutor
POST/ai/enrichุชุญุณูŠู† ู…ู†ุชุฌโœ…ai.enrich
POST/ai/translateุชุฑุฌู…ุฉโœ…ai.translate
POST/ai/generate-descriptionุชูˆู„ูŠุฏ ูˆุตูโœ…ai.generate
GET/ai/usageุงุณุชู‡ู„ุงูƒ AIโœ…โ€”
Webhooks
POST/webhooks/sallawebhooks ู…ู† ุณู„ุฉsignatureโ€”
POST/webhooks/stripewebhooks ู…ู† Stripesignatureโ€”
POST/webhooks/moyasarwebhooks ู…ู† Moyasarsignatureโ€”
Admin (requires admin)
GET/admin/tenantsูƒู„ ุงู„ู€tenantsโœ… adminโ€”
GET/admin/jobsุญุงู„ุฉ ุงู„ุทูˆุงุจูŠุฑโœ… adminโ€”
POST/admin/jobs/{id}/retryุฅุนุงุฏุฉ ู…ุญุงูˆู„ุฉโœ… adminโ€”
GET/admin/auditุณุฌู„ ุงู„ุชุฏู‚ูŠู‚โœ… adminโ€”
Health
GET/healthุตุญุฉ ุงู„ุฎุฏู…ุฉโŒโ€”
GET/metricsู…ู‚ุงูŠูŠุณ PrometheusโŒโ€”

ู…ุซุงู„ ุชุทุจูŠู‚ูŠ โ€” Sync Endpoint:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Http\Controllers;

use KOSMOS\Studio\Application\Jobs\JobQueue;
use KOSMOS\Studio\Domain\Entitlement\EntitlementService;

final class SourceController {
    
    public function __construct(
        private \KOSMOS\Studio\Domain\Source\SourceService $sources,
        private EntitlementService $entitlements
    ) {}
    
    /**
     * POST /sources/{id}/sync
     */
    public function sync(\WP_REST_Request $req): \WP_REST_Response {
        $tenantId = get_current_user_id(); // simplified
        $sourceId = (int) $req->get_param('id');
        
        // 1. ุชุญู‚ู‚ ู…ู† ุงู„ุงุดุชุฑุงูƒ
        if (!$this->entitlements->can($tenantId, 'sources.sync')) {
            return new \WP_REST_Response([
                'success' => false,
                'error' => [
                    'code'    => 'entitlement_exhausted',
                    'message' => 'Sync limit reached. Upgrade your plan.',
                ],
            ], 402);
        }
        
        // 2. ุชุญู‚ู‚ ู…ู† ุญุฏ ุงู„ู…ุตุงุฏุฑ
        $source = $this->sources->find($sourceId, $tenantId);
        if (!$source) {
            return new \WP_REST_Response([
                'success' => false,
                'error'   => ['code' => 'not_found'],
            ], 404);
        }
        
        // 3. ุงุฏูุน job
        $jobId = JobQueue::push($tenantId, 'sync_source', [
            'source_id' => $sourceId,
            'mode'      => $req->get_param('mode') ?? 'full',
        ]);
        
        // 4. ุงุณุชู‡ู„ูƒ ู…ู† ุงู„ุญุตุฉ
        $this->entitlements->consume($tenantId, 'sources.sync');
        
        return new \WP_REST_Response([
            'success' => true,
            'data' => [
                'job_id'  => $jobId,
                'status'  => 'queued',
                'poll_url' => "/wp-json/kosmos/v1/jobs/{$jobId}",
            ],
        ], 202);
    }
}

๐Ÿ” ุงู„ุฌุฒุก 6: Roles & Capabilities

Custom Roles:

php

<?php
// ูŠูู†ูู‘ุฐ ุนู†ุฏ activation
function kosmos_register_roles(): void {
    
    // 1. Platform Owner โ€” ู…ุงู„ูƒ ุงู„ู…ู†ุตุฉ
    add_role('kosmos_owner', 'KOSMOS Owner', [
        'read'                    => true,
        'kosmos_manage_platform'  => true,
        'kosmos_manage_tenants'   => true,
        'kosmos_manage_plans'     => true,
        'kosmos_view_audit'       => true,
        'kosmos_manage_workers'   => true,
    ]);
    
    // 2. Tenant Admin โ€” ู…ุฏูŠุฑ ุญุณุงุจ
    add_role('kosmos_tenant_admin', 'Tenant Admin', [
        'read'                    => true,
        'kosmos_manage_tenant'    => true,
        'kosmos_manage_sources'   => true,
        'kosmos_manage_targets'   => true,
        'kosmos_manage_products'  => true,
        'kosmos_manage_billing'   => true,
        'kosmos_use_ai'           => true,
        'kosmos_invite_users'     => true,
    ]);
    
    // 3. Tenant Editor โ€” ู…ุญุฑุฑ
    add_role('kosmos_tenant_editor', 'Tenant Editor', [
        'read'                    => true,
        'kosmos_manage_sources'   => true,
        'kosmos_manage_products'  => true,
        'kosmos_use_ai'           => true,
    ]);
    
    // 4. Tenant Viewer โ€” ู…ุดุงู‡ุฏ ูู‚ุท
    add_role('kosmos_tenant_viewer', 'Tenant Viewer', [
        'read'                    => true,
        'kosmos_view_products'    => true,
    ]);
    
    // 5. API Consumer โ€” ูˆุตูˆู„ API ูู‚ุท
    add_role('kosmos_api', 'API Consumer', [
        'read'                    => true,
        'kosmos_api_access'       => true,
    ]);
}

Capabilities ุงู„ู…ุฎุตุตุฉ:

php

<?php
// ูŠูู†ูู‘ุฐ ุนู†ุฏ activation
function kosmos_register_capabilities(): void {
    $caps = [
        // Platform-level
        'kosmos_manage_platform',
        'kosmos_manage_tenants',
        'kosmos_manage_plans',
        'kosmos_view_audit',
        'kosmos_manage_workers',
        
        // Tenant-level
        'kosmos_manage_tenant',
        'kosmos_manage_billing',
        'kosmos_invite_users',
        
        // Sources
        'kosmos_manage_sources',
        'kosmos_sync_sources',
        
        // Targets
        'kosmos_manage_targets',
        'kosmos_publish_products',
        
        // Products
        'kosmos_manage_products',
        'kosmos_view_products',
        'kosmos_bulk_publish',
        
        // AI
        'kosmos_use_ai',
        'kosmos_manage_ai_settings',
        
        // API
        'kosmos_api_access',
    ];
    
    // ุงู…ู†ุญ ูƒู„ ุงู„ุตู„ุงุญูŠุงุช ู„ู„ู…ุฏูŠุฑ ุงู„ุนุงู…
    $admin = get_role('administrator');
    foreach ($caps as $cap) {
        $admin->add_cap($cap);
    }
}

ู…ุตููˆูุฉ ุงู„ุตู„ุงุญูŠุงุช:

CapabilityOwnerTenant AdminEditorViewerAPI
kosmos_manage_platformโœ…โŒโŒโŒโŒ
kosmos_manage_tenantsโœ…โŒโŒโŒโŒ
kosmos_manage_tenantโœ…โœ…โŒโŒโŒ
kosmos_manage_billingโœ…โœ…โŒโŒโŒ
kosmos_manage_sourcesโœ…โœ…โœ…โŒโŒ
kosmos_manage_targetsโœ…โœ…โŒโŒโŒ
kosmos_publish_productsโœ…โœ…โŒโŒโŒ
kosmos_manage_productsโœ…โœ…โœ…โŒโŒ
kosmos_view_productsโœ…โœ…โœ…โœ…โœ…
kosmos_use_aiโœ…โœ…โœ…โŒโŒ
kosmos_api_accessโœ…โŒโŒโŒโœ…

๐Ÿ’ณ ุงู„ุฌุฒุก 7: ู†ุธุงู… ุงู„ุจุงู‚ุงุช ูˆุงู„ู€Entitlements

ุงู„ุจุงู‚ุงุช (Plans):

php

<?php
// config/plans.php
return [
    'trial' => [
        'name'          => 'ุชุฌุฑูŠุจูŠ',
        'description'   => '14 ูŠูˆู…ุงู‹ ู„ุชุฌุฑุจุฉ ูƒู„ ุงู„ู…ูŠุฒุงุช',
        'price_monthly' => 0,
        'price_yearly'  => 0,
        'trial_days'    => 14,
        'limits' => [
            'products'   => 50,
            'sources'    => 1,
            'targets'    => 1,
            'users'      => 1,
            'ai_credits' => 100,
            'storage_mb' => 256,
        ],
        'features' => [
            'salla_integration'   => true,
            'shopify_integration' => false,
            'ai_tutor'            => true,
            'knowledge_graph'     => true,
            'white_label'         => false,
            'api_access'          => false,
            'priority_support'    => false,
            'custom_domain'       => false,
        ],
    ],
    
    'starter' => [
        'name'          => 'ุงู„ู…ุจุชุฏุฆ',
        'description'   => 'ู„ู„ุชุฌุงุฑ ุงู„ุฃูุฑุงุฏ',
        'price_monthly' => 99,
        'price_yearly'  => 990,
        'trial_days'    => 7,
        'limits' => [
            'products'   => 500,
            'sources'    => 2,
            'targets'    => 1,
            'users'      => 1,
            'ai_credits' => 1000,
            'storage_mb' => 1024,
        ],
        'features' => [
            'salla_integration'   => true,
            'shopify_integration' => true,
            'ai_tutor'            => true,
            'knowledge_graph'     => true,
            'white_label'         => false,
            'api_access'          => false,
            'priority_support'    => false,
            'custom_domain'       => false,
        ],
    ],
    
    'professional' => [
        'name'          => 'ุงู„ุงุญุชุฑุงููŠ',
        'description'   => 'ู„ู„ูˆูƒุงู„ุงุช ุงู„ุตุบูŠุฑุฉ',
        'price_monthly' => 299,
        'price_yearly'  => 2990,
        'trial_days'    => 7,
        'limits' => [
            'products'   => 5000,
            'sources'    => 5,
            'targets'    => 3,
            'users'      => 5,
            'ai_credits' => 5000,
            'storage_mb' => 10240,
        ],
        'features' => [
            'salla_integration'   => true,
            'shopify_integration' => true,
            'ai_tutor'            => true,
            'knowledge_graph'     => true,
            'white_label'         => false,
            'api_access'          => true,
            'priority_support'    => true,
            'custom_domain'       => true,
        ],
    ],
    
    'enterprise' => [
        'name'          => 'ุงู„ู…ุคุณุณูŠ',
        'description'   => 'ู„ู„ุดุฑูƒุงุช ุงู„ูƒุจูŠุฑุฉ',
        'price_monthly' => 999,
        'price_yearly'  => 9990,
        'trial_days'    => 14,
        'limits' => [
            'products'   => -1, // unlimited
            'sources'    => -1,
            'targets'    => -1,
            'users'      => -1,
            'ai_credits' => 50000,
            'storage_mb' => -1,
        ],
        'features' => [
            'salla_integration'   => true,
            'shopify_integration' => true,
            'ai_tutor'            => true,
            'knowledge_graph'     => true,
            'white_label'         => true,
            'api_access'          => true,
            'priority_support'    => true,
            'custom_domain'       => true,
            'sla'                 => true,
            'dedicated_support'   => true,
        ],
    ],
];

Feature Keys:

php

<?php
// config/features.php
return [
    // Quantifiable
    'products.sync'       => ['type' => 'quota', 'unit' => 'product'],
    'products.publish'    => ['type' => 'quota', 'unit' => 'product'],
    'sources.create'      => ['type' => 'quota', 'unit' => 'source'],
    'sources.sync'        => ['type' => 'quota', 'unit' => 'sync'],
    'targets.create'      => ['type' => 'quota', 'unit' => 'target'],
    'users.invite'        => ['type' => 'quota', 'unit' => 'user'],
    'ai.credits'          => ['type' => 'metered', 'unit' => 'credit'],
    'storage.bytes'       => ['type' => 'quota', 'unit' => 'mb'],
    
    // Boolean
    'salla_integration'   => ['type' => 'boolean'],
    'shopify_integration' => ['type' => 'boolean'],
    'ai_tutor'            => ['type' => 'boolean'],
    'knowledge_graph'     => ['type' => 'boolean'],
    'white_label'         => ['type' => 'boolean'],
    'api_access'          => ['type' => 'boolean'],
    'custom_domain'       => ['type' => 'boolean'],
    'priority_support'    => ['type' => 'boolean'],
];

Entitlement Middleware:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Http\Middleware;

use KOSMOS\Studio\Domain\Entitlement\EntitlementService;

/**
 * ูŠู…ู†ุน ุงู„ูˆุตูˆู„ ู„ู„ู€endpoints ุจุฏูˆู† ุตู„ุงุญูŠุฉ
 * ูŠูุณุชุฎุฏู… ุนุจุฑ: ->middleware('entitlement:sources.sync')
 */
final class EntitlementMiddleware {
    
    public function __construct(
        private EntitlementService $entitlements
    ) {}
    
    public function handle(\WP_REST_Request $req, string $featureKey): bool|\WP_Error {
        $tenantId = get_current_user_id();
        
        if (!$this->entitlements->can($tenantId, $featureKey)) {
            return new \WP_Error(
                'entitlement_exhausted',
                'Your subscription does not allow this action',
                [
                    'status'  => 402,
                    'feature' => $featureKey,
                    'limits'  => $this->entitlements->getLimits($tenantId),
                    'upgrade' => '/pricing',
                ]
            );
        }
        
        return true;
    }
}

๐Ÿ”— ุงู„ุฌุฒุก 8: WooCommerce Hooks Integration

Hooks ุงู„ู…ุณุชู…ุน ุฅู„ูŠู‡ุง:

php

<?php
// src/Integrations/WooCommerce/Hooks.php
declare(strict_types=1);

namespace KOSMOS\Studio\Integrations\WooCommerce;

use KOSMOS\Studio\Application\Bridge\LockOrchestrator;
use KOSMOS\Studio\Domain\Subscription\SubscriptionService;

final class Hooks {
    
    public function __construct(
        private SubscriptionService $subscriptions,
        private LockOrchestrator $locker
    ) {}
    
    public function register(): void {
        // ===== Subscription lifecycle =====
        add_action('woocommerce_subscription_status_active',
            [$this, 'onActive'], 10, 1);
        add_action('woocommerce_subscription_status_pending-cancel',
            [$this, 'onPendingCancel'], 10, 1);
        add_action('woocommerce_subscription_status_cancelled',
            [$this, 'onCancelled'], 10, 1);
        add_action('woocommerce_subscription_status_expired',
            [$this, 'onExpired'], 10, 1);
        add_action('woocommerce_subscription_status_on-hold',
            [$this, 'onHold'], 10, 1);
        
        // ===== Renewal =====
        add_action('woocommerce_subscription_renewal_payment_complete',
            [$this, 'onRenewalComplete'], 10, 2);
        add_action('woocommerce_subscription_renewal_payment_failed',
            [$this, 'onRenewalFailed'], 10, 2);
        
        // ===== Order =====
        add_action('woocommerce_order_status_completed',
            [$this, 'onOrderCompleted'], 10, 1);
        add_action('woocommerce_order_refunded',
            [$this, 'onRefunded'], 10, 2);
        
        // ===== Product =====
        add_action('woocommerce_product_set_stock',
            [$this, 'onStockChange'], 10, 1);
        
        // ===== Cron =====
        add_action('kosmos/check_expirations', [$this, 'checkExpirations']);
    }
    
    public function onActive($subscription): void {
        $tenantId = $this->resolveTenantId($subscription);
        $this->subscriptions->markActive($subscription->get_id());
        $this->locker->handleSubscriptionRenewed($tenantId);
    }
    
    public function onExpired($subscription): void {
        $tenantId = $this->resolveTenantId($subscription);
        $this->subscriptions->markExpired($subscription->get_id());
        $this->locker->handleSubscriptionExpired($tenantId);
    }
    
    public function onCancelled($subscription): void {
        // ู„ุง ู†ู‚ูู„ ููˆุฑุงู‹ โ€” ู†ู†ุชุธุฑ ุงู†ุชู‡ุงุก ุงู„ูุชุฑุฉ ุงู„ู…ุฏููˆุนุฉ
        $tenantId = $this->resolveTenantId($subscription);
        $this->subscriptions->markCancelled($subscription->get_id());
        
        // ุฌุฏูˆู„ ูุญุต ุจุนุฏ ุงู†ุชู‡ุงุก ุงู„ูุชุฑุฉ
        wp_schedule_single_event(
            $subscription->get_date('end')->getTimestamp() + 3600,
            'kosmos/check_expirations',
            [$tenantId]
        );
    }
    
    public function onHold($subscription): void {
        $tenantId = $this->resolveTenantId($subscription);
        // ุงู…ู†ุญ 3 ุฃูŠุงู… grace period
        $graceEnds = time() + (3 * DAY_IN_SECONDS);
        $this->subscriptions->setGracePeriod($subscription->get_id(), $graceEnds);
    }
    
    public function onRenewalFailed($subscription, $lastOrder): void {
        $tenantId = $this->resolveTenantId($subscription);
        $this->subscriptions->incrementFailedPayments($subscription->get_id());
        
        $fails = $this->subscriptions->getFailedCount($subscription->get_id());
        
        // 3 ู…ุญุงูˆู„ุงุช โ†’ ุซู… ุงู„ู‚ูู„
        if ($fails >= 3) {
            $this->locker->handleSubscriptionExpired($tenantId);
        }
    }
    
    public function checkExpirations(?int $tenantId = null): void {
        if ($tenantId) {
            $sub = $this->subscriptions->getActiveByTenant($tenantId);
            if ($sub && $sub->current_period_end < time()) {
                $this->locker->handleSubscriptionExpired($tenantId);
            }
            return;
        }
        
        // ูุญุต ุฌู…ุงุนูŠ
        foreach ($this->subscriptions->getAllExpiring() as $sub) {
            $this->locker->handleSubscriptionExpired($sub->tenant_id);
        }
    }
    
    private function resolveTenantId($subscription): int {
        $userId = $subscription->get_user_id();
        return (int) get_user_meta($userId, '_kosmos_tenant_id', true);
    }
}

Cron Schedule ู…ุฎุตุต:

php

<?php
// ุฅุถุงูุฉ ูุชุฑุงุช cron ู…ุฎุตุตุฉ
add_filter('cron_schedules', function ($schedules) {
    $schedules['every_minute'] = [
        'interval' => 60,
        'display'  => 'Every Minute',
    ];
    $schedules['every_five_minutes'] = [
        'interval' => 300,
        'display'  => 'Every 5 Minutes',
    ];
    return $schedules;
});

// ุชุณุฌูŠู„ ุฃุญุฏุงุซ cron
add_action('init', function () {
    if (!wp_next_scheduled('kosmos/check_expirations')) {
        wp_schedule_event(time(), 'hourly', 'kosmos/check_expirations');
    }
    if (!wp_next_scheduled('kosmos/worker_tick')) {
        wp_schedule_event(time(), 'every_minute', 'kosmos/worker_tick');
    }
});

๐Ÿค– ุงู„ุฌุฒุก 9: AI Gateway ุงู„ูƒุงู…ู„

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Application\AI;

use KOSMOS\Studio\Application\AI\Providers\ProviderInterface;
use KOSMOS\Studio\Domain\Entitlement\EntitlementService;
use KOSMOS\Studio\Support\Logger;

/**
 * ู†ู‚ุทุฉ ุฏุฎูˆู„ ูˆุงุญุฏุฉ ู„ูƒู„ ุงุณุชุฏุนุงุกุงุช AI
 */
final class AIGateway {
    
    /** @var array<string,ProviderInterface> */
    private array $providers = [];
    
    public function __construct(
        private EntitlementService $entitlements,
        private Logger $logger
    ) {}
    
    public function registerProvider(string $name, ProviderInterface $provider): void {
        $this->providers[$name] = $provider;
    }
    
    /**
     * @param string $feature tutor|enrich|translate|generate
     */
    public function call(
        int $tenantId,
        string $feature,
        array $payload,
        ?string $preferredProvider = null
    ): array {
        // 1. ุชุญู‚ู‚ ู…ู† ุงู„ุตู„ุงุญูŠุฉ
        $featureKey = "ai.{$feature}";
        if (!$this->entitlements->can($tenantId, $featureKey)) {
            return [
                'success' => false,
                'error'   => [
                    'code'    => 'entitlement_exhausted',
                    'message' => 'AI credits exhausted. Please upgrade.',
                ],
            ];
        }
        
        // 2. ุงุฎุชุฑ ุงู„ู…ุฒูˆุฏ
        $providerName = $preferredProvider ?? $this->selectProvider($feature);
        $provider = $this->providers[$providerName] ?? null;
        
        if (!$provider) {
            return [
                'success' => false,
                'error'   => ['code' => 'provider_unavailable'],
            ];
        }
        
        // 3. ู†ูู‘ุฐ
        $start = microtime(true);
        try {
            $result = $provider->execute($feature, $payload);
            $duration = (int) ((microtime(true) - $start) * 1000);
            
            // 4. ุงุณุชู‡ู„ูƒ
            $credits = $this->calculateCredits($result['tokens'] ?? 0);
            $this->entitlements->consume($tenantId, $featureKey, $credits);
            
            // 5. ุณุฌู‘ู„
            $this->logUsage($tenantId, $feature, $providerName, $result, $duration);
            
            return [
                'success' => true,
                'data'    => $result['data'] ?? $result,
                'meta' => [
                    'provider'     => $providerName,
                    'tokens'       => $result['tokens'] ?? 0,
                    'credits_used' => $credits,
                    'duration_ms'  => $duration,
                ],
            ];
        } catch (\Throwable $e) {
            $this->logger->error('AI call failed', [
                'feature'  => $feature,
                'provider' => $providerName,
                'error'    => $e->getMessage(),
            ]);
            
            return [
                'success' => false,
                'error'   => [
                    'code'    => 'ai_provider_error',
                    'message' => $e->getMessage(),
                ],
            ];
        }
    }
    
    private function selectProvider(string $feature): string {
        // ูŠู…ูƒู† ุฃู† ูŠูƒูˆู† ุฐูƒูŠุงู‹: ุญุณุจ ุงู„ุชูƒู„ูุฉ/ุงู„ุณุฑุนุฉ/ุงู„ุฌูˆุฏุฉ
        $map = [
            'tutor'      => 'openai',
            'enrich'     => 'openai',
            'translate'  => 'google',
            'generate'   => 'openai',
        ];
        return $map[$feature] ?? 'openai';
    }
    
    private function calculateCredits(int $tokens): float {
        // 1000 tokens = 1 credit
        return round($tokens / 1000, 4);
    }
    
    private function logUsage(int $tenantId, string $feature, string $provider, array $result, int $duration): void {
        global $wpdb;
        $wpdb->insert("{$wpdb->prefix}kosmos_ai_usage", [
            'tenant_id'    => $tenantId,
            'feature'      => $feature,
            'model'        => $result['model'] ?? 'unknown',
            'provider'     => $provider,
            'tokens_in'    => $result['tokens_in'] ?? 0,
            'tokens_out'   => $result['tokens_out'] ?? 0,
            'credits_used' => $this->calculateCredits($result['tokens'] ?? 0),
            'cost_usd'     => $result['cost'] ?? 0,
            'duration_ms'  => $duration,
            'success'      => 1,
        ]);
    }
}

Provider Interface + OpenAI:

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Application\AI\Providers;

interface ProviderInterface {
    public function execute(string $feature, array $payload): array;
    public function name(): string;
}

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Application\AI\Providers;

use OpenAI\Client;

final class OpenAIProvider implements ProviderInterface {
    
    private Client $client;
    
    public function __construct() {
        $apiKey = get_option('kosmos_openai_key') ?: getenv('OPENAI_API_KEY');
        $this->client = \OpenAI::client($apiKey);
    }
    
    public function name(): string {
        return 'openai';
    }
    
    public function execute(string $feature, array $payload): array {
        return match($feature) {
            'tutor'    => $this->tutor($payload),
            'enrich'   => $this->enrich($payload),
            'generate' => $this->generate($payload),
            default    => throw new \InvalidArgumentException("Unknown feature: {$feature}"),
        };
    }
    
    private function tutor(array $payload): array {
        $messages = [
            ['role' => 'system', 'content' => 'ุฃู†ุช KOSMOS Tutor...'],
        ];
        
        if (!empty($payload['context'])) {
            $messages[] = ['role' => 'system', 'content' => "ุงู„ุณูŠุงู‚:\n{$payload['context']}"];
        }
        
        foreach ($payload['history'] ?? [] as $msg) {
            $messages[] = $msg;
        }
        
        $messages[] = ['role' => 'user', 'content' => $payload['query']];
        
        $response = $this->client->chat()->create([
            'model'       => 'gpt-4o-mini',
            'messages'    => $messages,
            'temperature' => 0.3,
            'max_tokens'  => 900,
        ]);
        
        return [
            'data'       => $response->choices[0]->message->content,
            'tokens'     => $response->usage->totalTokens,
            'tokens_in'  => $response->usage->promptTokens,
            'tokens_out' => $response->usage->completionTokens,
            'model'      => 'gpt-4o-mini',
            'cost'       => $this->calculateCost($response->usage),
        ];
    }
    
    private function enrich(array $payload): array {
        $product = $payload['product'] ?? [];
        
        $response = $this->client->chat()->create([
            'model'    => 'gpt-4o-mini',
            'messages' => [
                ['role' => 'system', 'content' => 'ุฃู†ุช ุฎุจูŠุฑ ุชุฌุงุฑุฉ ุฅู„ูƒุชุฑูˆู†ูŠุฉ. ุญุณู‘ู† ุจูŠุงู†ุงุช ุงู„ู…ู†ุชุฌ.'],
                ['role' => 'user', 'content' => wp_json_encode($product)],
            ],
            'response_format' => ['type' => 'json_object'],
        ]);
        
        return [
            'data'   => json_decode($response->choices[0]->message->content, true),
            'tokens' => $response->usage->totalTokens,
            'model'  => 'gpt-4o-mini',
        ];
    }
    
    private function generate(array $payload): array {
        $response = $this->client->chat()->create([
            'model'    => 'gpt-4o-mini',
            'messages' => [
                ['role' => 'user', 'content' => $payload['prompt'] ?? ''],
            ],
        ]);
        
        return [
            'data'   => $response->choices[0]->message->content,
            'tokens' => $response->usage->totalTokens,
            'model'  => 'gpt-4o-mini',
        ];
    }
    
    private function calculateCost(object $usage): float {
        // $0.15/1M input, $0.60/1M output (gpt-4o-mini)
        return ($usage->promptTokens * 0.15 + $usage->completionTokens * 0.60) / 1_000_000;
    }
}

๐Ÿ“Š ุงู„ุฌุฒุก 10: ุงู„ู€Dashboards

1๏ธโƒฃ Tenant Dashboard (Frontend):

php

<?php
// src/Frontend/Shortcodes/DashboardShortcode.php
declare(strict_types=1);

namespace KOSMOS\Studio\Frontend\Shortcodes;

final class DashboardShortcode {
    
    public static function render(): string {
        if (!is_user_logged_in()) {
            return '<p>ูŠุฌุจ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„.</p>';
        }
        
        $tenantId = (int) get_user_meta(get_current_user_id(), '_kosmos_tenant_id', true);
        $tenant = \KOSMOS\Studio\Domain\Tenant\TenantRepository::find($tenantId);
        
        if (!$tenant) {
            return '<p>ู„ุง ูŠูˆุฌุฏ ุญุณุงุจ ู…ุฑุชุจุท.</p>';
        }
        
        ob_start();
        ?>
        <div class="kosmos-dashboard" data-tenant="<?= esc_attr($tenantId) ?>">
          <header class="kosmos-header">
            <h1><?= esc_html($tenant->name) ?></h1>
            <div class="kosmos-sub-status <?= esc_attr($tenant->status) ?>">
              <?php if ($tenant->status === 'active'): ?>
                โœ… ุงุดุชุฑุงูƒ ู†ุดุท โ€” ูŠุชุฌุฏุฏ ููŠ <?= esc_html($tenant->current_period_end) ?>
              <?php else: ?>
                โš ๏ธ <?= esc_html($tenant->status) ?> โ€” <a href="/pricing">ุฌุฏู‘ุฏ ุงู„ุขู†</a>
              <?php endif; ?>
            </div>
          </header>
          
          <div class="kosmos-grid">
            <?php
            $stats = [
              ['๐Ÿ“ฆ', 'ุงู„ู…ู†ุชุฌุงุช', \KOSMOS\Studio\Domain\Product\ProductRepository::countByTenant($tenantId)],
              ['๐Ÿ”Œ', 'ุงู„ู…ุตุงุฏุฑ', \KOSMOS\Studio\Domain\Source\SourceRepository::countByTenant($tenantId)],
              ['๐ŸŽฏ', 'ู‚ู†ูˆุงุช ุงู„ุจูŠุน', \KOSMOS\Studio\Domain\Target\TargetRepository::countByTenant($tenantId)],
              ['๐Ÿง ', 'ุงุณุชู‡ู„ุงูƒ AI', self::getAiUsage($tenantId)],
            ];
            foreach ($stats as [$icon, $label, $value]): ?>
              <div class="kosmos-card">
                <div class="icon"><?= $icon ?></div>
                <div class="value"><?= esc_html((string) $value) ?></div>
                <div class="label"><?= esc_html($label) ?></div>
              </div>
            <?php endforeach; ?>
          </div>
          
          <div class="kosmos-actions">
            <a href="/dashboard/sources" class="btn">ุฅุฏุงุฑุฉ ุงู„ู…ุตุงุฏุฑ</a>
            <a href="/dashboard/targets" class="btn">ู‚ู†ูˆุงุช ุงู„ุจูŠุน</a>
            <a href="/dashboard/products" class="btn">ุงู„ู…ู†ุชุฌุงุช</a>
            <a href="/dashboard/tutor" class="btn">ุงุณุฃู„ AI</a>
          </div>
        </div>
        <?php
        return ob_get_clean();
    }
    
    private static function getAiUsage(int $tenantId): string {
        global $wpdb;
        $sum = $wpdb->get_var($wpdb->prepare(
            "SELECT SUM(credits_used) FROM {$wpdb->prefix}kosmos_ai_usage 
             WHERE tenant_id = %d AND created_at >= %s",
            $tenantId,
            date('Y-m-01')
        ));
        return number_format((float) $sum, 1);
    }
}

2๏ธโƒฃ Admin Dashboard (WP Admin):

php

<?php
// src/Admin/Pages/Dashboard.php
declare(strict_types=1);

namespace KOSMOS\Studio\Admin\Pages;

final class Dashboard {
    
    public function render(): void {
        global $wpdb;
        
        $stats = [
            'tenants'       => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}kosmos_tenants WHERE status='active'"),
            'mrr'           => (float) $wpdb->get_var("SELECT SUM(amount) FROM {$wpdb->prefix}kosmos_subscriptions WHERE status='active' AND billing_cycle='monthly'"),
            'products'      => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}kosmos_products"),
            'locked'        => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}kosmos_products WHERE locked_by_subscription=1"),
            'ai_calls_24h'  => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}kosmos_ai_usage WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)"),
            'jobs_queued'   => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}kosmos_jobs WHERE status='queued'"),
        ];
        ?>
        <div class="wrap kosmos-admin-dashboard">
          <h1>๐ŸŒŒ KOSMOS Control Center</h1>
          
          <div class="kosmos-kpi-grid">
            <?php foreach ($stats as $key => $value): ?>
              <div class="kpi-card">
                <div class="kpi-value"><?= esc_html((string) $value) ?></div>
                <div class="kpi-label"><?= esc_html($key) ?></div>
              </div>
            <?php endforeach; ?>
          </div>
          
          <div class="kosmos-panels">
            <div class="panel">
              <h2>ุงุดุชุฑุงูƒุงุช ุชู†ุชู‡ูŠ ู‚ุฑูŠุจุงู‹</h2>
              <?php $this->renderExpiringSubscriptions(); ?>
            </div>
            
            <div class="panel">
              <h2>ู…ู‡ุงู… ู‚ูŠุฏ ุงู„ุชู†ููŠุฐ</h2>
              <?php $this->renderRunningJobs(); ?>
            </div>
            
            <div class="panel">
              <h2>ุฃุฎุทุงุก ุญุฏูŠุซุฉ</h2>
              <?php $this->renderRecentErrors(); ?>
            </div>
          </div>
        </div>
        <?php
    }
    
    private function renderExpiringSubscriptions(): void {
        global $wpdb;
        $rows = $wpdb->get_results("
            SELECT s.id, t.name, s.current_period_end
            FROM {$wpdb->prefix}kosmos_subscriptions s
            JOIN {$wpdb->prefix}kosmos_tenants t ON t.id = s.tenant_id
            WHERE s.status = 'active'
              AND s.current_period_end <= DATE_ADD(NOW(), INTERVAL 7 DAY)
            ORDER BY s.current_period_end ASC
            LIMIT 20
        ");
        
        if (!$rows) {
            echo '<p>ู„ุง ูŠูˆุฌุฏ.</p>';
            return;
        }
        
        echo '<table class="widefat"><thead><tr>';
        echo '<th>Tenant</th><th>ูŠู†ุชู‡ูŠ ููŠ</th><th>ุงู„ุญุงู„ุฉ</th>';
        echo '</tr></thead><tbody>';
        foreach ($rows as $r) {
            $days = (strtotime($r->current_period_end) - time()) / 86400;
            echo '<tr>';
            echo '<td>' . esc_html($r->name) . '</td>';
            echo '<td>' . esc_html($r->current_period_end) . '</td>';
            echo '<td>' . ($days < 0 ? 'โš ๏ธ ู…ู†ุชู‡ูŠ' : sprintf('ุฎู„ุงู„ %.1f ูŠูˆู…', $days)) . '</td>';
            echo '</tr>';
        }
        echo '</tbody></table>';
    }
    
    private function renderRunningJobs(): void {
        global $wpdb;
        $rows = $wpdb->get_results("
            SELECT id, job_type, tenant_id, status, attempts, started_at
            FROM {$wpdb->prefix}kosmos_jobs
            WHERE status IN ('queued','running')
            ORDER BY priority ASC, scheduled_at ASC
            LIMIT 20
        ");
        
        if (!$rows) {
            echo '<p>ู„ุง ูŠูˆุฌุฏ ู…ู‡ุงู… ู‚ูŠุฏ ุงู„ุชู†ููŠุฐ.</p>';
            return;
        }
        
        echo '<table class="widefat"><thead><tr>';
        echo '<th>#</th><th>ุงู„ู†ูˆุน</th><th>Tenant</th><th>ุงู„ุญุงู„ุฉ</th><th>ุงู„ู…ุญุงูˆู„ุงุช</th>';
        echo '</tr></thead><tbody>';
        foreach ($rows as $r) {
            echo '<tr>';
            echo '<td>' . (int) $r->id . '</td>';
            echo '<td>' . esc_html($r->job_type) . '</td>';
            echo '<td>' . (int) $r->tenant_id . '</td>';
            echo '<td>' . esc_html($r->status) . '</td>';
            echo '<td>' . (int) $r->attempts . '</td>';
            echo '</tr>';
        }
        echo '</tbody></table>';
    }
    
    private function renderRecentErrors(): void {
        global $wpdb;
        $rows = $wpdb->get_results("
            SELECT id, source, event_type, error_message, created_at
            FROM {$wpdb->prefix}kosmos_webhook_events
            WHERE error_message IS NOT NULL
            ORDER BY created_at DESC
            LIMIT 10
        ");
        
        if (!$rows) {
            echo '<p>ู„ุง ูŠูˆุฌุฏ ุฃุฎุทุงุก.</p>';
            return;
        }
        
        echo '<ul>';
        foreach ($rows as $r) {
            echo '<li>';
            echo '<strong>' . esc_html($r->source) . '/' . esc_html($r->event_type) . '</strong>: ';
            echo esc_html(substr($r->error_message, 0, 100));
            echo ' <em>(' . esc_html($r->created_at) . ')</em>';
            echo '</li>';
        }
        echo '</ul>';
    }
}

โšก ุงู„ุฌุฒุก 11: Job Queue & Workers

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Application\Jobs;

final class JobQueue {
    
    public static function push(
        int $tenantId,
        string $jobType,
        array $payload,
        int $priority = 5,
        ?int $delaySeconds = null
    ): int {
        global $wpdb;
        
        $wpdb->insert("{$wpdb->prefix}kosmos_jobs", [
            'tenant_id'     => $tenantId,
            'job_type'      => $jobType,
            'payload'       => wp_json_encode($payload),
            'status'        => 'queued',
            'priority'      => $priority,
            'scheduled_at'  => $delaySeconds 
                ? date('Y-m-d H:i:s', time() + $delaySeconds)
                : current_time('mysql'),
            'created_at'    => current_time('mysql'),
        ]);
        
        return (int) $wpdb->insert_id;
    }
    
    public static function claim(string $workerId): ?array {
        global $wpdb;
        
        // ู‚ูู„ ุฐุฑูŠ โ€” ูŠู…ู†ุน ุชู†ููŠุฐ ู†ูุณ ุงู„ู…ู‡ู…ุฉ ู…ุฑุชูŠู†
        $wpdb->query('START TRANSACTION');
        
        $job = $wpdb->get_row($wpdb->prepare("
            SELECT * FROM {$wpdb->prefix}kosmos_jobs
            WHERE status = 'queued'
              AND scheduled_at <= NOW()
            ORDER BY priority ASC, scheduled_at ASC
            LIMIT 1
            FOR UPDATE SKIP LOCKED
        "));
        
        if (!$job) {
            $wpdb->query('COMMIT');
            return null;
        }
        
        $wpdb->update(
            "{$wpdb->prefix}kosmos_jobs",
            [
                'status'     => 'running',
                'started_at' => current_time('mysql'),
                'worker_id'  => $workerId,
                'attempts'   => $job->attempts + 1,
            ],
            ['id' => $job->id]
        );
        
        $wpdb->query('COMMIT');
        
        return (array) $job;
    }
    
    public static function complete(int $jobId, array $result = []): void {
        global $wpdb;
        $wpdb->update(
            "{$wpdb->prefix}kosmos_jobs",
            [
                'status'       => 'completed',
                'completed_at' => current_time('mysql'),
                'result'       => wp_json_encode($result),
            ],
            ['id' => $jobId]
        );
    }
    
    public static function fail(int $jobId, string $error): void {
        global $wpdb;
        
        $job = $wpdb->get_row($wpdb->prepare(
            "SELECT attempts, max_attempts FROM {$wpdb->prefix}kosmos_jobs WHERE id = %d",
            $jobId
        ));
        
        $status = ($job && $job->attempts >= $job->max_attempts) ? 'failed' : 'queued';
        
        // Exponential backoff
        $delay = min(3600, pow(2, $job->attempts ?? 1) * 30);
        
        $wpdb->update(
            "{$wpdb->prefix}kosmos_jobs",
            [
                'status'        => $status,
                'error_message' => $error,
                'scheduled_at'  => date('Y-m-d H:i:s', time() + $delay),
            ],
            ['id' => $jobId]
        );
    }
}

php

<?php
declare(strict_types=1);

namespace KOSMOS\Studio\Application\Jobs;

use KOSMOS\Studio\Application\Bridge\SyncOrchestrator;
use KOSMOS\Studio\Application\Bridge\PublishOrchestrator;
use KOSMOS\Studio\Domain\Product\ProductLockManager;

final class Worker {
    
    private const WORKER_ID_PREFIX = 'kosmos-worker-';
    private const MAX_JOBS_PER_TICK = 5;
    private const TIME_BUDGET_SECONDS = 50;
    
    public static function tick(): void {
        $workerId = self::WORKER_ID_PREFIX . gethostname() . '-' . getmypid();
        $start = time();
        
        for ($i = 0; $i < self::MAX_JOBS_PER_TICK; $i++) {
            if ((time() - $start) > self::TIME_BUDGET_SECONDS) break;
            
            $job = JobQueue::claim($workerId);
            if (!$job) break;
            
            try {
                $result = self::execute($job);
                JobQueue::complete($job['id'], $result);
            } catch (\Throwable $e) {
                JobQueue::fail($job['id'], $e->getMessage());
            }
        }
    }
    
    private static function execute(array $job): array {
        $payload = json_decode($job['payload'], true) ?: [];
        $tenantId = (int) $job['tenant_id'];
        
        return match ($job['job_type']) {
            'sync_source'      => self::handleSyncSource($tenantId, $payload),
            'publish_product'  => self::handlePublishProduct($tenantId, $payload),
            'bulk_lock'        => self::handleBulkLock($tenantId, $payload),
            default            => throw new \RuntimeException("Unknown job: {$job['job_type']}"),
        };
    }
    
    private static function handleSyncSource(int $tenantId, array $payload): array {
        $orchestrator = \KOSMOS\Studio\Core\Plugin::instance()
            ->container()
            ->get(SyncOrchestrator::class);
        
        return $orchestrator->syncSource(
            $tenantId,
            (int) $payload['source_id'],
            $payload['mode'] ?? 'full'
        );
    }
    
    private static function handlePublishProduct(int $tenantId, array $payload): array {
        $orchestrator = \KOSMOS\Studio\Core\Plugin::instance()
            ->container()
            ->get(PublishOrchestrator::class);
        
        return $orchestrator->publish(
            $tenantId,
            (int) $payload['product_id'],
            (int) $payload['target_id']
        );
    }
    
    private static function handleBulkLock(int $tenantId, array $payload): array {
        $targets = \KOSMOS\Studio\Domain\Target\TargetRepository::find(
            (int) $payload['target_id'],
            $tenantId
        );
        
        if (!$targets) return ['skipped' => true];
        
        $publisher = new \KOSMOS\Studio\Integrations\Salla\SallaPublisher(
            new \KOSMOS\Studio\Integrations\Salla\SallaClient(),
            new \KOSMOS\Studio\Integrations\Salla\SallaMapper()
        );
        
        $action = $payload['action'] ?? 'hide';
        $products = \KOSMOS\Studio\Domain\Product\ProductRepository::findByTarget($tenantId, (int) $payload['target_id']);
        
        $done = 0;
        foreach ($products as $p) {
            if (!$p['external_id']) continue;
            
            $ok = $action === 'hide'
                ? $publisher->hide($targets, $p['external_id'])
                : $publisher->show($targets, $p['external_id']);
            
            if ($ok) $done++;
        }
        
        return ['action' => $action, 'affected' => $done];
    }
}

๐Ÿš€ ุงู„ุฌุฒุก 12: ุฎุทุฉ ุงู„ุชู†ููŠุฐ โ€” ุฃูˆู„ 60 ุฏู‚ูŠู‚ุฉ ู…ู„ูู‹ุง ุจู…ู„ู

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 0-5 | Bootstrap

bash

# 1. ุฃู†ุดุฆ ุงู„ู…ุดุฑูˆุน
mkdir -p kosmos-studio && cd kosmos-studio
composer init --name=kosmos/studio --type=wordpress-plugin

json

// composer.json
{
  "name": "kosmos/studio",
  "type": "wordpress-plugin",
  "require": {
    "php": ">=8.2",
    "openai-php/client": "^0.10",
    "guzzlehttp/guzzle": "^7.8",
    "monolog/monolog": "^3.6",
    "ramsey/uuid": "^4.7",
    "defuse/php-encryption": "^2.4"
  },
  "require-dev": {
    "phpunit/phpunit": "^11.0",
    "mockery/mockery": "^1.6",
    "squizlabs/php_codesniffer": "^3.10",
    "phpstan/phpstan": "^1.11"
  },
  "autoload": {
    "psr-4": {
      "KOSMOS\\Studio\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "KOSMOS\\Studio\\Tests\\": "tests/"
    }
  },
  "config": {
    "optimize-autoloader": true
  },
  "scripts": {
    "test": "phpunit",
    "lint": "phpcs --standard=PSR12 src/",
    "analyze": "phpstan analyse src/ --level=8"
  }
}

bash

composer install

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 5-10 | Plugin Main File

bash

cat > kosmos-studio.php << 'EOF'
<?php
/**
 * Plugin Name:  KOSMOS Studio
 * Description:  Subscription-as-a-Service Knowledge & Commerce Bridge
 * Version:      1.0.0
 * Requires PHP: 8.2
 */
declare(strict_types=1);

namespace KOSMOS\Studio;

if (!defined('ABSPATH')) exit;
require_once __DIR__ . '/vendor/autoload.php';

define('KOSMOS_VERSION', '1.0.0');
define('KOSMOS_FILE', __FILE__);
define('KOSMOS_PATH', plugin_dir_path(__FILE__));
define('KOSMOS_URL', plugin_dir_url(__FILE__));

Core\Plugin::instance()->boot();
EOF

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 10-20 | Core Classes

bash

mkdir -p src/{Core,Database/{Migrations,Seeds},Domain/{Tenant,Subscription,Entitlement,Product,Source,Target},Application/{Bridge,AI/Providers,Jobs/Handlers},Integrations/{Salla,Shopify,Payments},Http/{Middleware,Controllers},Admin/Pages,Frontend/Shortcodes,Support,Exceptions}

bash

# Core/Plugin.php
cat > src/Core/Plugin.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Core;

use KOSMOS\Studio\Database\Migrator;
use KOSMOS\Studio\Http\Router;

final class Plugin {
    private static ?self $instance = null;
    private Container $container;
    
    public static function instance(): self {
        return self::$instance ??= new self();
    }
    
    private function __construct() {
        $this->container = new Container();
    }
    
    public function boot(): void {
        register_activation_hook(KOSMOS_FILE, [$this, 'onActivate']);
        add_action('plugins_loaded', [$this, 'onPluginsLoaded']);
        add_action('init', [$this, 'onInit']);
        add_action('rest_api_init', [$this, 'registerRestRoutes']);
    }
    
    public function onActivate(): void {
        (new Migrator())->run();
        (new \KOSMOS\Studio\Database\Seeds\PlansSeeder())->run();
    }
    
    public function onPluginsLoaded(): void {
        if (!class_exists('WooCommerce')) return;
        load_plugin_textdomain('kosmos', false, dirname(plugin_basename(KOSMOS_FILE)) . '/languages');
    }
    
    public function onInit(): void {
        add_shortcode('kosmos_pricing', [\KOSMOS\Studio\Frontend\Shortcodes\PricingShortcode::class, 'render']);
    }
    
    public function registerRestRoutes(): void {
        (new Router())->register();
    }
    
    public function container(): Container { return $this->container; }
}
EOF

bash

# Core/Container.php
cat > src/Core/Container.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Core;

final class Container {
    private array $bindings = [];
    private array $instances = [];
    
    public function bind(string $abstract, callable $factory): void {
        $this->bindings[$abstract] = $factory;
    }
    
    public function singleton(string $abstract, ?callable $factory = null): void {
        $factory ??= fn() => new $abstract();
        $this->bindings[$abstract] = function () use ($factory, $abstract) {
            return $this->instances[$abstract] ??= $factory($this);
        };
    }
    
    public function get(string $abstract): mixed {
        if (isset($this->instances[$abstract])) return $this->instances[$abstract];
        
        $factory = $this->bindings[$abstract] ?? null;
        if (!$factory) {
            if (!class_exists($abstract)) throw new \RuntimeException("Not bound: {$abstract}");
            return $this->instances[$abstract] = new $abstract();
        }
        
        return $factory($this);
    }
}
EOF

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 20-30 | Database Schema

bash

# Database/Migrator.php
cat > src/Database/Migrator.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Database;

final class Migrator {
    public function run(): void {
        $migrations = glob(KOSMOS_PATH . 'src/Database/Migrations/*.php');
        sort($migrations);
        
        $ran = get_option('kosmos_migrations', []);
        
        foreach ($migrations as $file) {
            $name = basename($file, '.php');
            if (in_array($name, $ran, true)) continue;
            
            require_once $file;
            $class = 'KOSMOS\\Studio\\Database\\Migrations\\' . $name;
            
            if (class_exists($class)) {
                (new $class())->up();
                $ran[] = $name;
            }
        }
        
        update_option('kosmos_migrations', $ran);
    }
}
EOF

bash

# Migration 001 - Tenants
cat > src/Database/Migrations/Migration_001_CreateTenants.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Database\Migrations;

final class Migration_001_CreateTenants {
    public function up(): void {
        global $wpdb;
        $charset = $wpdb->get_charset_collate();
        
        $wpdb->query("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}kosmos_tenants (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            uuid CHAR(36) NOT NULL UNIQUE,
            slug VARCHAR(64) NOT NULL UNIQUE,
            name VARCHAR(191) NOT NULL,
            owner_user_id BIGINT UNSIGNED NOT NULL,
            plan_slug VARCHAR(32) NOT NULL DEFAULT 'trial',
            status ENUM('active','suspended','cancelled','trial') DEFAULT 'trial',
            subdomain VARCHAR(64) NOT NULL UNIQUE,
            custom_domain VARCHAR(191) NULL UNIQUE,
            salla_store_id VARCHAR(64) NULL,
            salla_access_token TEXT NULL,
            salla_refresh_token TEXT NULL,
            salla_token_expires DATETIME NULL,
            max_products INT DEFAULT 100,
            max_users INT DEFAULT 3,
            max_sources INT DEFAULT 2,
            ai_credits INT DEFAULT 500,
            trial_ends_at DATETIME NULL,
            current_period_start DATETIME NULL,
            current_period_end DATETIME NULL,
            metadata JSON NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            deleted_at DATETIME NULL,
            INDEX idx_status (status),
            INDEX idx_plan (plan_slug),
            INDEX idx_owner (owner_user_id),
            INDEX idx_period_end (current_period_end)
        ) $charset;");
    }
}
EOF

(ูŠูƒุฑุฑ ู†ูุณ ุงู„ู†ู…ุท ู„ู€ 11 migration ู…ุชุจู‚ูŠุฉ ุญุณุจ ุงู„ุฌุฏุงูˆู„ ุงู„ู…ูุนุฑู‘ูุฉ ููŠ ุงู„ุฌุฒุก 2)

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 30-45 | Core Domain Services

bash

# Domain/Entitlement/EntitlementService.php
cat > src/Domain/Entitlement/EntitlementService.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Domain\Entitlement;

final class EntitlementService {
    private const CACHE_TTL = 300;
    
    public function can(int $tenantId, string $featureKey, ?int $scopeId = null): bool {
        $cacheKey = "entitlement:{$tenantId}:{$featureKey}:{$scopeId}";
        $cached = wp_cache_get($cacheKey, 'kosmos');
        if ($cached !== false) return (bool) $cached;
        
        global $wpdb;
        $row = $wpdb->get_row($wpdb->prepare("
            SELECT * FROM {$wpdb->prefix}kosmos_entitlements
            WHERE tenant_id = %d AND feature_key = %s
              AND (scope_id = %d OR scope_id IS NULL)
              AND is_enabled = 1
              AND (expires_at IS NULL OR expires_at > NOW())
            LIMIT 1
        ", $tenantId, $featureKey, $scopeId ?? 0));
        
        $allowed = $row && ($row->limit_value === null || $row->used_value < $row->limit_value);
        
        wp_cache_set($cacheKey, $allowed ? 1 : 0, 'kosmos', self::CACHE_TTL);
        return (bool) $allowed;
    }
    
    public function consume(int $tenantId, string $featureKey, float $amount = 1): bool {
        global $wpdb;
        
        $affected = $wpdb->query($wpdb->prepare("
            UPDATE {$wpdb->prefix}kosmos_entitlements
            SET used_value = used_value + %f, updated_at = NOW()
            WHERE tenant_id = %d 
              AND feature_key = %s
              AND is_enabled = 1
              AND (limit_value IS NULL OR used_value + %f <= limit_value)
        ", $amount, $tenantId, $featureKey, $amount));
        
        if ($affected) {
            wp_cache_delete("entitlement:{$tenantId}:{$featureKey}:0", 'kosmos');
        }
        
        return $affected > 0;
    }
    
    public function grant(int $tenantId, string $featureKey, int $limit, ?int $scopeId = null): void {
        global $wpdb;
        
        $wpdb->replace("{$wpdb->prefix}kosmos_entitlements", [
            'tenant_id'     => $tenantId,
            'feature_key'   => $featureKey,
            'scope_type'    => 'global',
            'scope_id'      => $scopeId,
            'limit_value'   => $limit,
            'used_value'    => 0,
            'reset_period'  => 'monthly',
            'last_reset_at' => current_time('mysql'),
            'is_enabled'    => 1,
            'created_at'    => current_time('mysql'),
            'updated_at'    => current_time('mysql'),
        ]);
        
        wp_cache_delete("entitlement:{$tenantId}:{$featureKey}:{$scopeId}", 'kosmos');
    }
    
    public function revokeAll(int $tenantId): void {
        global $wpdb;
        $wpdb->update(
            "{$wpdb->prefix}kosmos_entitlements",
            ['is_enabled' => 0],
            ['tenant_id' => $tenantId]
        );
        wp_cache_flush_group('kosmos');
    }
    
    public function grantFromPlan(int $tenantId): void {
        global $wpdb;
        $plan = $wpdb->get_row($wpdb->prepare("
            SELECT p.* FROM {$wpdb->prefix}kosmos_plans p
            JOIN {$wpdb->prefix}kosmos_tenants t ON t.plan_slug = p.slug
            WHERE t.id = %d
        ", $tenantId));
        
        if (!$plan) return;
        
        $limits = json_decode($plan->limits, true) ?: [];
        foreach ($limits as $feature => $value) {
            $this->grant($tenantId, $feature, (int) $value);
        }
    }
}
EOF

bash

# Domain/Product/ProductLockManager.php
cat > src/Domain/Product/ProductLockManager.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Domain\Product;

use KOSMOS\Studio\Application\Jobs\JobQueue;

final class ProductLockManager {
    
    public function lockAll(int $tenantId, string $reason = 'subscription_expired'): int {
        global $wpdb;
        
        $count = $wpdb->query($wpdb->prepare("
            UPDATE {$wpdb->prefix}kosmos_products
            SET locked_by_subscription = 1, sync_status = 'locked', updated_at = NOW()
            WHERE tenant_id = %d AND deleted_at IS NULL
        ", $tenantId));
        
        // ุฃุถู job ู„ุฅุฎูุงุก ุงู„ู…ู†ุชุฌุงุช ู…ู† ุณู„ุฉ
        $targets = $wpdb->get_results($wpdb->prepare("
            SELECT id FROM {$wpdb->prefix}kosmos_targets
            WHERE tenant_id = %d AND deleted_at IS NULL
        ", $tenantId));
        
        foreach ($targets as $t) {
            JobQueue::push($tenantId, 'bulk_lock', [
                'target_id' => (int) $t->id,
                'action'    => 'hide',
                'reason'    => $reason,
            ], priority: 1);
        }
        
        do_action('kosmos/products_locked', $tenantId, $count, $reason);
        
        return (int) $count;
    }
    
    public function unlockAll(int $tenantId): int {
        global $wpdb;
        
        $count = $wpdb->query($wpdb->prepare("
            UPDATE {$wpdb->prefix}kosmos_products
            SET locked_by_subscription = 0, sync_status = 'outdated', updated_at = NOW()
            WHERE tenant_id = %d AND locked_by_subscription = 1
        ", $tenantId));
        
        $targets = $wpdb->get_results($wpdb->prepare("
            SELECT id FROM {$wpdb->prefix}kosmos_targets
            WHERE tenant_id = %d AND is_locked = 1
        ", $tenantId));
        
        foreach ($targets as $t) {
            JobQueue::push($tenantId, 'bulk_lock', [
                'target_id' => (int) $t->id,
                'action'    => 'show',
            ], priority: 1);
        }
        
        do_action('kosmos/products_unlocked', $tenantId, $count);
        
        return (int) $count;
    }
}
EOF

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 45-55 | Salla Integration

bash

# Integrations/Salla/SallaPublisher.php
cat > src/Integrations/Salla/SallaPublisher.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Integrations\Salla;

final class SallaPublisher {
    public function __construct(
        private SallaClient $client,
        private SallaMapper $mapper
    ) {}
    
    public function publish(object $target, array $product): array {
        $payload = $this->mapper->toSalla($product);
        return $this->client->forTarget($target)->post('/products', $payload);
    }
    
    public function hide(object $target, string $externalId): bool {
        $r = $this->client->forTarget($target)->put("/products/{$externalId}", [
            'status' => 'hidden',
        ]);
        return $r['success'] ?? false;
    }
    
    public function show(object $target, string $externalId): bool {
        $r = $this->client->forTarget($target)->put("/products/{$externalId}", [
            'status' => 'sale',
        ]);
        return $r['success'] ?? false;
    }
}
EOF

bash

# Integrations/Salla/SallaClient.php
cat > src/Integrations/Salla/SallaClient.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Integrations\Salla;

use KOSMOS\Studio\Support\Encrypter;

final class SallaClient {
    private const BASE_URL = 'https://api.salla.dev/admin/v2';
    private ?object $target = null;
    
    public function forTarget(object $target): self {
        $clone = clone $this;
        $clone->target = $target;
        return $clone;
    }
    
    public function get(string $path, array $q = []): array {
        return $this->request('GET', $path, $q);
    }
    
    public function post(string $path, array $body = []): array {
        return $this->request('POST', $path, [], $body);
    }
    
    public function put(string $path, array $body = []): array {
        return $this->request('PUT', $path, [], $body);
    }
    
    private function request(string $method, string $path, array $q = [], array $b = []): array {
        if (!$this->target) throw new \RuntimeException('Target not set');
        
        $creds = json_decode(Encrypter::decrypt($this->target->credentials), true);
        $token = $creds['access_token'] ?? '';
        
        $url = self::BASE_URL . $path;
        if ($q) $url .= '?' . http_build_query($q);
        
        $response = wp_remote_request($url, [
            'method'  => $method,
            'timeout' => 30,
            'headers' => [
                'Authorization' => "Bearer {$token}",
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
            ],
            'body' => $b ? wp_json_encode($b) : null,
        ]);
        
        if (is_wp_error($response)) {
            return ['success' => false, 'message' => $response->get_error_message()];
        }
        
        return json_decode(wp_remote_retrieve_body($response), true) ?: ['success' => false];
    }
}
EOF

๐Ÿ• ุงู„ุฏู‚ูŠู‚ุฉ 55-60 | REST Router + Test

bash

# Http/Router.php
cat > src/Http/Router.php << 'EOF'
<?php
declare(strict_types=1);
namespace KOSMOS\Studio\Http;

final class Router {
    public function register(): void {
        register_rest_route('kosmos/v1', '/health', [
            'methods'             => 'GET',
            'callback'            => fn() => [
                'status'  => 'alive',
                'version' => KOSMOS_VERSION,
                'time'    => current_time('c'),
            ],
            'permission_callback' => '__return_true',
        ]);
        
        register_rest_route('kosmos/v1', '/sources/(?P<id>\d+)/sync', [
            'methods'             => 'POST',
            'callback'            => [new \KOSMOS\Studio\Http\Controllers\SourceController(), 'sync'],
            'permission_callback' => fn() => current_user_can('kosmos_sync_sources'),
        ]);
        
        register_rest_route('kosmos/v1', '/ai/tutor', [
            'methods'             => 'POST',
            'callback'            => [new \KOSMOS\Studio\Http\Controllers\AiController(), 'tutor'],
            'permission_callback' => fn() => current_user_can('kosmos_use_ai'),
        ]);
    }
}
EOF

bash

# ุงุฎุชุจุฑ ุงู„ุขู†
echo "โœ… KOSMOS Studio โ€” Skeleton Ready"
docker compose up -d
sleep 30
curl -s http://localhost:8080/wp-json/kosmos/v1/health | jq

ุงู„ู†ุชูŠุฌุฉ ุงู„ู…ุชูˆู‚ุนุฉ:

json

{
  "status": "alive",
  "version": "1.0.0",
  "time": "2025-01-15T10:30:00+00:00"
}

๐Ÿ“‹ ุงู„ุฌุฒุก 13: ุฎู„ุงุตุฉ ุงู„ู…ูˆุงุตูุฉ

ู…ุง ุชู… ุชูˆุซูŠู‚ู‡ ูƒุงู…ู„ุงู‹:

ุงู„ู…ูƒูˆู‘ู†ุงู„ุญุงู„ุฉ
โœ… ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช12 ุฌุฏูˆู„ + ุนู„ุงู‚ุงุช + ูู‡ุงุฑุณ
โœ… ู‡ูŠูƒู„ Plugin~80 ู…ู„ู + namespaces
โœ… Domain Classesูƒูˆุฏ ูƒุงู…ู„ ู„ู€: Tenant, Subscription, Entitlement, Product, Source, Target
โœ… REST API45+ endpoint ู…ูˆุซู‘ู‚
โœ… ู†ุธุงู… ุงู„ุจุงู‚ุงุช4 ุจุงู‚ุงุช + Entitlements
โœ… WooCommerce Hooks15+ hook ู…ุฑุชุจุท
โœ… AI GatewayProvider-based + Metering
โœ… Roles & Capabilities5 roles + 20 capability
โœ… DashboardsTenant + Admin ูƒุงู…ู„ูŠู†
โœ… Job QueueAtomic claim + Retry + Backoff
โœ… Salla IntegrationClient + Publisher + Hide/Show
โœ… Product Lock Systemุงู„ู‚ูู„ ุงู„ุชู„ู‚ุงุฆูŠ ุนู†ุฏ ุงู†ุชู‡ุงุก ุงู„ุงุดุชุฑุงูƒ
โœ… ุฎุทุฉ 60 ุฏู‚ูŠู‚ุฉ12 ู…ุฑุญู„ุฉ ู…ู„ูู‹ุง ุจู…ู„ู

๐ŸŽฏ ุงู„ุฎุทูˆุฉ ุงู„ุชุงู„ูŠุฉ โ€” ุงุฎุชุฑ

#ุงู„ุฎูŠุงุฑุงู„ู…ุญุชูˆู‰
A๐Ÿ”Œ Source Adapters ูƒุงู…ู„ุฉShopify + WooCommerce + AliExpress + CSV + Custom API
B๐Ÿ’ณ Payment Gateways ูƒุงู…ู„ุฉStripe + Moyasar + Tap + PayPal + Webhooks
C๐ŸŽจ Frontend ูƒุงู…ู„React/Next.js Dashboard + Pricing + Tutor UI
D๐Ÿงช Test Suite ูƒุงู…ู„ุฉPHPUnit + Integration + E2E + Load (k6)
E๐Ÿš€ CI/CD + DeploymentGitHub Actions + K8s + Monitoring
F๐Ÿ“š ูƒู„ ู…ุง ุณุจู‚ุงู„ู…ูˆุงุตูุฉ ุชูุตุจุญ ู…ู†ุตุฉ ูƒุงู…ู„ุฉ

ู‚ู„ ุงู„ุญุฑู ูˆุณุฃูƒู…ู„. ๐Ÿ”ฅ

Scroll to Top

๐Ÿค– ู…ูˆุตู‰ ุจู‡ ู„ูƒ ุจู†ุงุกู‹ ุนู„ู‰ ุงู‡ุชู…ุงู…ุงุชูƒ