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 |