<?php
// ============================================================
//  Netcash Pay Now integration library
// ============================================================
//
//  Netcash uses a service-key authentication model — there is
//  no HMAC signature like PayFast. Outgoing requests carry the
//  M1 (Pay Now Service Key) and M2 (Software Vendor Key).
//
//  Notify-handler integrity is verified two ways:
//    1. Optional source-IP filter (Netcash IPs vary, so we use
//       a soft check that logs but doesn't block in production
//       unless explicitly enabled).
//    2. RequestTrace callback to ws.netcash.co.za to confirm
//       the transaction details independently of what was
//       posted to us. This is the primary integrity check.
//
//  Test mode is controlled in the Netcash NetConnector profile
//  in the Netcash dashboard — NOT via a query parameter. To use
//  sandbox, configure a TEST profile and use those service keys.
//
// ============================================================

/**
 * True if Netcash is configured to use sandbox mode.
 * For Netcash this just means we have a TEST profile's keys
 * configured and we're forgiving about validation failures.
 */
function nc_is_sandbox(): bool {
    return defined('NETCASH_SANDBOX') && NETCASH_SANDBOX;
}

/**
 * True if Netcash credentials are configured.
 */
function nc_is_configured(): bool {
    return defined('NETCASH_SERVICE_KEY')
        && defined('NETCASH_VENDOR_KEY')
        && NETCASH_SERVICE_KEY !== ''
        && NETCASH_VENDOR_KEY !== '';
}

/**
 * Pay Now form action URL (same for sandbox and live).
 */
function nc_paynow_url(): string {
    return 'https://paynow.netcash.co.za/site/paynow.aspx';
}

/**
 * Build the form fields to POST to Netcash Pay Now.
 *
 * @param array $params Options:
 *   - reference     (string)  required — unique transaction ID (becomes p2)
 *   - description   (string)  required — what's being paid for (becomes p3)
 *   - amount_cents  (int)     required — total in cents (we render to "X.XX")
 *   - email         (string)  optional — cardholder email (becomes m9)
 *   - mobile        (string)  optional — cardholder mobile (becomes m11)
 *   - extra1        (string)  optional — round-trip data (becomes m4)
 *   - extra2        (string)  optional — round-trip data (becomes m5)
 *   - extra3        (string)  optional — round-trip data (becomes m6)
 *   - request_token (bool)    optional — request CC token for future use (m14)
 *   - is_subscription (bool)  optional — flag as subscription (m16)
 *   - sub_cycles    (int)     optional — number of cycles (m17)
 *   - sub_frequency (int)     optional — frequency code (m18)
 *   - sub_start     (string)  optional — YYYY-MM-DD start date (m19)
 *   - sub_amount    (int)     optional — recurring amount in cents (m20)
 *   - existing_token (string) optional — existing CC token to reuse (m15)
 *
 * @return array Field name => value pairs ready to render in a form.
 */
function nc_build_payment_fields(array $params): array {
    if (!nc_is_configured()) {
        throw new RuntimeException('Netcash credentials not configured');
    }

    $reference   = (string)($params['reference']   ?? '');
    $description = (string)($params['description'] ?? '');
    $amount_c    = (int)   ($params['amount_cents'] ?? 0);

    if ($reference === '')   throw new InvalidArgumentException('reference is required');
    if ($description === '') throw new InvalidArgumentException('description is required');
    if ($amount_c <= 0)      throw new InvalidArgumentException('amount_cents must be > 0');

    // Netcash requires "X.XX" — exactly 2 decimals.
    $amount_str = number_format($amount_c / 100, 2, '.', '');

    $fields = [
        'M1'     => NETCASH_SERVICE_KEY,
        'M2'     => NETCASH_VENDOR_KEY,
        'p2'     => $reference,
        'p3'     => mb_substr($description, 0, 100),
        'p4'     => $amount_str,
        'Budget' => 'Y',                          // compulsory per Netcash docs
    ];

    if (!empty($params['email']))  $fields['m9']  = (string)$params['email'];
    if (!empty($params['mobile'])) $fields['m11'] = (string)$params['mobile'];

    // Round-trip extras — round-tripped back as Extra1/2/3 in the Notify post
    if (isset($params['extra1'])) $fields['m4'] = (string)$params['extra1'];
    if (isset($params['extra2'])) $fields['m5'] = (string)$params['extra2'];
    if (isset($params['extra3'])) $fields['m6'] = (string)$params['extra3'];

    // Subscription / tokenization
    if (!empty($params['request_token'])) $fields['m14'] = '1';
    if (!empty($params['existing_token'])) $fields['m15'] = (string)$params['existing_token'];
    if (!empty($params['is_subscription'])) {
        $fields['m16'] = '1';
        if (!empty($params['sub_cycles']))    $fields['m17'] = (string)(int)$params['sub_cycles'];
        if (!empty($params['sub_frequency'])) $fields['m18'] = (string)(int)$params['sub_frequency'];
        if (!empty($params['sub_start']))     $fields['m19'] = (string)$params['sub_start'];
        if (!empty($params['sub_amount'])) {
            $fields['m20'] = number_format(((int)$params['sub_amount']) / 100, 2, '.', '');
        }
    }

    return $fields;
}

/**
 * Map Netcash Method code → human label.
 */
function nc_method_label(int $method): string {
    static $map = [
        1  => 'Credit card',
        2  => 'Bank EFT',
        3  => 'Retail',
        4  => 'Instant EFT',
        5  => 'MasterPass',
        6  => 'Visa Click to Pay',
        7  => 'MasterPass QR',
        8  => 'Mobicred',
        9  => 'Payflex',
        10 => 'Flash 1Voucher',
        11 => 'Pay My Way',
    ];
    return $map[$method] ?? "Method $method";
}

/**
 * Verify a Netcash transaction by RequestTrace.
 *
 * Calls https://ws.netcash.co.za/PayNow/TransactionStatus/Check?RequestTrace=X
 * and returns the parsed JSON response, or null on failure.
 *
 * Returns shape:
 *   ['RequestTrace'=>'...', 'Amount'=>'4.00',
 *    'TransactionAccepted'=>true|false, 'Reference'=>'...', 'Reason'=>'...']
 */
function nc_verify_via_lookup(string $request_trace): ?array {
    $request_trace = trim($request_trace);
    if ($request_trace === '') return null;

    $url = 'https://ws.netcash.co.za/PayNow/TransactionStatus/Check?RequestTrace='
         . urlencode($request_trace);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_CONNECTTIMEOUT => 8,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = curl_exec($ch);
    $http     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($response === false || $http < 200 || $http >= 300) return null;
    $data = json_decode($response, true);
    return is_array($data) ? $data : null;
}

/**
 * Soft check that the request looks like it could be from Netcash.
 *
 * Netcash doesn't publish a stable public IP list, so we don't
 * hard-block on this. Instead we record what we saw and rely on
 * the RequestTrace callback for actual integrity checking.
 *
 * Returns true if the IP is plausibly Netcash (or sandbox is on),
 * false otherwise.
 */
function nc_soft_check_source(string $remote_ip): bool {
    if (nc_is_sandbox()) return true;
    if ($remote_ip === '') return false;
    // Netcash uses Azure-fronted infrastructure; their IPs change.
    // Without an authoritative list we accept any well-formed IP and
    // depend on the lookup callback for verification.
    return (bool)filter_var($remote_ip, FILTER_VALIDATE_IP);
}

/**
 * Generate a unique reference for a Pay Now transaction.
 * Format: BLLW-{member_id}-{rand}-{Ymd-His}
 */
function nc_generate_reference(int $member_id): string {
    return sprintf(
        'BLLW-%d-%s-%s',
        $member_id,
        substr(bin2hex(random_bytes(3)), 0, 6),
        date('Ymd-His')
    );
}