Anleitungen eCommerce Erfahrene PHP Symfony Web Development

Der Leitfaden zur Paypal Integration in PHP (Teil 1)

Vor Kurzem hatte ich mich mit der Paypal Integration in PHP für eine eCommerce-Lösung beschäftigt und es begegneten mir dabei leider ein paar Stolpersteinen. Mir fiel auf, dass vermehrt Probleme bei der Integration von Paypal in PHP oder anderen Programmiersprachen vorhanden sind.
Deshalb soll der hier vorgestellte Leitfaden die Paypal-Integration in PHP mit einigen Code Beispielen beschreiben und Lösungen zu verschiedenen Problemen aufzeigen.

Möglicherweise sind eigenen Probleme für den ein oder anderen banal, trotzdem finde ich es wichtig, dass auch solche Probleme geschildert werden. Schließlich wäre es ebenso möglich, dass andere die gleichen Probleme haben könnten.

Was wird für eine Paypal Integration in PHP benötigt?

Es wird davon ausgegangen, dass Paypal selbständig in einen Online-Shop integriert werden soll, der unterschiedliche Produkte zur Auswahl hat und in unterschiedlicher Kombination eingekauft werden können. Ansonsten wäre der Einbau eines oder mehrerer einfacher Paypal-Buttons die bessere Lösung.

Selbstverständlich gibt es für fast jede Shop-Software ein eigenes Modul um Paypal zu integrieren. Der folgende Artikel befasst sich jedoch darum, wie Paypal ohne vorhandenes Modul integriert werden kann.

REST-Api App

Zuerst sollte eine sog. Paypal REST-Api App erstellt werden. Nach der Erstellung der App lässt sich die Paypal Integration in PHP relativ simpel durchführen. Die Erstellung und Einrichtung einer REST-Api App wird jetzt genauer beschrieben.

Geschäftskonto

Um Paypal zu integrieren, werden mindestens zwei Dinge benötigt. Zuerst wird ein Paypal Account benötigt. Wenn bereits eines vorhanden ist, sollten Sie überprüfen, ob es sich um ein privates oder ein geschäftliches Konto handelt. Um Paypal in einen Online-Shop zu integrieren, ist ein Geschäftskonto notwendig. Zwei Möglichkeiten wären gegeben, wenn ein Privatkonto vorhanden wäre:

  1. Umwandlung des vorhanden Accounts in ein Geschäftskonto
  2. Erstellung eines neuen Geschäftskontos

Persönlich würde ich Variante zwei empfehlen um private und geschäftliche Zahlungen strikt voneinander zu trennen. Es ist jedoch Ihnen überlassen, wie Sie es handhaben.

Erstellung einer Paypal REST-Api App

Der nächste Schritt für eine Paypal Integration in PHP ist die Erstellung einer sog. Web App. Auf dem Paypal Developer Portal. Loggen Sie sich mit Ihrem Geschäftskonto ein und betreten das Dashboard. Hier erstellen Sie sich ein App über den Button „Create App“.

Developer Paypal Rest-API App
Developer Paypal Rest-API App

Nach der Erstellung der App können Sie für die Sandbox (Testumgebung) und den Orginal-Zugang (Live) die jeweilige ClientID und den Secret finden. Beides wird benötigt um eine gültige Authentifizierung von Paypal zu erhalten. Der gesamte Code wird zuerst über die Sandbox getestet, um zu überprüfen, ob alles ordentlich funktioniert.

REST-Api App Einstellungen anpassen

Die nachfolgenden Einstellungen sollten unbedingt zuerst durchgeführt werden. Der Grund ist ganz einfach: Paypal benötigt oft eine längere Zeit um die Einstellungen erfolgreich abzuspeichern und durchführbar zu machen. An diesem Punkt sind bereits mehrere gescheitert, da nicht lang genug gewartet wurde und dadurch Fehler von Paypal zurückgegeben wurden. Daraufhin wurde auch von meiner Seite aus stundenlang nach der Fehlerursache gesucht. Dabei war die Ursache nur eine verspätete Aktivierung der neuen Einstellungen. Denken Sie bitte daran und schonen Sie Ihre Nerven, sollten Änderungen bei den Einstellungen erst vor kurzem durchgeführt worden sein.

Die wirklich wichtigen Einstellungen für die eigene App sind die sog. Return URLs. Hier sollten wirklich alle URLs eingetragen werden, die von Paypal zurückführen, egal ob bei einem Login oder der Bezahlung. Die Einträge sollten für die Sandbox, wie auch für das Live-System eingestellt werden.

Developer Paypal REST-Api Settings
Developer Paypal REST-Api Settings

Test-Accounts für die Sandbox

Neben der REST-Api App sollten noch diverse Test-Accounts angelegt werden. Dazu gehen Sie in den Bereich Accounts und erstellen diverse Test-Accounts. Dabei sollten verschiedene Typen erstellt werden, wie bspw. mit Kreditkarte, ohne Kreditkarte, kein Paypal-Guthaben, etc. Mithilfe der verschiedenen Accounts können diverse Rückmeldungen von Paypal untersucht und eingeschätzt werden.

„PHP SDK for PayPal RESTful APIs“ einbinden

Um eine vernünftige Paypal Integration in PHP vorzunehmen, sollte die PHP SDK von Paypal verwendet werden. Um die aktuelle Version in Ihr Projekt zu integrieren kann bspw. Composer genutzt werden. In die Datei composer.json im Abschnitt „require“ muss folgende Zeile eingefügt werden:

"require": {
        ...
        "paypal/rest-api-sdk-php" : "dev-master"
    },

Danach kann über die Konsole die Installation über folgenden Befehl ausgeführt werden: composer update. Generell müsste das ausreichen. Sollten jedoch noch Komponenten für die SDK fehlen, wird Composer die Information weitergeben. Die fehlenden Komponenten müssten dann vorher installiert werden.

Ein wirklich großes Problem, dass bei meiner ersten Implementierung geschah, war die Verwendung einer alten Version der Paypal SDK. Anstelle der Versionierung dev-master wurde eine ältere Version in Composer angegeben und damit installiert. Die Verschlüsselungstechnik hatte sich bis dahin jedoch geändert und es wurde bei jedem Aufruf von Paypal ein sog. Hand-Shake-Error geworfen. Der Fehler klingt zwar sehr banal, es dauerte jedoch Stunden um den Fehler zu ermitteln.

Mit der Verwendung der Versionierung dev-master sollten Sie jedoch bei Updates mittels Composer darauf achten, dass auch die SDK ein Update erhalten könnte. Die bisherige Funktionalität Ihrer Paypal Integration sollte daraufhin nochmals überprüft werden.

Die ersten Schritte der Paypal Integration in PHP

Je nach Implementierung ihres Online-Shops, kann das Einbinden der API-Komponenten anders aussehen. Auch die Nutzung eines PHP-Frameworks spielt eine Rolle. Wenn Sie eines nutzen, hängt die Einbindungsart von dem verwendeten Framework ab. Die hier vorgestellten Beispiele sind mit dem Framework Symfony umgesetzt.

Um Paypal nun als Bezahlmethode nutzen zu können, ist es notwendig Paypal auf einen möglichen Bezahlvorgang vorzubereiten. Je nach Situation Ihrer eCommerce Lösung ist es möglicherweise sinnvoller eine eigenständige Klasse zu erstellen, welche die Ablauflogik für Paypal abarbeitet. Auch in den vorgestellten Code Beispielen wird eine Klasse erzeugt um die Ablauflogik zu steuern und alle notwendigen Informationen für Paypal bereitzustellen.

Grundfunktionen der eigenen Klasse

Die vorgestellte Klasse zeigt vorerst nur zwei Methoden an, den Konstruktor und die Methode fnSetEnvironmentDetails:

<?php
namespace MyShopBundle\Controller\Shop\Payment;

use Symfony\Bundle\FrameworkBundle\Controller\Controller,
    Symfony\Component\HttpFoundation\Session\Session;

// Paypal requirements
use PayPal\Rest\ApiContext;
use PayPal\Api\OpenIdSession;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Api\Address;
use PayPal\Api\PayerInfo;
use PayPal\Api\Payer;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Details;
use PayPal\Api\Amount;
use PayPal\Api\Transaction;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\PaymentExecution;

class PaypalPayment extends Controller {
protected $container;
protected $request;
protected $totalsCalculator;
protected $paymentInformation;	

protected $prodClientId = "";
protected $prodSecret   = "";
protected $devClientId 	= "";
protected $devSecret    = "";
protected $clientId	= "";
protected $secret       = "";

public function __construct($container, $devMode = false, $clientId = false, $secret = false, $devClient = false, $devSecret = false) {
   $this->container    	   = $container;
   $this->request	   = $this->container->get('request');
   $this->totalsCalculator = $this->container->get('total_calculator');
	
   if ($clientId === false && $this->container->hasParameter('PaypalClientId') === false )
	throw new \Exception('No existing Parameter "PaypalClientId"! Please add this parameter.');
   else
	$this->prodClientId = $this->container->getParameter('PaypalClientId');
	
   if ($secret === false && $this->container->hasParameter('PaypalSecret') === false )
	throw new \Exception('No existing Parameter "PaypalSecret"! Please add this parameter.');
   else
	$this->prodSecret   = $this->container->getParameter('PaypalSecret');
	
   if ($devMode === true) {
	if ($clientId === false && $this->container->hasParameter('PaypalDevClientId') === false )
	   throw new \Exception('No existing Parameter "PaypalDevClientId"! Please add this parameter.');
	else
	   $this->devClientId  = $this->container->getParameter('PaypalDevClientId');
		
	if ($clientId === false && $this->container->hasParameter('PaypalDevSecret') === false )
	   throw new \Exception('No existing Parameter "PaypalDevSecret"! Please add this parameter.');
	else
	   $this->devSecret    = $this->container->getParameter('PaypalDevSecret');
   }
	
   $this->fnSetEnvironmentDetails($devMode);
}

protected function fnSetEnvironmentDetails($devMode = false) {
   if ($devMode === false ) {
	$this->clientId    = $this->prodClientId;
	$this->secret      = $this->prodSecret;
  }
  else {
        $this->clientId    = $this->devClientId;
	$this->secret      = $this->devSecret;
   }
}  

Dem Konstruktor wird einmal ein Objekt namens $container übergeben und weitere Argumente, die wichtig für die Testumgebung und den Live-Betrieb sind. Der sog. Container wird von Symfony bereitgestellt, mit dem man verschiedene Klassen laden und initialisieren kann, sowie verschiedene Parameter aus den Konfigurations-Dateien laden.

Der Konstruktor überprüft auf vorhandene Client IDs und die dazugehörigen Secrets. Sollte einer der Parameter fehlen, wird eine Exception geworfen. Zudem wird festgehalten, ob die Sandbox verwendet werden soll oder nicht. Dies erfolgt über das Argument $devMode. Hat das Argument den Wert false, wird der Live-Betrieb gestartet. Bei einem Wert true wird die Sandbox genutzt. Egal ob Sie eine Paypal Integration in PHP oder andere Bezahlmethoden implementieren, sollte eine solche oder ähnliche Möglichkeit zum Umstellen der Umgebungen vorhanden sein. Natürlich nur, wenn der Anbieter eine Testumgebung anbietet. Die Methode fnSetEnvironmentDetails setzt je nach dem Wert des Arguments $devMode die Parameter für die Sandbox oder das Live-System.

Aufbau des Paypal Payments

Um dem Kunden die Möglichkeit zu bieten über Paypal zu bezahlen, wird im nächsten Schritt Paypal auf eine mögliche Bezahlung vorbereitet:

public function fnGetPaymentInformation($devMode = false){
   if (empty($this->paymentInformation))
      $this->paymentInformation = $this->fnBuildPaymentInformation($devMode);

      return $this->paymentInformation;
}

protected function fnBuildPaymentInformation($devMode) {
   $totalValues = $this->totalsCalculator->fnGetTotalValues();
        
   $paymentInformation['total']         = $totalValues['total'];
   $apiContext		                = $this->fnGetApiContext($devMode);
   $paymentInformation['paypalPayment']	= $this->fnCreatePaypalPayment($apiContext);
	
    return $paymentInformation;	
}

protected function fnCreatePaypalPayment($apiContext) {  
   $payer		= new Payer();
   $payer->setPaymentMethod("paypal");
	
   $transaction	= $this->fnBuildTransaction();
   $baseUrl	= $this->request->getSchemeAndHttpHost();
   $responseUrl = '/your-paypal-payment-response-url/'
   $redirectUrls 	= new RedirectUrls(); 
   $redirectUrls->setReturnUrl($baseUrl . $responseUrl .'?success=true')
		->setCancelUrl($baseUrl . $responseUrl .'?cancel=true');

   $payment 	= new Payment();
   $payment->setIntent("sale")
	   ->setPayer($payer)
	   ->setRedirectUrls($redirectUrls)
           ->setTransactions(array($transaction));
		
   $paypalPaymentObj   = $payment->create($apiContext);
   return $paypalPaymentObj;
}
    
protected function fnGetApiContext($devMode = false) {
   $oauthCredential  = new OAuthTokenCredential($this->clientId, $this->secret);
   $apiContext 	     = new ApiContext($oauthCredential);
	
   $apiContext->setConfig(
      array(
         'mode' => ($devMode === false ? 'live' : 'sandbox'),
	 'log.LogLevel' => ($devMode === false ? 'INFO' : 'DEBUG'), //'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
      )
    );
    return $apiContext;
}
protected function fnBuildTransaction($orderId = false) {
   $totalValues	= $this->totalsCalculator->fnGetTotalValues();
	
   $bagItemsForPP	= array();
   $currentSubTotal	= 0.00;
   foreach( $bagItems = $this->totalsCalculator->fnGetConfiguratedBagItems() as $index => $singleBagItem) {
        $currentSubTotal += ($singleBagItem['price'] - $singleBagItem['discount_amount_of_single_article']) * $singleBagItem['quantity'];
	    
	$bagItemsForPP[$index]	= new Item();
	$bagItemsForPP[$index]->setName($singleBagItem['fullProductName'])
			      ->setCurrency($this->container->hasParameter('currency_normal') ? $this->container->getParameter('currency_normal') : '$')
			      ->setQuantity($singleBagItem['quantity'])
			      ->setSku($singleBagItem['articleID']) // Similar to `item_number` in Classic API
			      ->setPrice($singleBagItem['price'] - $singleBagItem['discount_amount']);
   }
   $itemList 	= new ItemList();
   $itemList->setItems($bagItemsForPP);
   $details = new Details();
   $details->setShipping((float)$totalValues['shipping_costs'])
	   ->setFee((float)$totalValues['vat_amount'])
	   ->setSubtotal((float)$currentSubTotal);
   $currentSubTotal += $totalValues['shippingInfo']['amount'];
	
   $amount 	= new Amount();
   $amount->setCurrency("EUR")
	  ->setTotal($totalValues['total'])
	  ->setDetails($details);
	
   $transaction 	= new Transaction();
   $transaction->setDescription("My Shop Order - " . ($orderId == false ? date('Y-m-d H:i:s') : $orderId))
	       ->setAmount($amount)
	       ->setItemList($itemList);
		    
   return $transaction;
}

Um eine erfolgreiche Paypal Integration in PHP für Ihre eCommerce-Lösung bereitzustellen, sind verschiedene Schritte notwendig. Hierzu wird nicht im Detail auf die Einzelheiten der Methoden eingegangen. Jedoch soll auf einige wenige Dinge hingewiesen werden.

Hinweise und mögliche Fehlermeldungen

Der sog. ApiContext wird bei jedem Aufruf von Paypal benötigt um eine valide Verbindung zu Paypal aufzubauen. Hierzu wird die gegebene ClientID und der Secret überprüft. Ist hier ein Fehler vorhanden, also gibt es keine solche Kombination an Werten, wird Paypal einen 404-Fehler zurückgeben. Der Fehler wird jedoch erst erscheinen, sobald die Verbindung aufgebaut wird oder zumindest der Versuch gestartet wird. In dem oben aufgeführten Code ist es folgende Zeile:
$paypalPaymentObj = $payment->create($apiContext);

Theoretisch ist es nicht notwendig die einzelnen Artikel einer Bestellung an Paypal weiterzugeben. Es wurde jedoch versucht so viele Möglichkeiten wie möglich im Code anzugeben, damit bei der Paypal Integration in PHP die API weniger auf Funktionalitäten überprüft werden muss.

Ein weiterer Grund für den Einbau der Artikel ist eine mögliche Fehlermeldung, welche bspw. bei Rabatten auftreten kann. Je nach Rabatt-System wird der Rabatt an unterschiedlichen Stellen gespeichert. In meinem Fall war der berechnete Wert des Warenkorbs in der Variable $totalValues größer als der wirkliche Warenkorb-Wert nach Abzug des Rabatts. Folglich wurde ein 404-Fehler geworfen, sobald ein bestimmter Rabatt eingesetzt wurde. Paypal berechnete bspw. den Wert 55€ aus dem Preis und der Anzahl der angegebenen Artikel. Dem Objekt $details wurde jedoch ein höher Wert übergeben (setSubtotal). Paypal erkennt die Unstimmigkeit und gibt einen Fehler zurück. Daher wurde in dieser Lösung der Wert selber nochmals berechnet und über die Variabel $currentSubTotal an Paypal weitergeben.

Einbau in ein Template

Der Einbau des Links zur Paypal-Bezahlmaske ist relativ simpel. Mit dem Aufruf der Funktion fnGetPaymentInformation der Klasse PaypalPayment wird eine Instanz der Klasse Payment zurückgegeben. Wäre die Instanz nun mit der Variablen $paypalPayment aufrufbar, dann müsste der Einbau des Links zu Paypal über echo $paypalPayment->getApprovalLink(); funktionieren.

Fazit

Der hier vorgestellte Code kann von allen genutzt werden um eine Paypal Integration in PHP durchzuführen. Es sollte jedoch darauf geachtet werden, dass der Code an die eigene eCommerce-Lösung angepasst werden muss. Die Variablen, Objekte und Methoden zur Berechnung des Warenkorbwertes, der Steuer, der Versandkosten, etc. hängt schlussendlich von dem verwendeten System ab. Die Verwendeten Objekte und Methoden zur Erzeugung der Werte ist nur ein Beispiel zur Erzeugung der Werte. Denken Sie bitte daran, ansonsten werden Fehler erzeugt, da die Objekte und Methoden wahrscheinlich nicht in ihrer eCommerce-Lösung vorhanden sind. Im nächsten Teil werden die Rückgabe, bzw. die Response einer Payal-Bezahlung besprochen und erläutert.