PayPal

Wprowadzenie do PayPal

PayPal jest jedną z bardziej rozpoznawalnych metod płatności na świecie. Znany jest głównie ze swojego mechanizmu e-portfela, ale umożliwia też płatności kartami czy przeprowadzanie płatności cyklicznych.

Dość często PayPal jest wybierany przez młode biznesy jako pierwsza metoda płatności, ale zazwyczaj dobrze spisuje się jako dodatkowy kanał płatniczy także w późniejszym czasie.

Wdrożenie płatności PayPal

Zanim zaczniesz wywoływać metody naszego API, pamiętaj, aby poprawnie zainicjować PayLane Rest Client. To bardzo łatwe, po prostu załącz odpowiedni plik oraz podaj swoją nazwę użytkownika i hasło.

Więcej informacji na temat integracji z naszymi systemami znajdziesz na stronie wdrażanie i testy.

PayLane Rest Client
PHP/Ruby/Python/Android
include_once ('PayLaneRestClient.php');
$client = new PayLaneRestClient('your_login', 'your_password');
require 'paylane_client'
client = PayLane::Client.new('your_login', 'your_password')
from client import PayLaneRestClient
client = PayLaneRestClient("your_login", "your_password")
PayLaneApi api = PayLaneClientFactory.createClassicClient(context, "your_login", "your_password");

Pojedyncza transakcja

Przygotuj dane potrzebne do przeprowadzenia płatności jak w przykładzie.

Teraz po prostu wykonaj płatność używając metody paypalSale.

Możesz sprawdzić, czy płatność się powiodła, wywołując metodę is_success. Pozyskanie numeru ID transakcji (lub informacji o błędzie, jeśli coś pójdzie nie tak), jest również bardzo proste i może wyglądać jak w przykładzie.

Jeśli metoda paypalSale nie zwróciła błędu, możesz przekierować klienta na stronę PayPala, gdzie dokona płatności. Użyj adresu URL zwróconego przez metodę paypalSale.

Przygotowanie danych do transakcji
PHP/Ruby/Python/Android
$paypal_params = array(
    'sale'     => array(
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    ),
    'back_url'  => 'http://example-page.com',
);
paypal_params = {
    'sale' => {
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    },
    'back_url' => 'http://example-page.com'
}
paypal_params = {
  'sale' : {
    'amount'      : 19.99,
    'currency'    : 'EUR',
    'description' : 'Product #1'
  },
  'back_url' : 'http://example.com'
}
Sale sale = new Sale(19.99, "EUR", "Product #1");
Realizacja transakcji
PHP/Ruby/Python/Android
try {
    $status = $client->paypalSale($paypal_params);
} catch (Exception $e) {
    // handle exceptions here
}   

if ($client->isSuccess()) {
    echo "Success, id_sale: {$status['id_sale']} \n"; 
} else {
    die("Error ID: {$status['error']['id_error']}, \n".
        "Error number: {$status['error']['error_number']}, \n".
        "Error description: {$status['error']['error_description']}");
}

header('Location: ' . $status['redirect_url']);
die;
begin
    status = client.paypal_sale(paypal_params)
rescue PayLane::ClientError => e
    # handle exceptions here
end 

if client.success?
    puts "Success, id_sale: #{status["id_sale"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
end

# redirect to url in status['redirect_url']
exit
try:
    status = client.paypal_sale(paypal_params)
except Exception, e:
    # handle exceptions here

if client.is_success():
    print 'Success, id_sale: %s' % status['id_sale']
else:
    print 'Error (%s), %s - %s' % (status['error'].get('id_error'),
                                   status['error'].get('error_number'),
                                   status['error'].get('error_description'))

# redirect to url in status['redirect_url']
sys.exit()
api.payPalSale(sale,"http://example-page.com",new Callback<RedirectSaleResult>(){

    @Override
    public void onFinish(RedirectSaleResult result) {
        WebView webview =...;
        webview.loadUrl(result.getRedirectUrl());
    }

    @HandleException
    public void onProtocolError(ProtocolException e) {
        // invoke if not success
        // e.getCode() - error code
        // e.getMessage() - error message
    }

    @Override
    public void onError(Exception e) {
        // connection error etc.
    }
});

Weryfikacja transakcji

Po zatwierdzeniu płatności na stronie PayPala, klient zostanie przekierowany z powrotem na Twoją stronę (zgodnie z parametrem back_url). Powinieneś teraz zweryfikować zwrócone dane, aby uniknąć ewentualnych prób oszustwa i sprawdzić status transakcji.
Zweryfikowanie transakcji
PHP/Ruby/Python/Android
$salt        = 'YOUR_HASH_SALT';
$status      = $_GET['status'];
$description = $_GET['description'];
$amount      = $_GET['amount'];
$currency    = $_GET['currency'];
$hash        = $_GET['hash'];

$id = '';
if ($status !== 'ERROR') // success, get id_sale
    $id = $_GET['id_sale'];

$calc_hash = sha1("{$salt}|{$status}|{$description}|{$amount}|{$currency}|{$id}");

// check hash salt
if ( $calc_hash !== $hash ) {
    die ("Error, wrong hash");
}

 // check transaction status
switch ($status) {
    case 'PERFORMED':
        echo "Success, transaction completed, id_sale: {$_GET['id_sale']}";
        break;
        
    default:
        die("Error, transaction declined, {$_GET['error_description']}");
        break;
}
# Simple controller action code in Rails
# it's just an example - most of the logic should be moved to model

salt        = 'YOUR_HASH_SALT'
status      = params['status']
description = params['description']
amount      = params['amount']
currency    = params['currency']
hash        = params['hash']

id = ''
unless status == 'ERROR'
    id = params['id_sale']
else
# redirect to an index action to correct the payment + simple notice
# for Rails: redirect_to :index, :notice => "Error, transaction declined, #{description}"
end

calc_hash = Digest::SHA1.hexdigest("#{salt}|#{status}|#{description}|#{amount}|#{currency}|#{id}")

unless calc_hash == hash
# redirect to an index action to correct payment
# for Rails: redirect_to :index, :notice => "Wrong hash"
end


# check transaction status

if status = 'PERFORMED'
# redirect to some index action to correct payment and simple notice
# for rails: redirect_to :index, :notice => "Success, transaction completed, id_sale: #{id}"
else
# redirect to some index action to correct payment and simple notice
# for rails: redirect_to :index, :notice => "Transaction pending"
end
# after back redirect
salt        = 'YOUR_HASH_SALT'
status      = get_request_param('status')
description = get_request_param('description')
amount      = get_request_param('amount')
currency    = get_request_param('currency')
hash        = get_request_param('hash')
id_sale     = None

# success, get id_sale
if status != 'ERROR':
    id_sale = get_request_param('id_sale')

calc_hash = hashlib.sha1(
    '|'.join([salt, status, description, amount, currency, id_sale])).hexdigest()

# check hash salt
if calc_hash != hash:
    sys.exit('Error, wrong hash')

# check transaction status
if status == 'PERFORMED':
    print 'Success, transaction completed, id_sale: %s' % id_sale
else:
    sys.exit('Error, transaction declined, %s' % \
        get_request_param('error_description'))


# Uwaga dot. Pythona: 
# Funkcja get_request_param ma za zadanie pobrać wartości GET. Użyj odpowiednich funkcji oferowanych przez framework, z którego korzystasz, lub napisz własną funkcję. 

# W przypadku Django możesz użyć:
# param_from_get = request.GET.get('param_name')
# W przypadku Pylons możesz użyć:
# from pylons import request
# param_from_get = request.GET.get('param_name')
webview.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {

        if (url.contains(redirectUrl)) {
            try {
                Map<String, String> map = getQueryMap(new URL(url).getQuery());
                String salt = "YOUR_HASH_SALT";
                String status = map.get("status");
                String description = map.get("description");
                String amount = map.get("amount");
                String currency = map.get("currency");
                String id = map.get("id_sale");

                String calcHash = sha1(String.format("%1$s|%2$s|%3$s|%4$s|%5$s|%6$s", salt, status, description, amount, currency, id));

                // check hash salt
                if (!calcHash.equals(hash)) {
                    // Error, wrong hash
                }

                if (status.equals("PERFORMED")) {
                   String idSale=map.get("id_sale");
                    // Success, transaction completed

                } else {
                    String errorDescription=map.get("error_description");
                    // Error, transaction declined
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else {
            view.loadUrl(url);
        }
        return true;
    }
});

public static Map<String, String> getQueryMap(String query) {
    String[] params = query.split("&");
    Map<String, String> map = new HashMap<String, String>();
    for (String param : params) {
        String name = param.split("=")[0];
        String value = param.split("=")[1];
        map.put(name, value);
    }
    return map;
}

private static String convertToHex(byte[] data) {
    StringBuilder buf = new StringBuilder();
    for (byte b : data) {
        int halfbyte = (b >>> 4) & 0x0F;
        int two_halfs = 0;
        do {
            buf.append((0 <= halfbyte) && (halfbyte <= 9) ? (char) ('0' + halfbyte) : (char) ('a' + (halfbyte - 10)));
            halfbyte = b & 0x0F;
        } while (two_halfs++ < 1);
    }
    return buf.toString();
}

public static String sha1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    md.update(text.getBytes("utf-8"), 0, text.length());
    byte[] sha1hash = md.digest();
    return convertToHex(sha1hash);
}

Płatności cykliczne

Płatności cykliczne to po prostu ponowne obciążenia karty (resale) wykonywane w określonych odstępach czasowych.

Możesz wykonywać je według schematu określanego przez Twój model biznesowy.

Transakcje cykliczne PayPal

Standardowo transakcje cykliczne odbywają się za pośrednictwem tzw. profili recurringowych. Dzięki nim PayPal sam automatycznie inicjuje kolejne płatności według instrukcji, jakie przekażemy mu wraz z pierwszą transakcją.

Konfiguracja powiadomień: Aby poprawnie zintegrować takie transakcje z systemem PayLane, należy ustawić powiadomienia na koncie PayPal tak, by wskazywały na serwer PayLane: https://secure.paylane.com/paypalIPN.

Tworzenie profilu recurringowego: Aby stworzyć profil recurringowy należy do parametrów pierwszej płatności dodać strukturę recurring określającą własności profilu. Przykładowo:

W strukturze recurring mamy trzy parametry:

  • amount – kwota, jaka będzie cyklicznie pobierana (separator dziesiętny: kropka),
  • start_date – data (w formacie RRRR-MM-DD) określająca początek cyklu,
  • period – okres cyklu płatności; możliwe wartości: day – dzień, week – tydzień, month – miesiąc, year – rok.
Przygotowanie danych do profilu recurringowego
PHP/Ruby/Python
$paypal_params = array(
    'sale'     => array(
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    ),
    'back_url'  => 'http://example-page.com',
    'recurring' => array(
        'amount'     => '19.99', 
        'start_date' => '2014-11-01', 
        'period'     => 'month'
    ),
);
paypal_params = {
    'sale' => {
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    },
    'back_url' => 'http://example-page.com',
    'recurring' => array(
        'amount'     => '19.99', 
        'start_date' => '2014-11-01', 
        'period'     => 'month'
    )
}
paypal_params = {
  'sale' : {
    'amount'      : 19.99,
    'currency'    : 'EUR',
    'description' : 'Product #1'
  },
  'back_url' : 'http://example.com',
  'recurring' : {
     'amount'     => '19.99', 
     'start_date' => '2014-11-01', 
     'period'     => 'month'
  }
}

Transakcje referencyjne

Transakcje referencyjne (tzw. resale odwołujące sie do wcześniejszych płatności) oferują nieco większe możliwości dla bardziej doświadczonych użytkowników – to oni sami bezpośrednio inicjują każdą kolejną płatność. Należy jednak mieć na uwadze, że takie rozwiązanie trzeba dodatkowo samodzielnie zaimplementować.

Co więcej, aby móc skorzystać z tego rozwiązania, należy zgłosić się do PayPala w celu aktywacji transakcji referencyjnych. PayPal wymaga odpowiednich miesięcznych obrotów (np. rzędu 100000 USD) i decyzja o włączeniu transakcji referencyjnych należy do niego.

Resale nie wymaga kompletu informacji o transakcji/kliencie. Dlatego właśnie potrzebna jest najpierw przynajmniej jedna płatność, do której można się odwołać.

Zacznij od przygotowania informacji potrzebnych do zrealizowania ponownej płatności. W tym celu musisz najpierw pozyskać numer ID wcześniejszej transakcji, który jednoznacznie określa daną płatność w systemie PayLane. Możesz łatwo odczytać ten numer podczas przeprowadzania transakcji, jak w przykładzie.

Wprawdzie możesz odwołać się do dowolnej wcześniejszej transakcji PayPal, to jednak zalecamy odnoszenie się do ostatniej (najnowszej) płatności. Takie podejście ma wiele zalet, pozwala m.in. na łatwe śledzenie przepływu płatności.

Zazwyczaj sprzedawcy przechowują takie numery ID w swoich bazach danych. W ten sposób nie muszą przechowywać żadnych wrażliwych danych, a jednocześnie mają możliwość bezpośredniego odwołania się do wybranej transakcji.

Gdy posiadasz już numer ID (np. pobrany z bazy danych), możesz przygotować dane potrzebne do obciążenia karty i wywołać metodę resaleBySale.

Możesz sprawdzić czy płatność została wykonana prawidłowo wywołując metodę isSuccess. Pozyskanie numeru ID transakcji (lub danych o błędzie, jeśli operacja się nie powiodła) jest również bardzo proste i może wyglądać jak w przykładzie.

Płatności cykliczne są po prostu ponownymi obciążeniami konta PayPal. Od Ciebie zależy, czy będą to odstępy tygodniowe, miesięczne, roczne czy inne; podobnie możesz wykonać resale np. dopiero, gdy klient osiągnie określoną kwotę do spłaty.

Zrealizowanie pierwszej transakcji PayPal
PHP/Ruby/Python/Android
$status = $client->paypalSale($paypal_params);
$id_first_sale = $status['id_sale'];
status = client.paypal_sale(paypal_params)
id_first_sale = status['id_sale']
status = client.paypal_sale(paypal_params)
id_first_sale = status['id_sale']
api.payPalSale(sale,"http://example-page.com",new Callback<RedirectSaleResult>() {
    @Override
    public void onFinish(RedirectSaleResult result) {
        long idSale=result.getIdSale();
    }
});
Realizacja transakcji / resale
PHP/Ruby/Python/Android
$resale_params = array(
    'id_sale'     => $id_first_sale,
    'amount'      => 99.99,
    'currency'    => 'EUR',
    'description' => 'Recurring billing product #1',
);

try {
    $status = $client->resaleBySale($resale_params);
} catch (Exception $e) {
    // handle exceptions here
}

if ($client->isSuccess()) {
    echo "Success, second id_sale: {$status['id_sale']} \n";
} else {
    die("Error ID: {$status['error']['id_error']}, \n".
        "Error number: {$status['error']['error_number']}, \n".
        "Error description: {$status['error']['error_description']}");
}
resale_params = {
    'id_sale'     => id_first_sale,
    'amount'      => 99.99,
    'currency'    => 'EUR',
    'description' => 'Recurring billing product #1'
}

begin
    status = client.resale_by_sale(resale_params)
rescue PayLane::ClientError => e
    # handle exceptions here
end 

if client.success?
    puts "Success, id_second_sale: #{status["id_sale"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
    exit
end
resale_params = {
  'id_sale'     : id_first_sale,
  'amount'      : 99.99,
  'currency'    : 'EUR',
  'description' : 'Recurring billing product #1'
}

try:
    status = client.resale_by_sale(resale_params)
except Exception, e:
    # handle exceptions here

if client.is_success():
    print 'Success, second id_sale: %s' % status['id_sale']
else:
    sys.exit('Error ID: ' + str(status["error"]["id_error"]) + '\n' \
             'Error number: ' + str(status["error"]["error_number"]) + '\n' \
             'Error description: ' + str(status["error"]["error_description"]))
api.resaleSale(idSale, 99.99, "EUR", "Recurring billing product #1", new Callback<FraudSaleResult>() {

    @Override
    public void onFinish(FraudSaleResult result) {
        // success
    }

    @HandleException
    public void onProtocolError(ProtocolException e) {
        // invoke if not success
        // e.getCode() - error code
        // e.getMessage() - error message
    }

    @Override
    public void onError(Exception e) {
        // connection error etc.
    }
});

Zwrot środków

Zdarza się, że chcesz zwrócić klientowi pieniądze (np. oddał lub reklamował towar) – całą kwotę lub tylko jej część. Do tego właśnie służy refund, czyli zwrot środków; pozwala na oddanie określonej kwoty w powiązaniu z konkretną transakcją.

Aby przeprowadzić refund, musisz znać numer ID transakcji, który jednoznacznie identyfikuje daną płatność w systemach PayLane. Możesz łatwo pozyskać ten numer podczas dokonywania transakcji np. jak w przykładzie.

Na stronie Pojedyncza transakcja znajdziesz więcej informacji na temat dokonywania pojedynczej płatności.

Zazwyczaj sprzedawcy zapisują takie numery ID transakcji w swoich bazach danych. Dzięki temu nie muszą sami przechowywać wrażliwych danych i wciąż są w stanie odnieść się do konkretnej płatności.

Przygotuj dane potrzebne do wykonania refunda (wywołaj metodę refund). Zauważ, że możesz podać kwotę transakcji lub mniejszą; jeśli podasz kwotę większą niż kwota transakcji, system zwróci błąd. Możesz również określić walutę oraz powód zwrotu środków.

Podobnie jak w przypadku każdej innej transakcji, tak i tu możesz sprawdzić czy refund został wykonany prawidłowo wywołując metodę isSuccess. Pozyskanie numeru ID refundu (lub danych o błędzie, jeśli operacja się nie powiodła) jest również bardzo proste i może wyglądać jak na poniższym przykładzie.

Pobranie ID pierwszej transakcji
PHP/Ruby/Python/Android
$status = $client->paypalSale($paypal_params);
$id_sale = $status['id_sale'];
status = client.paypal_sale(paypal_params)
id_sale = status['id_sale']
status = client.paypal_sale(paypal_params)
id_sale = status['id_sale']
api.payPalSale(sale,"http://example-page.com",new Callback<RedirectSaleResult>() {
    @Override
    public void onFinish(RedirectSaleResult result) {
        long idSale=result.getIdSale();
    }
});
Realizacja refundu
PHP/Ruby/Python/Android
$refund_params = array(
    'id_sale'  => $id_sale,
    'amount'   => 9.99,
    'reason'   => 'Partial refund',
);

try {
    $status = $client->refund($refund_params);
} catch (Exception $e) {
    // handle exceptions here
}

if ($client->isSuccess()) {
    echo "Success, id_refund: {$status['id_refund']} \n";
} else {
    die("Error ID: {$status['error']['id_error']}, \n".
        "Error number: {$status['error']['error_number']}, \n".
        "Error description: {$status['error']['error_description']}");
}
refund_params = {
    'id_sale' => id_sale,
    'amount'  => 9.99,
    'reason'  => 'Partial refund.'
}

begin
    status = client.refund(refund_params)
rescue PayLane::ClientError => e
    # handle exceptions here
end 

if client.success?
    puts "Success, id_refund: #{status["id_refund"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
    exit
end
refund_params = {
  'id_sale'  : id_sale,
  'amount'   : 9.99,
  'reason'   : 'Partial refund'
}

try:
    status = client.refund(refund_params)
except Exception, e:
    # handle exceptions here

if client.is_success():
    print 'Success, id_refund: %s' % status['id_refund']
else:
    sys.exit('Error ID: ' + str(status["error"]["id_error"]) + '\n' \
             'Error number: ' + str(status["error"]["error_number"]) + '\n' \
             'Error description: ' + str(status["error"]["error_description"]))
api.refund(idSale, 9.99, "EUR", "Partial refund", new Callback<FraudSaleResult>() {

    @Override
    public void onFinish(FraudSaleResult result) {
        // success
    }

    @HandleException
    public void onProtocolError(ProtocolException e) {
        // invoke if not success
        // e.getCode() - error code
        // e.getMessage() - error message
    }

    @Override
    public void onError(Exception e) {
        // connection error etc.
    }
});

Autoryzacja

Możesz nie chcieć od razu pobrać środków klienta, a jedynie dokonać autoryzacji. Może to być użyteczne w różnych przypadkach biznesowych, np. gdy chcesz sprawdzić, czy płatność jest możliwa lub zablokować środki, by pobrać je (lub ich część) później.

Przygotuj dane potrzebne do autoryzacji. Będą to identyczne informacje jak w przypadku “zwykłej” płatności.

Mając te dane, po prostu wywołaj metodę paypalAuthorization (podobnie jak w przypadku płatności wywołasz paypalSale).

Możesz łatwo sprawdzić, czy autoryzacja się powiodła oraz sprawdzić numer ID autoryzacji – możesz go potem użyć do ponownych obciążeń (resale), jeśli chcesz wykorzystać np. płatności cykliczne.

Przygotowanie danych do autoryzacji
PHP/Ruby/Python/Android
$paypal_params = array(
    'sale'     => array(
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    ),
    'back_url'  => 'http://example-page.com',
);
paypal_params = {
    'sale' => {
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    },
    'back_url' => 'http://example-page.com'
}
paypal_params = {
  'sale' : {
    'amount'      : 19.99,
    'currency'    : 'EUR',
    'description' : 'Product #1'
  },
  'back_url' : 'http://example.com'
}
Sale sale = new Sale(19.99, "EUR", "Product #1");
Realizacja autoryzacji
PHP/Ruby/Python/Android
try {
    $status = $client->paypalAuthorization($paypal_params);
} catch (Exception $e) {
    // handle exceptions here
}

if ($client->isSuccess()) {
    echo "Success, id_authorization: {$status['id_authorization']} \n"; 
} else {
    die("Error ID: {$status['error']['id_error']}, \n".
        "Error number: {$status['error']['error_number']}, \n".
        "Error description: {$status['error']['error_description']}");
}
begin
    status = client.paypal_authorization(paypal_params)
rescue PayLane::ClientError => e
    # handle exceptions here
end 

if client.success?
    puts "Success, id_authorization: #{status["id_authorization"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
    exit
end
try:
    status = client.paypal_authorization(paypal_params)
except Exception, e:
    # handle exceptions here

if client.is_success():
    print 'Success, id_authorization: %s' % status['id_authorization']
else:
    sys.exit('Error ID: ' + str(status["error"]["id_error"]) + '\n' \
             'Error number: ' + str(status["error"]["error_number"]) + '\n' \
             'Error description: ' + str(status["error"]["error_description"]))
api.payPalAuthorization(sale, "http://example-page.com", new Callback<PayPalAuthorizationResult>() {
    @Override
    public void onFinish(PayPalAuthorizationResult result) {
        // success
    }

    @HandleException
    public void onProtocolError(ProtocolException e) {
        // invoke if not success
        // e.getCode() - error code
        // e.getMessage() - error message
    }

    @Override
    public void onError(Exception e) {
        // connection error etc.
    }
});