# Functional Specification: ACI Gateway Payment Processing System

## 1. Purpose & Scope

This system provides a payment gateway service that processes payment transactions on behalf of ACI (payment service provider). The gateway acts as an intermediary between ACI clients and the underlying e-commerce payment processing service (Paynetics).

The system supports the following payment operations:
- **Purchase (DB)**: Direct debit/purchase transactions
- **Pre-Authorization (PA)**: Authorization hold on funds
- **Capture (CP)**: Completion of a pre-authorized transaction
- **Refund (RF)**: Reversal of a completed transaction
- **Reversal (RV)**: Cancellation of an authorized transaction

Additionally, the system manages 3D Secure (3DS) authentication flows for enhanced payment security, including:
- 3DS challenge collection
- 3DS authentication processing
- 3DS result handling

The system maintains session state for 3DS flows and sends webhook notifications to ACI upon transaction completion.

---

## 2. Actors & Roles

Based on the code analysis, the following actors interact with the system:

1. **ACI Client/Merchant**: External system that initiates payment requests
   - Provides payment transaction data
   - Receives transaction responses and webhook notifications
   - No explicit authentication mechanism is enforced in the code (authentication credentials are passed in request body)

2. **End User/Cardholder**: Customer making a payment
   - Interacts with 3DS authentication pages during payment flows
   - Provides card details and authentication responses

3. **E-Commerce Service (Paynetics)**: External payment processing service
   - Processes payment transactions
   - Handles 3DS authentication requests
   - Returns transaction results

**Note**: No explicit role-based access control (RBAC) or security configuration was found in the codebase. Authentication appears to be handled via credentials passed in the request body rather than through Symfony security mechanisms.

---

## 3. Preconditions

Before any functionality can be executed, the following conditions must be met:

### For Payment Processing (`/v1/payments`):
- Valid HTTP POST request
- Request body contains payment transaction data
- Authentication credentials must be present in request body:
  - `authentication.entityId`
  - `authentication.userId`
  - `authentication.password`
- For Capture, Refund, or Reversal operations: Valid transaction identifier (either in URL path or in `customParameters.initial.uuid`)

### For 3DS Collection (`/v1/3ds/{session}/collect`):
- Valid session token in URL path
- Session must exist in database (status = 1)

### For 3DS Authentication (`/v1/3ds/{session}/authenticate`):
- Valid session token in URL path
- Session must exist in database with status = 1
- Request body contains 3DS authentication data

### For 3DS Result (`/v1/3ds/{session}/result`):
- Valid session token in URL path
- Session must exist in database with status = 2
- Request contains `cres` parameter (challenge response)

### System-Level Preconditions:
- Database connection must be available
- E-Commerce Service endpoint must be accessible
- Environment variables must be configured:
  - `THREEDS_URL`: Base URL for 3DS redirects
  - `ECOMMERCE_SERVICE`: E-Commerce service endpoint URL

---

## 4. Functional Overview

The ACI Gateway system provides a RESTful API for processing payment transactions. The system receives payment requests from ACI clients, validates the input data, and routes transactions to the underlying e-commerce payment service.

**Key Capabilities:**
1. **Payment Processing**: Accepts payment requests for various transaction types (Purchase, Pre-Authorization, Capture, Refund, Reversal) and processes them through the e-commerce service.

2. **3D Secure Support**: Manages 3D Secure authentication flows for enhanced security. When 3DS is enabled, the system:
   - Creates a session to store transaction data
   - Redirects users to a 3DS challenge page
   - Processes authentication responses
   - Completes the payment transaction after successful authentication

3. **Webhook Notifications**: Sends transaction results to ACI clients via webhook URLs provided in the request.

4. **Response Mapping**: Translates e-commerce service response codes to ACI-standard response codes.

5. **Session Management**: Maintains session state for 3DS flows, tracking session status (1 = new, 2 = processing, 3 = processed, 4 = failed).

---

## 5. Detailed Functional Flow

### 5.1 Payment Processing Flow (`POST /v1/payments/{transaction?}`)

**Step 1: Request Reception**
- System receives POST request at `/v1/payments/{transaction?}`
- Optional `transaction` parameter in URL path (used for Capture, Refund, Reversal operations)

**Step 2: Data Deserialization**
- Request body data is deserialized into `AciDto` object
- Credentials are extracted into `CredentialsDto` object
- If deserialization fails, system throws `ApiException` with code 11001

**Step 3: Validation**
- `AciDtoValidator` validates the `AciDto` based on payment type:
  - For `DB` (Purchase) or `PA` (Pre-Authorization): Validates using `['Initial', 'Default']` validation groups
  - For `RV` (Reversal), `CP` (Capture), or `RF` (Refund): Validates using `['CaptureRefundReversal']` validation group
- Validation errors are collected and returned as `ApiException` with code 11001

**Step 4: Payment Type Routing**
The system routes to different processing paths based on `paymentType`:

#### 5.1.1 Purchase (DB) or Pre-Authorization (PA) - Non-3DS Flow
- If `customParameters.3DS_Enabled` is NOT `true`:
  - Builds `GatewayDto` using `AciBuilder::buildPurchasePreAuthDto()`
  - Calls e-commerce service:
    - `purchaseRequest()` for DB
    - `preAuthRequest()` for PA
  - Builds response using `AciBuilder::buildResponseDto()`
  - Returns JSON response with transaction result

#### 5.1.2 Purchase (DB) or Pre-Authorization (PA) - 3DS Flow
- If `customParameters.3DS_Enabled` is `true`:
  - Creates a new `Session` entity with:
    - Status = 1 (new)
    - Token = auto-generated UUID
    - Payload = entire request data (JSON encoded)
  - Builds 3DS response using `AciBuilder::buildResponse3DSDto()`
  - Returns JSON response containing:
    - `id`: Session token
    - `result.code`: "000.200.000"
    - `redirect.url`: URL to 3DS collection page (`/v1/3ds/{session}/collect`)
    - `redirect.method`: "GET"

#### 5.1.3 Capture (CP)
- Builds `GatewayDto` using `AciBuilder::buildCaptureRefundDto()`
- Determines transaction ID:
  - If `transaction` parameter provided in URL, use it
  - Otherwise, use `customParameters.initial.uuid`
- Calls e-commerce service `captureRequest()` with transaction ID
- Builds response and returns JSON

#### 5.1.4 Refund (RF)
- Builds `GatewayDto` using `AciBuilder::buildCaptureRefundDto()`
- Uses `transaction` parameter from URL path
- Calls e-commerce service `refundRequest()` with transaction ID
- Builds response and returns JSON

#### 5.1.5 Reversal (RV)
- Builds `GatewayDto` using `AciBuilder::buildReversalDto()`
- Uses `transaction` parameter from URL path
- Calls e-commerce service `reversalRequest()` with transaction ID
- Builds response and returns JSON

**Step 5: Response Building**
- E-commerce service response is mapped to `AciResponseDto`:
  - Response codes are mapped using `AciBuilder::mapResponseCode()`
  - Transaction ID, result details, custom parameters, and NDC are extracted
- JSON response is returned with HTTP status 200

### 5.2 3DS Collection Flow (`GET /v1/3ds/{session}/collect`)

**Step 1: Session Retrieval**
- System retrieves session by token with status = 1
- If session not found, returns empty HTML page (`base.html.twig`)

**Step 2: Page Rendering**
- If session exists, renders `base.html.twig` template with session token
- This page is expected to collect 3DS authentication data from the user

### 5.3 3DS Authentication Flow (`POST /v1/3ds/{session}/authenticate`)

**Step 1: Session Validation**
- Retrieves session by token with status = 1
- If session not found, throws `ApiException` with code 11000 ("Session not found")

**Step 2: Data Reconstruction**
- Deserializes session payload into `AciDto`
- Merges request body data into `AciDto` (updates 3DS data from user input)
- Extracts credentials from session payload

**Step 3: Session Status Update**
- Updates session status to 2 (processing)
- Persists session update

**Step 4: 3DS Authentication Request**
- Builds `Authenticate3DSDto` using `AciBuilder::buildAuthenticate3DSDto()`
- Calls e-commerce service `authenticateRequest()`
- Notification URL is set to `/v1/3ds/{session}/result`

**Step 5: Response Handling**

**Scenario 5.3.1: HTML Challenge Response**
- If response contains `html` field:
  - Returns HTML content directly (3DS challenge page)
  - User completes challenge in browser

**Scenario 5.3.2: Authentication Failure**
- If `eci` is "00" or "07", OR `status` is "failed":
  - Builds error response using `AciBuilder::buildErrorResponse()`
  - Returns JSON with error code "100.390.114"

**Scenario 5.3.3: Successful Authentication**
- If authentication succeeds:
  - Builds `GatewayDto` with 3DS data from authentication response
  - Executes payment transaction:
    - `purchaseRequest()` for DB
    - `preAuthRequest()` for PA
  - Builds `AciResponseDto` from transaction result
  - Sends webhook notification to `notificationUrl`
  - Redirects user to `shopperResultUrl`

### 5.4 3DS Result Flow (`POST /v1/3ds/{session}/result`)

**Step 1: Session Validation**
- Retrieves session by token with status = 2
- If session not found, throws `ApiException` with code 11000

**Step 2: Data Reconstruction**
- Deserializes session payload into `AciDto`
- Merges request body data (contains `cres` parameter)
- Extracts credentials from session payload

**Step 3: Session Status Update**
- Updates session status to 2 (processing)
- Persists session update

**Step 4: 3DS Result Processing**
- Calls e-commerce service `resultRequest()` with:
  - `response`: `cres` parameter from request
  - `transaction_id`: New UUID

**Step 5: Response Handling**

**Scenario 5.4.1: Authentication Failure**
- If `eci` is "00" or "07", OR `status` is "failed":
  - Builds error response
  - Returns JSON with error code "100.390.114"

**Scenario 5.4.2: Successful Authentication**
- Builds `GatewayDto` with 3DS data from result response
- Executes payment transaction:
  - `purchaseRequest()` for DB
  - `preAuthRequest()` for PA
- Builds `AciResponseDto` from transaction result
- Sends webhook notification to `notificationUrl`

**Step 6: Redirect with Signature**
- Parses `shopperResultUrl` to extract query parameters
- Adds `status` parameter with result code
- Sorts query parameters alphabetically
- Generates HMAC-SHA256 signature:
  - Signature payload: `key1=value1|key2=value2|...` (sorted)
  - Secret: `authentication.password`
- Appends signature to URL as `signature` parameter
- Redirects user to signed URL

### 5.5 Webhook Notification Flow

**Trigger**: After successful payment transaction in 3DS flows

**Step 1: URL Construction**
- Parses `notificationUrl` from `AciDto`
- Extracts existing query parameters

**Step 2: Signature Generation**
- Adds `status` parameter with result code
- Sorts all query parameters alphabetically
- Creates signature payload: `key1=value1|key2=value2|...`
- Generates HMAC-SHA256 signature using `authentication.password`
- Appends signature to query parameters

**Step 3: Webhook Delivery**
- Sends HTTP GET request to notification URL with signed parameters
- Uses `AciRequestManager::webhooks()` method

---

## 6. Alternative / Error Flows

### 6.1 Validation Failures

**Trigger**: Input data fails validation rules

**Behavior**:
- `AciDtoValidator` collects all validation errors
- Throws `ApiException` with code 11001
- Error response format:
  ```json
  {
    "result": {
      "code": "800.100.156",
      "description": "field1:error1;field2:error2"
    }
  }
  ```
- HTTP Status: 200 (error code in response body)

**Common Validation Errors**:
- Missing required fields (amount, currency, paymentType, merchantTransactionId, transactionCategory, notificationUrl)
- Invalid payment type (must be one of: PA, DB, CD, CP, RV, RF)
- Invalid currency (must be 3 characters)
- Invalid card expiry month (must be 01-12)
- Invalid transaction category (must be one of: EC, MO, TO, PO, PM, MOTO, RC)
- Invalid 3DS ECI value (must be 00-07)
- Invalid country codes
- Field length violations

### 6.2 Deserialization Failures

**Trigger**: Request body cannot be deserialized into DTO objects

**Behavior**:
- Catches `Throwable` exception during deserialization
- Throws `ApiException` with code 11001
- Error message contains exception details
- HTTP Status: 200

### 6.3 Session Not Found

**Trigger**: 3DS endpoints called with invalid or non-existent session token

**Behavior**:
- For `/v1/3ds/{session}/collect`: Returns empty HTML page
- For `/v1/3ds/{session}/authenticate` or `/v1/3ds/{session}/result`: Throws `ApiException` with code 11000
- Error response:
  ```json
  {
    "result": {
      "code": "900.100.200",
      "description": null
    }
  }
  ```
- HTTP Status: 200

### 6.4 E-Commerce Service Errors

**Trigger**: E-commerce service returns error or throws exception

**Behavior**:
- `RequestException` is caught and mapped to ACI error codes via `KernelException` subscriber
- Error code mapping:
  - 90002 → "600.300.101"
  - 90003, 90007, 90008 → "100.150.300"
  - 90004 → "600.200.201"
  - 90005, 90006, 90018 → "700.300.300"
  - 90010 → "800.100.174"
  - 90011 → "700.100.200"
  - 90012 → "700.400.510"
  - 90013 → "800.900.201"
  - 90014 → "700.400.100"
  - 90015 → "700.400.520"
  - 90016 → "700.400.200"
  - 90017 → "600.200.200"
  - 90019 → "600.200.501"
  - 90020 → "600.200.400"
  - 90021 → "300.100.100"
  - Default → "900.100.200"
- HTTP Status: 200

### 6.5 3DS Authentication Failures

**Trigger**: 3DS authentication returns ECI "00" or "07", or status "failed"

**Behavior**:
- System builds error response using `AciBuilder::buildErrorResponse()`
- Returns JSON with:
  ```json
  {
    "id": "<uuid>",
    "result": {
      "code": "100.390.114",
      "description": "",
      "avsResponse": "",
      "cvvResponse": ""
    }
  }
  ```
- HTTP Status: 200
- No payment transaction is executed
- No webhook is sent

### 6.6 Missing Transaction ID (Capture)

**Trigger**: Capture operation attempted without transaction ID in URL or `customParameters.initial.uuid`

**Behavior**:
- System attempts to use `customParameters.initial.uuid` if URL parameter missing
- If both are missing, e-commerce service call will fail
- Error handled as per Section 6.4

### 6.7 Webhook Delivery Failures

**Trigger**: Webhook URL is invalid or unreachable

**Behavior**:
- **Not Explicitly Defined in Code**: The code does not handle webhook delivery failures
- Webhook is sent via HTTP GET request
- No retry mechanism is visible in code
- No error handling for failed webhook deliveries

---

## 7. Input Data & Validation Rules

### 7.1 Payment Request (`POST /v1/payments`)

All fields are provided in the request body (form data or JSON, depending on Content-Type).

#### 7.1.1 Core Payment Fields

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `amount` | `amount` | float | Yes (DB, PA, CP, RF) | Must be > 0 | Request body |
| `currency` | `currency` | string | Yes | Length: 3 characters | Request body |
| `paymentType` | `paymentType` | string | Yes | One of: PA, DB, CD, CP, RV, RF<br>Length: 1-32 | Request body |
| `paymentBrand` | `paymentBrand` | string | No | Length: 1-32 | Request body |
| `merchantTransactionId` | `merchantTransactionId` | string | Yes | Length: 8-255 | Request body |
| `transactionCategory` | `transactionCategory` | string | Yes | One of: EC, MO, TO, PO, PM, MOTO, RC<br>Length: 0-32 | Request body |
| `notificationUrl` | `notificationUrl` | string | Yes | Not null, not blank | Request body |
| `shopperResultUrl` | `shopperResultUrl` | string | No | - | Request body |
| `testMode` | `testMode` | string | No | - | Request body |

#### 7.1.2 Merchant Information

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `merchantPostCode` | `merchantPostCode` | string | No | Length: 1-10 | Request body |
| `merchantState` | `merchantState` | string | No | Length: 3 characters | Request body |
| `merchantLegalName` | `merchantLegalName` | string | No | Length: 1-25 | Request body |

#### 7.1.3 Card Information

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `cardHolder` | `card.holder` | string | Yes (DB, PA) | Length: 2-45 | Request body |
| `cardNumber` | `card.number` | string | Yes (DB, PA) | Length: 8-32 | Request body |
| `cardExpiryMonth` | `card.expiryMonth` | string | Yes (DB, PA) | Length: 2 characters<br>Must be: 01-12 | Request body |
| `cardExpiryYear` | `card.expiryYear` | string | Yes (DB, PA) | Length: 2-4 characters | Request body |
| `cardCvv` | `card.cvv` | string | No | Length: 3 characters | Request body |

**Note**: Custom validators exist for CVV and expiration date, but implementation details are not visible in provided code.

#### 7.1.4 Authentication Credentials

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `authenticationEntityId` | `authentication.entityId` | string | No | - | Request body |
| `authenticationUserId` | `authentication.userId` | string | No | - | Request body |
| `authenticationPassword` | `authentication.password` | string | No | - | Request body |

**Note**: While not explicitly marked as mandatory in validation, these fields are required for e-commerce service authentication.

#### 7.1.5 3D Secure Fields

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `threeDSecureEci` | `threeDSecure.eci` | string | No | Length: 1-8<br>One of: 00, 01, 02, 03, 04, 05, 06, 07 | Request body |
| `threeDSecureVerificationId` | `threeDSecure.verificationId` | string | No | Length: 1-28 | Request body |
| `threeDSecureDsTransactionId` | `threeDSecure.dsTransactionId` | string | No | Length: max 36 | Request body |
| `threeDSecureChallengeIndicator` | `threeDSecure.challengeIndicator` | string | No | Length: 2 characters | Request body |
| `threeDSecureExemptionFlag` | `threeDSecure.exemptionFlag` | string | No | Length: 2 characters<br>Must be: 01 | Request body |

#### 7.1.6 Billing Address

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `billingCity` | `billing.city` | string | No | Length: 1-50 | Request body |
| `billingCountry` | `billing.country` | string | No | Length: 2 characters<br>Valid country code | Request body |
| `billingStreet1` | `billing.street1` | string | No | Length: 1-50 | Request body |
| `billingStreet2` | `billing.street2` | string | No | Length: 1-50 | Request body |
| `billingPostcode` | `billing.postcode` | string | No | Length: 1-16 | Request body |
| `billingState` | `billing.state` | string | No | Length: 1-3 | Request body |

#### 7.1.7 Shipping Address

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `shippingCity` | `shipping.city` | string | No | Length: 2-50 | Request body |
| `shippingCountry` | `shipping.country` | string | No | Length: 2-3 | Request body |
| `shippingStreet1` | `shipping.street1` | string | No | Length: max 50 | Request body |
| `shippingStreet2` | `shipping.street2` | string | No | Length: max 50 | Request body |
| `shippingPostcode` | `shipping.postcode` | string | No | Length: max 16 | Request body |
| `shippingState` | `shipping.state` | string | No | Length: max 3 | Request body |

#### 7.1.8 Customer Information

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `customerEmail` | `customer.email` | string | No | - | Request body |
| `customerPhone` | `customer.phone` | string | No | - | Request body |
| `customerWorkPhone` | `customer.workPhone` | string | No | - | Request body |
| `customerMobile` | `customer.mobile` | string | No | - | Request body |

#### 7.1.9 Customer Browser Information (Required for 3DS)

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `customerIp` | `customer.ip` | Yes (3DS) | string | Valid IP address | Request body |
| `customerBrowserAcceptHeader` | `customer.browser.acceptHeader` | string | Yes (3DS) | Not blank | Request body |
| `customerBrowserScreenColorDepth` | `customer.browser.screenColorDepth` | string | Yes (3DS) | Length: 1-2 | Request body |
| `customerBrowserJavaEnabled` | `customer.browser.javaEnabled` | boolean | No | - | Request body |
| `customerBrowserLanguage` | `customer.browser.language` | string | Yes (3DS) | Length: 1-8 | Request body |
| `customerBrowserScreenHeight` | `customer.browser.screenHeight` | string | Yes (3DS) | Length: 1-6 | Request body |
| `customerBrowserScreenWidth` | `customer.browser.screenWidth` | string | Yes (3DS) | Length: 1-6 | Request body |
| `customerBrowserTimezone` | `customer.browser.timezone` | string | Yes (3DS) | Length: 1-5 | Request body |
| `customerBrowserChallengeWindow` | `customer.browser.challengeWindow` | string | No | - | Request body |
| `customerBrowserUserAgent` | `customer.browser.userAgent` | string | Yes (3DS) | Length: 1-2048 | Request body |

#### 7.1.10 Standing Instruction (Recurring Payments)

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `standingInstructionMode` | `standingInstruction.mode` | string | No | - | Request body |
| `standingInstructionType` | `standingInstruction.type` | string | No | - | Request body |
| `standingInstructionSource` | `standingInstruction.source` | string | No | - | Request body |
| `standingInstructionRecurringType` | `standingInstruction.recurringType` | string | No | - | Request body |
| `standingInstructionInitialTransactionId` | `standingInstruction.initialTransactionId` | string | No | - | Request body |

#### 7.1.11 Custom Parameters

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `threeDomainByPaynetics` | `customParameters.3DS_Enabled` | boolean | No | - | Request body |
| `shortId` | `customParameters.ShortId` | string | No | - | Request body |
| `sendCryptocurrencyIndicator` | `customParameters.SEND_CRYPTOCURRENCY_INDICATOR` | string | No | - | Request body |
| `initialUuid` | `customParameters.initial.uuid` | string | No | - | Request body |
| `accountOwnerAddressLine1` | `customParameters.account.owner.address.Line1` | string | No | - | Request body |
| `accountOwnerAddressLine2` | `customParameters.account.owner.address.Line2` | string | No | - | Request body |
| `mvv` | `customParameters.mvv` | string | No | - | Request body |

#### 7.1.12 Additional Fields (Account Transfer)

| Field Name | Serialized Name | Data Type | Mandatory | Validation Rules | Source |
|------------|----------------|-----------|-----------|------------------|--------|
| `recipientStreet` | `recipient.street` | string | No | - | Request body |
| `recipientPostCode` | `recipient.postcode` | string | No | - | Request body |
| `recipientCity` | `recipient.city` | string | No | - | Request body |
| `recipientState` | `recipient.state` | string | No | - | Request body |
| `recipientCountry` | `recipient.country` | string | No | - | Request body |
| `transactionBai` | `transactionBAI` | string | No | - | Request body |
| `accountNumber` | `recipient.accountNumber` | string | No | - | Request body |
| `senderName` | `sender.name` | string | No | - | Request body |
| `senderStreet` | `sender.street` | string | No | - | Request body |
| `senderCity` | `sender.city` | string | No | - | Request body |
| `senderState` | `sender.state` | string | No | - | Request body |
| `senderCountry` | `sender.country` | string | No | - | Request body |
| `givenName` | `recipient.givenName` | string | No | - | Request body |
| `surname` | `recipient.surname` | string | No | - | Request body |

#### 7.1.13 URL Path Parameter

| Parameter | Data Type | Mandatory | Description | Source |
|-----------|-----------|-----------|-------------|--------|
| `transaction` | string | No | Transaction ID for Capture, Refund, or Reversal operations | URL path |

### 7.2 3DS Collection Request (`GET /v1/3ds/{session}/collect`)

| Parameter | Data Type | Mandatory | Description | Source |
|-----------|-----------|-----------|-------------|--------|
| `session` | string | Yes | Session token (UUID) | URL path |

### 7.3 3DS Authentication Request (`POST /v1/3ds/{session}/authenticate`)

| Parameter | Data Type | Mandatory | Description | Source |
|-----------|-----------|-----------|-------------|--------|
| `session` | string | Yes | Session token (UUID) | URL path |
| Request body | object | Yes | Contains 3DS authentication data (fields from AciDto) | Request body |

**Note**: Request body structure is not explicitly defined in code. It appears to accept any fields from `AciDto` that update 3DS-related information.

### 7.4 3DS Result Request (`POST /v1/3ds/{session}/result`)

| Parameter | Data Type | Mandatory | Description | Source |
|-----------|-----------|-----------|-------------|--------|
| `session` | string | Yes | Session token (UUID) | URL path |
| `cres` | string | Yes | Challenge response from 3DS authentication | Request body (query parameter) |

**Note**: Additional fields from `AciDto` may be accepted in request body, but `cres` is the primary field used.

---

## 8. Output Data

### 8.1 Payment Response (Success)

**HTTP Status**: 200

**Response Structure**:
```json
{
  "id": "string",
  "result": {
    "code": "string",
    "description": "string",
    "avsResponse": "string",
    "cvvResponse": "string"
  },
  "customParameters": {
    "MID": "string",
    "TID": "string"
  },
  "resultDetails": {
    "AuthCode": "string",
    "AcquirerResponse": "string",
    "MerchantAdviceCode": "string"
  },
  "ndc": "string",
  "redirect": {
    "url": "string",
    "method": "string"
  }
}
```

**Field Descriptions**:

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Transaction ID from e-commerce service (or session token for 3DS initiation) |
| `result.code` | string | ACI-standard response code (mapped from e-commerce service response) |
| `result.description` | string | Error description from e-commerce service (if any) |
| `result.avsResponse` | string | Address Verification System response status |
| `result.cvvResponse` | string | CVV verification response status |
| `customParameters.MID` | string | Merchant ID (card acceptor identification code) |
| `customParameters.TID` | string | Terminal ID (card acceptor terminal ID) |
| `resultDetails.AuthCode` | string | Authorization code from acquirer |
| `resultDetails.AcquirerResponse` | string | Original network response code or response code |
| `resultDetails.MerchantAdviceCode` | string | Merchant advice code (mapped from action code, if present) |
| `ndc` | string | Network Data Code (STAN + AuthCode) |
| `redirect.url` | string | URL for 3DS redirect (only present for 3DS flows) |
| `redirect.method` | string | HTTP method for redirect (typically "GET") |

**Response Code Mapping**:
The system maps e-commerce service response codes to ACI codes using `AciBuilder::mapOriginalCode()`. Common mappings include:
- "00" → "000.000.000" (Success)
- "01", "02", "05" → "800.100.152" (General error)
- "03" → "600.300.101" (Invalid transaction)
- "04" → "800.100.171" (Card expired)
- "07" → "100.100.701" (Invalid card)
- "14" → "800.100.195" (Invalid card number)
- "51" → "800.100.203" (Insufficient funds)
- "54" → "800.100.157" (Expired card)
- "55" → "100.350.400" (Invalid password)
- "85" → "000.400.110" (Success with warning)
- "94" → "800.110.100" (Duplicate transaction)
- And many others (see `AciBuilder::mapOriginalCode()` for complete mapping)

**Special Response Codes**:
- "000.200.000": 3DS session created successfully
- "100.390.114": 3DS authentication failed

### 8.2 Payment Response (Error)

**HTTP Status**: 200 (error information in response body)

**Response Structure**:
```json
{
  "result": {
    "code": "string",
    "description": "string"
  }
}
```

**Error Code Examples**:
- "800.100.156": Validation error
- "900.100.200": General system error
- "600.300.101": Invalid transaction
- "100.150.300": Service unavailable
- "700.300.300": Processing error
- See Section 6.4 for complete error code mapping

### 8.3 3DS Collection Response

**HTTP Status**: 200

**Response Type**: HTML

**Content**: Rendered `base.html.twig` template

**Behavior**: 
- If session exists: Template rendered with session token
- If session not found: Empty template rendered

### 8.4 3DS Authentication Response

**HTTP Status**: 200 or 302 (Redirect)

**Response Types**:

1. **HTML Challenge** (HTTP 200):
   - Content-Type: text/html
   - Body: HTML content from e-commerce service for 3DS challenge

2. **Error Response** (HTTP 200):
   ```json
   {
     "id": "<uuid>",
     "result": {
       "code": "100.390.114",
       "description": "",
       "avsResponse": "",
       "cvvResponse": ""
     }
   }
   ```

3. **Redirect Response** (HTTP 302):
   - Location: `shopperResultUrl` from original request
   - User is redirected after successful payment processing

### 8.5 3DS Result Response

**HTTP Status**: 302 (Redirect)

**Response Type**: Redirect

**Location**: `shopperResultUrl` with appended query parameters:
- `status`: Result code from transaction
- `signature`: HMAC-SHA256 signature of all query parameters
- All original query parameters from `shopperResultUrl`

**Signature Generation**:
1. Parse `shopperResultUrl` to extract existing query parameters
2. Add `status` parameter with result code
3. Sort all parameters alphabetically by key
4. Create signature payload: `key1=value1|key2=value2|...`
5. Generate HMAC-SHA256 using `authentication.password` as secret
6. Append signature as `signature` query parameter

---

## 9. State Changes & Data Persistence

### 9.1 Session Entity

**Table**: `session`

**Fields**:
- `id`: Primary key (auto-generated)
- `token`: UUID (unique, auto-generated on creation)
- `payload`: JSON (stores entire request payload)
- `status`: Small integer
  - 1 = new (initial state)
  - 2 = processing (3DS authentication in progress)
  - 3 = processed (transaction completed)
  - 4 = failed (transaction failed)
- `createdAt`: Timestamp (from BaseEntity)
- `updatedOn`: Timestamp (from BaseEntity)
- `isDeleted`: Boolean (from BaseEntity)

**State Transitions**:

1. **Session Creation** (3DS Flow Initiation):
   - **Trigger**: Payment request with `customParameters.3DS_Enabled = true`
   - **Action**: New session created with:
     - `token`: Auto-generated UUID
     - `payload`: JSON-encoded request data
     - `status`: 1 (new)

2. **Session Status Update to Processing**:
   - **Trigger**: 3DS authentication or result processing begins
   - **Action**: `status` updated to 2 (processing)
   - **Occurs in**: 
     - `threeDSAuthenticate()` method
     - `threeDSResult()` method

3. **Session Status Update to Processed/Failed**:
   - **Not Explicitly Defined in Code**: The code does not update session status to 3 or 4 after transaction completion
   - **Assumption**: Status may be updated by other processes or remains at 2

### 9.2 Communication Log Entity

**Table**: `t_communication_log`

**Fields**:
- `id`: Primary key
- `headers`: JSON (HTTP headers)
- `request`: JSON (request data)
- `response`: JSON (response data)
- `operation`: String (operation name)
- `ip`: String (client IP address)
- `path`: String (request path)
- `query`: JSON (query parameters)
- `responseCode`: String (HTTP response code)
- `method`: String (HTTP method)
- `requestId`: String (request identifier)
- `instance`: String (indexed)
- `merchant`: String (indexed)
- `createdAt`: Timestamp
- `updatedOn`: Timestamp
- `isDeleted`: Boolean

**Note**: While this entity exists in the codebase, it is not explicitly used in the controller code provided. It may be populated by event subscribers or other services.

### 9.3 No Explicit Transaction Records

**Not Explicitly Defined in Code**: The system does not create explicit transaction records in the database. Transaction data is:
- Stored in session payload (for 3DS flows)
- Sent to e-commerce service
- Returned in responses
- Logged via CommunicationLog (if enabled)

---

## 10. Security & Permissions

### 10.1 Authentication

**Method**: Credential-based authentication via request body

**Credentials Required**:
- `authentication.entityId`: Entity identifier
- `authentication.userId`: User identifier (used as API key)
- `authentication.password`: Password (used for HMAC signature generation)

**Authentication Flow**:
1. Credentials are extracted from request body into `CredentialsDto`
2. Credentials are passed to e-commerce service for authentication
3. E-commerce service validates credentials and processes requests

**Note**: No explicit authentication middleware or security configuration was found in the codebase. Authentication appears to be handled by the e-commerce service rather than the gateway itself.

### 10.2 Authorization

**Not Explicitly Defined in Code**: No role-based access control (RBAC), voters, or access control rules are implemented in the provided code.

**Implicit Authorization**:
- Authorization is effectively handled by credential validation at the e-commerce service level
- Invalid credentials result in e-commerce service errors, which are mapped to ACI error codes

### 10.3 Data Protection

**Sensitive Data Handling**:
- Card details (number, CVV, expiry) are received in request body
- Card data is passed to e-commerce service (assumed to be PCI-DSS compliant)
- Session payloads store entire request data (including card details) in JSON format
- No explicit encryption or tokenization is visible in the code

**Signature Generation**:
- Webhook and redirect URLs are signed using HMAC-SHA256
- Signature secret: `authentication.password`
- Signature payload: Sorted query parameters in format `key1=value1|key2=value2|...`

### 10.4 Session Security

**Session Token**:
- Auto-generated UUID (v4)
- Stored in database with unique constraint
- Used in URL paths for 3DS flows

**Session Validation**:
- Sessions are retrieved by token
- Status validation ensures session is in correct state for operation
- Invalid sessions result in error responses

### 10.5 Input Validation

**Validation Layers**:
1. **Symfony Validator**: Field-level validation using constraints
2. **Custom Validator**: `AciDtoValidator` applies validation groups based on payment type
3. **E-Commerce Service**: Additional validation at service level

**Validation Groups**:
- `['Initial', 'Default']`: For Purchase and Pre-Authorization
- `['CaptureRefundReversal']`: For Capture, Refund, and Reversal
- `['3ds']`: For 3DS-specific fields (not explicitly used in provided code)

---

## 11. External Dependencies

### 11.1 E-Commerce Service (Paynetics)

**Service Name**: `ECOMMERCE_SERVICE`

**Base URL**: Configured via environment variable `ECOMMERCE_SERVICE` (default: `https://acquiring.paynetics.net`)

**Endpoints Used**:
- `POST /v1/purchase`: Purchase transaction
- `POST /v1/pre-authorization`: Pre-authorization transaction
- `POST /v1/capture/{transaction}`: Capture transaction
- `POST /v1/refund/{transaction}`: Refund transaction
- `POST /v1/reversal/{transaction}`: Reversal transaction
- `POST /v1/3d/authenticate`: 3DS authentication
- `POST /v1/3d/result/2`: 3DS result processing

**Authentication**:
- Method: HMAC-SHA256 signature
- Headers:
  - `x-api-key`: `authentication.userId`
  - `x-hash`: HMAC-SHA256 signature
  - `x-timestamp`: Unix timestamp
  - `x-request-id`: Request ID from storage
- Signature calculation: `HMAC-SHA256(apiKey + timestamp + operation + body, apiSecret)`

**Request Format**: JSON

**Response Format**: JSON array with transaction details

### 11.2 Webhook Notifications

**Target**: ACI client's `notificationUrl`

**Method**: HTTP GET

**Authentication**: HMAC-SHA256 signature in query parameters

**Delivery**:
- Triggered after successful payment transaction in 3DS flows
- Sent via `AciRequestManager::webhooks()`
- No retry mechanism visible in code
- No error handling for delivery failures

### 11.3 Database

**Type**: Not explicitly defined (appears to be relational database via Doctrine ORM)

**Connection**: Configured via `DATABASE_URL` environment variable

**Entities**:
- `Session`: Stores 3DS session data
- `CommunicationLog`: Logs API communications (if used)

### 11.4 Environment Variables

**Required Variables**:
- `THREEDS_URL`: Base URL for 3DS redirects (e.g., `https://aci.paynetics.net`)
- `ECOMMERCE_SERVICE`: E-Commerce service base URL
- `DATABASE_URL`: Database connection string
- `APP_SECRET`: Application secret (for Symfony)

**Optional Variables**:
- `APP_ENV`: Application environment (prod, dev, test)

### 11.5 No External APIs, Message Queues, or AI Services

**Not Present in Code**: The system does not integrate with:
- External payment gateways (beyond e-commerce service)
- Message queues
- AI services
- Other webhook consumers

---

## 12. Assumptions & Open Points

### 12.1 Missing Implementation Details

1. **Session Status Finalization**:
   - **Issue**: Code updates session status to 2 (processing) but never updates to 3 (processed) or 4 (failed)
   - **Assumption**: Status may be updated by background processes or remains at 2
   - **Clarification Needed**: What is the intended lifecycle of session status?

2. **Communication Log Usage**:
   - **Issue**: `CommunicationLog` entity exists but is not explicitly used in controller code
   - **Assumption**: May be populated by event subscribers or logging services
   - **Clarification Needed**: Is communication logging enabled? What triggers log creation?

3. **Webhook Retry Logic**:
   - **Issue**: No retry mechanism for failed webhook deliveries
   - **Assumption**: Webhook failures are not retried
   - **Clarification Needed**: Should webhook failures be retried? What is the retry strategy?

4. **Error Response Details**:
   - **Issue**: Some error responses have empty `description` fields
   - **Assumption**: Descriptions may be populated by e-commerce service responses
   - **Clarification Needed**: What information should be included in error descriptions?

### 12.2 Security Concerns

1. **No Explicit Authentication Middleware**:
   - **Issue**: No Symfony security configuration found
   - **Assumption**: Authentication is handled by e-commerce service
   - **Clarification Needed**: Should the gateway validate credentials before forwarding requests?

2. **Card Data Storage**:
   - **Issue**: Session payloads store card data in JSON format
   - **Assumption**: Database may be PCI-DSS compliant
   - **Clarification Needed**: Is card data encrypted at rest? What is the data retention policy?

3. **Session Token Security**:
   - **Issue**: Session tokens are UUIDs in URL paths
   - **Assumption**: UUIDs provide sufficient entropy
   - **Clarification Needed**: Should session tokens be time-limited? Should they be signed?

### 12.3 Business Logic Gaps

1. **Transaction ID Resolution for Capture**:
   - **Issue**: Capture operation uses `customParameters.initial.uuid` if URL parameter missing
   - **Assumption**: This is the intended fallback behavior
   - **Clarification Needed**: What happens if both are missing? Should this be validated?

2. **3DS Challenge Window**:
   - **Issue**: `customer.browser.challengeWindow` field exists but usage is not clear
   - **Assumption**: May be used by e-commerce service for 3DS configuration
   - **Clarification Needed**: How is challenge window configured?

3. **Recurring Payment Logic**:
   - **Issue**: Standing instruction fields are mapped to recurring types, but validation is not explicit
   - **Assumption**: E-commerce service validates recurring payment data
   - **Clarification Needed**: What are the valid combinations of standing instruction fields?

4. **Cryptocurrency Indicator**:
   - **Issue**: `SEND_CRYPTOCURRENCY_INDICATOR` maps to industry-specific transaction code "06"
   - **Assumption**: This is for cryptocurrency-related transactions
   - **Clarification Needed**: What are the business rules for cryptocurrency transactions?

### 12.4 Technical Assumptions

1. **Request Format**:
   - **Issue**: Code uses `$request->request->all()` which suggests form data
   - **Assumption**: API accepts both form data and JSON (via deserializer)
   - **Clarification Needed**: What Content-Types are supported?

2. **Error Code Mapping**:
   - **Issue**: Error code mappings are hardcoded in `KernelException` and `AciBuilder`
   - **Assumption**: Mappings are stable and do not change
   - **Clarification Needed**: Should mappings be configurable?

3. **Response Code Mapping**:
   - **Issue**: Response codes are mapped using `mapOriginalCode()` method
   - **Assumption**: All e-commerce service codes are mapped
   - **Clarification Needed**: What happens with unmapped codes? (Currently returns "Unknown code")

4. **Year Format Handling**:
   - **Issue**: Code converts 4-digit years to 2-digit for card expiry
   - **Assumption**: E-commerce service expects 2-digit years
   - **Clarification Needed**: Is this conversion always correct?

### 12.5 TODOs in Code

1. **Webhook Sending** (Lines 183, 232 in `AciController.php`):
   - **TODO Comment**: "//TODO: send webhook to aci"
   - **Status**: Webhook sending is actually implemented via `AciRequestManager::webhooks()`
   - **Action**: TODO comment should be removed or updated

2. **Error Response Building** (Line 212 in `AciBuilder.php`):
   - **Issue**: `buildErrorResponse()` references undefined variable `$responseFromEcomm`
   - **Status**: This appears to be a bug
   - **Action**: Variable should be removed or method signature updated

### 12.6 Configuration Gaps

1. **Validation Groups**:
   - **Issue**: Validation groups are hardcoded in `AciDtoValidator`
   - **Assumption**: Groups are correct for all payment types
   - **Clarification Needed**: Should validation groups be configurable?

2. **Response Code Mappings**:
   - **Issue**: Mappings are hardcoded in `AciBuilder`
   - **Assumption**: Mappings match ACI requirements
   - **Clarification Needed**: Should mappings be externalized to configuration?

3. **3DS URL Configuration**:
   - **Issue**: 3DS URLs are constructed using `$_ENV['THREEDS_URL']`
   - **Assumption**: Environment variable is always set
   - **Clarification Needed**: What happens if variable is missing?

### 12.7 Data Validation Gaps

1. **Custom Validators**:
   - **Issue**: `CardCVV`, `CardExpDate`, and `PageAmount` validators exist but implementation is not visible
   - **Assumption**: Validators are implemented in corresponding Validator classes
   - **Clarification Needed**: What are the exact validation rules?

2. **EitherOr Constraint**:
   - **Issue**: `EitherOr` constraint exists but usage is not visible in provided code
   - **Assumption**: May be used for conditional field validation
   - **Clarification Needed**: What fields use this constraint?

3. **Amount Validation**:
   - **Issue**: Amount is validated as "NotBlank" but no minimum value check (except PageAmount validator)
   - **Assumption**: PageAmount validator ensures amount > 0
   - **Clarification Needed**: What is the minimum/maximum amount allowed?

---

## Document Control

**Version**: 1.0  
**Date**: Generated from codebase analysis  
**Author**: Automated Functional Specification Generator  
**Status**: Based strictly on provided Symfony source code

**Note**: This specification reflects the actual implementation as found in the codebase. Any discrepancies between this document and the code should be investigated, as the code is the source of truth.

