Интеграция нового Stripe Checkout в React-приложение с использованием Firebase Functions

12.09.2019

14 сентября 2019 года, в Европе вступает в силу Strong Customer Authentication (SCA), которое требует использование 3D Secure при оплате картами. Если до этого вы использовали на проекте Stripe Checkout, то теперь вам необходимо интегрировать новую версию, для проведения платежей в который происходит посредством редиректа на сервера Stripe.

У нового Stripe Checkout есть два метода интеграции: Сlient side и Сlient and server side. В случае, когда нам необходимо обрабатывать данные успешно оплаченного заказа, например, отправляя веб-хуки, первый способ не подходит, потому что не позволяет связать заказ с Stripe sessionId.

В этой статье мы разберем Сlient and server side интеграцию Stripe Checkout для One-time payment. В качестве Client side мы будем использовать React-приложение, а в качестве Server side - Firebase Functions.

Для начала создадим новое React-приложение и вставим в файла /public/index.html скрипт Stripe:

<script src="https://js.stripe.com/v3/"></script>

Установим в наше приложение firebase-tools, чтобы создавать Firebase functions:

npm install -g firebase-tools

Инициализуем наш Firebase проект с указанием сервисов Firebase, которые мы хотим использовать, я укажу только Functions:

firebase init

Наши functions будут находиться в одноименной папке и файле index.js.

Напишем функцию которая будет создавать сессию Stripe при клике по кнопке в приложении:

const functions = require('firebase-functions');

// Нам необходимо установить следующие пакеты (stripe, body-parser) с помощью npm install в папку /functions/
const bodyParser = require('body-parser');
const cors = require('cors')({origin: true});
const express = require('express');

// Secret Key из Stripe Dashboard
const stripe = require('stripe')('sk_********************');

// Функция для отправки ответов
function send(res, code, body) {
  res.send({
    statusCode: code,
    headers: {'Access-Control-Allow-Origin': '*'},
    body: JSON.stringify(body),
  });
}

// Приложение должно использовать express
const createOrderAndSessionApp = express();

// Приложение должно использовать cors
createOrderAndSessionApp.use(cors);

// Функция, которая берет данные с front-end и создает платежную сессию
function createOrderAndSession(req, res) {
  const body = JSON.parse(req.body);

  // Creating session data from payload
  const currency = body.currency;
  const quantity = body.quantity;
  const amount = body.amount;
  const name = body.name;
  const description = body.description;
  let images = [];
  images[0] = body.image;
  const customerEmail = body.customerEmail;
  const clientId = body.clientId;

  // Здесь мы так же можем обработать наши данные, например, записать их в firebase database

  // Создание сессии с помощью данных выше
  stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [{
      name: name,
      description: description,
      images: images,
      amount: amount,
      currency: currency,
      quantity: quantity,
    }],
    client_reference_id: clientId,
    customer_email: customerEmail,

    // Для простоты укажем единственную страницу нашего приложения
    success_url: 'http://localhost:3000/#/',
    cancel_url: 'https://localhost:3000/#/',
  }).then(session => {
  // Getting the session id
  var sessionId = session.id;

  // Здесь мы можем что-то сделать с нашим session id, например добавить его в данные заказа в firebase database

  // Отправка sessionId на front-end
  send(res, 200, {
    sessionId: sessionId
  });
  return;
  }).catch(error => {
    console.log(error);
    return;
  });
}

// Creating a route
createOrderAndSessionApp.post('/', (req, res) => {
  try {
    createOrderAndSession(req, res);
  } catch(e) {
    console.log(e);
    send(res, 500, {
      error: `The server received an unexpected error. Please try again and contact the site admin if the error persists.`,
    });
  }
});

// Экспорт http-функции
exports.createOrderAndSession = functions.https.onRequest(createOrderAndSessionApp);

В файле App.js создадим кнопку, которую мы будем использовать для оплаты с помощью Stripe и метод componentDidMount который будет получать sessionId и оправлять нас на страницу в формой оплаты:

import React from 'react';
import './App.css';

class App extends React.Component {
  componentDidMount() {

    // Publishable Key из Stripe Dashboard
    const stripe =    window.Stripe('pk_********************');
    const paymentBtn = document.getElementById('stripe-payment-btn');
    let sessionId;

    paymentBtn.addEventListener('click', () => {
      let orderData = {
      currency: 'EUR',
      quantity: 1,
      amount: 10,
      name: 'Some product',
      description: 'Product description',
      image: 'https://image-url/',
      customerEmail: 'customer@email.com',
      clientId: '12345'
    }

    // Url нашей Firebase функции
    fetch('https://your-firebase-project.cloudfunctions.net/createOrderAndSession/', {
      method: 'POST',

      // Добавление данных заказа в payload
      body: JSON.stringify(orderData)
      }).then(response => {

        return response.json();
      }).then(data => {

        // Получение sessionId firebase function
        var body = JSON.parse(data.body);
        return sessionId = body.sessionId;
      }).then(sessionId => {

        // Редирект на страницу с формой оплаты
        stripe.redirectToCheckout({
          sessionId: sessionId
        }).then(function (result) {
          result.error.message
        });
      });
    });
  }

  render() {
    return 
Checkout with Stripe
} } export default App;

Теперь вернемся в /functions/index.js и напишем функцию для веб-хука, который будет отправлять Stripe после успешной оплаты. Она нужна для того чтобы зная sessionId производить дальнейшую обработку данных конкретного заказа (в моем случае мы следили за состоянием заказа, который был записан в Firebase database, меняли его статус на paid с помощью веб-хука, и с помощью еще одной функции отправляли его с помощью API если статус менялся на paid.

// Мы должны добавить url веб-хук функции в Stripe Dashboard, добавить событие checkout.session.completed в его настройках и получить endpointSecret
const endpointSecret = 'whsec_********************';

// Приложение должно использовать express
const processTheOrderApp = express();

processTheOrderApp.post('/', bodyParser.raw({type: 'application/json'}), (request, response) => {
  const sig = request.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);
  } catch (err) {
    console.log(err);
    return response.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Обработка события checkout.session.completed
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;

    // Здесь мы можем обработать данные заказа после успешной оплаты
    // (например, поменять статус заказа в Firebase Database и вызвать другую функцию)

  }

  // Вернуть ответ, чтобы подтвердить получение события
  response.json({received: true});
});

// Экспорт http-функции
exports.processTheOrder = functions.https.onRequest(processTheOrderApp);

Уточню, что нам нужно добавить url для веб-хука в Stripe Dashboard, в ее настройках добавить checkout.session.completed и получить endpointSecret.

Теперь сделаем firebase deploy из папки /functions/ и наше приложение готово.

« список статей