createPayment method

Step 1: Create payment transaction POST /payments/collect

Implementation

Future<LokotroPaymentResponse> createPayment(LokotroPaymentRequest request) async {
  try {
    // Auth-v3: customer_reference / amount / currency are bound to the
    // session_token by the merchant backend and read off the session by
    // the gateway. They're omitted from [request.toJson] when null, so
    // log only what's actually on the wire — no `null` placeholders.
    LokotroPayLog.d('payment.create', {
      if (request.customerReference != null)
        'customer_reference': request.customerReference,
      if (request.amount != null) 'amount': request.amount,
      if (request.currency != null) 'currency': request.currency,
      'payment_method': request.paymentMethod,
    });

    // Generate idempotency key to prevent duplicate payments. Null-safe
    // fallback for Auth-v3 flows where customerReference is bound to the
    // session token instead of being sent on the wire.
    final idempotencyKey =
        '${request.customerReference ?? 'no_ref'}_${DateTime.now().millisecondsSinceEpoch}';

    final response = await _httpClient.post<Map<String, dynamic>>(
      '/payments/collect',
      data: request.toJson(),
      options: Options(headers: {'idempotency-key': idempotencyKey}),
      parser: (data) => data as Map<String, dynamic>,
    );

    if (response.isSuccess && response.data != null) {
      final paymentResponse = LokotroPaymentResponse.fromJson(response.data!);

      LokotroPayLog.d('payment.create.ok', {
        'transaction_id': paymentResponse.transactionId,
        'status': paymentResponse.status,
      });

      return paymentResponse;
    } else {
      throw LokotroPaymentException(
        message: response.message,
        code: 'PAYMENT_CREATION_FAILED',
      );
    }
  } catch (e) {
    LokotroPayLog.m('payment.create.err', 'create payment failed (type=${e.runtimeType})');
    rethrow;
  }
}