
В этом руководстве мы создадим простое приложение для магазина, в которое мы интегрируем онлайн-платежи с помощью 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.