Превратите своих пользователей в клиентов
Введение
Вы когда-нибудь хотели создать приложение, в котором пользователи должны будут платить несколько разовых сборов, а также иметь периодическую подписку? С одной стороны, вы хотите хранить столько информации, сколько необходимо, чтобы контролировать своих клиентов и их платежи. С другой стороны, данные кредитных карт действительно конфиденциальны, и хранить их в своей базе данных было бы очень рискованно.
К счастью, есть очень интересное решение. Интеграция вашего приложения с Stripe сэкономит ваше время и обеспечит действительно чистый и безопасный процесс оплаты. Благодаря обширному API вы сможете реализовать любой тип денежных транзакций, который только можете себе представить, и, кроме того, все конфиденциальные данные будут в безопасности в облаке Stripe.
Наша цель
Для целей этой статьи мы создадим простое приложение, используя Django REST framework для нашего API-интерфейса и React для внешнего интерфейса.
Основной поток нашего приложения
- Пользователь предоставляет свой адрес электронной почты и данные кредитной / дебетовой карты.
- Мы создаем нового клиента Stripe на основе данных, предоставленных пользователем.
- С пользователя будет взиматься единовременная плата за создание учетной записи.
- Пользователь также будет подписан на ежемесячную подписку, чтобы его учетная запись оставалась активной.
Кажется, довольно просто, не правда ли? Итак, начнем!
Настроить Django API
Предположим, мы уже создали проект Django под названием StripeApp
с установленной библиотекой Django REST framework.
Давайте создадим новое приложение в корневом каталоге StripeApp
project
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 - взимать с клиента единовременную комиссию
Давайте достигнем нашей третьей цели и взимаем с клиента единовременную плату. Как упоминалось ранее, мы будем использовать для этого PaymentIntent
object.
# 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. Когда вы откроете вкладку Платежи, вы должны увидеть что-то вроде этого:
Похоже, платеж был создан, но не завершен. Что случилось? Давайте откроем сведения о платеже и прокрутим вниз, чтобы просмотреть события и журналы.
Похоже, для оплаты требуется подтверждение. Вернемся к документации и еще раз прочитаем о создании PaymentIntent
s:
После создания
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, потому что там много полезной информации, которая поможет вам избежать ошибок и упростит вашу жизнь.