Skip to content

Arteel Client Library

Overview

Standalone PHP client for the Arteel loyalty program API. Enables integration with Arteel's point-based reward system.

Package: atraxion/arteel-client

Installation

composer require atraxion/arteel-client

Configuration

use Atraxion\Arteel\Config;
use Atraxion\Arteel\ArteelClient;

$config = new Config(
    apiKey: 'your-api-key',
    secretKey: 'your-secret-key',
    partnerId: 'PARTNER123',
    baseUrl: 'https://api.arteel.com/v1',
    timeout: 30
);

$client = new ArteelClient($config);

Core Features

Get Customer Balance

$balance = $client->getBalance('customer@example.com');

echo $balance->getPoints();           // 1500
echo $balance->getPointsValue();      // 15.00 (€)
echo $balance->getExpiringPoints();   // 200
echo $balance->getExpirationDate();   // 2024-03-31

Award Points

use Atraxion\Arteel\Request\AwardPointsRequest;

$result = $client->awardPoints(new AwardPointsRequest(
    customerEmail: 'customer@example.com',
    customerName: 'Jan Janssen',
    points: 100,
    reason: 'order_completed',
    reference: 'ORDER-12345',
    metadata: [
        'order_total' => 125.50,
        'tenant' => 'atraxion',
    ]
));

echo $result->getTransactionId();     // "TXN-ABC123"
echo $result->getNewBalance();        // 1600

Redeem Points

use Atraxion\Arteel\Request\RedeemPointsRequest;

$result = $client->redeemPoints(new RedeemPointsRequest(
    customerEmail: 'customer@example.com',
    points: 500,
    reason: 'reward_claimed',
    reference: 'REWARD-789',
    description: 'Claimed €5 voucher'
));

echo $result->getTransactionId();
echo $result->getNewBalance();        // 1100

Get Transaction History

$history = $client->getTransactions(
    customerEmail: 'customer@example.com',
    limit: 50,
    offset: 0
);

foreach ($history->getTransactions() as $txn) {
    echo $txn->getId();           // "TXN-ABC123"
    echo $txn->getType();         // "award" or "redeem"
    echo $txn->getPoints();       // 100
    echo $txn->getReason();       // "order_completed"
    echo $txn->getReference();    // "ORDER-12345"
    echo $txn->getCreatedAt()->format('Y-m-d H:i');
}

echo $history->getTotal();        // 150 transactions total

Register Customer

use Atraxion\Arteel\Request\RegisterCustomerRequest;

$customer = $client->registerCustomer(new RegisterCustomerRequest(
    email: 'new.customer@example.com',
    firstName: 'Jan',
    lastName: 'Janssen',
    phone: '+31612345678',
    language: 'nl',
    metadata: [
        'customer_id' => 'CUST-456',
        'tenant' => 'atraxion',
    ]
));

echo $customer->getArteelId();       // "ART-XYZ789"
echo $customer->getEnrollmentDate();

Check Customer Exists

$exists = $client->customerExists('customer@example.com');

if (!$exists) {
    $client->registerCustomer(...);
}

Get Rewards Catalog

$rewards = $client->getRewardsCatalog();

foreach ($rewards as $reward) {
    echo $reward->getId();              // "REWARD-001"
    echo $reward->getName();            // "€10 Voucher"
    echo $reward->getDescription();
    echo $reward->getPointsCost();      // 1000
    echo $reward->getImageUrl();
    echo $reward->isAvailable();        // true
    echo $reward->getStock();           // 50 or null (unlimited)
}

Claim Reward

use Atraxion\Arteel\Request\ClaimRewardRequest;

$claim = $client->claimReward(new ClaimRewardRequest(
    customerEmail: 'customer@example.com',
    rewardId: 'REWARD-001',
    shippingAddress: new Address(
        street: 'Hoofdstraat 1',
        city: 'Amsterdam',
        zipCode: '1234AB',
        country: 'NL'
    )
));

echo $claim->getClaimId();
echo $claim->getStatus();            // "processing"
echo $claim->getPointsDeducted();    // 1000

Models

Balance

readonly class Balance
{
    public function __construct(
        public int $points,
        public float $pointsValue,
        public int $expiringPoints,
        public ?\DateTimeImmutable $expirationDate,
        public int $pendingPoints,
        public int $lifetimePoints
    ) {}

    public function hasExpiringPoints(): bool
    {
        return $this->expiringPoints > 0;
    }

    public function canRedeem(int $points): bool
    {
        return $this->points >= $points;
    }
}

Transaction

readonly class Transaction
{
    public function __construct(
        public string $id,
        public string $type,      // 'award', 'redeem', 'expire', 'adjust'
        public int $points,
        public string $reason,
        public ?string $reference,
        public ?string $description,
        public int $balanceAfter,
        public \DateTimeImmutable $createdAt
    ) {}

    public function isAward(): bool
    {
        return $this->type === 'award';
    }

    public function isRedeem(): bool
    {
        return $this->type === 'redeem';
    }
}

Reward

readonly class Reward
{
    public function __construct(
        public string $id,
        public string $name,
        public string $description,
        public int $pointsCost,
        public ?string $imageUrl,
        public bool $isAvailable,
        public ?int $stock,
        public string $category,
        public array $metadata = []
    ) {}

    public function isInStock(): bool
    {
        return $this->isAvailable && ($this->stock === null || $this->stock > 0);
    }
}

Adapting to Loyalty Interface

use Atraxion\Loyalty\LoyaltyProviderInterface;
use Atraxion\Loyalty\Model\PointsBalance;
use Atraxion\Loyalty\Model\PointsTransaction;

class ArteelLoyaltyAdapter implements LoyaltyProviderInterface
{
    public function __construct(
        private ArteelClient $client,
        private LoyaltyConfig $config
    ) {}

    public function getBalance(string $customerId): PointsBalance
    {
        $email = $this->getCustomerEmail($customerId);
        $balance = $this->client->getBalance($email);

        return new PointsBalance(
            available: $balance->getPoints(),
            pending: $balance->getPendingPoints(),
            expiring: $balance->getExpiringPoints(),
            expirationDate: $balance->getExpirationDate()
        );
    }

    public function awardPoints(
        string $customerId,
        int $points,
        string $reason,
        string $reference
    ): PointsTransaction {
        $email = $this->getCustomerEmail($customerId);

        // Check if customer is enrolled
        if (!$this->client->customerExists($email)) {
            $customer = $this->getCustomer($customerId);
            $this->client->registerCustomer(new RegisterCustomerRequest(
                email: $email,
                firstName: $customer->getFirstName(),
                lastName: $customer->getLastName()
            ));
        }

        $result = $this->client->awardPoints(new AwardPointsRequest(
            customerEmail: $email,
            points: $points,
            reason: $reason,
            reference: $reference,
            metadata: ['tenant' => $this->config->getTenantId()]
        ));

        return new PointsTransaction(
            id: $result->getTransactionId(),
            points: $points,
            newBalance: $result->getNewBalance()
        );
    }

    public function getProviderId(): string
    {
        return 'arteel';
    }
}

Error Handling

use Atraxion\Arteel\Exception\ApiException;
use Atraxion\Arteel\Exception\CustomerNotFoundException;
use Atraxion\Arteel\Exception\InsufficientPointsException;
use Atraxion\Arteel\Exception\RewardNotFoundException;

try {
    $result = $client->redeemPoints($request);
} catch (CustomerNotFoundException $e) {
    // Customer not enrolled in Arteel
} catch (InsufficientPointsException $e) {
    $available = $e->getAvailablePoints();
    $required = $e->getRequiredPoints();
    throw new \RuntimeException("Not enough points: {$available} < {$required}");
} catch (RewardNotFoundException $e) {
    // Reward no longer available
} catch (ApiException $e) {
    $this->logger->error('Arteel error', ['error' => $e->getMessage()]);
}

Webhook Handling

use Atraxion\Arteel\Webhook\WebhookParser;
use Atraxion\Arteel\Webhook\WebhookValidator;

$parser = new WebhookParser();
$validator = new WebhookValidator($config->getSecretKey());

// Validate signature
$signature = $_SERVER['HTTP_X_ARTEEL_SIGNATURE'] ?? '';
if (!$validator->isValid(file_get_contents('php://input'), $signature)) {
    throw new \RuntimeException('Invalid webhook signature');
}

$webhook = $parser->parse($_POST);

switch ($webhook->getEvent()) {
    case 'points.expired':
        // Handle expired points notification
        break;
    case 'reward.shipped':
        // Handle reward shipment notification
        break;
}

Gherkin Scenarios

Feature: Arteel Client
  As a developer
  I want to integrate Arteel loyalty program
  Using a clean client library

  Scenario: Get customer balance
    Given customer is enrolled in Arteel
    When I request their balance
    Then I should receive available points
    And I should see expiring points if any

  Scenario: Award points for order
    Given customer placed order #12345
    When I award 100 points
    Then points should be added to balance
    And transaction should be recorded

  Scenario: Award points to new customer
    Given customer is not enrolled in Arteel
    When I award points
    Then customer should be auto-enrolled
    And points should be awarded

  Scenario: Redeem points
    Given customer has 1500 points
    When they redeem 500 points
    Then balance should be reduced to 1000
    And redemption transaction recorded

  Scenario: Insufficient points
    Given customer has 100 points
    When they try to redeem 500 points
    Then InsufficientPointsException should be thrown
    And available points should be in error

  Scenario: Claim reward from catalog
    Given reward "€10 Voucher" costs 1000 points
    And customer has 1500 points
    When customer claims the reward
    Then reward claim should be created
    And 1000 points should be deducted

Configuration Reference

Parameter Type Required Default Description
apiKey string Yes - Arteel API key
secretKey string Yes - Webhook signature key
partnerId string Yes - Partner ID
baseUrl string No production API base URL
timeout int No 30 Request timeout

API Endpoints Used

Endpoint Method Purpose
/customers/{email}/balance GET Get points balance
/customers/{email}/transactions GET Get transaction history
/customers POST Register new customer
/customers/{email} GET Check if customer exists
/transactions/award POST Award points
/transactions/redeem POST Redeem points
/rewards GET Get rewards catalog
/rewards/{id}/claim POST Claim reward