# رفع خطای `Class "SoapClient" not found` در پرداخت ملت (سرور prod)

## پروژه

`clinicpro` (backend / infra — Docker image)

## زمینه

درگاه ملت (`MellatGateway`) در مسیر prod برای فراخوانی وب‌سرویس بانک از `new \SoapClient(...)` استفاده می‌کند (کلاس PHP از افزونه `ext-soap`). روی محیط لوکال (ddev) افزونه `soap` به‌صورت پیش‌فرض نصب است، پس مشکلی دیده نمی‌شود؛ اما image پرود بر پایه `php:8.2-fpm-alpine` ساخته می‌شود و در `Dockerfile` فقط این افزونه‌ها نصب می‌شوند:

```dockerfile
docker-php-ext-install pdo_mysql intl opcache
```

`soap` نصب نمی‌شود، بنابراین در پرود هنگام initiate کردن پرداخت، fatal می‌شود:

```
Payment initiate failed (mellat): Class "SoapClient" not found @ /app/src/Payment/Gateway/MellatGateway.php:70
context {"orderId":"6","amount":150000}
```

## مشکل / هدف

افزونه `ext-soap` روی image پرود نصب شود تا مسیر prod درگاه ملت (`bpPayRequest` / `bpVerifyRequest` / `bpSettleRequest` / `bpRefundRequest` / `bpReversalRequest`) روی `SoapClient` کار کند.

نکته مهم: sandbox این مشکل را ندارد چون مسیر sandbox از REST (JSON + Basic Auth) استفاده می‌کند نه SOAP. مشکل فقط در حالت prod (فلگ `mellat_sandbox` غیرفعال) رخ می‌دهد.

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `Dockerfile` (stage 3 — runtime) | نصب افزونه‌های PHP؛ باید `soap` اضافه شود |
| `src/Payment/Gateway/MellatGateway.php` | خط ۷۰: `new \SoapClient($this->wsdlUrl(), ...)` — نیازمند `ext-soap` |
| `composer.json` | افزودن `ext-soap` به `require` تا نبود افزونه زودتر (build-time) شناسایی شود |

## وضعیت فعلی

`Dockerfile` — بلوک نصب افزونه‌ها در stage runtime (خطوط ~62–73):

```dockerfile
RUN set -eu; \
    export MAKEFLAGS="-j$(nproc)"; \
    retry() { for i in 1 2 3 4 5; do "$@" && return 0; echo "retry $i: $*"; sleep 5; done; return 1; }; \
    retry apk add --no-cache nginx supervisor icu-libs \
 && retry apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev \
 && docker-php-ext-install pdo_mysql intl opcache \
 && retry curl -sSLf "https://github.com/phpredis/phpredis/archive/refs/tags/${PHPREDIS_VERSION}.tar.gz" -o /tmp/phpredis.tar.gz \
 && mkdir -p /usr/src/php/ext/redis \
 && tar -xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1 \
 && docker-php-ext-install redis \
 && rm -rf /tmp/phpredis.tar.gz \
 && apk del .build-deps
```

`MellatGateway.php:67-78`:

```php
private function soap(): \SoapClient
{
    if ($this->soap === null) {
        $this->soap = new \SoapClient($this->wsdlUrl(), [
            'trace'              => true,
            'exceptions'         => true,
            'encoding'           => 'UTF-8',
            'connection_timeout' => 10,
        ]);
    }
    return $this->soap;
}
```

## وظایف

### ۱. افزودن `soap` به `docker-php-ext-install` در `Dockerfile`

افزونهٔ `soap` جزو افزونه‌های bundled خودِ PHP است و نیاز به شبکه (pecl/github) ندارد؛ فقط برای کامپایل به هدرهای `libxml2-dev` نیاز دارد. `libxml2-dev` را به `.build-deps` اضافه کن (تا بعد از build با `apk del .build-deps` پاک شود) و `soap` را به لیست `docker-php-ext-install` اضافه کن.

نتیجهٔ مطلوب:

```dockerfile
 && retry apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev libxml2-dev \
 && docker-php-ext-install pdo_mysql intl opcache soap \
```

نکته: `libxml2` (کتابخانهٔ runtime، بدون `-dev`) در image پایهٔ PHP موجود است، پس افزونهٔ کامپایل‌شده در runtime بدون مشکل load می‌شود و نیازی به افزودن آن به لیست runtime نیست. فقط هدرهای `-dev` که در build لازم‌اند در `.build-deps` قرار می‌گیرند.

### ۲. اعلام نیازمندی در `composer.json`

`ext-soap` را به بخش `require` اضافه کن تا اگر روی محیطی افزونه نبود، در زمان `composer install` هشدار/خطا داده شود و مشکل زودتر از runtime دیده شود:

```jsonc
"require": {
    // ... موجود
    "ext-soap": "*"
}
```

توجه: در `Dockerfile` مرحلهٔ vendor از `--ignore-platform-reqs` استفاده می‌کند، پس این افزوده build را نمی‌شکند؛ صرفاً به‌عنوان مستندسازی نیازمندی و اعتبارسنجی در محیط‌های دیگر عمل می‌کند.

### ۳. تأیید و rebuild

بعد از تغییر `Dockerfile`، image پرود باید دوباره build/deploy شود (Coolify). برای تأیید نصب افزونه پس از build:

```bash
php -m | grep -i soap        # باید soap را چاپ کند
php -r 'var_dump(class_exists("SoapClient"));'   # bool(true)
```

## نکات مهم

- مشکل صرفاً محیطی (نبود افزونه در image پرود) است، نه باگ منطقی در کد `MellatGateway`. کد درست است؛ فقط وابستگی runtime روی پرود غایب بود.
- بعد از deploy، پرداخت واقعی ملت (حالت prod، فلگ `mellat_sandbox=0`) باید یک‌بار تست شود تا مسیر SOAP (`bpPayRequest`) واقعاً پاسخ بگیرد.
- الگوی موجود در `Dockerfile` را رعایت کن: build-deps مجازی که بعد از کامپایل با `apk del .build-deps` حذف می‌شوند تا حجم image کم بماند. `libxml2-dev` را داخل همین گروه بگذار، نه به‌صورت دائمی.
- این تغییر API را عوض نمی‌کند؛ نیازی به به‌روزرسانی `docs/api/payment.md` نیست (فقط اگر رفتار/قرارداد endpoint تغییر کند لازم می‌شد).
