Skip to main content

SATIM Backend Integration

Introduction: Why payment_attempts Exists

payment_attempts is the audit and control table for SATIM payment initiation and follow-up.

It matters because it lets backend teams:

  • track every payment try from initiated to final acknowledge state
  • keep a strict link between merchant orderNumber and SATIM identifiers (orderId / mdOrder)
  • persist raw request/response payloads for support, dispute handling, and certification evidence
  • avoid orphan transactions by ensuring a transaction always references a known payment attempt
  • debug gateway issues quickly using status history and gateway messages
  • stay ready if SATIM requests technical logs during certification or incident review

In short, payment_attempts is the source of truth for SATIM flow traceability and reconciliation.

Scope Note

This guide assumes a platform using one payment terminal configuration. For dynamic multi-terminal setups, the SATIM flow and status logic remain the same, but force_terminal_id, userName, and password must be resolved from database configuration using business rules tied to the user performing the payment.

1. Critical Rules (Must Follow)

Rule 1: Persist Before Register

Do not call register.do before persisting the payment_attempts row with status initiated.

Rule 2: orderNumber Format

orderNumber must be alphanumeric and exactly 10 characters. Example: K9m2X7qL4P

Backend must implement a unique generation mechanism for orderNumber (per order/payment attempt) and verify it does not already exist in the database before calling register.do (use retry/regenerate on conflict). Use a bounded retry strategy (max retry depth/attempts) to avoid infinite loops, even if collision probability is low; if max attempts are reached, fail fast and return an internal error.

Testing Tip

During tests, avoid sending the same orderNumber to register.do more than once. SATIM registers that orderNumber, and reusing it can return duplicate/already-processed errors.

Rule 3: Amount Format

amount sent to SATIM must be in centimes.

Formula: amount_for_satim = amount * 100

Example: 5966.56 DZD must be sent as 596656.

Rule 4: Amount Storage Precision

Persist business monetary amounts as decimal values with 2-digit precision (for example DECIMAL(15,2)), not integers. Use integer centimes only at SATIM API boundaries (request/response mapping), then convert back to decimal for storage.

Audit Logging Tip

Log and persist request/response payloads for each SATIM endpoint (register.do and acknowledgeTransaction.do). These logs are required for traceability and may be requested later by SATIM during certification, incident analysis, or audit reviews.

2. Data Model and Status Lifecycle

2.1 payment_attempts Required Fields

  • user_id
  • order_number (unique)
  • gateway_order_id (nullable, stores SATIM orderId / same logical value as mdOrder)
  • form_url (nullable)
  • amount (DECIMAL(15,2))
  • status
  • payment_method (nullable)
  • payment_gateway (nullable, default SATIM)
  • register_request_payload (JSON raw payload sent to SATIM register.do, nullable)
  • register_response_payload (JSON raw payload received from SATIM register.do, nullable)
  • acknowledge_request_payload (JSON raw payload sent to SATIM acknowledgeTransaction.do, nullable)
  • acknowledge_response_payload (JSON raw payload received from SATIM acknowledgeTransaction.do, nullable)
  • ip_address (nullable)
  • created_at
  • updated_at
CREATE TABLE payment_attempts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
order_number VARCHAR(10) NOT NULL UNIQUE,
gateway_order_id VARCHAR(255) NULL,
form_url VARCHAR(1024) NULL,
amount DECIMAL(15,2) NOT NULL,
status VARCHAR(40) NOT NULL DEFAULT 'initiated',
payment_method VARCHAR(100) NULL,
payment_gateway VARCHAR(100) NULL DEFAULT 'SATIM',
register_request_payload JSONB NULL,
register_response_payload JSONB NULL,
acknowledge_request_payload JSONB NULL,
acknowledge_response_payload JSONB NULL,
ip_address VARCHAR(45) NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

2.2 payment_attempts Statuses

Use this status set consistently:

  • initiated
  • registered
  • registered_failed
  • acknowledged
  • acknowledge_failed

2.3 Lifecycle Updates

  1. Generate unique orderNumber.
  2. Insert payment_attempts row with status initiated.
  3. Save raw register request JSON in register_request_payload.
  4. Call register.do.
  5. Save raw register response JSON in register_response_payload.
  6. Update same row:
    • registered on success
    • registered_failed on failure
  7. Save raw acknowledge request JSON in acknowledge_request_payload.
  8. Call acknowledgeTransaction.do.
  9. Save raw acknowledge response JSON in acknowledge_response_payload.
  10. Update same row:
  • acknowledged on success
  • acknowledge_failed on failure
  1. Persist transactions row linked to payment_attempts (always).

2.4 transactions Minimum Recommendation

  • payment_attempt_id (foreign key to payment_attempts.id)
  • reference (unique)
  • authorization_number (nullable)
  • status
  • payment_method (nullable)
  • payment_gateway (nullable, default SATIM)
  • gateway_error_message (nullable)
  • gateway_success_message (nullable)
  • ip_address (nullable)
  • created_at
  • updated_at
Critical Integrity Rule

Once a transactions record is created, it must never be modified or altered, whether manually or by application logic. Any correction/reversal must be handled by creating a new compensating record and preserving full audit traceability.

3. Register (register.do)

Use environment variables or secret management for credentials. Recommended keys in this guide: SATIM_USER, SATIM_PASSWORD, SATIM_TERMINAL_ID.

Success criterion used in this guide: treat register as successful when orderId is present in the SATIM response.

$endpoint = 'https://test2.satim.dz/payment/rest/register.do';
$payload = [
'userName' => getenv('SATIM_USER'),
'password' => getenv('SATIM_PASSWORD'),
'orderNumber' => 'K9m2X7qL4P',
'amount' => 500000,
'currency' => '012',
'returnUrl' => 'https://merchant.example.com/payments/satim/return',
'failUrl' => 'https://merchant.example.com/payments/satim/fail',
'description' => 'Order K9m2X7qL4P',
'language' => 'FR',
'jsonParams' => json_encode([
'force_terminal_id' => getenv('SATIM_TERMINAL_ID'),
'udf1' => 'customer-12345',
'udf5' => 'invoice-7788',
]),
];

$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);

if (! empty($data['orderId'])) {
// payment_attempts.status = registered
// payment_attempts.register_request_payload = $payload
// payment_attempts.register_response_payload = $data
} else {
// payment_attempts.status = registered_failed
// payment_attempts.register_request_payload = $payload
// payment_attempts.register_response_payload = $data
}

3.1 Request Parameters

NameTypeMandatoryDescription
userNameAN..30YesMerchant login received during SATIM registration.
passwordAN..30YesMerchant password received during SATIM registration.
orderNumberAN..10YesMerchant order identifier. Must be unique per transaction.
amountN..20YesMinimum amount is 50 DA. Must be sent in centimes (amount * 100).
currencyN3YesISO 4217 currency code (012 for DZD).
returnUrlAN..512YesRedirect URL after successful payment.
failUrlAN..512NoRedirect URL after failed payment.
descriptionAN..512NoFree-form order description.
languageA2YesISO 639-1 language (AR, FR, EN).
jsonParamsAN..1024YesAdditional parameters map ({"param":"value"}) agreed during integration.
Amount Conversion Rule

amount must be in centimes.

  • 5000 DA => 500000
  • 806.5 DA => 80650

3.2 jsonParams Parameter List

FieldFormatExampleMandatoryDescription
force_terminal_idAN..16E0123456789YesTerminal ID assigned by bank to merchant.
udf1AN..20Cmd123456YesMerchant custom value (invoice/order context).
udf2AN..20value2NoOptional SATIM-specific custom value.
udf3AN..20value3NoOptional SATIM-specific custom value.
udf4AN..20value4NoOptional SATIM-specific custom value.
udf5AN..20value5NoOptional SATIM-specific custom value.

3.3 SATIM-Specific Optional Parameter

NameMethodTypeDescription
fundingTypeIndicatorregister.do (in jsonParams[])StringPayment transaction type indicator. Current values: CP or 698 (Bill payment).

3.4 Register Example URL

https://test2.satim.dz/payment/rest/register.do?userName=xxxxxxxx&password=xxxxxxxx&orderNumber=K9m2X7qL4P&amount=500000&currency=012&language=fr&returnUrl=https://...&failUrl=https://...&jsonParams={"force_terminal_id":"${SATIM_TERMINAL_ID}","udf1":"2018105301346","udf5":"ggsf85s42524s5uhgsf"}

3.5 Response Parameters

NameTypeMandatoryDescription
errorCodeN3No0 on success, other values indicate request/processing errors.
orderIdANS20NoUnique EPG order id (same logical value as mdOrder). Absent if registration fails.
formUrlAN..512NoSATIM payment page URL for customer redirection. Absent if registration fails.

3.6 Register Error Codes

CodeMessage
0No system error.
1Order with given order number has already been processed or the childId is incorrect.
1Order with this number was registered but not paid.
1Submerchant is blocked or deleted.
3Unknown currency.
4Order number is not specified.
4Merchant user name is not specified.
4Amount is not specified.
4Return URL cannot be empty.
4Password cannot be empty.
5Incorrect value of a request parameter.
5Incorrect value in the Language parameter.
5Access is denied.
5Merchant must change the password.
5Invalid jsonParams[].
7System error.
14Paymentway is invalid.

4. Acknowledge (acknowledgeTransaction.do)

acknowledgeTransaction.do confirms final order/payment details after customer redirection. If this confirmation is not sent, the gateway may automatically cancel the order after a delay.

$endpoint = 'https://test2.satim.dz/payment/rest/public/acknowledgeTransaction.do';
$payload = [
'userName' => getenv('SATIM_USER'),
'password' => getenv('SATIM_PASSWORD'),
'mdOrder' => 'V721uPPfNNofVQAAABL3',
'language' => 'FR',
];

$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);

$isSuccess = isset($data['ErrorCode']) && (int) $data['ErrorCode'] === 0
&& isset($data['params']['respCode']) && $data['params']['respCode'] === '00'
&& isset($data['OrderStatus']) && (int) $data['OrderStatus'] === 2;

// derive gateway messages
$respCodeDesc = trim((string)($data['params']['respCode_desc'] ?? ''));
$actionCodeDescription = trim((string)($data['actionCodeDescription'] ?? ''));
$language = strtoupper((string)($payload['language'] ?? 'EN'));
$localizedRejected = match ($language) {
'FR' => 'Votre transaction a ete rejetee',
'AR' => 'تم رفض معاملتك',
default => 'Your transaction was rejected',
};

$gatewaySuccessMessage = null;
$gatewayErrorMessage = null;

if ($isSuccess) {
$attemptStatus = 'acknowledged';
$transactionStatus = 'acknowledged';
$gatewaySuccessMessage = $respCodeDesc !== '' ? $respCodeDesc : $actionCodeDescription;
} else {
$attemptStatus = 'acknowledge_failed';
$transactionStatus = 'acknowledge_failed';
$isRejectedCase1 = (string)($data['params']['respCode'] ?? '') === '00'
&& (int)($data['ErrorCode'] ?? -1) === 0
&& (int)($data['OrderStatus'] ?? -1) === 3;

$gatewayErrorMessage = $isRejectedCase1
? $localizedRejected
: ($respCodeDesc !== '' ? $respCodeDesc : $actionCodeDescription);
}

// update payment_attempts
// - status = $attemptStatus
// - acknowledge_request_payload = $payload
// - acknowledge_response_payload = $data

$paymentAttemptId = $paymentAttempt['id']; // current payment_attempts.id from DB context
$merchantReference = 'TXN-' . date('YmdHis') . '-' . strtoupper(bin2hex(random_bytes(3))); // generate unique internal reference

$transactionData = [
'payment_attempt_id' => $paymentAttemptId,
'reference' => $merchantReference,
'authorization_number' => $data['approvalCode'] ?? $data['authorizationResponseId'] ?? null,
'status' => $transactionStatus,
'payment_method' => isset($data['Pan']) ? 'CIB/EDAHABIA' : null,
'payment_gateway' => 'SATIM',
'gateway_success_message' => $gatewaySuccessMessage,
'gateway_error_message' => $gatewayErrorMessage,
'ip_address' => $data['Ip'] ?? null,
];

// persist transactions row with $transactionData (always, even on failure)

4.1 Request Parameters

NameTypeMandatoryDescription
userNameAN..30YesMerchant login received during registration.
passwordAN..30YesMerchant password received during registration.
mdOrderANS20YesOrder number generated by EPG after registration.
languageA2YesISO 639-1 language (AR, FR, EN).

4.2 Acknowledge Example URL

https://test2.satim.dz/payment/rest/public/acknowledgeTransaction.do?language=EN&mdOrder=6bbnSxXAZJ4eDAAAABHE&password=xxxxxxxxxxxxxxxx&userName=xxxxxxxxxxxxxxxx

4.3 Response Parameters

NameTypeMandatoryDescription
expirationN6NoCard expiration date in YYYYMM. Only for paid orders.
cardholderNameAN..20NoCardholder name. Valid chars: Latin letters, 0-9, $, ), (, -, ., space. Must start with a letter. Min 2, max 26, null allowed.
depositAmountN5YesAmount debited in order currency.
currencyN3NoISO 4217 payment currency code.
authorizationResponseId (approvalCode deprecated name)AN..6NoNetwork authorization code (fixed length 6).
approvalCodeAN6NoIPS authorization code (fixed length 6).
actionCodeN3YesProcessing system authorization code.
actionCodeDescriptionAN..512YesAction code description in requested language.
ErrorCodeN3YesError code.
ErrorMessageAN..512NoError description in requested language.
OrderStatusN2NoOrder status in EPG. Absent if order ID is unknown.
OrderNumberAN..20YesMerchant order identifier.
PanN..19NoMasked card number used for payment.
AmountN..20YesAmount in centimes (amount * 100). Minimum is 50 DA.
IpAN..20NoCustomer IP address.
clientIdAN..255NoCustomer identifier in merchant system. Mandatory for bindings.
bindingIdAN..255NoBinding identifier if bindings are enabled/used.
paymentAccountReferenceANS..999NoPayment account data when configured on host.
DescriptionANS..512NoOrder description; omitted if order description is null.

4.4 Acknowledge Error Codes

CodeMessage
0Success.
2Order declined due to payment credentials error.
5Access is denied.
5User must change password.
5orderId is empty.
6Unregistered order ID.
7System error.

4.5 OrderStatus Values

CodeDescription
0Order registered but not paid.
-1Generic decline fallback status.
1Approved transaction (one-phase) or preauthorization hold (two-phase).
2Amount deposited successfully.
3Authorization reversed.
4Transaction refunded.
6Authorization declined.
7Card added.
8Card updated.
9Card verified.
10Recurring template added.
11Debited.

4.6 Acknowledge Response Example

{
"expiration": "202701",
"cardholderName": "cardholder Name",
"depositAmount": 100320,
"currency": "012",
"authorizationResponseId": "913180",
"approvalCode": "913180",
"actionCode": 0,
"actionCodeDescription": "Votre paiement a été accepté",
"ErrorCode": "0",
"ErrorMessage": "Success",
"OrderStatus": 2,
"OrderNumber": "CMD0000004",
"Pan": "6280****7215",
"Amount": 100320,
"Ip": "10.12.12.14",
"params": {
"respCode_desc": "Votre paiement a été accepté",
"udf1": "Bill00001",
"respCode": "00"
},
"SvfeResponse": "00"
}

4.7 Backend Mapping for gateway_success_message and gateway_error_message

Backend must mirror the same decision logic used by return-page rules so persisted transaction messages stay consistent with frontend behavior.

Use this mapping:

  1. Accepted payment condition:
    • params.respCode = "00" and ErrorCode = "0" and OrderStatus = "2"
    • Set gateway_success_message = respCode_desc (fallback to actionCodeDescription if empty)
    • Keep gateway_error_message = null
  2. Rejected payment (Case 1):
    • params.respCode = "00" and ErrorCode = "0" and OrderStatus = "3"
    • Set gateway_error_message to localized rejection text based on user selected language:
      • Votre transaction a ete rejetee (FR)
      • Your transaction was rejected (EN)
      • تم رفض معاملتك (AR)
    • Keep gateway_success_message = null
  3. Rejected payment (Case 2 - otherwise):
    • Set gateway_error_message = respCode_desc
    • If respCode_desc is empty, set gateway_error_message = actionCodeDescription
    • Keep gateway_success_message = null

Always persist SATIM support guidance (3020) in API response contracts or UI payload where applicable.

5. Backend Final Notes

  • Keep SATIM credentials server-side only.
  • Never trust only front-end redirect; always confirm with acknowledgeTransaction.do.
  • Persist both merchant orderNumber and SATIM orderId/mdOrder.
  • Log SATIM response fields for audit and support.
  • Receipt generation is a backend responsibility: generate the receipt once after final payment confirmation, persist it (or its storage reference), and reuse it for print/download/email requests instead of regenerating each time.
  • For multi-terminal platforms, keep the same SATIM register/acknowledge lifecycle and mapping logic; only terminal/credential resolution changes (load force_terminal_id, userName, and password from DB according to user-routing rules).