PayPal IPN - PHP

crazy_chicken

Erfahrenes Mitglied
Hallo Zusammen,

ich versuche mit PayPal eine Direkt-Zahlung zu ermöglich, sodass der Kunde direkt nach der Bezahlung auf die Seite umgeleitet wird.
Die Umleitung habe ich über PayPal Account aktiviert, auch die URL, an welche der Kunde weitergeleitet werden soll.

Folgendes Problem:

- Der Kunde wird erst nach 3-5 Sekunden weitergeleitet. D.h. wenn jemand bezahlt und kurz darauf das fenster schließt, wird die Rück-URL nicht aufgerufen. So kann die Zahlung gar nicht festgestellt werden..
- Wenn man diese 5 Sekunden wartet, wird der Kunde umgeleitet, aber es kommen die IPN-Daten weder per POST noch per GET bei mir an.

Kann da mir jemand weiterhelfen?

Danke im Voraus!
 
Hast du dir den einen PayPalIPN auf dein Server installiert?

Ansonsten lade diese hier runter:

https://github.com/paypal/ipn-code-samples/tree/master/php

Dies lädst du auf deinen Server hoch, die URL merkst du dier.

Besuche:

Hier schreibst du die URL, mit vollständiger Pfad zu deiner PayPalIPN auf deinem Server.

Ich empfehle dir noch bei der PayPalIPN Klasse noch einen "Logger" zu implementieren, Beispielsweise:

PHP:
public function verifyIPN()
{
if ( ! count($_POST)) {
throw new Exception("Missing POST Data");
}

$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = array();
foreach ($raw_post_array as $keyval) {
$keyval = explode('=', $keyval);
if (count($keyval) == 2) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ($keyval[0] === 'payment_date') {
if (substr_count($keyval[1], '+') === 1) {
$keyval[1] = str_replace('+', '%2B', $keyval[1]);
}
}
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
}
// HIER
$meinLog = json_encode($myPost);
mail('meine@mail.de','Log-Paypal:'.time(), $meinLog);

..
..
..

Edit:

Achja, wozu das Alles? Nun gut, wie du erwähnt hast, falls der Kunde mal nicht so geduldig ist und nicht auf die Redirect wartet, bekommst du aus PayPalseits immernoch eine Benachrichtigung, welche du bearbeiten kannst. Dies hilft dabei, persönliche Mails mit Dank oder auch für interne Kundenverwaltung etc. Ebenso ist es viel sicherer, da man nicht die GET-Parameter manipulieren kann.
 
Zuletzt bearbeitet:
Danke, das hat mich schon mal weitergebracht. Auch wenn der Kunde das Fenster schließt, wird die angegebene URL auf dem Server aufgerufen.

- Kommen die Daten nicht per POST/GET an!?

Könntest du mir vllt noch sagen, wie die PayPal-URL lautet, wo man die Transaktionsnummer verifizieren kann??

Danke im Voraus!
 
PayPal senden die Daten quasi mit PUT, daher auch:
Code:
$raw_post_data = file_get_contents('php://input');

Hier werden sie dann nocheinmal in ein Array gespeichert:
Code:
$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = array();
foreach ($raw_post_array as $keyval) {
$keyval = explode('=', $keyval);
if (count($keyval) == 2) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ($keyval[0] === 'payment_date') {
if (substr_count($keyval[1], '+') === 1) {
$keyval[1] = str_replace('+', '%2B', $keyval[1]);
}
}
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
}

Wenn du gerne wissen möchtest was PayPal bzw. Extern auf deinen Server zugeschickt wird, so nutze einen Logger - wie oben z.b. Mail.

Diese PUT Daten werden dann aus deinem Server nochmal an PayPal geschickt. Somit versichert man das die externe PUT-Anfrage auch wirklich nicht manipuliert wurde, denn PayPal muss alle Angaben überprüfen und eine Rückmeldung geben - hier zu gehören Transaktionsnummer, Preis, Artikel, ggbfs. Anschrift etc.

Code:
$ch = curl_init($this->getPaypalUri());
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

// This is often required if the server is missing a global cert bundle, or is using an outdated one.
if ($this->use_local_certs) {
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
}
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
$res = curl_exec($ch);
if ( ! ($res)) {
$errno = curl_errno($ch);
$errstr = curl_error($ch);
curl_close($ch);
throw new Exception("cURL error: [$errno] $errstr");
}

$info = curl_getinfo($ch);
$http_code = $info['http_code'];
if ($http_code != 200) {
throw new Exception("PayPal responded with http code $http_code");
}

curl_close($ch);

Somit lautet die Antwort: Die Transaktion, und jegliche weitere Informationen werden aus deinem IPNListener herraus verifiziert. PayPal oder jmd. anders (wenn er die URL kennt) schickt eine PUT-Anfrage mit jeglichen Parametern. Dein IPN exploded diese und schickst sie anschließend zu PayPal. PayPal gibt eine Rückmeldung wie: Diese Meldung ist echt, oder diese Meldung stammt nicht aus Paypal oder wurde verändert. Falls du die erste Meldung bekommst, welche ja in der Methode verifyIPN() geschieht, so kannst du z.B. eine Mail an den Kunden schicken:

PayPalIPN: (neue Funtion gibDaten())
PHP:
<?php

class PaypalIPN
{

    /**
     * @var bool $use_sandbox     Indicates if the sandbox endpoint is used.
     */
    private $use_sandbox = false;
    /**
     * @var bool $use_local_certs Indicates if the local certificates are used.
     */
    private $use_local_certs = true;

    /** Production Postback URL */
    const VERIFY_URI = 'https://ipnpb.paypal.com/cgi-bin/webscr';
    /** Sandbox Postback URL */
    const SANDBOX_VERIFY_URI = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';


    /** Response from PayPal indicating validation was successful */
    const VALID = 'VERIFIED';
    /** Response from PayPal indicating validation failed */
    const INVALID = 'INVALID';


    /**
     * Sets the IPN verification to sandbox mode (for use when testing,
     * should not be enabled in production).
     * @return void
     */
  
   public function gibDaten(){  // NEU*** //
       return this->datenArr;
   }
  
    public function useSandbox()
    {
        $this->use_sandbox = true;
    }

    /**
     * Sets curl to use php curl's built in certs (may be required in some
     * environments).
     * @return void
     */
    public function usePHPCerts()
    {
        $this->use_local_certs = false;
    }


    /**
     * Determine endpoint to post the verification data to.
     * @return string
     */
    public function getPaypalUri()
    {
        if ($this->use_sandbox) {
            return self::SANDBOX_VERIFY_URI;
        } else {
            return self::VERIFY_URI;
        }
    }


    /**
     * Verification Function
     * Sends the incoming post data back to PayPal using the cURL library.
     *
     * @return bool
     * @throws Exception
     */
    public function verifyIPN()
    {
        if ( ! count($_POST)) {
            throw new Exception("Missing POST Data");
        }

        $raw_post_data = file_get_contents('php://input');
        $raw_post_array = explode('&', $raw_post_data);
        $myPost = array();
        foreach ($raw_post_array as $keyval) {
            $keyval = explode('=', $keyval);
            if (count($keyval) == 2) {
                // Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
                if ($keyval[0] === 'payment_date') {
                    if (substr_count($keyval[1], '+') === 1) {
                        $keyval[1] = str_replace('+', '%2B', $keyval[1]);
                    }
                }
                $myPost[$keyval[0]] = urldecode($keyval[1]);
            }
        }
      
       this->datenArr = $myPost;// NEU*** //
      
        // Build the body of the verification post request, adding the _notify-validate command.
        $req = 'cmd=_notify-validate';
        $get_magic_quotes_exists = false;
        if (function_exists('get_magic_quotes_gpc')) {
            $get_magic_quotes_exists = true;
        }
        foreach ($myPost as $key => $value) {
            if ($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
                $value = urlencode(stripslashes($value));
            } else {
                $value = urlencode($value);
            }
            $req .= "&$key=$value";
        }

        // Post the data back to PayPal, using curl. Throw exceptions if errors occur.
        $ch = curl_init($this->getPaypalUri());
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
        curl_setopt($ch, CURLOPT_SSLVERSION, 6);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

        // This is often required if the server is missing a global cert bundle, or is using an outdated one.
        if ($this->use_local_certs) {
            curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
        }
        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
        $res = curl_exec($ch);
        if ( ! ($res)) {
            $errno = curl_errno($ch);
            $errstr = curl_error($ch);
            curl_close($ch);
            throw new Exception("cURL error: [$errno] $errstr");
        }

        $info = curl_getinfo($ch);
        $http_code = $info['http_code'];
        if ($http_code != 200) {
            throw new Exception("PayPal responded with http code $http_code");
        }

        curl_close($ch);

        // Check if PayPal verifies the IPN data, and if so, return true.
        if ($res == self::VALID) {
            return true;
        } else {
            return false;
        }
    }
}

Beispiel Listener:
PHP:
<?php namespace Listener;
require('PaypalIPN.php');
use PaypalIPN;
$ipn = new PaypalIPN();
// Use the sandbox endpoint during testing.
$ipn->useSandbox();
$verified = $ipn->verifyIPN();
if ($verified) {
   $arr = $ipn->gibDaten();
  
   $arr['payer_email'];
   mail('meine@mail.de', 'Erfolgreich gekauft', json_encode($arr));
}
// Reply with an empty 200 response to indicate to paypal the IPN was received correctly.
header("HTTP/1.1 200 OK");
 
Wohlmöglich liegt es an deinem Button, da dein IPN den webscr nicht benötigt.

Dein IPN ist unabhängig von allem, es übernimmt nur die Benachrichtigung.

Überprüfe deinen Button, ggbfs. erstelle sie neu.

Achja, den Link den du so angibst, sollte auch nicht funktionieren, denn es übernimmt eig. Parameter, welche er bearbeitet und dann zu einer "Bezahle jetzt" Seite weiterleitet.

Dein Button, welche eine Form-DOM beinhaltet generiert einen Link mit GET-Parametern auf der Basis von https://ipnpb.paypal.com/cgi-bin/webscr

EDIT:

Habe die URL mit https://www.paypal.com/cgi-bin/webscr verwechselt. Sind ja sehr beianander liegende URLs.

Die URL https://ipnpb.paypal.com/cgi-bin/webscr ist ein Listener. Diese reagiert nicht auf normale Aufrufe. Diese ist dazu gedacht um die Anfragen, welche auf deinen IPN geschiehen, von PayPal zu verifzieren.

Achja, du darfst auch die Sandbox benutzen, evt. hast du soger per default die Sandbox aktiviert. Enfernte für den echten Gebrauch den Codeschnippsel in deinem IPN:
Code:
$ipn->useSandbox();

Quasi so (lese sie dir durch, dann wird dir Einiges klar :D ):

Szenario 1:
Code:
Kunde betätitgt Kauf-Button.
Kunde wird auf Paypal weitergeleitet.
Kunde betätitgt den Kauf. (Login, bezahlen etc.)
Kunde wird von PayPal auf die Ausgangsseite weitergeleitet.
Kunde sieht deinen "Danke schön"-Seite.

PayPal registiert eine Transaktion unter deinem "Namen"
PayPal benachrichtig dich via:
 - E-Mail
 - ggbfs. SMS
 - deiner IPN

Du empfängst:
 - Eine Mail mit: Sie haben eine Zahlung erhalten.
 - ggbfs. SMS mit: Sie haben eine Zahlung erhalten.

Dein Server empfängt:
 - IPN : Alle Daten, Transaktioncode, Zahlung, Käuferdaten (Mail, Adresse, Name etc.), gefallene Gebühren etc.

Du reagierst:
 - Erfreut liest du die Mail. Benötigst keine Verifizierung, da Mail verifiziert von Paypal stammt.
 - ggbfs. genervt löschst du die SMS :)

Dein Server reagiert:
 - Erhält eine PUT Anfrage mit vielen Information, ist sich unsicher, ob es ein Angreifer ist oder die Daten manipuliert sind.
- Server ist nicht naiv, fragt bei PayPal mit samt all der erhaltenen Daten nach, ob diese Transaktion seiner Wahrheit entspricht
- PayPal antwortet:
---- JA, Server verarbeitet eine Mail, oder überreicht die Daten an einen Drittanbieter (DHL & co).


Szenario 2:
Code:
Kunde betätitgt Kauf-Button.
Kunde wird auf Paypal weitergeleitet.
Kunde betätitgt den Kauf. (Login, bezahlen etc.)
Kunde wird von PayPal auf die Ausgangsseite weitergeleitet, JEDOCH ist er ungeduldig, schließt den Browser.

PayPal registiert eine Transaktion unter deinem "Namen"
PayPal benachrichtig dich via:
 - E-Mail
 - ggbfs. SMS
 - deiner IPN

Du empfängst:
 - Eine Mail mit: Sie haben eine Zahlung erhalten.
 - ggbfs. SMS mit: Sie haben eine Zahlung erhalten.

Dein Server empfängt:
 - IPN : Alle Daten, Transaktioncode, Zahlung, Käuferdaten (Mail, Adresse, Name etc.), gefallene Gebühren etc.

Du reagierst:
 - Erfreut liest du die Mail. Benötigst keine Verifizierung, da Mail verifiziert von Paypal stammt.
 - ggbfs. genervt löschst du die SMS :)

Dein Server reagiert:
 - Erhält eine PUT Anfrage mit vielen Information, ist sich unsicher, ob es ein Angreifer ist oder die Daten manipuliert sind.
- Server ist nicht naiv, fragt bei PayPal mit samt all der erhaltenen Daten nach, ob diese Transaktion seiner Wahrheit entspricht
- PayPal antwortet:
---- JA, Server verarbeitet eine Mail, oder überreicht die Daten an einen Drittanbieter (DHL & co).


Szenario 4:
Code:
Kunde betätitgt Kauf-Button.
Kunde wird auf Paypal weitergeleitet.
Kunde betätitgt NICHT den Kauf. (Login, bezahlen etc.)
Kunde wird von PayPal auf die Ausgangsseite weitergeleitet.
Kunde sieht "Schade"-Seite

Szenario 5:
Code:
Angreifer kennt deine IPN-URL
Angreifer schickt eine PayPal ähnliche PUT-Anfrage

Du empfängst:
- Nichts

Du reagierst:
- überhaupt nicht

Dein Server reagiert:
 - Erhält eine PUT Anfrage mit vielen Information, ist sich unsicher, ob es ein Angreifer ist oder die Daten manipuliert sind.
- Server ist nicht naiv, fragt bei PayPal mit samt all der erhaltenen Daten nach, ob diese Transaktion seiner Wahrheit entspricht
- PayPal antwortet:
---- Nein, so eine Transaktion hat nicht stattgefunden, Server ignoriert weiteres.
 
Zuletzt bearbeitet:
Achja ein Rat von mir:
Auf deiner "Danke schön"-Seite, würde ich mich nur bei dem Kunden bedanken und ihn auf eine Mail (Bestellbestätigung o.Ä.) hinweisen. Hier würde ich nichts in die Datenbank oder Kundenverwaltungssystem einschreiben.

In deinem IPN, würde ich ab:
Code:
$verified = $ipn->verifyIPN();
if ($verified) {
 $arr = $ipn->gibDaten();
// HIER

Die Transaktion ggbfs. in meine Datenbank übernehmen und eine Bestellbestätigung oder Zahlungseingang per Mail an den Kunden überreichen.
Vorher würde ich dir raten einmal die $arr auszulesen. Ob du diese dir via Mail zuschickst - hier würde ich json_encode nochmal raten - oder in eine txt-File abspeicherst, spielt keine Rolle. Dir soll nur die Array bekannt sein, welche Information du erhälts etc. um diese auch entsprechend zu verwenden.

Mit der Sandbox kannst du einige Käufe simulieren., sodass im echten Gebrauch alles einwandfrei läuft.
 
Vielen Dank, jetzt klappt es endlich mal...:) Top Hilfe :)

Hast du zufällig auch die Lösung für Sofort-Überweisung, bevor ich mich auf die Suche im Internet mache?

Grüße!
 

Neue Beiträge

Zurück