Opłacanie transakcji kartami płatniczymi cieszy się coraz większą popularnością na rynku e-commerce

Ten tutorial pozwoli zarówno zrozumieć mechanizm działania tego rodzaju płatności jak i pomoże w poprawnym wdrożeniu ich na własnej stronie. Ta metoda może również posłużyć do zapisania karty klienta, poprzez wygenerowanie tokenu, który będzie później wiązał kartę kredytową z kontem sprzedawcy i umożliwiał płatności bez potrzeby ponownego wprowadzania danych.

Mechanizm płatności kartą przez zintegrowaną bramkę

Ze względu na potrzebę zmaksymalizowania bezpieczeństwa wprowadzanych danych kart, ta metoda płatności wymaga wdrożenia dodatkowych zabezpieczeń na stronie sklepu i jest jedną z najbardziej zaawansowanych integracji.

  1. W pierwszej kolejności należy dodać możliwość wyboru metody płatności kartą swoim klientom, poprzez np. wyświetlenie dodatkowego przycisku "Szybka płatność kartą" np.
  2. Po wybraniu tej metody płatności, należy wyświetlić klientowi formularz, w którym będzie mógł wprowadzić dane karty, oraz regulamin płatności Tpay.com.
  3. Po wprowadzeniu danych, akcję sprawdzenia poprawności numeru karty można wykonać poprzez skrypt sprawdzający składnię. Każdy numer karty jest generowany w oparciu o konkretny schemat i te informacje są publicznie dostępne. W tym miejscu należy również rozpoznać czy wprowadzone dane są obsługiwane przez nasz system. Aktualnie akceptujemy wpłaty wyłączenie kartami VISA, MasterCard i Maestro.
  4. Po wprowadzeniu obsługiwanych danych i kliknięciu "Złóż zamówienie", należy wykonać ich szyfrowanie przed dalszym przesłaniem.
  5. Tpay.com udostępnia gotowe biblioteki programistyczne w języku PHP, które obsługują wszystkie te akcje automatycznie i nie wymagają dużego nakładu pracy deweloperów. Wystarczy tylko dostosować gotowe pliki do swoich potrzeb.
  6. Dalszy proces zależy od zabezpieczenia 3D Secure na karcie klienta:
    1. Karta z 3DS
      1. Płatność kartą zabezpieczoną będzie wymagała wykonania przekierowania klienta do tzw. bramki 3DS, gdzie będzie musiał potwierdzić płatność, w sposób uzależniony od swojego banku (np. poprzez wprowadzenie jednorazowego hasła SMS).
      2. Po udanej płatności klient zostanie przekierowany do strony sukcesu w sklepie.
      3. Powiadomienie o wpłacie zostanie wysłane na adres wynikowy sklepu, skonfigurowany w ustawieniach konta sprzedawcy.
    2. Karta bez 3DS
      1. Próba pobrania środków zostanie wykonana natychmiast po złożeniu zamówienia, a informacja o sukcesie lub niepowodzeniu zostanie zwrócona natychmiast.
      2. W tej sytuacji nie ma potrzeby wykonywania przekierowania do stron zewnętrznych, a zwrócone dane świadczą jednoznacznie o przypisaniu środków do konta sprzedawcy.
      3. Jeśli płatność się nie powiedzie należy umożliwić klientowi ponowną próbę zapłaty inną kartą.

 Techniczne wyjaśnienie płatności kartą na stronie sklepu

  1. Wykonanie transakcji kartą odbywa się poprzez wysłanie zapytania przez API. Do rozpoczęcia integracji będą potrzebne dane dostępowe takie jak klucz, hasło, kod weryfikacyjny i klucz publiczny RSA, które należy wygenerować zgodnie z instrukcją.
  2. Do wyświetlenia gotowego oskryptowanego formularza, zalecamy posłużyć się gotową biblioteką PHP tak jak w tym przykładzie. Wspomniany przykład wyświetli gotowy, oskryptowany formularz do wprowadzania danych karty.
  3. W momencie złożenia zamówienia, serwis powinien dysponować już danymi zamawiającego, takimi jak adres email oraz imię i nazwisko, które będą wymagane do przygotowania transakcji.
  4. Przed wysłaniem formularza z danymi, należy zaszyfrować wprowadzone dane:
    • numer karty,
    • kod CVV,
    • data ważności,
    kluczem publicznym RSA zgodnie z wytycznymi opisanymi w szczegółach metody securesale.
  5. Następnie wprowadzone dane należy wyczyścić i przesyłać jedynie zaszyfrowany jednorazowy ciąg. Ta operacja powinna odbyć się po stronie klienta, tak aby uniknąć jakiegokolwiek przesłania danych karty klienta w formie jawnej. Przykład skryptu JS obsługującego takie szyfrowanie znajduje się w bibliotece Tpay w serwisie github.com.
    Bliblioteka Tpay PHP zawiera formularz, który wykonuje te czynności automatycznie!
  6. Drugi krok to wywołanie metody securesale, służącej do przesłania potrzebnych danych do serwera Tpay.
    Przykład wykonania tej metody znajduje się w serwisie github w bibliotece PHP tpay-com, a także można wykonać testowe wywołanie klikając "Try" w dokumentacji.
    Minimalny zestaw parametrów, jakie należy wysłać w tej metodzie to:
    {
      "name": "john doe",
      "email": "[email protected]",
      "desc": "payment for order xyz",
      "amount": 10.99,
      "api_password": "XtCns9OAue8zSFJ",
      "sign": "a0be2d6884ce71265e38faa71a84228676784d81",
      "currency": 985,
      "card": "SrKhV6VEPiaw4cQEM2Mr0Nj7fuVhRdAdQ9Bk+sv7iM/St12s/W4zRNyqVptN3KIVMqr6coRXNfSPF/o/N9pLy94koY255N5U8wc/ApsBVyo7tlV7xQDxM+I5ksZ3f11qhxzIsk8inmzQGJywrg9CGvZAoiHazgDSVRuy7Tj4vbk="
    }​

    Dodatkowy parametr "onetimer" decyduje o zapamiętaniu karty płatniczej. Jeżeli metoda ta nie ma posłużyć do zapamiętania karty płatniczej, należy zawsze przesyłać w niej parametr "onetimer".
    Zapamiętanie karty można wykorzystać do wdrożenia płatności rekurencyjnych. Zapamiętany token będzie zwrócony w parametrze "cli_auth", w powiadomieniu systemowym lub odpowiedzi na wywołanie API (w zależności od posiadania zabezpieczenia 3D Secure).

  7. Dalszy proces zależy od zabezpieczenia 3D Secure na karcie klienta:
    1. Karta z 3DS:
      1. Zwrócony parametr "3ds_url" będzie zawierał link do bramki 3DS, pod który należy przekierować klienta aby mógł dokończyć walidację 3DS w swoim banku.
      2. Po udanym zatwierdzeniu płatności przez klienta, na adres wynikowy serwera (zdefiniowany w ustawieniach konta) zostanie wysłane asynchroniczne powiadomienie o wpłacie, tak samo jak w przypadku transferów bankowych.
        Przeczytaj dokumentację opisującą powiadomienia transakcji kartowych. W serwisie github.com udostępniliśmy gotowy przykład automatycznie weryfikujący powiadomienie i zwracający jego zwalidowaną zawartość.
      3. Adres URL, na który zostanie przekierowany płatnik po zatwierdzeniu płatności można zdefiniować statycznie w ustawieniach konta sprzedawcy lub przesyłać dynamicznie w odpowiednich parametrach.
    2. Karta bez 3DS:
      1. Wynik próby obciążenia karty zostanie zwrócony natychmiast i w przypadku powodzenia należy uznać go za wiążący.
      2. W przypadku niepowodzenia, w parametrze "reason" zostanie zwrócony identyfikator powodu odrzucenia transakcji, który można odnieść do tej tabeli.

Przykład wdrożenia w oparciu o biblioteki PHP Tpay

  1. Pobierz biblioteki Tpay z serwisu Github.com
  2. Stwórz plik (np. w folderze examples) rozszerzający klasę PaymentCardForms, w którego konstruktorze podaj swoje dane dostępowe do API kart płatniczych:
    <?php
    
    namespace tpayLibs\examples;
    
    use tpayLibs\src\_class_tpay\PaymentForms\PaymentCardForms;
    use tpayLibs\src\_class_tpay\Utilities\Util;
    use tpayLibs\src\Dictionaries\FieldsConfigDictionary;
    
    include_once 'config.php';
    include_once 'loader.php';
    
    class CardGate extends PaymentCardForms
    {
        public function __construct()
        {
            $this->cardApiKey = 'bda5eda723bf1ae71a82e90a249803d3f852248d';
            $this->cardApiPass = 'IhZVgraNcZoWPLgA';
            $this->cardKeyRSA = 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0NCk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRQ2NLRTVZNU1Wemd5a1Z5ODNMS1NTTFlEMEVrU2xadTRVZm1STS8NCmM5L0NtMENuVDM2ekU0L2dMRzBSYzQwODRHNmIzU3l5NVpvZ1kwQXFOVU5vUEptUUZGVyswdXJacU8yNFRCQkxCcU10TTVYSllDaVQNCmVpNkx3RUIyNnpPOFZocW9SK0tiRS92K1l1YlFhNGQ0cWtHU0IzeHBhSUJncllrT2o0aFJDOXk0WXdJREFRQUINCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ';
            $this->cardVerificationCode = '6680181602d396e640cb091ea5418171';
            $this->cardHashAlg = 'sha1';
            parent::__construct();
        }
    }​
  3. Dodaj publiczną funkcję sterującą klasą, w której udostępnimy bramkę płatności na stronie sklepu wywołując metodę getOnSiteCardForm():
    public function init()
    {
        if (empty($_POST)) {
            echo $this->getOnSiteCardForm('CardGate.php');
        }
    }

    Parametr wejściowy funkcji getOnSiteCardForm($url) musi zawierać nazwę pliku, który obierze dane z formularza i wykona próbę realizacji zapłaty. W tym przykładzie będzie to ten sam plik, który edytujemy.

  4. Dodaj funkcję odbierającą dane z formularza i wykonującą zapytanie do serwera Tpay:
    private function makeCardPayment()
    {
        $cardData = Util::post('carddata', FieldsConfigDictionary::STRING);
        $clientName = Util::post('client_name', FieldsConfigDictionary::STRING);
        $clientEmail = Util::post('client_email', FieldsConfigDictionary::STRING);
        $saveCard = Util::post('card_save', FieldsConfigDictionary::STRING);
        Util::log('Secure Sale post params', print_r($_POST, true));
        if ($saveCard === 'on') {
            $this->setOneTimer(false);
        }
        $this->setAmount(123)->setCurrency(985)->setOrderID('test payment 123');
        $this->setLanguage('en')->setReturnUrls('https://tpay.com', 'https://google.pl');
    
        return $this->registerSale($clientName, $clientEmail, 'test sale', $cardData);
    }​

    Niektóre dane należy pobrać z bazy danych (kwotę, walutę itp.), w tym przykładzie wprowadziliśmy dane przykładowe.

  5. Wykonanie funkcji MakeCardPayment() zwróci wynik, który może zawierać link do bramki 3DS lub dane udanej sprzedaży (w zależności od zabezpieczenia 3DS na karcie klienta).
    Przykładowy zestaw parametrów zwracany przez API dla kart bez 3DS:
    $response = [
      'result' => '1',
      'test_mode' => '1',
      'sale_auth' => 't59c28295aeb071b0cf6471b24f727f6456998de',
      'cli_auth' => 't59c2810d59285e3e0ee9d1f1eda1c2f4c554e24',
      'currency' => '985',
      'amount' => '10.99',
      'date' => '2017-06-28 15:31:46',
      'status' => 'correct',
      'card' => '1234****',
      'sign' => '5b7667a28605179ca60350e7468b123d1a911097',
    ];​
  6. Jeżeli płatność się powiedzie, można korzystając ze zwróconych danych oznaczyć zamówienie w sklepie jako opłacone. W tym celu należy dopisać funkcję setOrderAsComplete, która w oparciu o tablicę danych w $response, wywoła mechanizm sklepowy zmieniający status zamówienia i zapisujący przydatne dane - np. token klienta do ponownego użytku (cli_auth) oraz tytuł transakcji (sale_auth). W przypadku karty z 3DS należy wykonać przekierowanie do bramki 3DS.
  7. Na wypadek wprowadzenia niepoprawnych danych karty lub niewystarczających środków na koncie, warto dać klientowi drugą szansę i wygenerować dla niego kolejną transakcję metodą register_sale. Metoda ta różni się od secure sale tylko tym, że dane karty płatniczej są podawane w panelu transakcyjnym, a nie na stronie sklepu.
    W tym celu zmodyfikujemy funkcję makeCardPayment() oraz dodamy funkcję tryToSaleAgain():
    private function tryToSaleAgain()
    {
        //Spróbuj utworzyć nową transakcję i przekierować klienta do panelu Tpay
        $response = $this->makeCardPayment(true);
        if (isset($response['sale_auth'])) {
            header("Location: " . 'https://secure.tpay.com/cards/?sale_auth=' . $response['sale_auth']);
        } else {
            echo $response['err_desc'];
        }
    }​
    private function makeCardPayment($failOver = false)
    {
        $cardData = Util::post('carddata', FieldsConfigDictionary::STRING);
        $clientName = Util::post('client_name', FieldsConfigDictionary::STRING);
        $clientEmail = Util::post('client_email', FieldsConfigDictionary::STRING);
        $saveCard = Util::post('card_save', FieldsConfigDictionary::STRING);
        Util::log('Secure Sale post params', print_r($_POST, true));
        if ($saveCard === 'on') {
            $this->setOneTimer(false);
        }
        $this->setAmount(123)->setCurrency(985)->setOrderID('test payment 123');
        $this->setLanguage('en')->setReturnUrls('https://tpay.com', 'https://google.pl');
    
        return $failOver === false ?
            $this->registerSale($clientName, $clientEmail, 'test sale', $cardData) :
            $this->setCardData(null)->registerSale($clientName, $clientEmail, 'test sale');
    }
  8. W ostatnim kroku należy zmodyfikować sterowanie funkcjami klasy, układając je w logiczny ciąg:
    public function init()
    {
       if (empty($_POST)) {
           //Wyświetl formularz jeśli nie przesłano żadnych danych
           $this->showCardDirectHtml();
       } else {
           //Wykonaj próbę zapłaty
           $response = $this->makeCardPayment();
           if (isset($response['result']) && (int)$response['result'] === 1) {
               //Płatność kartą bez 3DS się powiodła
               $this->setOrderAsComplete($response);
           } elseif(isset($response['3ds_url'])) {
               //Karta ma zabezpieczenie 3DS, wykonaj przekierowanie do strony 3DS
               header("Location: " . $response['3ds_url']);
           } else {
               //Wprowadzono niepoprawne dane karty
               $this->tryToSaleAgain();
           }
       }
    }​
  9. Cały plik można wykonać dodając poza klasą linijkę:
    (new CardGate())->init();​
  10. Pamiętaj, że w przypadku płatności wymagającej przekierowania do panelu transakcyjnego Tpay lub bramki 3DS, powiadomienie o poprawnym opłaceniu zostanie wysłane do Twojego sklepu, dopiero po finalizacji transakcji.
    W celu odebrania takiego powiadomienia, utwórz plik CardNotification.php, rozszerzający klasę CardNotificationHandler. W panelu odbiorcy płatności należy podać adres URL prowadzący do tego pliku zgodnie z dokumentacją.
    Dodaj konstruktor nowej klasy:
    <?php
    
    namespace tpayLibs\examples;
    
    use tpayLibs\src\_class_tpay\Notifications\CardNotificationHandler;
    
    include_once 'config.php';
    include_once 'loader.php';
    
    class CardNotification extends CardNotificationHandler
    {
        public function __construct()
        {
            $this->cardApiKey = 'bda5eda723bf1ae71a82e90a249803d3f852248d';
            $this->cardApiPass = 'IhZVgraNcZoWPLgA';
            $this->cardKeyRSA = 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0NCk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRQ2NLRTVZNU1Wemd5a1Z5ODNMS1NTTFlEMEVrU2xadTRVZm1STS8NCmM5L0NtMENuVDM2ekU0L2dMRzBSYzQwODRHNmIzU3l5NVpvZ1kwQXFOVU5vUEptUUZGVyswdXJacU8yNFRCQkxCcU10TTVYSllDaVQNCmVpNkx3RUIyNnpPOFZocW9SK0tiRS92K1l1YlFhNGQ0cWtHU0IzeHBhSUJncllrT2o0aFJDOXk0WXdJREFRQUINCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ';
            $this->cardVerificationCode = '6680181602d396e640cb091ea5418171';
            $this->cardHashAlg = 'sha1';
            parent::__construct();
        }
    
    }​
  11. Następnie dodaj wywołanie funkcji sprawdzającej powiadomienie systemowe (wbudowanej w bibliotekę Tpay) oraz funkcję getOrderDetailsFromDatabase, która zwróci kwotę i walutę zamówienia zapisane w sklepie, na potrzeby walidacji powiadomienia:
    private function getTpayNotification()
    {
        //Jeżeli chcesz wyłączyć sprawdzanie adresu IP serwera Tpay, wykonaj tą komendę:
        $this->disableValidationServerIP();
        //Jeżeli korzystasz z proxy, wykonaj tą komendę aby sprawdzić adres IP w tablicy HTTP_X_FORWARDED_FOR:
        $this->enableForwardedIPValidation();
    
        $notification =  $this->handleNotification();
        //Pobierz szczegóły zamówienia z bazy danych sklepu
        $shopOrderData = $this->getOrderDetailsFromDatabase($notification['order_id']);
        //Sprawdź poprawność podpisu powiadomienia
        $this
            ->setAmount($shopOrderData['amount'])
            ->setCurrency($shopOrderData['currency'])
            ->setOrderID($notification['order_id']);
        $this->validateCardSign($notification['sign'], $notification['sale_auth'],
            $notification['card'], $notification['date'], $notification['status']);
    
        return $notification;
    }​
    private function getOrderDetailsFromDatabase($orderId)
    {
        //Zaprogramuj pobranie szczegółów zamówienia z bazy sklepu w oparciu o OrderId
        //Przykładowe dane na potrzeby przykładu
        return [
            'amount' => 123.00,
            'currency' => 985,
        ];
    }
  12. Dodaj funkcję sterującą klasą init() oraz własną funkcję setOrderAsConfirmed(), która w oparciu o zwrócone dane oznaczy zamówienie jako opłacone oraz zapisze przydatne dane - np. token klienta do ponownego użytku (cli_auth) oraz tytuł transakcji (sale_auth).
    public function init()
    {
        $notification = $this->getTpayNotification();
        if (isset($notification['status']) && $notification['status'] === 'correct') {
            $this->setOrderAsConfirmed($notification);
        }
    }​

    Zmienna $notification będzie zawierała tablicę danych, opisanych w dokumentacji.

  13. Dodaj wywołanie nowo stworzonej klasy na jej końcu:
    (new CardNotification())->init();​