Uruchomienie systemu płatności na stronie internetowej

Prędzej czy później każdy programista pracujący na zlecenie spotka się z zadaniem wpięcia do tworzonej aplikacji modułu płatności automatycznej. To, jaki system wybierze, to już jego sprawa – prawie wszystkie (z tego, co zdążyłem się zorientować) działa na podobnej zasadzie. Osobiście polecam system Transferuj.pl, wszystkim też odradzam PayU – z tym pierwszym nie miałem jak do tej pory żadnych problemów, PayU natomiast do tej pory nie odblokowało właściwego (nie testowego) kanału płatności na kilku serwisach, z którymi pracowałem…

Zasada działania elektronicznych systemów płatności

Schemat systemu płatności

Jak można wywnioskować z powyższej ryciny, nic raczej skomplikowanego tam nie znajdziemy. Wątpliwości może nasuwać to, dlaczego formularz do wysyłania zapytania nie jest wbudowany w stronę, która podsumowuje nam płatność (w przykładowym scenariuszu), oraz dlaczego w ogóle taki formularz istnieje?

Otóż cały kod ASP.NET na stronie jest już otoczony tagiem <form>, a umieszczenie formularza w formularzu… Krótko mówiąc, nie zadziała. Można niby umieścić dwa formularze z runat="server", co nie jest ani wygodne, ani praktyczne, zwłaszcza jeśli strona oparta jest na Master Page‘ach.

Wysyłanie natomiast danych poprzez POST od strony kodu źródłowego jest karkołomne i często też niemożliwe. Próbowałem cały dzień, żeby dojść do tego wniosku i zrobić to “normalnie”.

Przygotowania

Na początku możemy utworzyć klasę, w której będziemy trzymać potrzebne nam funkcje (liczącą sumę kontrolną, sprawdzająca poprawność otrzymanych danych, zmieniającą status transakcji) oraz potrzebnych danych (otrzymanych od Transferuj.pl oraz ustalony kod bezpieczeństwa). Jako, że baza danych ma kolumnę statusu jako nvarchar(256), zdefiniowałem również statusy transakcji.

Jako, że pierwsza strona (z podsumowaniem płatności) u każdego będzie wyglądać inaczej, to z technicznego punktu widzenia jej zadaniem będzie:

  • Utworzenie nowej płatności w systemie – musi mieć status płatności oczekującej
  • Przekierowanie na stronę Redirect.aspx?id={ID}, gdzie – jak łatwo się domyśleć, {ID} jest identyfikatorem płatności w naszej bazie danych

Zalążek naszej klasy wyglądać więc będzie tak:

using System.Collections.Specialized;
using System.Net;
using System.Security.Cryptography;
using System.Text;

namespace PaymentsExt
{
    public static class Payments
    {
         public static void ChangePaymentStatus(Guid PaymentID, string Status)
         {
             // ...
         }

        public static bool IsValid(string Md5sum, string Id, string Amount, string Crc)
        {
             // ...
        }

        public static string Checksum(string Input)
        {
             // ...
        }

        public const string StatusOK = "ok";
        public const string StatusWaiting = "wait";
        public const string StatusError = "error";

        public const string AutomaticID = "12345";               // otrzymane od Transferuj.pl
        public const string SecurityCode = "abcdefgh1234567890"; // wymyślamy sami
    }
}

Należy mieć również na uwadze to, że ID transakcji to GUID, więc w zależności od stosowanego identyfikatora należy odpowiednio pozmieniać kod.

ChangePaymentStatus() to natomiast funkcja zmieniająca nam status płatności w naszym systemie – kwestia indywidualnego podejścia, dlatego pominę jej wprowadzanie

Jeszcze więcej kodu

Przy funkcji Checksum() nie ma zbytniej filozofii – otwieramy dokumentację i generujemy hash MD5 pamiętając, że ta funkcja w C# jest IDisposable:

public static string Checksum(string Input)
{
    using (MD5 md5 = MD5.Create())
    {
        return BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(Input))).Replace("-", String.Empty);
    }
}

Podobnie sprawa ma się z funkcją IsValid(), która sprawdzi nam, czy to, co odebraliśmy od serwera jest poprawne:

public static bool IsValid(string Md5sum, string Id, string Amount, string Crc)
{
    return Md5sum.ToLower() == Checksum(AutomaticID + Id + Amount + Crc + SecurityCode).ToLower();
}

W tym miejscu wypadałoby wyjaśnić, że Crc to tak naprawdę identyfikator płatności – w moim przypadku GUID skonwertowany na string, natomiast Md5sum to suma kontrolna, którą otrzymaliśmy od serwera płatności.

Struktura plików

Przed przystąpieniem polecam zapoznać się z dokumentacją techniczną systemu płatności, gdyż nie zamierzam przeklejać tutaj technikaliów.

Jak już można się domyślić z powyższej ryciny, potrzeba nam będzie utworzyć dwa dodatkowe pliki (zakładając, że koszyk już mamy):

  • Redirect.aspx – plik z formularzem i automatycznym przekierowaniem na stronę Transferuj.pl
  • Report.aspx – tutaj serwis będzie wysyłał wszystkie raporty dotyczące naszej transakcji

Redirect.aspx

Tak wygląda nasz formularz:

<div id="main-pay">
    Czekaj, trwa przekierowywanie na stronę systemu płatności... Jeżeli przekierowanie nie nastąpi automatycznie, 
    <form id="RedirForm" runat="server" action="https://secure.transferuj.pl" clientidmode="static" method="post">
        <div style="display: none;">
            <asp:TextBox ID="id" runat="server"></asp:TextBox>
            <asp:TextBox ID="kwota" runat="server"></asp:TextBox>
            <asp:TextBox ID="opis" runat="server"></asp:TextBox>
            <asp:TextBox ID="crc" runat="server"></asp:TextBox>
            <asp:TextBox ID="md5sum" runat="server"></asp:TextBox>
            <asp:TextBox ID="nazwisko" runat="server"></asp:TextBox>
            <asp:TextBox ID="email" runat="server"></asp:TextBox>
            <asp:TextBox ID="pow_url" runat="server"></asp:TextBox>
            <asp:TextBox ID="pow_url_blad" runat="server"></asp:TextBox>
        </div>
        <input type="submit" value="Kliknij tutaj" />
    </form>
</div>

Warto zaznaczyć, że ID musi być dokładnie takie, jak jest u góry – wynika to z tego, że w dokumentacji technicznej są to określone nazwy pól, a ID zamieni się nam również na name.

Warto również na sam koniec pliku dodać mały skrypt, który automatycznie nas przekieruje:

<script type="text/javascript">
    document.getElementById("RedirForm").submit();
</script>

Strona kodu również nie jest zbytnio skomplikowana – wszystkie instrukcje są oczywiście umieszczone w Page_Load()

Jak więc zostało napisane w sekcji z przygotowaniem, potrzebujemy pozyskać najpierw identyfikator oraz pobrać z bazy danych naszą płatność, oczywiście cały czas sprawdzając, czy wszystko gra:

// sprawdzamy, czy jest podany parametr id
if (Request.QueryString["id"] == null) {
    Response.Write("Nie podano ID transakcji");
    return;
}

// jeżeli tak, to generujemy GUIDa (w moim przypadku)
Guid PaymentID = new Guid(Request.QueryString["id"]);

// i robimy SELECTa z bazy danych używając LINQ
Payment thePayment = db.Payments.
    Where(k => k.PaymentID == PaymentID).
    FirstOrDefault();

// przy czym sprawdzamy, czy taka płatność na pewno istnieje
if (thePayment == null) {
    Response.Write("Błędny ID transakcji");
    return;
}

Gdy już to zrobimy, pora wypełnić nasze pola w formularzu – tutaj także bierzemy specyfikację techniczną w rękę i bierzemy się do dzieła:

id.Text = PaymentsExt.Payments.AutomaticID;              // nasz identyfikator przyznany przez Transferuj.pl
crc.Text = PaymentID.ToString();                         // identyfikator płatności
md5sum.Text = PaymentsExt.Payments.Checksum(             // wygenerowana suma kontrolna
    PaymentsExt.Payments.AutomaticID +
    thePayment.Amount.ToString().Replace(',', '.') +     // tutaj mam zapisaną kwotę płatności
    PaymentID.ToString() +
    PaymentsExt.Payments.SecurityCode);

pow_url.Text = "http://serwer/Koszyk?status=ok";         // na ten adres użytkownik zostanie przekierowany, jeśli
                                                         // wszystko pójdzie zgodnie z planem...
pow_url_blad.Text = "http://serwer/Koszyk?status=error"; // ...a tutaj, jeżeli coś się popsuje

Resztę pól z danymi uzupełniamy według uznania – nic więcej nie trzeba tutaj robić. Jeżeli jakieś dane nie zostaną podane, a będą wymagane – użytkownik i tak będzie musiał je podać na stronie Płatności.pl.

Report.aspx

Teraz kolej na moduł, który będzie służył nam do odczytywania stanu o wysłanych transakcjach. Adres bezwzględny musi zostać podany w panelu użytkownika na stronie Transferuj.pl!

Strona ASPX musi zostać bezwzględnie pusta – w pliku projektu jedyne, co może się na niej znajdować to

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Report.aspx.cs" Inherits="Crow.Payments.Report" %>

Nie ma co się rozmyślać – trzeba odebrać wszystkie dane (w Page_Load()), które serwer nam podsyła (nie używam większości z nich, ale dopisałem je tak na wszelki wypadek):

string ID = Request.Form["id"];                 // ID odbiorcy
string trID = Request.Form["tr_id"];            // ID transakcji
string trDate = Request.Form["tr_date"];        // data zrealizowania transakcji
string trCRC = Request.Form["tr_crc"];          // nasz ident. transakcji
string trAmount = Request.Form["tr_amount"];    // kwota transakcji
string trPaid = Request.Form["tr_paid"];        // kwota zapłacona
string trDesc = Request.Form["tr_desc"];        // opis
string trStatus = Request.Form["tr_status"];    // status (TRUE/FALSE)
string trError = Request.Form["tr_error"];      // błąd transakcji (none/overpay/surcharge)
string trEmail = Request.Form["tr_email"];      // e-mail nadawcy
string Md5sum = Request.Form["md5sum"];         // suma MD5

Kolejnym krokiem jest sprawdzenie, czy otrzymaliśmy wszystkie dane – ja do tego napisałem sobie funkcję pomocniczą, sprawdzającą czy którykolwiek z parametrów nie jest pusty albo nullem:

private bool ContainsNullOrEmptyString(params string[] Strings)
{
    var IsEmpty = false;
    foreach (var item in Strings.ToList())
    {
        IsEmpty = String.IsNullOrEmpty(item) ? true : IsEmpty;
    }
    return IsEmpty;
}

Całkiem użyteczna funkcja zwłaszcza, że nie ma określonej liczby parametrów wywołania.

Sprawdźmy więc, czy odebrane dane są OK, jeśli tak – dajemy odpowiedź serwerowi (dlatego w pliku ASPX nie mogło się absolutnie nic znajdować) oraz zmieniamy status naszej transakcji:

if (!ContainsNullOrEmptyString(ID, trID, trDate, trAmount, trCRC))
{
    Response.Write("TRUE"); // odpowiedź do serwera płatności, że otrzymane dane są poprawne

    Guid PaymentID = new Guid(trCRC);

    bool isSuccesfull = PaymentsExt.Payments.IsValid(Md5sum, trID, trAmount, trCRC);

    string NewStatus = isSuccesfull ?
        PaymentsExt.Payments.StatusOK :
        PaymentsExt.Payments.StatusError;

    PaymentsExt.Payments.ChangePaymentStatus(PaymentID, NewStatus);
}

I to już by było na tyle

Przedstawiłem najszybszą (moim zdaniem) drogę do wdrożenia systemu płatności na już istniejącym bądź też nowym serwisie – nie jest to tak straszne, jakby się mogło wydawać. Podstawą jest jednak zapoznanie się z oficjalną dokumentacją techniczną

W praktycznie identyczny sposób podepniemy się też pod PayU, choć – tak jak mówiłem – odrzucił mnie ten serwis. Nie pozostało mi jednak nic innego, jak życzyć innym szczęścia w kodzeniu 😛

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>