Превратите своих пользователей в клиентов

Введение

Вы когда-нибудь хотели создать приложение, в котором пользователи должны будут платить несколько разовых сборов, а также иметь периодическую подписку? С одной стороны, вы хотите хранить столько информации, сколько необходимо, чтобы контролировать своих клиентов и их платежи. С другой стороны, данные кредитных карт действительно конфиденциальны, и хранить их в своей базе данных было бы очень рискованно.

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

Наша цель

Для целей этой статьи мы создадим простое приложение, используя Django REST framework для нашего API-интерфейса и React для внешнего интерфейса.

Основной поток нашего приложения

  1. Пользователь предоставляет свой адрес электронной почты и данные кредитной / дебетовой карты.
  2. Мы создаем нового клиента Stripe на основе данных, предоставленных пользователем.
  3. С пользователя будет взиматься единовременная плата за создание учетной записи.
  4. Пользователь также будет подписан на ежемесячную подписку, чтобы его учетная запись оставалась активной.

Кажется, довольно просто, не правда ли? Итак, начнем!

Настроить Django API

Предположим, мы уже создали проект Django под названием StripeApp с установленной библиотекой Django REST framework.

Давайте создадим новое приложение в корневом каталоге StripeAppproject

python manage.py startapp payments

Наша структура проекта должна выглядеть так (убедитесь, что у вас есть файлы с полужирным именем, и при необходимости создайте недостающие):

- StripeApp/
  - payments
    - migrations/
    - __init.py__
    - admin.py
    - apps.py
    - models.py
    - tests.py
    - urls.py
    - views.py
  - stripeapp/
    - __init.py__
    - settings.py
    - urls.py

Установите библиотеку Stripe

pip install --upgrade stripe

Зарегистрируйтесь в панели управления Stripe и получите тестовые ключи

Создайте свою Stripe account и получите свои тестовые ключи (публикуемые и секретные) из панели управления.

Скопируйте секретный ключ в свой проект Django

# payments/views.py
stripe.api_key = ‘sk_test_’ # your real key will be much longer

Сделайте тестовый запрос к Stripe API

Создайте простое представление, выполнив платеж Stripe, чтобы проверить, действителен ли ваш ключ.

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

# payments/views.py
@api_view(['POST'])
def test_payment(request):
test_payment_intent = stripe.PaymentIntent.create(
    amount=1000, currency='pln', 
    payment_method_types=['card'],
    receipt_email='[email protected]')
return Response(status=status.HTTP_200_OK, data=test_payment_intent)

Соедините представление с URL-адресом в payments/urls.py.

# payments/urls.py
from django.conf.urls import url
from payments import views
urlpatterns = [
    url(r'^test-payment/$', views.test_payment),
]

И определите префикс URL payments в stripeapp/urls.py.

# stripeapp/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    
    # add this line
    path('payments/', include('payments.urls'))
]

Отправьте новый запрос (http://localhost:8000/payments/test-payment) с помощью Postman. Если вы получили объект JSON, подобный приведенному ниже, это означает, что вы только что успешно отправили свой первый запрос в Stripe.

{ 
  "id": "pi_123", #you will have a unique id every time
  "object": "payment_intent",
  "amount": 1000,
  "amount_capturable": 0,
  "amount_received": 0, 
  "application": null,
  "application_fee_amount": null,
...
}

Настроить интерфейсный проект

Предположим, мы уже создали проект React под названием StripeAppFront.

Установить React Stripe

npm install @stripe/react-stripe-js @stripe/stripe-js

Создайте форму оформления заказа

Создайте новый компонент React, который будет отображать такую ​​форму:

Мы хотим, чтобы пользователь предоставил свой адрес электронной почты и данные кредитной / дебетовой карты. Хотя в наш API будет отправлено электронное письмо, конфиденциальные данные карты будут обрабатываться CardElement Stripe, поэтому мы не храним их в небезопасных местах. Согласно документации Stripe:

«Stripe Elements делает сбор данных о платежах более безопасным и помогает предотвратить кражу конфиденциальной информации злоумышленниками. Мы создаем безопасный iframe и изолируем конфиденциальную информацию с вашего сайта, устраняя целые классы атак, но при этом предоставляя вам полный визуальный контроль ».

Итак, давайте напишем код:

В App.js загрузите публикуемый бей Stripe и импортируйте Elements.

// App.js

import React from 'react';
import './App.css';
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from "@stripe/stripe-js/pure";
import CheckoutForm from "./components/CheckoutForm";
const stripePromise = loadStripe('pk_test_');
const App = () => (
  <Elements stripe={stripePromise}>
    <CheckoutForm />
  </Elements>
);
export default App;

Создайте новую папку с именем components и внутри нее создайте файл CheckoutForm.js. Это будет самый важный компонент во фронтенд-проекте.

# components/CheckoutForm.js
import {CardElement, useElements, useStripe} from "@stripe/react-stripe-js";
import React, {useState} from "react";
import ApiService from "../api";
const CheckoutForm = () => {
  const [error, setError] = useState(null);
  const [email, setEmail] = useState('');
  const stripe = useStripe();
  const elements = useElements();
// Handle real-time validation errors from the CardElement.
const handleChange = (event) => {
  if (event.error) {
    setError(event.error.message);
  } else {
    setError(null);
  }
}
// Handle form submission.
const handleSubmit = async (event) => {
  event.preventDefault();
};

return (
  <form onSubmit={handleSubmit} className="stripe-form">
    <div className="form-row">
      <label htmlFor="email">Email Address</label>
      <input className="form-input" id="email" name="name"    type="email" placeholder="[email protected]" required 
value={email} onChange={(event) => { setEmail(event.target.value)}} />
    </div>
    <div className="form-row">
      <label for="card-element">Credit or debit card</label> 
      <CardElement id="card-element" onChange={handleChange}/>
      <div className="card-errors" role="alert">{error}</div>
    </div>
    <button type="submit" className="submit-btn">
      Submit Payment
    </button>
  </form>
 );
};
export default CheckoutForm;

Создайте способ оплаты

Затем мы воспользуемся Stripe API, чтобы создать объект PaymentMethod и отправить его идентификатор в наш API. Еще раз, давайте быстро взглянем на документацию Stripe и проверим определение PaymentMethod:

«PaymentMethod объекты представляют собой платежные инструменты вашего клиента. Их можно использовать с PaymentIntents для сбора платежей или сохранять в Customer объектах для хранения сведений об инструментах для будущих платежей ».

Это означает, что PaymentMethod хранит данные карты пользователя для использования в платежных транзакциях.

Итак, давайте добавим несколько строк к методу handleSubmit:

const handleSubmit = async (event) => {
  event.preventDefault();
  const card = elements.getElement(CardElement);
 
 // add these lines
  const {paymentMethod, error} = await stripe.createPaymentMethod({
     type: 'card',
     card: card
});
}

Примечание. Вы можете проверить в документации различные типы PaymentMethod Stripe.

Мы также добавили операцию console.log, чтобы проверить, как на самом деле выглядит объект PaymentMethod. Итак, откройте свою страницу (http://localhost:3000) и передайте тестовые данные в форму.

Примечание. В качестве номера карты вы можете использовать одну из тестовых карт, предоставленных Stripe (например, 4242 4242 4242 4242). CVC и почтовый индекс могут быть любым числом, а срок действия может быть любым в будущем.

Нажмите кнопку «Отправить платеж» и загляните в консоль браузера. Вы можете увидеть объект, содержащий довольно много данных, но наиболее важным является свойство card (показано на изображении ниже). Мы видим, что он хранит не полный номер карты, а только четыре последних числа. Теперь мы уверены, что никто не сможет получить данные карты пользователя.

Отправьте PaymentMethod.id в Django API

Установите пакет axios для обработки отправки запросов на стороне внешнего интерфейса.

npm install axios --save

Затем в корне проекта создайте файл api.js и создайте класс ApiService. Внутри только что созданного класса определите статический метод saveStripeInfo для отправки запроса POST в наш API (мы обработаем этот запрос через некоторое время):

// api.js
import axios from "axios";
export const API_URL ='http://localhost:8000'
export const api = axios.create({
  baseURL: API_URL,
  headers: {
    "Content-type": "application/json"
  }
});
export default class ApiService{
  static saveStripeInfo(data={}){
    return api.post(`${API_URL}/payments/save-stripe-info/`, data)
  }
}

Наконец, вызовите метод в компоненте CheckoutForm:

// CheckoutForm.js
const handleSubmit = async (event) => {
[...]
  const {paymentMethod, error} = await stripe.createPaymentMethod({
    type: 'card',
    card: card
  });
  //add these lines
  ApiService.saveStripeInfo({
    email, payment_method_id: paymentMethod.id})
  .then(response => {
    console.log(response.data);
  }).catch(error => {
    console.log(error)
  })
  };
}

Реализуйте запрос API, соединяющийся с Stripe

Пришло время создать представление API, которое достигнет наших основных целей. В этом представлении мы собираемся создать новую полосу Customer и списать с его карты единовременную плату (PaymentIntent). Затем мы настроим его ежемесячную подписку (Subscription). Прежде чем мы начнем кодировать, давайте взглянем на документацию Stripe и прочитаем об упомянутых объектах.

«Customer объекты позволяют выполнять регулярные платежи и отслеживать несколько платежей, связанных с одним и тем же клиентом».

«PaymentIntent проведет вас через процесс получения платежа от вашего клиента. Мы рекомендуем вам создавать ровно один PaymentIntent для каждого заказа или сеанса клиента в вашей системе ».

«Subscriptions позволяют вам взимать плату с клиента на регулярной основе».

Хорошо, теперь мы знаем гораздо больше, не так ли? Итак, давайте запрограммируем это.

Создайте своего клиента

В payments/views.py создайте новый метод save_stripe_info. Мы передадим email и payment_method_id в Stripe, чтобы пользователь был привязан к предоставленным данным карты.

def save_stripe_info(request):
    data = request.data
    email = data['email']
    payment_method_id = data['payment_method_id']
    
    # creating customer
    customer = stripe.Customer.create(
      email=email, payment_method=payment_method_id)
     
    return Response(status=status.HTTP_200_OK, 
      data={
        'message': 'Success', 
        'data': {'customer_id': customer.id}   
    )                                                        

И добавляем в payments/urls.py:

urlpatterns = [
    url(r'^test-payment/$', views.test_payment),
    url(r'^save-stripe-info/$', views.save_stripe_info),

Теперь вы можете обновить нашу веб-страницу и протестировать ее. Откройте консоль браузера, заполните форму [email protected], используйте любой номер тестовой карточки и отправьте ее.

В консоли вы должны увидеть сообщение о состоянии Success вместе с вновь созданным идентификатором клиента.

Давай проверим что-нибудь еще. Войдите в свою панель управления Stripe и откройте вкладку Клиенты. Здесь вы можете увидеть нового клиента с адресом электронной почты, который мы указали в форме. Если вы нажмете на него и откроете сведения о клиенте, вы увидите тот же идентификатор, который был напечатан в консоли.

Если вы прокрутите страницу со сведениями о клиенте, то обнаружите, что в разделе о способах оплаты содержатся данные карты, указанные в форме.

Это означает, что мы только что создали нашего первого клиента Stripe.

Убедитесь, что клиент уже существует

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

Stripe API использует метод list, который выводит список наших клиентов. С помощью параметра email мы можем получить отфильтрованный список и проверить, было ли это письмо уже связано с другим клиентом. Давай проверим:

# payments/views.py
@api_view(['POST'])
def save_stripe_info(request):
  data = request.data
  email = data['email']
  payment_method_id = data['payment_method_id']
  extra_msg = '' # add new variable to response message
  # checking if customer with provided email already exists
  customer_data = stripe.Customer.list(email=email).data   
 
  # if the array is empty it means the email has not been used yet  
  if len(customer_data) == 0:
    # creating customer
    customer = stripe.Customer.create(
    email=email, payment_method=payment_method_id)
  else:
    customer = customer_data[0]
    extra_msg = "Customer already existed."
  return Response(status=status.HTTP_200_OK, 
    data={'message': 'Success', 'data': {
      'customer_id': customer.id, 'extra_msg': extra_msg}
   })

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

Примечание. Конечно, массив, возвращаемый из stripe.Customer.list, может содержать более одного элемента, но для целей этой статьи мы предполагаем, что электронные письма клиентов должны быть уникальными.

Давайте быстро проверим текущий код. Обновите браузер и снова отправьте то же письмо. Вы должны увидеть в консоли ответ, содержащий ключ extra_msg, а customer_id такой же, как и предыдущий. Панель управления Stripe также не изменилась, и остался только один клиент.

Давайте проведем еще один тест и отправим данные на адрес электронной почты [email protected].

Что случилось на этот раз?

Консоль показала нам другой customer_id и пустой extra_msg. Теперь на панели инструментов Stripe мы видим нового клиента с адресом электронной почты [email protected].

Наша проверка работает!

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

Create PaymentIntent - взимать с клиента единовременную комиссию

Давайте достигнем нашей третьей цели и взимаем с клиента единовременную плату. Как упоминалось ранее, мы будем использовать для этого PaymentIntentobject.

# payments/views.py
@api_view(['POST'])
def save_stripe_info(request):
  [...]
  else:
    customer = customer_data[0]
    extra_msg = "Customer already existed."
  # add these lines
  stripe.PaymentIntent.create(
    customer=customer, 
    payment_method=payment_method_id,  
    currency='pln', # you can provide any currency you want
    amount=999)     # it equals 9.99 PLN

Мы передали методу stripe.PaymentIntent.create наш customer объект, payment_method_id, currency и amount комиссии. Давайте отправим форму еще раз и проверим, что будет происходить на панели инструментов Stripe. Когда вы откроете вкладку Платежи, вы должны увидеть что-то вроде этого:

Похоже, платеж был создан, но не завершен. Что случилось? Давайте откроем сведения о платеже и прокрутим вниз, чтобы просмотреть события и журналы.

Похоже, для оплаты требуется подтверждение. Вернемся к документации и еще раз прочитаем о создании PaymentIntents:

После создания PaymentIntent прикрепите способ оплаты и« подтвердите , чтобы продолжить платеж. (…) Когда confirm=true используется во время создания, это эквивалентно созданию и подтверждению PaymentIntent в том же вызове (...) Этот параметр по умолчанию равен false ».

Это означает, что у нас есть две возможности: (1) вызвать метод stripe.PaymentIntent.confirm для подтверждения платежа или (2) установить параметр confirm=True в методе stripe.PaymentIntent.create.
Давайте выберем второй вариант и немного изменим наш код:

# payments/views.py
stripe.PaymentIntent.create(
    customer=customer, 
    payment_method=payment_method_id,  
    currency='pln', # you can provide any currency you want
    amount=1500, # I modified amount to distinguish payments
    confirm=True)

Отправьте форму еще раз и обновите панель управления Stripe. Вы должны увидеть новый платеж со статусом Succeeded:

А что с журналами?

Все нормально работало. Платеж был создан и подтвержден за один раз. Мы только что достигли третьей цели.

Создать подписку

Наконец, мы можем настроить подписку для нашего пользователя. Согласно документации Stripe, Subscription необходимо связать с объектом Price, а Price обычно назначается какому-нибудь Product.

Чтобы лучше это объяснить, представим себе разные типы билетов в кино: обычные, за полцены и бесплатные. В этом случае нашим Product будет объект Ticket с тремя разными Price. Просто взгляните на изображение ниже:

В нашем проекте мы просто создадим Product под названием Monthly Subscription для нашего приложения с ежемесячной платой 50,00 злотых.

Stripe разделяет запросы API для создания всех упомянутых объектов в коде. Но есть и другой вариант - намного быстрее и проще: с помощью панели управления. Итак, давайте откроем вкладку Продукты и нажмем кнопку Добавить продукт.

Затем заполните поля Имя и (необязательно) Описание:

И создайте Price с повторяющимся (ежемесячным) расчетным периодом:

Большой! Теперь вы должны увидеть новый Product на панели инструментов. Скопируйте его идентификатор Price (начиная с price_) и отредактируйте наш save_stripe_info.

# payments/views.py
@api_view(['POST'])
def save_stripe_info(request):
  [...]
  stripe.Subscription.create(
    customer=customer,
    items=[
      {
       'price': 'price_' #here paste your price id
      }
    ]
  )

Вроде все нормально. Он должен работать - не так ли? Давайте проверим.

О, нет! Вы только что получили ошибку, аналогичную приведенной ниже?

stripe.error.InvalidRequestError: Request req_123: 
This customer has no attached payment source or default payment method.

Что это значит? Вернемся к документации, где вы найдете очень важную информацию:

Customer.invoice_settings.default_payment_method (optional)

«Идентификатор способа оплаты, привязанного к клиенту, который будет использоваться в качестве способа оплаты по умолчанию для подписок и счетов».

Это означает, что если вы хотите выставить счет своему клиенту за повторяющиеся подписки, вы должны сначала назначить default_payment_method.

Хорошо давай сделаем это! Измените метод stripe.Customer.create:

customer = stripe.Customer.create(
  email=email,
  payment_method=payment_method_id,
  invoice_settings={
    'default_payment_method': payment_method_id
  }
)

И попробуйте еще раз с новым адресом электронной почты:

Вы должны увидеть сообщение Success в консоли, а когда вы обновите панель управления Stripe, должен появиться новый платеж:

И когда вы нажмете на нее, вы даже увидите дату следующего платежа:

Большой! Вы только что закончили работу с приложением. Поздравляю!

Вы можете найти полный код здесь: https://github.com/ewelina29/StripeApp/tree/master

Заключение

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

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