Blog jak widać już nie jest aktualizowany. I raczej nie będzie. Wisi sobie ot tak, ze względów historycznych :)

Paypal - Instant Payment Notification

Krzysztof Rygielski | 2009-04-10 11:22 | Kategorie: Paypal, Rails

Poniższy post powstał dzięki informacjom z bloga Bseanvt. Following post was made with use of information from Bseanvt blog, so all credit goes to Sean.

Ostatnio pracuję nad aplikacją na Rails, obsługującą płatności Paypal. Nie byłoby to nic specjalnie skomplikowanego, gdyby nie to, że potrzebne mi jest powiadomienie, kiedy zostanie dokonany transfer pieniędzy z konta klienta na konto sprzedawcy. Paypal udostępnia narzędzie o nazwie IPN (Instant Payment Notification), które wysyła powiadomienia pod url zdefiniowany w profilu sprzedawcy.

Na początku załozyłem sobie, że zrobię prosty skrypt, który zwyczajnie zapisze przekazywane od Paypala parametry. Postanowiłem skorzystać z narzędzia IPN simulator dostępnego w Paypal Sandbox. Niestety, za każdym razem przy próbie wysłania powiadomienia pod wskazany adres otrzymywałem komunikat:

IPN delivery failed. Unable to connect to the specified URL. Please verify the URL and try again.

Dziwna sprawa, ponieważ URL był bez problemu dostępny przez przeglądarkę. Długo szukałem rozwiązania, zarówno w dokumentacji (która to, nawiasem mówiąc, jest chaotyczna i rozgrzebana) jak i na forach Paypala. Dopiero na blogu Bseanvt znalazłem rozwiązanie. Okazało się, że Paypal, bez względu na to czy url jest błędny, czy poprawny, ale tylko nie poprawnie obsługuje wymianę danych, zwraca ten sam komunikat. Zamiast mówić "Twój URL jest BE" powinno mówić "Twój URL jest cacy, tylko Twoja obsługa IPN jest BE". Cóż, widać developerzy Paypala nie widzą różnicy. Na wspomnianym blogu opisano rozwiązanie, jednak przytoczę je też tutaj.

Do kontrolera należy dołączyć następujące rzeczy:

require 'uri'
require 'net/http'
require 'net/https'
#aby nie było problemów z autoryzacją
#i powiadomienie było przyjęte
protect_from_forgery :except=>[:ipn]

Następnie funkcja obsługująca powiadomienie IPN musi powinna wyglądać mniej więcej tak:

def ipn
  begin
    if request.post?
      #po  otrzymaniu powiadomienia, dane należy odesłać
      #w tym samym porządku, dodając na końcu cmd=_notify-validate
     
      from_pp = request.raw_post
      data = from_pp + "&cmd=_notify-validate"
      url = URI.parse 'https://sandbox.paypal.com/cgi-bin/webscr'
      http = Net::HTTP.new url.host, url.port
      http.use_ssl = true

      response, data = http.post url.path, data, { 'Content-Type' => 'application/x-www-for-urlencoded' }
    end
  rescue Exception => e
    logger.info("Error: paypal transaction #{e.message}")
  end
end

Żeby obsłużyć dalsze przetwarzanie danych otrzymanych w powiadomieniu należy napisać już własny kod. Aktualnie sam nad tym pracuję :-)

Jeszcze raz podziękowania dla Sean'a z Bseanvt.