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

Что будем строить?

Мы создадим простое приложение от магазина, в котором будет производиться покупка книги, далее будет производиться оплата через PayPal API, в конце оплаты, если она сделана правильно, у вас будет доступ к загрузке книги в цифровом виде.

В этом руководстве мы будем использовать AdonisJS4.1.

Требования

В этом руководстве предполагается, что на вашем компьютере установлено следующее:

узел›= 8.0 или выше

$ node --version

npm›= 5.0 или выше

$ npm --version

В дополнение к этому потребуются учетные данные: (CLIENT_IDи CLIENT_SECRET) учетной записи Песочницы PayPal. Которые будут использоваться для проведения испытаний, а все деньги, которые двигаются, являются фиктивными. Не нужно указывать кредитные карты или что-то в этом роде, все будет фиктивно, доказательства. Как если бы вы расплатились в своем магазине билетами Монополии.

Установка Adonis CLI

Сначала нам нужно установить Adonis CLI, который поможет нам создавать новые приложения AdonisJS:

$ npm i -g adonisjs-cli

Создать новый проект

Мы начнем с создания нового приложения AdonisJS. Мы будем использовать Adonis CLI.

$ adonis new adonisjs-paypal

Приведенная выше команда создаст новое приложение AdonisJS с именем adonisjs-paypal, используя шаблон приложения fullstack. Чтобы убедиться, что все работает как положено, давайте запустим только что созданное приложение. Сначала мы входим в папку adonisjs-paypal и выполняем следующую команду:

$ cd adonisjs-paypal 
$ adonis serve --dev 
#info: serving app on http://127.0.0.1:3333

Открываем http://localhost:3333 в браузере, чтобы увидеть страницу приветствия.

Что ж! Теперь приступим к разработке приложения.

Создание контроллера ShopController

В настоящее время мы будем использовать только один основной контроллер с именем ShopController. Мы будем использовать команду adonis make:controller Shop из Adonis CLI для его создания:

$ adonis make:controller Shop

При появлении запроса выберите параметр Для HTTP-запросов и нажмите Ввод. Теперь у нас есть файл с именем ShopController.js в каталоге app/Controllers/Http.

Контроллер ShopController будет иметь 4 метода: index(), paySuccess(), payError() и >download(), в дополнение к объекту с именем книга, который будет описывать данные основной книги для покупки в нашем магазине. Откройте файл ShopController.js и добавьте следующий код:

//app/Controllers/Http/ShopController.js
'use strict'
// We define the object "book" as a global variable
const book = {
  sku: 'P001',
  title: 'Build Apps with Adonis.JS',
  image: 'http://www.victorvr.com/img/resources/Book-P001.png',
  description: 'Building Node.JS Applications with Javascript.',
  author: 'Victor Valencia Rico',
  price: 5,
  currency: 'USD'
}
class ShopController {
  
  // Displays the main product
  async index ({ view, request }) {
    const paymentId = request.input('paymentId')
    return view.render('index', {book: book, paymentId: paymentId} )
  }
  // Display notification of successful payment
  async paySuccess ({ request, response, session }) {
    const paymentId = request.input('paymentId')
    session.flash({
      paymentId: paymentId,
      notification_class: 'alert-success',
      notification_icon: 'fa-check',
      notification_message: 'Thanks for you purchase! ' + paymentId
    })
    response.redirect('/?paymentId=' + paymentId);
  }
  // Display notification of a failed payment or others errors
  async payError ({ request, response, session }) {
    const name = request.input('name')
    const message = request.input('message')
    session.flash({
      notification_class: 'alert-danger',
      notification_icon: 'fa-times-circle',
      notification_message: 'Payment error! ' + name + ': ' + message
    })
    response.redirect('/');
  }
  // Display download message
  async download () {
    return 'Download File...'
  }}
module.exports = ShopController

Метод index() просто отображает главную книгу магазина, используя представление index (которое мы создадим ранее). Метод paySuccess() получает только параметр paymentId и сохраняет в переменной session данные об успешном уведомлении для отображения при перенаправлении на главную дорожка. Метод payError() получает 2 параметра: имя и сообщение, чтобы присвоить их переменной сессии в соответствии с не удалось отобразить уведомление при перенаправлении на основной путь. Наконец, метод donwload() просто отображает простое сообщение.

Создание маршрутов приложений

Откройте файл start/routes.js и обновите его, как показано ниже:

//start/routes.js
...
Route.get('/','ShopController.index').as('book.index')
Route.get('/pay/success','ShopController.paySuccess').as('pay.success')
Route.get('/pay/error','ShopController.payError').as('pay.error')
Route.get('/download','ShopController.download').as('book.download')
...

Определенные маршруты будут служить для придания приложению начального поведения. Очень важно определить каждый из них и их псевдонимы, так как этот псевдоним поможет нам ссылаться на наши маршруты в главном представлении.

Создание основного вида

Мы собираемся создать единое представление с именем index для нашего приложения. Все файлы представлений должны находиться в каталоге resources/views. Итак, внутри каталога мы создадим новое представление и назовем его index.edge. Откройте только что созданный файл и вставьте следующий код:

<!-- resources/views/index.edge [Delete line] -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>AdonisJS - PayPal</title>
    {{ style('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css') }}
    {{ style('https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css') }}
    <style>
      .book-title,
      .book-author,
      .book-description {
        text-align: center;
      }
      .book-image {
        width: 200px;
      }
    </style>
  </head>
  <body>
    <!-- Begin : Nabvar -->
    <nav class="navbar navbar-dark bg-dark">
      <a class="navbar-brand" href="{{ route('book.index') }}">
        <img src="/logo.svg" width="30" height="30" class="d-inline-block align-top" alt="">
        The Book Store
      </a>
    </nav>
    <!-- End : Nabvar -->
    <!-- Begin : Container -->
    <div class="container pt-3">
      <div class="row justify-content-center">
        <div class="col-md-6">
          <!-- Begin : Notification -->
          @if(flashMessage('notification_message'))
            <div class="alert {{ flashMessage('notification_class') }}" role="alert">
              <i class="fa {{ flashMessage('notification_icon') }}"></i> {{ flashMessage('notification_message') }}
            </div>
          @endif
          <!-- End : Notification -->
          <!-- Begin : Card - Book -->
          <div class="card flex-md-row mb-4 shadow-sm h-md-250 bg-light">
            <div class="card-body d-flex flex-column">
              <strong class="book-title d-inline-block mb-3 text-primary">{{ book.sku }} - {{ book.title }}</strong>
              <div class="book-author text-muted small pb-4">By {{ book.author }}</div>
              <p class="book-description mb-auto alig-center">{{ book.description }}</p>
              @if(paymentId)
                <!-- Begin : Button - Download -->
                <a href="{{ route('book.download') }}?paymentId={{paymentId}}" class="btn btn-outline-success" role="button">
                  <i class="fa fa-cloud-download"></i> Download PDF
                </a>
                <!-- End : Button - Download -->
              @else
                <!-- Begin : Button - Buy -->
                <a href="{{ route('book.index') }}?paymentId=PAYID-XYZ" class="btn btn-outline-primary mt-3" role="button">
                  <i class="fa fa-credit-card"></i> Buy for only ${{ book.price }} {{ book.currency }}
                </a>
                <!-- End : Button - Buy -->
              @endif
            </div>
            <img src="{{ book.image }}" class="book-image card-img-right flex-auto d-none d-lg-block" alt="Book [200x250]">
          </div>
          <!-- End : Card - Book -->
          @if(paymentId)
            <!-- Begin : Button - Go Home -->
            <div class=" text-center pt-3">
              <a href="{{ route('book.index') }}" class="btn btn-outline-dark">
                <i class="fa fa-home"></i> Go Home
              </a>
            </div>
            <!-- End : Button - Go Home -->
          @endif
        </div>
      </div>
    </div>
    <!-- End : Container -->
  </body>
</html>

Мы будем использовать структуру CSS под названием Bootstrap и библиотеку значков FontAwesome в дополнение к глобальной функции style() из AdonisJS для ссылки на наши стили .css в CDN.

До этого момента мы просто делали базу приложения. При нажатии кнопки Купить всего за 5 долл. США (Кнопка — Купить) будет считаться, что оплата произведена, и появится новый вид для загрузки купленная книга, Кроме того, при нажатии кнопки Скачать PDF (Кнопка — Скачать) начнется загрузка книги, купленной в цифровом виде. Если мы зайдем в приложение в браузере, мы должны получить что-то похожее на следующее изображение:

Установите SDK PayPal

Чтобы продолжить, мы установим Paypal SDK, который позволит нам обрабатывать платежи. Итак, нам нужно установить драйвер Node.js для PayPal SDK.

$ npm install paypal-rest-sdk --save

После установки SDK мы должны сделать его доступным для приложения и настроить некоторые переменные среды. Конфигурация PayPal SDK включает: режим, который представляет собой песочницу для тестирования или живого для рабочей и его учетных данных client_id и client_secret.

var paypal = require('paypal-rest-sdk');
paypal.configure({
  mode: 'sandbox', // sandbox | live
  client_id: {YOUR_CLIENT_ID},
  client_secret: {YOUR_CLIENT_SECRET}
});

Создание файла конфигурации

Чтобы структурировать наше приложение, мы создадим новый файл конфигурации с именем paypal.js внутри папки config, где мы сосредоточим нашу PayPalглобальную переменные. Итак, откройте файл paypal.js и добавьте следующий код:

'use strict'
const Env = use('Env')
module.exports = {
  configure: {
    mode: Env.get('PAYPAL_MODE', 'sandbox'),
    client_id: Env.get('PAYPAL_CLIENT_ID'),
    client_secret: Env.get('PAYPAL_CLIENT_SECRET')
  },
  url_success: Env.get('APP_URL') + "/pay/success",
  url_error: Env.get('APP_URL') + "/pay/error"
}

Этот файл конфигурации будет считывать и инициализировать наши переменные среды: PAYPAL_MODE, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRETи APP_URL, используя >.env файл. Вот где мы можем определить их. Затем мы настраиваем переменные среды, вводя нашу конфигурацию в файл .env. Итак, откройте файл .env и добавьте следующие строки:

//.env
...
PAYPAL_MODE=sandbox // sandbox | live
PAYPAL_CLIENT_ID={YOUR_CLIENT_ID}
PAYPAL_CLIENT_SECRET={YOUR_CLIENT_SECRET}
...

Не забудьте обновить свои учетные данные, а также помните, что мы будем использовать режим песочницы для выполнения всех наших транзакций в тестовом режиме. После того, как эта конфигурация была выполнена, мы можем получить к ней доступ с любого из наших контроллеров.

Создание контроллера PaypalController

Для исключительного использования Paypal API мы создадим новый контроллер с именем PaypalController. Мы будем использовать команду adonis make:controller Paypal из Adonis CLI для его создания:

$ adonis make:controller Paypal

При появлении запроса выберите параметр Для HTTP-запросов и нажмите Ввод. Теперь у нас есть файл с именем PaypalController.js в каталоге app/Controllers/Http.

Контроллер PaypalController будет иметь 4 метода: getSuccessURL(), getErrorURL(), createPay() и >getPay(), который поможет нам взаимодействовать с PayPal API. Откройте файл PaypalController.js и добавьте следующий код:

//app/Controllers/Http/PaypalController.js
'use strict'
const Config = use('Config')
const Paypal = use('paypal-rest-sdk')
// We configure the PayPal SDK with our configuration
Paypal.configure(Config.get('paypal.configure'));
class PaypalController {
  
  // Return the URL to process a successful payment
  getSuccessURL () {
    return Config.get('paypal.url_success')
  }
  // Returns the URL to process a failed payment or other errors
  getErrorURL () {
    return Config.get('paypal.url_error')
  }
  // "Promise" function to create payments in the PayPal API.
  createPay ( payment ) {
    return new Promise( ( resolve, reject ) => {
      Paypal.payment.create( payment, function( err, payment ) {
        if ( err ) {
          reject(err);
        }
        else {
          resolve(payment);
        }
      });
    });
  }
  // "Promise" function to get a payment in the PayPal API.
  getPay ( paymentId ) {
    return new Promise( ( resolve, reject ) => {
      Paypal.payment.get( paymentId, function( err, payment ) {
        if ( err ) {
          reject(err);
        }
        else {
          resolve(payment);
        }
      });
    });
  }}
module.exports = PaypalController

Методы getSuccessURL() и getErrorURL() возвращают URL-адрес для обработки успешного или неудачного платежа, в зависимости от обстоятельств. Другие методы createPay() и getPay() возвращают функции Promise для создания и получения платежей в PayPal API. .

Функции Promise значительно упрощают управление асинхроннымипотоками данных в приложении, а обещания являются основой для последующей реализации более продвинутого JavaScript. такие функции, как async/await, которые делают наш код еще проще.

Создать платеж

Теперь мы свяжем наше приложение, чтобы иметь возможность оплачивать книгу с помощью PayPal API. Мы модифицируем контроллер ShopController.js, в котором будем ссылаться на контроллер PaypalController.js, и создадим новый экземпляр этого контроллера. Также будет добавлен новый метод tryPay(), который мы будем использовать для осуществления платежа в PayPal API. Этот метод определяет платеж внутри переменной платежа. Эта переменная будет отправлена ​​в метод createPay() контроллера PaypalController.js. Мы будем использовать префикс async при вызове метода createPay(), поскольку он имеет тип Promise. Откройте файл ShopController.js и добавьте следующий код:

//app/Controllers/Http/ShopController.js
// Remember to reference the PaypalController at the top
const PaypalController = use('App/Controllers/Http/PaypalController')
const Paypal = new PaypalController()
...
// Method to make the payment in the PayPal API.
async tryPay({ response }) {
  const success_url = Paypal.getSuccessURL()
  const error_url = Paypal.getErrorURL()
  // Create the payment object
  var payment = {
    "intent": "authorize",
    "payer": {
      "payment_method": "paypal"
    },
    "redirect_urls": {
      "return_url": success_url,
      "cancel_url": error_url
    },
    "transactions": [{
      "item_list": {
        "items": [{
          "name": book.title,
          "sku": book.sku,
          "price": book.price,
          "currency": book.currency,
          "quantity": 1
        }]
      },
      "amount": {
        "total": book.price,
        "currency": book.currency
      },
      "description": book.sku + ': ' + book.title
    }]
  }
  await Paypal.createPay( payment )
    // Indicates that the payment was successful
    .then( ( transaction ) => {
      var links = transaction.links;
      var counter = links.length;
      while( counter -- ) {
        if ( links[counter].method == 'REDIRECT') {
          // Redirect to PayPal where the user approves the transaction
          return response.redirect( links[counter].href )
        }
      }
    })
    // Indicates that the payment was failed
    .catch( ( err ) => {
      var details = err.response
      if(err.response.httpStatusCode == 401) {
        return response.redirect(error_url + '?name=' + details.error + '&message=' + details.error_description);
      }
      else {
        return response.redirect(error_url + '?name=' + details.name + '&message=' + details.message);
      }
    });
}
...

Объект платежа API PayPal определяет способ оплаты в PayPal, детали транзакции, URL-адрес, на который перенаправляется клиент после того, как клиент принимает или отменяет >PayPalоплата и другая информация.

Не забудем добавить новый маршрут в файл start route.js, где мы привязываем метод tryPay() контроллера ShopController.js. и обновите его следующим образом:

//start/routes.js
...
Route.get('/pay/try', 'ShopController.tryPay').as('book.pay')
...

Чтобы закончить, мы изменили представление, в котором мы заменим только атрибут href кнопки для оплаты (Кнопка — Купить), заменив route ('book.index ') с добавлением нового маршрута ('book.pay') и удаления фиктивного параметра ?paymentId=PAYID-XYZ

<!-- resources/views/index.edge [Delete line] -->
...
  <!-- Begin : Button - Buy -->
  <a href="{{ route('book.pay') }}" class="btn btn-outline-primary mt-3" role="button">
    <i class="fa fa-credit-card"></i> Buy for only ${{ book.price }} {{ book.currency }}
  </a>
  <!-- End : Button - Buy -->
...

До этого момента мы сможем оплачивать нашу книгу через PayPal API. При нажатии кнопки Купить всего за 5 долларов США (Кнопка — Купить) нас перенаправит на страницу PayPal, где мы должны ввести данные для входа нашего тестового пользователя. После входа в корзину покупок PayPal мы покажем описание и цену нашей покупки. Затем нажимаем кнопку «Продолжить», чтобы подтвердить нашу покупку. Как только покупка будет авторизована, страница PayPal перенаправит нас в наш магазин, где будет отображаться уведомление об успешной покупке и разрешена загрузка нашей купленной книги. Мы должны получить что-то похожее на следующее изображение:

Подтвердить платеж

Чтобы закончить работу с приложением, мы, наконец, изменим контроллер ShopController в методе download(). Откройте файл ShopController.js и добавьте следующий код:

//app/Controllers/Http/ShopController.js
// Remember to reference the Helpers library at the top
const Helpers = use('Helpers')
...
// It will verify the payment before starting the download
async download ({ response, request }) {
  const paymentId = request.input('paymentId')
  await Paypal.getPay( paymentId )
    // Indicates that the payment exists
    .then( ( payment ) => {
      const item = payment.transactions[0].item_list.items[0]
      //Download book
      const name = item.sku + ' - ' + item.name + '.pdf'
      const source = Helpers.resourcesPath('/files/Book-' + item.sku + '.pdf')
      response.attachment(source, name)
    })
    // Indicates that the payment does not exist
    .catch( ( err ) => {
      // Show Error
      var details = err.response
      response.send('ERROR: ' + details.name + ' => ' + details.message)
    });
}
...

Поскольку метод download() получает параметр paymentId, мы проверим метод getPay() в PaypalController.js, если это существующий платеж через PayPal API. Если это так, мы разрешим загрузку книги в цифровом виде. В противном случае он покажет только сообщение с соответствующей ошибкой.

Примечание: файл цифровой книги можно загрузить здесь и хранить в личном каталоге resources/files, формат имени будет следующим образом: Book-[sku].pdf.

Если мы зайдем в приложение в браузере, мы должны получить следующий результат с существующим параметром paymentId и фиктивным:

Вывод

Прежде чем закончить, давайте посмотрим на окончательный результат:

Теперь да, мы закончили с нашим приложением, вы увидели, как легко реализовать онлайн-платежи через PayPal API с помощью AdonisJS. Это базовое приложение, и в него можно добавить еще больше функций, таких как добавление нескольких книг в одну покупку, сохранение данных о покупках, сделанных в базе данных, печать счета-фактуры покупки и т. д. Эти действия оставлены на ваше воображение и критерии, чтобы иметь более полное приложение.

Первоначально опубликовано на https://www.victorvr.com.