Pull to refresh

Получаем оплату через PayPal

Reading time4 min
Views34K
Закончил интеграцию нашей площадки с платежной системой PayPal. В моем случае, была небольшая особенность — мы уже принимаем платежи с Робокассы и нам хотелось бы получить похожий воркфлоу при оплате. Разных вариантов интеграции у PayPal очень много и самой большой «сложностью» был поиск нужного варианта, совпадающего с уже существующим воркфлоу.
Воркфлоу наш очень прост:
  • пользователь вводит сумму, нажимает кнопку, переходит на сайт платежной системы
  • оплачивает и возвращается системой к нам
  • мы дальше проверяем статус оплаты и делаем необходимые нам манипуляции.

В итоге у меня все получилось, хотя и не без маленьких косяков (как же без них).

Что нам понадобится




Шаг 1: Настройка PayPal

Заходим на developer.paypal.com. Для этого подойдет обычный аккаунт paypal. Переходим к вкладке Applications.

Создаем новое приложение. Сложностей у вас не должно возникнуть. Из важных параметров: «Application return URL». Это адрес на который PayPal будет направлять пользователя после оплаты (или отмены). Так же обратите внимание на Client ID и Secret. Это ключи, по которым пайпал будет авторизовывать нас при использовании api.

Далее нужно создать тестовый аккаунт для песочницы. Нажимаем ссылку Sandbox accounts создаем тестового пользователя (под ним мы будем проводить тестовые оплаты). Сложностей тоже возникнуть не должно.

Шаг 2: Тестовое приложение

Устанавливаем SDK:
    Install-Package RestApiSDK

Создаем тестовое приложение (например, консольное приложение, хотя я использую пустой тест в проекте для этих целей, QuickTests). И копипастим в него код ниже, предварительно заменив YOUR_CLIENT_ID и YOUR_CLIENT_SECRET на параметры нашего, только что созданного приложения. Код, в принципе, можно взять из Интерактивного гайда с примерами кода, я его только собрал вместе и привел в приятный мне вид.

    var sdkConfig = new Dictionary<string, string> { { "mode", "sandbox" } };
    string accessToken = new OAuthTokenCredential("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", sdkConfig)
                                .GetAccessToken();

    var redirectUrls = new RedirectUrls {
        cancel_url = "http://localhost:11180/PayPalResult?cancel=true",
        return_url = "http://localhost:11180/PayPalResult?success=true"
    };
    var amnt = new Amount { currency = "USD", total = "1" };
    var createdPayment = new Payment {
            intent = "sale",
            payer = new Payer { payment_method = "paypal" },
            transactions = new List<Transaction> {
                    new Transaction { description = "Sample payment", amount = amnt }},
            redirect_urls = redirectUrls}
        .Create(new APIContext(accessToken) { Config = sdkConfig });

    var approvalUrl = createdPayment.links.Single(l => l.rel == "approval_url").href;
    var paymentId = createdPayment.id;
    Console.WriteLine(approvalUrl);
    Console.WriteLine(paymentId);


Переходим по approvalUrl, логинимся под тестовым аккаунтом, подтверждаем платеж, нас перекидывает на http://localhost:11180/PayPalResult?success=true&token=EC-DSKFJDSKFJEO42M&PayerID=DFKJDFKLGJEOR (не важно, есть ли у вас вэбсервер, который обслуживает порт 11180).

Берем параметр PayerID из этого урла и вставляем его, вместе с PaymentId (мы его получили чуть раньше), в следующий код:

    string payerID = "YOUR_PAYER_ID";
    string paymentId = "YOUR_PAYMENT_ID";

    var sdkConfig = new Dictionary<string, string> { { "mode", "sandbox" } };
    string accessToken = new OAuthTokenCredential("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", sdkConfig)
                                .GetAccessToken();
    var pymntExecution = new PaymentExecution { payer_id = payerID };
    var payment = new Payment { id = paymentId }
                      .Execute(new APIContext(accessToken) { Config = sdkConfig }, pymntExecution);


Тут для нас важен payment.status, который теперь получил значение «approved». Ура, тест пройден!
Документация говорит, что дальше мы должны сделать Refund a sale (API reference), но я, пока, не разбирался что это такое.

Шаг 3: Интегрируем в наше приложение

С интеграцией кода выше в приложение, теоретически, не должно быть проблем. Но все же они у меня были. Связана эта проблема была с запоминанием paymentId между двумя методами. Как раз запомнить paymentId не сложно, сложно понять какому из запомненных платежей принадлежит текущий запрос. Ведь paypal в resultUrl не указывает никаких данных кроме PayerID. А по PayerID получить paymentIdо никак не получиться.

Можно хранить это значение в сессии и считать, что пользователь проводит только одну транзакцию за раз.

А можно поступить хитрее. Можно сгенерировать id для оплаты и попросить PayPal включить этот id в ResultUrl. Вот так:
    var redirectUrls = new RedirectUrls {
        cancel_url = "http://localhost:11180/PayPalResult?cancel=true&InvoiceId={SOME_ID}",
        return_url = "http://localhost:11180/PayPalResult?success=true&InvoiceId={SOME_ID}"
    };

С помощью этого значения уже можно найти соответствующий paymentId. В принципе, можно в RedirectUrls сразу передать paymentId, но мне кажется это не секьюрно (хотя безопасник из меня так себе, может эти значения хорошо защищены и по ним ничего нельзя получить).

Заключение

Вот и все. Не так уж и сложно, как казалось. Надеюсь кому-то еще эта инструкция пригодиться.

У меня есть пару открытых вопросов по интеграции. Если вы можете на них ответить — буду благодарен.
  • Как использовать token, который PayPal передает в ResultUrl. Понятно, что его можно как-то использовать, чтобы удостовериться, что это точно PayPal, а не злоумышленник. Вопрос как.
  • При оплате PayPal показывает пользователю только то, что мы ему передали. Т.е. если мы не передали сумму заказа, то пользователь ее не увидит. Для меня это выглядит странно. Может это особенности песочницы?
    Ответ: Если передать список товаров и их цену, то пайпал проверяет сумму заказа. подробнее...
  • Нужно разбираться с Refund. Похоже это важный шаг в оплате. Интересно, если не сделать refund, может ли пользователь отменить транзакцию?
    Ответ: «не нужно делать refund (это возврат денег), его нужно вызывать когда вы допустим не можете доставить товар но приняли оплату...», подробнее.... Спасибо tzlom,
  • Что делать, если пользователь оплатил, а потом отменил транзакцию (вроде PayPal это позволяет)? Придет ли cancel запрос на ResultUrl и каков срок отмены?
    Ответ: «Придет Instant Payment Notification (IPN)» (спасибо hannimed, подробнее...) и «в вашем случае придёт refund » (спасибо tzlom, подробнее...)
Tags:
Hubs:
Total votes 11: ↑8 and ↓3+5
Comments10

Articles