Przelewy24 - dostosowanie klasy do dokumentacji REST API

Przelewy24 - dostosowanie klasy do dokumentacji REST API
SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2
0

Na wstępie powiem, że moja klasa Przelewy24Service działa poprawnie w Laravelu 11, ale używa curla i starej składni gdzie pola zawierają przedrostek p24_. Chciałem przerobić ja zgodnie z najnowszą dokumentacją, ale po modyfikacji dostaję response z curla, że CRC jest niepoprawne. Rzecz w tym, że nie zmieniam żadnych kluczy, używam tych samych.

Oto moja klasa:

Kopiuj
<?php

namespace App\Services\Api\Przelewy24;

use App\DTOs\CounterpartyPaymentKeys;
use App\Interfaces\PaymentInterface;
use App\Models\Counterparty;
use App\Models\Order;
use App\Models\Payment;
use App\Services\Api\Przelewy24\Exceptions\Przelewy24ConnectionException;
use App\Services\Api\Przelewy24\Exceptions\Przelewy24TokenException;
use App\Services\Api\Przelewy24\Exceptions\Przelewy24VerifyPaymentException;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class Przelewy24Service
{
    private const PRZELEWY24_API_VERSION = '3.2';
    private const PRZELEWY24_COUNTRY = PaymentInterface::PAYMENT_COUNTRY;
    private const PRZELEWY24_CURRENCY = PaymentInterface::PAYMENT_CURRENCY;
    private const PRZELEWY24_TIME_LIMIT = PaymentInterface::PAYMENT_TIME_LIMIT_IN_MINUTES;
    private const PRZELEWY24_WAIT_FOR_RESULT = false;

    /**
     * @throws Przelewy24ConnectionException
     * @throws Przelewy24TokenException
     */
    public function pay(
        Order $order,
        Payment $payment,
    ): string {
        /** @var Counterparty $orderCounterparty */
        $orderCounterparty = $order->counterparty;

        $orderCounterpartyCanConnect = $this->counterpartyCanConnect($orderCounterparty);

        if (!$orderCounterpartyCanConnect) {
            throw new Przelewy24ConnectionException(
                sprintf(
                    'Counterparty %s cannot establish connection.',
                    $orderCounterparty->getKey(),
                )
            );
        }

        $paymentKeys = $orderCounterparty->getPaymentKeys();

        $amount = $order->convertOrderTotalToCents();
        $description = sprintf('%s: %s', config('app.name'), $order->order_number);
        $language = $this->getPaymentLanguageByOrderLanguage($order);
        $payerEmailAddress = $order->user->email;
        $sessionId = $payment->getPaymentSessionId();
        $signature = $this->generateSignature($sessionId, $amount, self::PRZELEWY24_CURRENCY, $paymentKeys);
        $urlReturn = $this->generateReturnUrl($order);
        $urlStatus = $this->generateStatusUrl();

        $token = $this->createToken(
            paymentKeys: $paymentKeys,
            sessionId: $sessionId,
            amount: $amount,
            description: $description,
            payerEmailAddress: $payerEmailAddress,
            urlReturn: $urlReturn,
            urlStatus: $urlStatus,
            language: $language,
            signature: $signature,
        );

        $order->payment_uuid = $payment->getKey();
        $order->save();

        $paymentUrl = sprintf('https://%s.przelewy24.pl/trnRequest/%s', $this->getEnvironment(), $token);

        $payment->savePaymentUrl($paymentUrl);

        return $paymentUrl;
    }

    /**
     * @throws Przelewy24TokenException
     */
    private function createToken(
        CounterpartyPaymentKeys $paymentKeys,
        string $sessionId,
        int $amount,
        string $description,
        string $payerEmailAddress,
        string $urlReturn,
        string $urlStatus,
        string $language,
        string $signature,
    ): string {
        $urlTransactionRegister = sprintf('https://%s.przelewy24.pl/trnRegister', $this->getEnvironment());
        $payerEmailAddress = Str::replace('+', '%2b', $payerEmailAddress);

        $headers[] = 'p24_amount=' . $amount;
        $headers[] = 'p24_api_version=' . self::PRZELEWY24_API_VERSION;
        $headers[] = 'p24_country=' . self::PRZELEWY24_COUNTRY;
        $headers[] = 'p24_crc=' . $paymentKeys->crc;
        $headers[] = 'p24_currency=' . self::PRZELEWY24_CURRENCY;
        $headers[] = 'p24_description=' . $description;
        $headers[] = 'p24_email=' . $payerEmailAddress;
        $headers[] = 'p24_language=' . $language;
        $headers[] = 'p24_merchant_id=' . $paymentKeys->merchantId;
        $headers[] = 'p24_pos_id=' . $paymentKeys->merchantId;
        $headers[] = 'p24_session_id=' . $sessionId;
        $headers[] = 'p24_sign=' . $signature;
        $headers[] = 'p24_time_limit=' . self::PRZELEWY24_TIME_LIMIT;
        $headers[] = 'p24_url_return=' . urlencode($urlReturn);
        $headers[] = 'p24_url_status=' . urlencode($urlStatus);
        $headers[] = 'p24_wait_for_result=' . self::PRZELEWY24_WAIT_FOR_RESULT;

        $response = $this->makeCurlRequest($headers, $urlTransactionRegister);

        parse_str($response, $output);

        return $output['token'] ?? throw new Przelewy24TokenException($output['errorMessage'] ?? '');
    }

    /**
     * @throws Przelewy24VerifyPaymentException
     */
    public function verify(
        Request $request,
        Payment $payment,
    ): true {
        $counterparty = $payment->order->counterparty;

        /** @var CounterpartyPaymentKeys $counterpartyPaymentKeys */
        $counterpartyPaymentKeys = $counterparty->getPaymentKeys();

        $signature = sprintf(
            '%s|%s|%s|%s|%s',
            $request->post('p24_session_id'),
            $request->post('p24_order_id'),
            $request->post('p24_amount'),
            self::PRZELEWY24_CURRENCY,
            $counterpartyPaymentKeys->crc,
        );

        $signature = md5($signature);

        $headers[] = 'p24_amount=' . $request->post('p24_amount');
        $headers[] = 'p24_currency=' . self::PRZELEWY24_CURRENCY;
        $headers[] = 'p24_merchant_id=' . $request->post('p24_merchant_id');
        $headers[] = 'p24_order_id=' . $request->post('p24_order_id');
        $headers[] = 'p24_pos_id=' . $request->post('p24_pos_id');
        $headers[] = 'p24_session_id=' . $request->post('p24_session_id');
        $headers[] = 'p24_sign=' . $signature;

        $urlTransactionVerify = sprintf('https://%s.przelewy24.pl/trnVerify', $this->getEnvironment());

        $response = $this->makeCurlRequest($headers, $urlTransactionVerify);

        parse_str($response, $output);

        if (isset($output['error']) && $output['error'] !== '0') {
            throw new Przelewy24VerifyPaymentException($output['errorMessage'] ?? '');
        }

        return true;
    }

    public function counterpartyCanConnect(
        Counterparty $counterparty,
    ): bool {
        $urlTestConnection = sprintf('https://%s.przelewy24.pl/api/v1/testAccess', $this->getEnvironment());

        $paymentKeys = $counterparty->getPaymentKeys();

        return Http::withBasicAuth($paymentKeys->merchantId, $paymentKeys->apiKey)->get($urlTestConnection)->ok();
    }

    public function counterpartiesCanConnect(
        Collection $counterparties
    ): Collection {
        $urlTestConnection = sprintf('https://%s.przelewy24.pl/api/v1/testAccess', $this->getEnvironment());

        $counterpartiesData = $counterparties->mapWithKeys(fn($counterparty) => [
            $counterparty->getKey() => $counterparty->getPaymentKeys(),
        ]);

        $responses = Http::pool(
            fn($pool) => $counterpartiesData
                ->map(
                    fn($paymentKeys) => $pool
                        ->withBasicAuth($paymentKeys->merchantId, $paymentKeys->apiKey)
                        ->get($urlTestConnection)
                )
                ->all()
        );

        return $counterpartiesData
            ->keys()
            ->combine(collect($responses)->map(fn($response) => $response->ok()));
    }

    private function generateSignature(
        string $sessionId,
        int $amount,
        string $currency,
        CounterpartyPaymentKeys $paymentKeys,
    ): string {
        $signature = sprintf(
            '%s|%s|%d|%s|%s',
            $sessionId,
            $paymentKeys->merchantId,
            $amount,
            $currency,
            $paymentKeys->crc,
        );

        return md5($signature);
    }

    private function generateReturnUrl(Order $order): string
    {
        return route('frontend.orders.show', $order);
    }

    private function generateStatusUrl(): string
    {
        return route('frontend.payments.verify');
    }

    /**
     * todo
     *
     * @param Order $order
     *
     * @return string
     */
    private function getPaymentLanguageByOrderLanguage(Order $order): string
    {
        return 'PL';
    }

    /**
     * Should be either:
     *
     *  - sandbox - for testing
     *  - secure - for production
     *
     * @return string
     */
    private function getEnvironment(): string
    {
        return config('przelewy24.environment');
    }

    private function makeCurlRequest(array $headers, string $curlUrl): bool|string
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $headers));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1');
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_URL, $curlUrl);

        $response = curl_exec($ch);

        curl_close($ch);

        return $response;
    }
}

Metoda pay() ma za zadanie zwrócić mi url'a do P24 gdzie użytkownik będzie mógł zapłacić, po płatności wraca na $urlReturn i po chwili P24 weryfikuje płatność poprzez wysłanie POST requesta do mojego routa z weryfikacją. Testowałem z ngrokiem i wszystko działa.

Kopiuj
HTTP Requests
-------------

18:40:25.210 CEST GET  /favicon-16x16.png                           200 OK
18:40:13.240 CEST GET  /orders/9ef936a1-0104-4f0e-aa33-1a9bf55cac93 200 OK
18:40:12.960 CEST POST /payments/verify                             200 OK <----- tu dzwoni do mnie P24 i jest wszystko poprawnie
18:40:04.888 CEST POST /cart/checkout/summary                       302 Found
18:40:01.828 CEST GET  /images/p24-logo.svg                         200 OK
18:40:00.636 CEST GET  /cart/checkout/summary                       200 OK
18:39:59.642 CEST POST /cart/checkout/start                         302 Found
18:39:56.380 CEST GET  /cart/checkout/start                         200 OK
18:39:53.874 CEST GET  /cart                                        200 OK
18:39:51.838 CEST GET  /

Moje pytanie czy w ogóle ruszać tą klasę czy nie? W teorii wszystko działa, ale jak zaczynam to przepisywać z użyciem guzzle'a (wstrzykuję http clienta do kontrolera) albo fasady Http w Laravelu to jak dochodzi do generowania tokenu to mam info, że błędne CRC. W nowej dokumentacji widzę, że sygnatura nie jest już generowana z md5 tylko z sha384 itp.

Czy ktoś może mi zasugerować czy użycie tej klasy w takiej formie jest OK w roku 2025? W teorii nie chcę sobie robić niepotrzebnej dodatkowej pracy (bo nie wiem dlaczego dla tych samych merchantId, apiKey i crc wszystko działa na klasie którą tu wkleiłem, ale jak przerabiam to już nie. Co ciekawe w metodzie counterpartyCanConnect() którą napisałem z odwołaniem do api/v1 i Http::withBasicAuth połączenie jest poprawne.

Proszę o opinie.

SS
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 180
0

Jest też oficjalna biblioteka przelewy24 dla PHP także może rozważ jej użycie

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2
0
ssquad napisał(a):

Jest też oficjalna biblioteka przelewy24 dla PHP także może rozważ jej użycie

A możesz proszę mi ją wskazać? https://www.przelewy24.pl/do-pobrania Ja widzę tylko wtyczki dla gotówców jak shopify czy woocommerce, na githubie tez nic nie widze: https://github.com/przelewy24

hzmzp
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 734
1

Kosztowało mnie to tylko jedno zapytanie do chatgpt masz dokumentacje, z niej możesz wygenerować gotowy kod za pomocą openapi generator
https://developers.przelewy24.pl/yaml/pl_documentation_1.0.yaml

SS
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 180
0

Możesz wpisać przelewy24 php sdk i coś znajdziesz :) Jak patrzyłem to faktycznie nie wiem czy jest oficjalna. Ja zawsze korzystam z https://github.com/mnastalski/przelewy24-php i jest spoko. Chyba że chcesz uruchomić np płatności cykliczne to w tej bibliotece chyba tego nie ma - przynajmniej ja nie znalazłem. Trzeba działać bezpośrednio z API.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.