# Payment Integration with Laravel (iyzico): A Real-World Experience

> Real problems and solutions encountered while integrating iyzico payments into a Laravel project — things not in the documentation.

- Published: 2017-08-06
- Category: Journal
- Tags: Laravel
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/payment-integration-with-laravel-iyzico-experience/
- Language: en-US
- Author: Muhammet Şafak

---
Payment integration looks straightforward in theory: read the API docs, send the required parameters, handle the response. The resource–method–status code triad from [the post on the fundamentals of REST](/en/blog/fundamentals-of-restful-api-design/) applies here too; the only difference is that this time you don't write the contract — the other side does. In practice, something unexpected comes up at every step. In this post I'm sharing what I learned while integrating iyzico into an e-commerce project — things that aren't in the documentation but that I ran into in the field.

## Why iyzico?

There are a few options for card payments in Turkey. I chose iyzico because it had a PHP SDK built for Turkish developers and a sandbox environment that actually worked. At the time, international alternatives like Stripe had more complex integration paths with Turkish banks.

Installing the SDK via Composer is straightforward:

```bash
composer require iyzico/iyzipay-php
```

## The Payment Flow

iyzico's basic payment flow consists of these steps:

1. Generate a payment form (iframe or redirect sent to the client).
2. The user enters their card details (on iyzico's own interface).
3. Verify the payment result using a token.

In the 3D Secure flow, once the transaction completes, iyzico sends a POST request to your `callbackUrl`. At that point your application needs to handle that request correctly.

```php
use Iyzipay\Options;
use Iyzipay\Model\Payment;
use Iyzipay\Request\CreatePaymentRequest;

$options = new Options();
$options->setApiKey(config('services.iyzico.api_key'));
$options->setSecretKey(config('services.iyzico.secret_key'));
$options->setBaseUrl(config('services.iyzico.base_url'));

$request = new CreatePaymentRequest();
$request->setLocale('tr');
$request->setConversationId((string) $order->id);
$request->setPrice($order->subtotal);
$request->setPaidPrice($order->total);
$request->setCurrency('TRY');
```

The `conversationId` field is critical: you put your own order ID here. iyzico sends this value back in the callback, so you can match the payment to the correct order.

## The Callback: Where You Need to Be Most Careful

In the 3D Secure flow, after the user passes the bank's verification screen, iyzico sends a POST request to your application's `callbackUrl`. What you need to do at this point:

1. Verify the payment result from the iyzico API using the `paymentId`.
2. Update the order.
3. Redirect the user to the appropriate page.

The most important thing I learned here is this: your callback URL must be reliable. The user might close the browser or lose their connection — in those cases the payment may have gone through but the order may not have been updated. That's why it's important to keep the callback handler synchronous rather than dispatching to a queue, and to keep it as short as possible. Just save the payment status; emails and other processing can wait in the queue.

```php
public function callback(Request $request)
{
    $token = $request->input('token');

    $retrieve = new RetrieveCheckoutFormRequest();
    $retrieve->setToken($token);
    $retrieve->setLocale('tr');

    $result = CheckoutForm::retrieve($retrieve, $this->options);

    if ($result->getPaymentStatus() === 'SUCCESS') {
        $order = Order::where('iyzico_token', $token)->firstOrFail();
        $order->markAsPaid($result->getPaymentId());
    }

    return redirect()->route('orders.show', $order);
}
```

## Differences Between Sandbox and Production

The sandbox environment works well overall, but I noticed two differences:

**Response times:** The sandbox is sometimes slower than production. You need to set timeout values accordingly; something that works in the sandbox may behave differently in production.

**3D Secure testing:** To test the 3D Secure flow in the sandbox you use iyzico's test card numbers. These cards don't always behave exactly as documented; setting up the specific scenario you want to test (success, failure, insufficient balance) with the right card number can take some time.

## Log Your Error Messages

iyzico's error codes in its responses aren't always descriptive enough. During integration, setting up a structure that logs both requests and responses saved a lot of time. In production, sensitive card data should never end up in logs — but if the payment ID, order ID, and iyzico's returned error code are in the logs, diagnosing problems becomes much easier.

Payment integration is a patience-testing job. The basic flow was up in two days, but closing the edge cases (repeated callbacks, duplicate payments, timeouts) took a week. Not having written tests for specific scenarios before spending that week became apparent over time — a lesson that paid off in subsequent integrations.
