1. Go to this page and download the library: Download sequra/integration-core library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
sequra / integration-core example snippets
ServiceRegister::registerService(
EncryptorInterface::class,
static function () {
return new Encryptor();
}
);
namespace YourNamespace\Integration;
use SeQura\Core\BusinessLogic\BootstrapComponent;
class Bootstrap extends BootstrapComponent
{
public static function init(): void
{
// Customize initialization logic here as needed.
// For example, register constant values parent::initServices();
// Register your services here
}
}
// Must extend SeQura\Core\Infrastructure\Configuration\Configuration
class PlatformConfiguration extends Configuration
{
// Platform-specific configuration storage and retrieval
public function getConfigValue($key, $defaultValue = null) { /* */ }
public function setConfigValue($key, $value) { /* */ }
public function getAsyncProcessUrl($guid) { /* */ }
// ... other
// Must implement SeQura\Core\Infrastructure\Logger\Interfaces\ShopLoggerAdapter
class PlatformLogger implements ShopLoggerAdapter
{
public function logMessage($level, $message, array $context = []) { /* */ }
public function isLoggingEnabled() { /* */ }
// ... other
// Must implement SeQura\Core\BusinessLogic\Utility\EncryptorInterface
class PlatformEncryptor implements EncryptorInterface
{
public function encrypt($data) { /* */ }
public function decrypt($data) { /* */ }
}
// Must implement StoreServiceInterface
class PlatformStoreService implements StoreServiceInterface
{
public function getStoreId() { /* */ }
public function getStoreName() { /* */ }
public function getStores() { /* */ }
public function getDefaultStore() { /* */ }
}
// Must implement VersionServiceInterface
class PlatformVersionService implements VersionServiceInterface
{
public function getShopName() { /* */ }
public function getShopVersion() { /* */ }
public function getModuleVersion() { /* */ }
}
// Must implement CategoryServiceInterface
class PlatformCategoryService implements CategoryServiceInterface
{
public function getCategory($categoryId) { /* */ }
public function getAllCategories() { /* */ }
}
// Must implement ProductServiceInterface
class PlatformProductService implements ProductServiceInterface
{
public function getProductById($productId) { /* */ }
public function getProductsByIds(array $productIds) { /* */ }
}
// Must implement OrderCreationInterface
class PlatformOrderCreation implements OrderCreationInterface
{
public function createOrder($cartData, $sequraOrderReference) { /* */ }
public function updateOrderStatus($orderId, $status) { /* */ }
}
// Must implement MerchantDataProviderInterface
class PlatformMerchantDataProvider implements MerchantDataProviderInterface
{
public function getMerchantData($orderId) { /* */ }
public function getOrderItems($orderId) { /* */ }
public function getShippingInfo($orderId) { /* */ }
}
// Must implement OrderReportServiceInterface
class PlatformOrderReportService implements OrderReportServiceInterface
{
public function getOrdersForReporting($from, $to) { /* */ }
public function markOrderAsReported($orderId) { /* */ }
}
// Must implement SellingCountriesServiceInterface
class PlatformSellingCountriesService implements SellingCountriesServiceInterface
{
public function getSellingCountries() { /* */ }
public function getCountryByCode($countryCode) { /* */ }
}
// Must implement DisconnectServiceInterface
class PlatformDisconnectService implements DisconnectServiceInterface
{
public function disconnect() { /* */ }
public function cleanup() { /* */ }
}
// Must implement WidgetConfiguratorInterface
class PlatformWidgetConfigurator implements WidgetConfiguratorInterface
{
public function getWidgetAssetsHtml() { /* */ }
public function getWidgetScriptUrl() { /* */ }
public function getSelectors() { /* */ }
}
// Must implement MiniWidgetMessagesProviderInterface
class PlatformMiniWidgetMessagesProvider implements MiniWidgetMessagesProviderInterface
{
public function getMessages($productId, $price) { /* */ }
}
protected static function initRepositories(): void
{
parent::initRepositories();
// Register entity repositories with your platform's storage implementation
RepositoryRegistry::registerRepository(ConfigEntity::class, PlatformBaseRepository::class);
RepositoryRegistry::registerRepository(QueueItem::class, PlatformQueueRepository::class);
RepositoryRegistry::registerRepository(SeQuraOrder::class, PlatformOrderRepository::class);
// ... register all
namespace YourNamespace\Integration\Core\Infrastructure;
use Magento\Framework\Exception\NoSuchEntityException;
use Sequra\Core\Helper\UrlHelper;
use SeQura\Core\Infrastructure\Configuration\Configuration;
class ConfigurationService extends Configuration
{
public const MIN_LOG_LEVEL = 1;
private const INTEGRATION_NAME = 'Magento2';
/**
* @var UrlHelper
*/
private $urlHelper;
/**
* @param UrlHelper $urlHelper
*/
public function __construct(UrlHelper $urlHelper)
{
parent::__construct();
$this->urlHelper = $urlHelper;
}
/**
* @inheritDoc
*/
public function getIntegrationName(): string
{
return self::INTEGRATION_NAME;
}
/**
* @inheritDoc
*
* @throws NoSuchEntityException
*/
public function getAsyncProcessUrl($guid): string
{
$params = [
'guid' => $guid,
'ajax' => 1,
'_nosid' => true,
];
return $this->urlHelper->getFrontendUrl('sequra/asyncprocess/asyncprocess', $params);
}
}
use SeQura\Core\Infrastructure\ServiceRegister;
use SeQura\Core\Infrastructure\Utility\RegexProvider;
// Get the RegexProvider service
$regexProvider = ServiceRegister::getService(RegexProvider::CLASS_NAME);
// Initialize the SequraFE object as an array to be injected in the frontend later as JavaScript object
$sequraFe = array(
// Other config options...
'regex' => $regexProvider->toArray()
);
// Magento Admin Controller using AdminAPI
class GeneralSettingsController extends AbstractConfigurationAction
{
/**
* Get shop categories for configuration dropdown
*/
protected function getShopCategories(): Json
{
// Use AdminAPI with store context and error handling
$response = AdminAPI::get()
->generalSettings($this->storeId) // Store context applied
->getShopCategories(); // Business logic call
$this->addResponseCode($response); // Handle HTTP response codes
return $this->result->setData($response->toArray());
}
/**
* Get current general settings
*/
protected function getGeneralSettings(): Json
{
$response = AdminAPI::get()
->generalSettings($this->storeId)
->getGeneralSettings();
$this->addResponseCode($response);
return $this->result->setData($response->toArray());
}
/**
* Save new general settings
*/
protected function setGeneralSettings(): Json
{
// Extract and validate POST data
$data = $this->getSequraPostData();
// Create strongly-typed request object
$request = new GeneralSettingsRequest(
$data['sendOrderReportsPeriodicallyToSeQura'] ?? true,
$data['showSeQuraCheckoutAsHostedPage'] ?? false,
$data['allowedIPAddresses'] ?? [],
$data['excludedProducts'] ?? [],
$data['excludedCategories'] ?? []
);
// Execute through AdminAPI
$response = AdminAPI::get()
->generalSettings($this->storeId)
->saveGeneralSettings($request);
$this->addResponseCode($response);
return $this->result->setData($response->toArray());
}
}
// Example of the underlying flow:
class GeneralSettingsService
{
public function saveGeneralSettings(GeneralSettings $settings): void
{
// Validate settings
$this->validateSettings($settings);
// Transform to entity
$entity = $this->transformToEntity($settings);
// Persist through repository
$this->repository->save($entity);
// Trigger events
$this->eventBus->publish(new SettingsUpdatedEvent($settings));
}
}
// Automatic validation in widget controllers
if (
!$this->widgetValidatorService->isCurrencySupported($request->getCurrentCurrency()) ||
!$this->widgetValidatorService->isIpAddressValid($request->getCurrentIpAddress()) ||
!$this->widgetValidatorService->isProductSupported($request->getProductId())
) {
return new GetWidgetsCheckoutResponse([]);
}
// Magento Product Block using CheckoutAPI
class ProductWidgetBlock extends Template
{
private $storeManager;
private $checkoutSession;
private $request;
/**
* Get available widgets for the current product page
*/
public function getAvailableWidgets(): array
{
try {
// Get current product ID from request
$productId = $this->request->getParam('id');
if (!$productId) {
return [];
}
$storeId = (string)$this->storeManager->getStore()->getId();
// Use CheckoutAPI to get widgets with context validation
$widgets = CheckoutAPI::get()
->promotionalWidgets($storeId)
->getAvailableWidgetsForProductPage(new PromotionalWidgetsCheckoutRequest(
$this->getShippingAddressCountry(), // Customer's shipping country
$this->getCurrentCountry(), // Store's country
$this->getCurrentCurrency(), // Current currency
$this->getCustomerIpAddress(), // For geo-validation
(string)$productId // Current product
));
if (!$widgets->isSuccessful()) {
return [];
}
return $widgets->toArray()['widgets'];
} catch (Exception $e) {
// CheckoutAPI aspects handle logging automatically
return [];
}
}
/**
* Get shipping country from customer session
*/
private function getShippingAddressCountry(): string
{
$quote = $this->checkoutSession->getQuote();
$shippingAddress = $quote->getShippingAddress();
return $shippingAddress->getCountryId() ?: 'ES';
}
/**
* Get current store country
*/
private function getCurrentCountry(): string
{
return $this->scopeConfig->getValue(
'general/country/default',
ScopeInterface::SCOPE_STORE
);
}
/**
* Get customer IP for geo-validation
*/
private function getCustomerIpAddress(): string
{
return $this->request->getClientIp() ?: '';
}
}
// Template usage (product.phtml)
$widgets = $block->getAvailableWidgets();
// Payment method service using CheckoutAPI
class PaymentMethodsService
{
/**
* Get available SeQura payment methods for current cart
*/
public function getAvailablePaymentMethods(Quote $quote): array
{
try {
// Build order request from cart data
$builder = new CreateOrderRequestBuilder($quote);
$builder->setDeliveryAddress($quote->getShippingAddress())
->setBillingAddress($quote->getBillingAddress())
->setItems($this->getCartItems($quote))
->setTotalWithTax($quote->getGrandTotal());
// Check if SeQura is allowed for this configuration
$generalSettings = AdminAPI::get()
->generalSettings((string)$quote->getStore()->getId())
->getGeneralSettings();
if (!$generalSettings->isSuccessful() || !$builder->isAllowedFor($generalSettings)) {
return [];
}
// Solicit available payment methods from SeQura
$response = CheckoutAPI::get()
->solicitation((string)$quote->getStore()->getId())
->solicitFor($builder);
if (!$response->isSuccessful()) {
return [];
}
return $response->toArray()['availablePaymentMethods'];
} catch (Exception $e) {
return [];
}
}
/**
* Get identification form for specific payment method
*/
public function getIdentificationForm(string $cartId, string $paymentMethod): array
{
$response = CheckoutAPI::get()
->solicitation($this->getStoreId())
->getIdentificationForm($cartId, $paymentMethod, null, true);
return $response->isSuccessful() ? $response->toArray() : [];
}
}
// Early validation prevents unnecessary API calls
if (!$this->widgetValidatorService->isCurrencySupported($currency)) {
return new GetWidgetsCheckoutResponse([]); // Return empty immediately
}
// Widgets adapt to customer context automatically
$request = new PromotionalWidgetsCheckoutRequest(
$shippingCountry, // Determines available financing options
$currentCountry, // Determines legal compliance
$currency, // Determines payment method availability
$ipAddress, // Geo-validation and fraud prevention
$productId // Product-specific exclusions
);
// Magento Webhook Controller
class WebhookController extends Action
{
private $invoiceService;
private $transactionFactory;
private $orderRepository;
/**
* Execute webhook endpoint
*/
public function execute(): void
{
// Only accept POST requests
if (!$this->getRequest()->isPost()) {
return;
}
// Get webhook payload from SeQura
$payload = $this->getRequest()->getPostValue();
// Transform SeQura payload format to internal format
$modifiedPayload = $this->transformPayload($payload);
if (empty($modifiedPayload['storeId'])) {
return;
}
// Set webhook processing flag to prevent interference
self::setIsWebhookProcessing(true);
try {
// Process webhook through WebhookAPI
$response = WebhookAPI::webhookHandler($modifiedPayload['storeId'])
->handleRequest($modifiedPayload);
if ($response->isSuccessful()) {
// Handle post-processing for approved orders
if ($modifiedPayload['sq_state'] === OrderStates::STATE_APPROVED) {
$this->createInvoiceForOrder($modifiedPayload['order_ref']);
}
return; // HTTP 200 response
}
// Handle errors with appropriate HTTP codes
$responseData = $response->toArray();
$httpCode = $this->getErrorCode($responseData);
$this->getResponse()->setHttpResponseCode($httpCode);
} finally {
self::setIsWebhookProcessing(false);
}
}
/**
* Transform SeQura webhook payload to internal format
*/
private function transformPayload(array $payload): array
{
$modifiedPayload = [];
foreach ($payload as $key => $value) {
// Transform 'event' to 'sq_state' and remove prefixes
$newKey = $key === 'event' ? 'sq_state' : $this->trimPrefixFromKey($key);
$modifiedPayload[$newKey] = $value;
}
return $modifiedPayload;
}
/**
* Create invoice for approved orders
*/
private function createInvoiceForOrder(string $orderRef): void
{
try {
// Find Magento order by SeQura reference
$order = $this->findOrderBySeQuraReference($orderRef);
if ($order && $order->canInvoice()) {
// Create invoice
$invoice = $this->invoiceService->prepareInvoice($order);
$invoice->setRequestedCaptureCase(Invoice::CAPTURE_ONLINE);
$invoice->register();
// Save transaction
$transaction = $this->transactionFactory->create()
->addObject($invoice)
->addObject($invoice->getOrder());
$transaction->save();
Logger::logInfo('Invoice created for SeQura order: ' . $orderRef);
}
} catch (Exception $e) {
Logger::logError('Failed to create invoice for order: ' . $orderRef, [
'error' => $e->getMessage()
]);
}
}
/**
* Get appropriate HTTP error code for webhook response
*/
private function getErrorCode(array $responseData): int
{
// 409 = Conflict (temporary issue, SeQura should retry)
// 410 = Gone (permanent issue, SeQura should not retry)
return isset($responseData['errorCode']) && $responseData['errorCode'] === 409
? 409
: 410;
}
}
// Platform implementation of ShopOrderService
class MagentoShopOrderService implements ShopOrderService
{
private $orderRepository;
private $orderStatusService;
/**
* Update order status based on SeQura webhook
*/
public function updateStatus(
Webhook $webhook,
string $status,
?int $reasonCode = null,
?string $message = null
): void {
try {
// Find platform order by SeQura reference
$order = $this->findOrderBySeQuraReference($webhook->getOrderRef());
if (!$order) {
throw new OrderNotFoundException(
"Order not found: {$webhook->getOrderRef()}"
);
}
// Map SeQura state to platform status
$platformStatus = $this->mapSeQuraStateToPlatformStatus(
$webhook->getSqState(),
$status
);
// Update order status
$order->setStatus($platformStatus);
$order->setState($this->getStateForStatus($platformStatus));
// Add status history comment
$comment = $this->buildStatusComment($webhook, $reasonCode, $message);
$order->addStatusHistoryComment($comment, $platformStatus);
// Save order
$this->orderRepository->save($order);
Logger::logInfo("Order status updated", [
'order_ref' => $webhook->getOrderRef(),
'sq_state' => $webhook->getSqState(),
'platform_status' => $platformStatus
]);
} catch (Exception $e) {
Logger::logError("Failed to update order status", [
'order_ref' => $webhook->getOrderRef(),
'sq_state' => $webhook->getSqState(),
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Map SeQura states to platform-specific statuses
*/
private function mapSeQuraStateToPlatformStatus(string $sqState, string $status): string
{
$mapping = [
OrderStates::STATE_APPROVED => 'processing',
OrderStates::STATE_CANCELLED => 'canceled',
OrderStates::STATE_NEEDS_REVIEW => 'payment_review'
];
return $mapping[$sqState] ?? $status;
}
/**
* Build informative status history comment
*/
private function buildStatusComment(
Webhook $webhook,
?int $reasonCode,
?string $message
): string {
$comment = "SeQura payment status updated to: {$webhook->getSqState()}";
if ($reasonCode) {
$comment .= " (Reason code: {$reasonCode})";
}
if ($message) {
$comment .= " - {$message}";
}
return $comment;
}
/**
* Get order IDs for reporting
*/
public function getReportOrderIds(int $page, int $limit = 5000): array
{
// Implementation to get orders for delivery reporting
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('status', 'shipped')
->addFilter('sequra_order_ref', null, 'neq')
->setPageSize($limit)
->setCurrentPage($page)
->create();
$orders = $this->orderRepository->getList($searchCriteria);
return array_map(function($order) {
return $order->getIncrementId();
}, $orders->getItems());
}
}
// The WebhookValidator ensures security and data integrity
class WebhookValidator
{
const ALLOWED_STATES = ['approved', 'cancelled', 'needs_review'];
public function validate(Webhook $webhook): void
{
// 1. Verify order exists
$order = $this->getSeQuraOrderByOrderReference($webhook->getOrderRef());
if (!$order) {
throw new OrderNotFoundException("Order not found", 404);
}
// 2. Validate signature
$expectedSignature = $order->getMerchant()
->getNotificationParameters()['signature'];
if ($webhook->getSignature() !== $expectedSignature) {
throw new InvalidSignatureException("Signature mismatch", 400);
}
// 3. Validate state
if (!in_array($webhook->getSqState(), self::ALLOWED_STATES)) {
throw new InvalidStateException("Unknown event", 400);
}
}
}
// OrderUpdateTask for reliable background processing
class OrderUpdateTask extends Task
{
protected $webhook;
protected $storeId;
public function execute(): void
{
// Execute in proper store context
StoreContext::doWithStore($this->storeId, function () {
$this->doExecute();
});
}
protected function doExecute(): void
{
// Get platform-specific status mapping
$shopStatus = $this->getOrderStatusMappingService()
->getMapping($this->webhook->getSqState());
// Update order in platform
$this->getShopOrderService()
->updateStatus($this->webhook, $shopStatus);
$this->reportProgress(100);
}
}
// Custom task for processing bulk order updates
class BulkOrderSyncTask extends Task
{
private $orderIds;
private $storeId;
private $syncType;
public function __construct(array $orderIds, string $syncType = 'status')
{
parent::__construct();
$this->orderIds = $orderIds;
$this->syncType = $syncType;
// Capture store context when task is created
$this->storeId = StoreContext::getInstance()->getStoreId();
}
/**
* Main task execution logic
*/
public function execute(): void
{
// Execute in proper store context
StoreContext::doWithStore($this->storeId, function() {
$this->doExecute();
});
}
private function doExecute(): void
{
$totalOrders = count($this->orderIds);
$processed = 0;
foreach ($this->orderIds as $orderId) {
try {
// Report progress periodically
$this->reportAlive();
// Process individual order
$this->syncOrderData($orderId);
$processed++;
// Update progress (0-100%)
$progress = ($processed / $totalOrders) * 100;
$this->reportProgress($progress);
} catch (Exception $e) {
Logger::logError("Failed to sync order: {$orderId}", [
'error' => $e->getMessage(),
'task_type' => $this->getType()
]);
// Decide whether to fail entire task or continue
if ($this->isCriticalError($e)) {
throw $e; // This will fail the entire task
}
// Otherwise continue with next order
}
}
// Final progress report
$this->reportProgress(100);
}
/**
* Task serialization for queue storage
*/
public function toArray(): array
{
return [
'storeId' => $this->storeId,
'orderIds' => $this->orderIds,
'syncType' => $this->syncType
];
}
/**
* Task deserialization from queue storage
*/
public static function fromArray(array $data): Serializable
{
return StoreContext::doWithStore($data['storeId'], function() use ($data) {
return new static($data['orderIds'], $data['syncType']);
});
}
/**
* Set task priority (webhooks should be HIGH)
*/
public function getPriority(): int
{
return $this->syncType === 'webhook' ? Priority::HIGH : Priority::NORMAL;
}
/**
* Handle task failure
*/
public function onFail(): void
{
Logger::logWarning("Bulk order sync task failed", [
'store_id' => $this->storeId,
'order_count' => count($this->orderIds),
'sync_type' => $this->syncType
]);
}
private function syncOrderData(string $orderId): void
{
// Implementation depends on sync type
switch ($this->syncType) {
case 'status':
$this->syncOrderStatus($orderId);
break;
case 'webhook':
$this->processWebhookData($orderId);
break;
default:
throw new InvalidArgumentException("Unknown sync type: {$this->syncType}");
}
}
private function isCriticalError(Exception $e): bool
{
// Define which errors should fail the entire task
return $e instanceof AuthenticationException
|| $e instanceof InvalidConfigurationException;
}
}
// Service for managing background operations
class BackgroundTaskManager
{
private $queueService;
public function __construct()
{
$this->queueService = ServiceRegister::getService(QueueService::class);
}
/**
* Enqueue order synchronization task
*/
public function scheduleOrderSync(array $orderIds, string $priority = 'normal'): QueueItem
{
$task = new BulkOrderSyncTask($orderIds, 'status');
$priorityLevel = $priority === 'high' ? Priority::HIGH : Priority::NORMAL;
return $this->queueService->enqueue(
'order-sync', // Queue name
$task, // Task instance
'bulk-operation', // Context for filtering
$priorityLevel // Priority level
);
}
/**
* Schedule webhook processing (high priority)
*/
public function scheduleWebhookProcessing(Webhook $webhook): QueueItem
{
$task = new WebhookProcessingTask($webhook);
return $this->queueService->enqueue(
'webhooks', // Dedicated webhook queue
$task,
"webhook-{$webhook->getOrderRef()}",
Priority::HIGH // Webhooks are always high priority
);
}
/**
* Schedule order reporting (background)
*/
public function scheduleOrderReporting(string $merchantId, array $orderIds): QueueItem
{
$task = new OrderReportTask($merchantId, $orderIds);
return $this->queueService->enqueue(
'reporting',
$task,
"report-{$merchantId}",
Priority::LOW // Reporting can wait
);
}
/**
* Check task status
*/
public function getTaskStatus(int $taskId): ?array
{
$queueItem = $this->queueService->find($taskId);
if (!$queueItem) {
return null;
}
return [
'id' => $queueItem->getId(),
'status' => $queueItem->getStatus(),
'progress' => $queueItem->getProgressPercent(),
'created_at' => $queueItem->getCreateTimestamp(),
'started_at' => $queueItem->getStartTimestamp(),
'finished_at' => $queueItem->getFinishTimestamp(),
'retries' => $queueItem->getRetries(),
'failure_description' => $queueItem->getFailureDescription()
];
}
/**
* Get running tasks for monitoring
*/
public function getRunningTasks(): array
{
$runningItems = $this->queueService->findRunningItems();
return array_map(function(QueueItem $item) {
return [
'id' => $item->getId(),
'type' => $item->getTaskType(),
'progress' => $item->getProgressPercent(),
'started_at' => $item->getStartTimestamp(),
'last_activity' => $item->getLastUpdateTimestamp()
];
}, $runningItems);
}
/**
* Find failed tasks for retry
*/
public function getFailedTasks(int $limit = 50): array
{
$filter = new QueryFilter();
$filter->where('status', Operators::EQUALS, QueueItem::FAILED)
->where('retries', Operators::LESS_THAN, QueueService::MAX_RETRIES)
->setLimit($limit);
$repository = RepositoryRegistry::getRepository(QueueItem::getClassName());
return $repository->select($filter);
}
/**
* Retry failed task
*/
public function retryTask(int $taskId): bool
{
$queueItem = $this->queueService->find($taskId);
if (!$queueItem || $queueItem->getStatus() !== QueueItem::FAILED) {
return false;
}
try {
$this->queueService->requeue($queueItem);
return true;
} catch (Exception $e) {
Logger::logError("Failed to retry task {$taskId}", [
'error' => $e->getMessage()
]);
return false;
}
}
}
// Task system health monitoring
class TaskHealthMonitor
{
private $queueService;
public function __construct()
{
$this->queueService = ServiceRegister::getService(QueueService::class);
}
/**
* Get system health overview
*/
public function getSystemHealth(): array
{
return [
'queue_stats' => $this->getQueueStatistics(),
'long_running_tasks' => $this->getLongRunningTasks(),
'failed_tasks_summary' => $this->getFailedTasksSummary(),
'queue_health' => $this->assessQueueHealth()
];
}
private function getQueueStatistics(): array
{
$filter = new QueryFilter();
$repository = RepositoryRegistry::getRepository(QueueItem::getClassName());
$stats = [];
foreach ([QueueItem::QUEUED, QueueItem::IN_PROGRESS, QueueItem::COMPLETED, QueueItem::FAILED] as $status) {
$filter->where('status', Operators::EQUALS, $status);
$stats[$status] = $repository->count($filter);
$filter->reset();
}
return $stats;
}
private function getLongRunningTasks(): array
{
$runningItems = $this->queueService->findRunningItems();
$longRunning = [];
foreach ($runningItems as $item) {
$runningTime = time() - $item->getStartTimestamp();
if ($runningTime > 300) { // More than 5 minutes
$longRunning[] = [
'id' => $item->getId(),
'type' => $item->getTaskType(),
'running_time_seconds' => $runningTime,
'progress' => $item->getProgressPercent()
];
}
}
return $longRunning;
}
private function assessQueueHealth(): string
{
$stats = $this->getQueueStatistics();
$longRunning = $this->getLongRunningTasks();
// Health assessment logic
if (count($longRunning) > 5) {
return 'CRITICAL';
}
if ($stats[QueueItem::FAILED] > 10) {
return 'WARNING';
}
if ($stats[QueueItem::QUEUED] > 100) {
return 'BUSY';
}
return 'HEALTHY';
}
}
// Standard task usage pattern:
$task = new CustomTask($data); // Create task with data
$queueItem = $queueService->enqueue( // Enqueue for processing
'queue-name',
$task,
'context',
Priority::NORMAL
);
// Monitor execution
$status = $queueService->find($queueItem->getId());
echo "Task status: {$status->getStatus()}, Progress: {$status->getProgressPercent()}%";
// Custom entity for storing order synchronization status
class OrderSyncStatus extends Entity
{
const CLASS_NAME = __CLASS__;
// Entity fields
protected $sequraOrderRef;
protected $platformOrderId;
protected $lastSyncTimestamp;
protected $syncStatus;
protected $errorMessage;
protected $retryCount;
protected $storeId;
// Define all fields for ORM
protected $fields = [
'id',
'sequraOrderRef',
'platformOrderId',
'lastSyncTimestamp',
'syncStatus',
'errorMessage',
'retryCount',
'storeId'
];
/**
* Entity configuration defines database schema
*/
public function getConfig(): EntityConfiguration
{
$config = new EntityConfiguration();
// Primary key
$config->setTable('sequra_order_sync_status')
->setPrimaryKey('id');
// Indexes for performance
$config->addIndex('idx_sequra_ref', ['sequraOrderRef'])
->addIndex('idx_platform_order', ['platformOrderId'])
->addIndex('idx_store_id', ['storeId'])
->addIndex('idx_status', ['syncStatus']);
return $config;
}
// Getters and setters
public function getSequraOrderRef(): ?string
{
return $this->sequraOrderRef;
}
public function setSequraOrderRef(string $sequraOrderRef): void
{
$this->sequraOrderRef = $sequraOrderRef;
}
public function getPlatformOrderId(): ?string
{
return $this->platformOrderId;
}
public function setPlatformOrderId(string $platformOrderId): void
{
$this->platformOrderId = $platformOrderId;
}
public function getLastSyncTimestamp(): ?int
{
return $this->lastSyncTimestamp;
}
public function setLastSyncTimestamp(int $timestamp): void
{
$this->lastSyncTimestamp = $timestamp;
}
public function getSyncStatus(): ?string
{
return $this->syncStatus;
}
public function setSyncStatus(string $status): void
{
$this->syncStatus = $status;
}
public function getErrorMessage(): ?string
{
return $this->errorMessage;
}
public function setErrorMessage(?string $message): void
{
$this->errorMessage = $message;
}
public function getRetryCount(): int
{
return $this->retryCount ?? 0;
}
public function setRetryCount(int $count): void
{
$this->retryCount = $count;
}
public function getStoreId(): ?string
{
return $this->storeId;
}
public function setStoreId(string $storeId): void
{
$this->storeId = $storeId;
}
/**
* Business logic methods
*/
public function markAsSuccessful(): void
{
$this->setSyncStatus('completed');
$this->setLastSyncTimestamp(time());
$this->setErrorMessage(null);
}
public function markAsFailed(string $errorMessage): void
{
$this->setSyncStatus('failed');
$this->setLastSyncTimestamp(time());
$this->setErrorMessage($errorMessage);
$this->setRetryCount($this->getRetryCount() + 1);
}
public function canRetry(int $maxRetries = 3): bool
{
return $this->getRetryCount() < $maxRetries;
}
}
// Magento implementation of repository
class MagentoOrderSyncStatusRepository implements RepositoryInterface
{
private $connection;
private $entityClass;
private $tableName = 'sequra_order_sync_status';
public function __construct()
{
// Get Magento database connection
$this->connection = ObjectManager::getInstance()
->get(ResourceConnection::class)
->getConnection();
}
public function setEntityClass(string $entityClass): void
{
$this->entityClass = $entityClass;
}
/**
* Find entities matching filter criteria
*/
public function select(QueryFilter $filter = null): array
{
$select = $this->connection->select()->from($this->tableName);
if ($filter) {
$this->applyFilter($select, $filter);
}
$rows = $this->connection->fetchAll($select);
return array_map(function($row) {
return $this->entityClass::fromArray($row);
}, $rows);
}
/**
* Find single entity
*/
public function selectOne(QueryFilter $filter = null): ?Entity
{
$results = $this->select($filter);
return $results ? $results[0] : null;
}
/**
* Save new entity
*/
public function save(Entity $entity): int
{
$data = $this->entityToArray($entity);
unset($data['id']); // Auto-increment
$this->connection->insert($this->tableName, $data);
$id = $this->connection->lastInsertId();
$entity->setId($id);
return $id;
}
/**
* Update existing entity
*/
public function update(Entity $entity): bool
{
$data = $this->entityToArray($entity);
$id = $data['id'];
unset($data['id']);
$affected = $this->connection->update(
$this->tableName,
$data,
['id = ?' => $id]
);
return $affected > 0;
}
/**
* Delete entity
*/
public function delete(Entity $entity): bool
{
$affected = $this->connection->delete(
$this->tableName,
['id = ?' => $entity->getId()]
);
return $affected > 0;
}
/**
* Count entities matching filter
*/
public function count(QueryFilter $filter = null): int
{
$select = $this->connection->select()
->from($this->tableName, 'COUNT(*) as count');
if ($filter) {
$this->applyFilter($select, $filter);
}
return (int)$this->connection->fetchOne($select);
}
/**
* Apply QueryFilter to database select
*/
private function applyFilter(Select $select, QueryFilter $filter): void
{
// Apply WHERE conditions
foreach ($filter->getConditions() as $condition) {
$column = $condition->getColumn();
$operator = $condition->getOperator();
$value = $condition->getValue();
switch ($operator) {
case Operators::EQUALS:
$select->where("{$column} = ?", $value);
break;
case Operators::NOT_EQUALS:
$select->where("{$column} != ?", $value);
break;
case Operators::GREATER_THAN:
$select->where("{$column} > ?", $value);
break;
case Operators::LESS_THAN:
$select->where("{$column} < ?", $value);
break;
case Operators::IN:
$select->where("{$column} IN (?)", $value);
break;
case Operators::LIKE:
$select->where("{$column} LIKE ?", $value);
break;
}
}
// Apply ORDER BY
if ($filter->getOrderBy()) {
$select->order($filter->getOrderBy() . ' ' . $filter->getOrderDirection());
}
// Apply LIMIT and OFFSET
if ($filter->getLimit()) {
$select->limit($filter->getLimit(), $filter->getOffset());
}
}
/**
* Convert entity to database array
*/
private function entityToArray(Entity $entity): array
{
$array = $entity->toArray();
// Handle date/time conversions
if (isset($array['lastSyncTimestamp']) && $array['lastSyncTimestamp']) {
$array['lastSyncTimestamp'] = date('Y-m-d H:i:s', $array['lastSyncTimestamp']);
}
return $array;
}
}
// Service using ORM for data operations
class OrderSyncService
{
private $repository;
public function __construct()
{
$this->repository = RepositoryRegistry::getRepository(OrderSyncStatus::class);
}
/**
* Record successful order synchronization
*/
public function recordSuccessfulSync(string $sequraRef, string $platformOrderId): void
{
$syncStatus = $this->findBySequraRef($sequraRef);
if (!$syncStatus) {
$syncStatus = new OrderSyncStatus();
$syncStatus->setSequraOrderRef($sequraRef);
$syncStatus->setPlatformOrderId($platformOrderId);
$syncStatus->setStoreId(StoreContext::getInstance()->getStoreId());
}
$syncStatus->markAsSuccessful();
if ($syncStatus->getId()) {
$this->repository->update($syncStatus);
} else {
$this->repository->save($syncStatus);
}
}
/**
* Record failed synchronization
*/
public function recordFailedSync(string $sequraRef, string $errorMessage): void
{
$syncStatus = $this->findBySequraRef($sequraRef);
if (!$syncStatus) {
$syncStatus = new OrderSyncStatus();
$syncStatus->setSequraOrderRef($sequraRef);
$syncStatus->setStoreId(StoreContext::getInstance()->getStoreId());
}
$syncStatus->markAsFailed($errorMessage);
if ($syncStatus->getId()) {
$this->repository->update($syncStatus);
} else {
$this->repository->save($syncStatus);
}
}
/**
* Find sync status by SeQura reference
*/
public function findBySequraRef(string $sequraRef): ?OrderSyncStatus
{
$filter = new QueryFilter();
$filter->where('sequraOrderRef', Operators::EQUALS, $sequraRef);
return $this->repository->selectOne($filter);
}
/**
* Get failed syncs that can be retried
*/
public function getRetryableFailed(int $maxRetries = 3): array
{
$filter = new QueryFilter();
$filter->where('syncStatus', Operators::EQUALS, 'failed')
->where('retryCount', Operators::LESS_THAN, $maxRetries)
->orderBy('lastSyncTimestamp', QueryFilter::ORDER_ASC)
->setLimit(50);
return $this->repository->select($filter);
}
/**
* Get sync statistics
*/
public function getSyncStatistics(string $storeId): array
{
$baseFilter = new QueryFilter();
$baseFilter->where('storeId', Operators::EQUALS, $storeId);
$stats = [];
foreach (['completed', 'failed', 'pending'] as $status) {
$filter = clone $baseFilter;
$filter->where('syncStatus', Operators::EQUALS, $status);
$stats[$status] = $this->repository->count($filter);
}
return $stats;
}
/**
* Clean up old sync records
*/
public function cleanupOldRecords(int $daysOld = 30): int
{
$cutoffTimestamp = time() - ($daysOld * 24 * 60 * 60);
$filter = new QueryFilter();
$filter->where('lastSyncTimestamp', Operators::LESS_THAN, $cutoffTimestamp)
->where('syncStatus', Operators::EQUALS, 'completed');
$oldRecords = $this->repository->select($filter);
$deleted = 0;
foreach ($oldRecords as $record) {
if ($this->repository->delete($record)) {
$deleted++;
}
}
return $deleted;
}
}
// Complex queries using QueryFilter
class AdvancedOrderQueries
{
private $repository;
public function __construct()
{
$this->repository = RepositoryRegistry::getRepository(OrderSyncStatus::class);
}
/**
* Find orders by multiple criteria
*/
public function findOrdersByDateRange(
DateTime $startDate,
DateTime $endDate,
array $statuses = []
): array {
$filter = new QueryFilter();
// Date range
$filter->where('lastSyncTimestamp', Operators::GREATER_THAN, $startDate->getTimestamp())
->where('lastSyncTimestamp', Operators::LESS_THAN, $endDate->getTimestamp());
// Status filter
if (!empty($statuses)) {
$filter->where('syncStatus', Operators::IN, $statuses);
}
// Ordering and pagination
$filter->orderBy('lastSyncTimestamp', QueryFilter::ORDER_DESC)
->setLimit(100);
return $this->repository->select($filter);
}
/**
* Find orders with high retry counts
*/
public function findProblematicOrders(): array
{
$filter = new QueryFilter();
$filter->where('retryCount', Operators::GREATER_THAN, 2)
->where('syncStatus', Operators::EQUALS, 'failed')
->orderBy('retryCount', QueryFilter::ORDER_DESC);
return $this->repository->select($filter);
}
/**
* Search orders by pattern
*/
public function searchOrdersByReference(string $pattern): array
{
$filter = new QueryFilter();
$filter->where('sequraOrderRef', Operators::LIKE, "%{$pattern}%")
->orderBy('lastSyncTimestamp', QueryFilter::ORDER_DESC)
->setLimit(50);
return $this->repository->select($filter);
}
/**
* Get aggregated statistics
*/
public function getDetailedStatistics(): array
{
// Since ORM doesn't support aggregation, use platform-specific queries
// This would typically be implemented in the repository layer
$stats = [
'total_orders' => $this->repository->count(),
'by_status' => [],
'retry_distribution' => []
];
// Get counts by status
foreach (['completed', 'failed', 'pending'] as $status) {
$filter = new QueryFilter();
$filter->where('syncStatus', Operators::EQUALS, $status);
$stats['by_status'][$status] = $this->repository->count($filter);
}
return $stats;
}
}
// Basic store context switching
class MultiStoreOrderService
{
private $orderRepository;
private $configService;
public function __construct()
{
$this->orderRepository = RepositoryRegistry::getRepository(SeQuraOrder::class);
$this->configService = ServiceRegister::getService(Configuration::class);
}
/**
* Process order in specific store context
*/
public function processOrderForStore(string $storeId, array $orderData): SeQuraOrder
{
// Execute operation with store context
return StoreContext::doWithStore($storeId, function() use ($orderData) {
// All operations inside this closure run with $storeId context
// 1. Configuration is store-specific
$merchantId = $this->configService->getConfigValue('merchant_id');
// 2. Repository queries are automatically filtered by store
$existingOrder = $this->findExistingOrder($orderData['reference']);
// 3. API calls use store-specific credentials
$orderService = ServiceRegister::getService(OrderService::class);
$newOrder = $orderService->createOrder($orderData);
// 4. Saved data is automatically tagged with store context
$this->orderRepository->save($newOrder);
return $newOrder;
});
}
/**
* Process orders for multiple stores
*/
public function processOrdersForMultipleStores(array $storeOrders): array
{
$results = [];
foreach ($storeOrders as $storeId => $orderDataArray) {
try {
// Switch context for each store
$storeResults = StoreContext::doWithStore($storeId, function() use ($orderDataArray) {
$orders = [];
foreach ($orderDataArray as $orderData) {
// Each order processed in correct store context
$orders[] = $this->processStoreOrder($orderData);
}
return $orders;
});
$results[$storeId] = [
'success' => true,
'orders' => $storeResults
];
} catch (Exception $e) {
$results[$storeId] = [
'success' => false,
'error' => $e->getMessage()
];
Logger::logError("Failed to process orders for store {$storeId}", [
'error' => $e->getMessage(),
'store_id' => $storeId
]);
}
}
return $results;
}
/**
* Get current store context
*/
public function getCurrentStoreOperations(): array
{
$currentStoreId = StoreContext::getInstance()->getStoreId();
return [
'store_id' => $currentStoreId,
'merchant_id' => $this->configService->getConfigValue('merchant_id'),
'environment' => $this->configService->getConfigValue('environment'),
'orders_count' => $this->getOrderCountForCurrentStore()
];
}
private function processStoreOrder(array $orderData): SeQuraOrder
{
// This runs within store context - all operations are store-specific
$orderService = ServiceRegister::getService(OrderService::class);
return $orderService->createOrder($orderData);
}
private function getOrderCountForCurrentStore(): int
{
// Repository automatically filters by current store context
return $this->orderRepository->count();
}
}
// Store-aware repository implementation
class StoreAwareOrderRepository implements RepositoryInterface
{
private $connection;
private $entityClass;
public function select(QueryFilter $filter = null): array
{
// Automatically add store context to all queries
$storeFilter = $this->addStoreContextFilter($filter);
$select = $this->connection->select()->from('sequra_orders');
$this->applyFilter($select, $storeFilter);
$rows = $this->connection->fetchAll($select);
return array_map(function($row) {
return $this->entityClass::fromArray($row);
}, $rows);
}
public function save(Entity $entity): int
{
$data = $entity->toArray();
// Automatically add store context to saved entities
$data['store_id'] = StoreContext::getInstance()->getStoreId();
$this->connection->insert('sequra_orders', $data);
$id = $this->connection->lastInsertId();
$entity->setId($id);
return $id;
}
/**
* Automatically add store context filter to all queries
*/
private function addStoreContextFilter(?QueryFilter $filter): QueryFilter
{
if (!$filter) {
$filter = new QueryFilter();
}
$currentStoreId = StoreContext::getInstance()->getStoreId();
if ($currentStoreId) {
$filter->where('store_id', Operators::EQUALS, $currentStoreId);
}
return $filter;
}
/**
* Count entities for current store only
*/
public function count(QueryFilter $filter = null): int
{
$storeFilter = $this->addStoreContextFilter($filter);
$select = $this->connection->select()
->from('sequra_orders', 'COUNT(*) as count');
$this->applyFilter($select, $storeFilter);
return (int)$this->connection->fetchOne($select);
}
}
// Store-aware task implementation
class MultiStoreReportTask extends Task
{
private $storeConfigurations;
private $reportType;
public function __construct(array $storeConfigurations, string $reportType)
{
parent::__construct();
$this->storeConfigurations = $storeConfigurations;
$this->reportType = $reportType;
}
public function execute(): void
{
$totalStores = count($this->storeConfigurations);
$processed = 0;
foreach ($this->storeConfigurations as $storeId => $config) {
try {
// Execute each store's processing in its context
StoreContext::doWithStore($storeId, function() use ($config) {
$this->processStoreReport($config);
});
$processed++;
$progress = ($processed / $totalStores) * 100;
$this->reportProgress($progress);
} catch (Exception $e) {
Logger::logError("Failed to process report for store {$storeId}", [
'store_id' => $storeId,
'report_type' => $this->reportType,
'error' => $e->getMessage()
]);
// Continue with other stores
$processed++;
}
}
}
private function processStoreReport(array $config): void
{
// All operations here run in correct store context
$orderService = ServiceRegister::getService(OrderService::class);
$reportService = ServiceRegister::getService(OrderReportService::class);
// Get orders for current store (context filtering automatic)
$orders = $orderService->getOrdersForReporting($config['date_from'], $config['date_to']);
// Generate report for current store
$reportService->generateReport($orders, $this->reportType);
}
public function toArray(): array
{
return [
'storeConfigurations' => $this->storeConfigurations,
'reportType' => $this->reportType
];
}
public static function fromArray(array $data): Serializable
{
return new static($data['storeConfigurations'], $data['reportType']);
}
}
// Store-specific configuration service
class MultiStoreConfigurationService
{
private $configService;
public function __construct()
{
$this->configService = ServiceRegister::getService(Configuration::class);
}
/**
* Get configuration for specific store
*/
public function getStoreConfiguration(string $storeId): array
{
return StoreContext::doWithStore($storeId, function() {
return [
'merchant_id' => $this->configService->getConfigValue('merchant_id'),
'environment' => $this->configService->getConfigValue('environment'),
'api_username' => $this->configService->getConfigValue('api_username'),
'webhook_url' => $this->configService->getConfigValue('webhook_url'),
'enabled' => $this->configService->getConfigValue('enabled', false)
];
});
}
/**
* Update configuration for specific store
*/
public function updateStoreConfiguration(string $storeId, array $config): void
{
StoreContext::doWithStore($storeId, function() use ($config) {
foreach ($config as $key => $value) {
$this->configService->setConfigValue($key, $value);
}
});
}
/**
* Get configurations for all stores
*/
public function getAllStoreConfigurations(array $storeIds): array
{
$configurations = [];
foreach ($storeIds as $storeId) {
try {
$configurations[$storeId] = $this->getStoreConfiguration($storeId);
} catch (Exception $e) {
$configurations[$storeId] = [
'error' => $e->getMessage(),
'enabled' => false
];
}
}
return $configurations;
}
/**
* Validate store configuration
*/
public function validateStoreConfiguration(string $storeId): array
{
return StoreContext::doWithStore($storeId, function() {
$config = $this->getStoreConfiguration($storeId);
$errors = [];
if (empty($config['merchant_id'])) {
$errors[] = 'Merchant ID is
// Context-aware API usage
class ContextAwareAPIService
{
/**
* AdminAPI automatically applies store context
*/
public function getStoreSettings(string $storeId): array
{
// Context is automatically applied to all AdminAPI calls
$response = AdminAPI::get()
->generalSettings($storeId) // Store context applied here
->getGeneralSettings();
return $response->toArray();
}
/**
* CheckoutAPI with store context
*/
public function getPaymentMethodsForStore(string $storeId, array $cartData): array
{
// Context ensures correct merchant credentials are used
$response = CheckoutAPI::get()
->solicitation($storeId) // Store context applied here
->solicitFor($cartData);
return $response->toArray();
}
/**
* WebhookAPI with store context
*/
public function processWebhookForStore(string $storeId, array $webhookData): bool
{
// Context ensures webhook is processed for correct store
$response = WebhookAPI::webhookHandler($storeId) // Store context applied here
->handleRequest($webhookData);
return $response->isSuccessful();
}
/**
* Process operations for multiple stores
*/
public function bulkStoreOperations(array $storeOperations): array
{
$results = [];
foreach ($storeOperations as $storeId => $operation) {
try {
switch ($operation['type']) {
case 'settings':
$results[$storeId] = $this->getStoreSettings($storeId);
break;
case 'payment_methods':
$results[$storeId] = $this->getPaymentMethodsForStore($storeId, $operation['data']);
break;
case 'webhook':
$results[$storeId] = $this->processWebhookForStore($storeId, $operation['data']);
break;
}
} catch (Exception $e) {
$results[$storeId] = ['error' => $e->getMessage()];
}
}
return $results;
}
}
// Standard multi-store usage pattern:
StoreContext::doWithStore($storeId, function() {
// All operations here are automatically store-scoped:
// - Configuration access
// - Database queries
// - API calls
// - Task execution
// - Event handling
});
// Custom API service using proxies
class SeQuraAPIService
{
private $authorizedProxyFactory;
private $connectionService;
public function __construct()
{
$this->authorizedProxyFactory = ServiceRegister::getService(AuthorizedProxyFactory::class);
$this->connectionService = ServiceRegister::getService(ConnectionService::class);
}
/**
* Create order using OrderProxy
*/
public function createOrder(string $merchantId, array $orderData): SeQuraOrder
{
try {
// Build order request
$orderRequest = $this->buildOrderRequest($orderData);
// Get authenticated proxy for merchant
$orderProxy = new OrderProxy($this->authorizedProxyFactory);
// Create order via API
$sequraOrder = $orderProxy->createOrder($orderRequest);
Logger::logInfo('Order created successfully', [
'merchant_id' => $merchantId,
'order_ref' => $sequraOrder->getReference(),
'amount' => $sequraOrder->getTotalWithTax()
]);
return $sequraOrder;
} catch (HttpApiUnauthorizedException $e) {
Logger::logError('Authentication failed for order creation', [
'merchant_id' => $merchantId,
'error' => $e->getMessage()
]);
throw new AuthenticationException('Invalid credentials for merchant');
} catch (HttpApiNotFoundException $e) {
Logger::logError('API endpoint not found', [
'merchant_id' => $merchantId,
'error' => $e->getMessage()
]);
throw new APIException('SeQura API endpoint not available');
} catch (HttpRequestException $e) {
Logger::logError('HTTP request failed for order creation', [
'merchant_id' => $merchantId,
'error' => $e->getMessage()
]);
throw new CommunicationException('Failed to communicate with SeQura API');
}
}
/**
* Get available payment methods
*/
public function getPaymentMethods(string $merchantId, array $requestData): array
{
try {
$request = new GetAvailablePaymentMethodsRequest(
$merchantId,
$requestData['amount'],
$requestData['currency'],
$requestData['country']
);
$orderProxy = new OrderProxy($this->authorizedProxyFactory);
$paymentMethods = $orderProxy->getAvailablePaymentMethods($request);
Logger::logInfo('Payment methods retrieved', [
'merchant_id' => $merchantId,
'method_count' => count($paymentMethods),
'amount' => $requestData['amount']
]);
return $paymentMethods;
} catch (Exception $e) {
Logger::logError('Failed to get payment methods', [
'merchant_id' => $merchantId,
'error' => $e->getMessage()
]);
// Return empty array for graceful degradation
return [];
}
}
/**
* Update order status
*/
public function updateOrder(string $merchantId, string $orderRef, array $updateData): bool
{
try {
$updateRequest = new UpdateOrderRequest($orderRef, $updateData);
$orderProxy = new OrderProxy($this->authorizedProxyFactory);
$success = $orderProxy->updateOrder($updateRequest);
if ($success) {
Logger::logInfo('Order updated successfully', [
'merchant_id' => $merchantId,
'order_ref' => $orderRef,
'update_type' => $updateData['type'] ?? 'unknown'
]);
}
return $success;
} catch (Exception $e) {
Logger::logError('Failed to update order', [
'merchant_id' => $merchantId,
'order_ref' => $orderRef,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Test API connection
*/
public function testConnection(string $merchantId): array
{
try {
// Test basic authentication
$connectionProxy = new ConnectionProxy();
$connectionData = $this->connectionService->getConnectionDataByMerchantId($merchantId);
$isValid = $connectionProxy->validateCredentials(
$connectionData->getAuthorizationCredentials()
);
if (!$isValid) {
return [
'success' => false,
'error' => 'Invalid credentials'
];
}
// Test order API access
$testRequest = new GetAvailablePaymentMethodsRequest(
$merchantId,
100, // Test amount
'EUR',
'ES'
);
$orderProxy = new OrderProxy($this->authorizedProxyFactory);
$methods = $orderProxy->getAvailablePaymentMethods($testRequest);
return [
'success' => true,
'payment_methods_count' => count($methods),
'environment' => $connectionData->getEnvironment()
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Submit order report
*/
public function submitOrderReport(string $merchantId, array $orderIds): bool
{
try {
$reportProxy = new OrderReportProxy($this->authorizedProxyFactory);
// Build report data
$reportData = $this->buildReportData($orderIds);
$success = $reportProxy->submitDeliveryReport($merchantId, $reportData);
if ($success) {
Logger::logInfo('Order report submitted', [
'merchant_id' => $merchantId,
'order_count' => count($orderIds)
]);
}
return $success;
} catch (Exception $e) {
Logger::logError('Failed to submit order report', [
'merchant_id' => $merchantId,
'order_count' => count($orderIds),
'error' => $e->getMessage()
]);
return false;
}
}
private function buildOrderRequest(array $orderData): CreateOrderRequest
{
// Build CreateOrderRequest from platform order data
// This would
// Extended proxy factory with additional features
class ExtendedProxyFactory extends AuthorizedProxyFactory
{
private $cache;
private $retryPolicy;
public function __construct(
HttpClient $httpClient,
ConnectionService $connectionService,
DeploymentsService $deploymentsService,
CacheInterface $cache = null
) {
parent::__construct($httpClient, $connectionService, $deploymentsService);
$this->cache = $cache;
$this->retryPolicy = new RetryPolicy();
}
/**
* Build proxy with caching
*/
public function buildWithCache(string $merchantId): AuthorizedProxy
{
$cacheKey = "proxy_{$merchantId}";
if ($this->cache && $this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
$proxy = $this->build($merchantId);
if ($this->cache) {
$this->cache->set($cacheKey, $proxy, 3600); // Cache for 1 hour
}
return $proxy;
}
/**
* Build proxy with retry capabilities
*/
public function buildWithRetry(string $merchantId): AuthorizedProxy
{
$proxy = $this->build($merchantId);
// Wrap proxy with retry logic
return new RetryableProxy($proxy, $this->retryPolicy);
}
/**
* Build proxy for specific environment
*/
public function buildForEnvironment(string $merchantId, string $environment): AuthorizedProxy
{
$connectionData = $this->connectionService->getConnectionDataByMerchantId($merchantId);
// Override environment
$connectionData->setEnvironment($environment);
$deployment = $this->deploymentsService->getDeploymentById(
$connectionData->getDeployment()
);
return new AuthorizedProxy($this->client, $connectionData, $deployment, $merchantId);
}
}
// API error handling and retry service
class APIErrorHandler
{
private $maxRetries = 3;
private $retryDelay = 1000; // milliseconds
/**
* Execute API call with retry logic
*/
public function executeWithRetry(callable $apiCall, array $context = []): mixed
{
$attempt = 0;
$lastException = null;
while ($attempt < $this->maxRetries) {
try {
return $apiCall();
} catch (HttpApiUnauthorizedException $e) {
// Don't retry authentication errors
Logger::logError('Authentication error - not retrying', array_merge($context, [
'error' => $e->getMessage(),
'attempt' => $attempt + 1
]));
throw $e;
} catch (HttpApiNotFoundException $e) {
// Don't retry 404 errors
Logger::logError('Resource not found - not retrying', array_merge($context, [
'error' => $e->getMessage(),
'attempt' => $attempt + 1
]));
throw $e;
} catch (HttpRequestException $e) {
$lastException = $e;
$attempt++;
if ($attempt < $this->maxRetries) {
Logger::logWarning('API call failed - retrying', array_merge($context, [
'error' => $e->getMessage(),
'attempt' => $attempt,
'retry_in_ms' => $this->retryDelay
]));
// Exponential backoff
usleep($this->retryDelay * 1000 * $attempt);
} else {
Logger::logError('API call failed after all retries', array_merge($context, [
'error' => $e->getMessage(),
'total_attempts' => $attempt
]));
}
}
}
throw $lastException ?? new Exception('API call failed');
}
/**
* Handle different types of API errors
*/
public function handleAPIError(Exception $e, array $context = []): array
{
switch (get_class($e)) {
case HttpApiUnauthorizedException::class:
return [
'type' => 'authentication',
'message' => 'Invalid credentials or expired token',
'retryable' => false,
'action' => 'check_credentials'
];
case HttpApiNotFoundException::class:
return [
'type' => 'not_found',
'message' => 'Resource or endpoint not found',
'retryable' => false,
'action' => 'check_endpoint'
];
case HttpApiInvalidRequestBodyException::class:
return [
'type' => 'invalid_request',
'message' => 'Request data is invalid',
'retryable' => false,
'action' => 'validate_data'
];
case HttpRequestException::class:
return [
'type' => 'network',
'message' => 'Network or server error',
'retryable' => true,
'action' => 'retry_later'
];
default:
return [
'type' => 'unknown',
'message' => $e->getMessage(),
'retryable' => false,
'action' => 'investigate'
];
}
}
}
// API health monitoring service
class APIHealthMonitor
{
private $proxyFactory;
private $metrics;
public function __construct()
{
$this->proxyFactory = ServiceRegister::getService(AuthorizedProxyFactory::class);
$this->metrics = new APIMetrics();
}
/**
* Check API health for all merchants
*/
public function checkAPIHealth(array $merchantIds): array
{
$results = [];
foreach ($merchantIds as $merchantId) {
$results[$merchantId] = $this->checkMerchantAPIHealth($merchantId);
}
return [
'overall_health' => $this->calculateOverallHealth($results),
'merchant_results' => $results,
'timestamp' => time()
];
}
/**
* Check API health for specific merchant
*/
public function checkMerchantAPIHealth(string $merchantId): array
{
$startTime = microtime(true);
try {
// Test basic connectivity
$proxy = $this->proxyFactory->build($merchantId);
// Test payment methods endpoint (lightweight)
$testRequest = new GetAvailablePaymentMethodsRequest(
$merchantId,
100,
'EUR',
'ES'
);
$orderProxy = new OrderProxy($this->proxyFactory);
$methods = $orderProxy->getAvailablePaymentMethods($testRequest);
$responseTime = (microtime(true) - $startTime) * 1000; // ms
$this->metrics->recordSuccess($merchantId, $responseTime);
return [
'status' => 'healthy',
'response_time_ms' => round($responseTime, 2),
'payment_methods_count' => count($methods),
'last_check' => time()
];
} catch (HttpApiUnauthorizedException $e) {
$this->metrics->recordError($merchantId, 'authentication');
return [
'status' => 'authentication_error',
'error' => 'Invalid credentials',
'last_check' => time()
];
} catch (Exception $e) {
$this->metrics->recordError($merchantId, 'error');
return [
'status' => 'error',
'error' => $e->getMessage(),
'last_check' => time()
];
}
}
/**
* Get API performance metrics
*/
public function getAPIMetrics(string $merchantId): array
{
return [
'success_rate' => $this->metrics->getSuccessRate($merchantId),
'average_response_time' => $this->metrics->getAverageResponseTime($merchantId),
'error_breakdown' => $this->metrics->getErrorBreakdown($merchantId),
'uptime_percentage' => $this->metrics->getUptimePercentage($merchantId)
];
}
private function calculateOverallHealth(array $results): string
{
$total = count($results);
$healthy = 0;
foreach ($results as $result) {
if ($result['status'] === 'healthy') {
$healthy++;
}
}
$healthPercentage = ($healthy / $total) * 100;
if ($healthPercentage >= 95) return 'excellent';
if ($healthPercentage >= 80) return 'good';
if ($healthPercentage >= 60) return 'warning';
return 'critical';
}
}
// Standard API proxy usage pattern:
$proxy = $authorizedProxyFactory->build($merchantId); // Get authenticated proxy
$response = $proxy->post($request); // Make API call
$data = $response->decodeBodyToArray(); // Parse response
namespace YourNamespace;
use SeQura\Core\Infrastructure\ServiceRegister;
use SeQura\Core\Infrastructure\BootstrapComponent;
use SeQura\Core\BusinessLogic\Domain\GeneralSettings\Services\GeneralSettingsService;
use YourNamespace\Controllers\CustomGeneralSettingsController;
class Bootstrap extends BootstrapComponent
{
/**
* Initializes controllers.
*/
protected static function initControllers(): void {
parent::initControllers();
// Extend GeneralSettingsController
ServiceRegister::registerService(
GeneralSettingsController::class,
static function () {
return new CustomGeneralSettingsController();
}
);
}
}
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.