Открыть приложение
Все статьи

Сделайте банку для чаевых на своём аккаунте Stripe

Три вызова API дают хостируемую страницу «плати сколько хочешь» с Apple Pay и Google Pay — и ни одного сервера. Вот вся сборка: ограниченный ключ, права, как читать чаевые без вебхука, и честная арифметика комиссий, которую никто не печатает.

Вам нужна банка для чаевых. Вам не хочется отдавать платформе 5 % вечера уличного музыканта, и вы прекрасно умеете разговаривать с API. Значит, вопрос не в том, в какой банке для чаевых зарегистрироваться, а в том, сколько на самом деле надо построить.

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

Весь фокус — это Price «плати сколько хочешь»

В Stripe есть режим цены, где сумму вводит сам фанат. Он называется pay what you want — и это вся функциональность. Вы создаёте Product, вешаете на него Price с custom_unit_amount[enabled]=true, а сверху — Payment Link.

# 1. то, что вы "продаёте"
curl https://api.stripe.com/v1/products \
  -u "$RK:" \
  -d name="Tips — Mira" \
  -d "metadata[managed_by]"=my-tip-jar

# 2. цена, которую выбирает фанат
curl https://api.stripe.com/v1/prices \
  -u "$RK:" \
  -d product=prod_... \
  -d currency=eur \
  -d "custom_unit_amount[enabled]"=true \
  -d "custom_unit_amount[preset]"=500 \
  -d "custom_unit_amount[minimum]"=200

# 3. страница
curl https://api.stripe.com/v1/payment_links \
  -u "$RK:" \
  -d "line_items[0][price]"=price_... \
  -d "line_items[0][quantity]"=1 \
  -d submit_type=pay

Третий вызов возвращает url. Этот URL и есть ваша банка для чаевых. Это страница, которую хостит Stripe: она PCI-совместима без вашего участия, локализована и показывает Apple Pay или Google Pay каждому фанату, у кого они настроены на телефоне, — динамические способы оплаты решают это за вас, исходя из устройства и страны. Вы не написали ни строчки фронтенда.

Закодируйте URL в QR-код любой библиотекой — это просто строка — распечатайте, приклейте к футляру. Код никогда не протухнет и не указывает ни на какой ваш сервер, потому что сервера у вас нет.

Два параметра, которые стоит знать:

  • custom_unit_amount[preset] — сумма, с которой открывается страница. 500 означает, что фанат видит уже вписанные 5,00 € и может их изменить. Это число делает для вашего среднего чаевого больше, чем всё остальное на странице.
  • custom_unit_amount[minimum] — пол. Поставьте его. Причина — в разделе о комиссиях ниже, и это не погрешность округления.

Можно также собирать имя и сообщение. Payment Links принимают до трёх custom_fields — так вы получаете «а от кого это было» без постройки формы:

  -d "custom_fields[0][key]"=nickname \
  -d "custom_fields[0][type]"=text \
  -d "custom_fields[0][label][type]"=custom \
  -d "custom_fields[0][label][custom]"="Ваше имя или ник" \
  -d "custom_fields[0][optional]"=true

У Stripe есть требования к приёму чаевых и пожертвований — прочитайте их один раз. «Плати сколько хочешь» также не сочетается с другими line items, скидками и регулярными платежами. Для банки с чаевыми ничто из этого не мешает.

Эту разницу стоит понимать правильно. Stripe формулирует так: чаевые дают за уже оказанный товар или услугу, а пожертвование должно быть привязано к благотворительной цели. Вы отыграли сет; чаевые за него и платят. Поэтому вызов выше отправляет submit_type=pay, а не donatedonate разместил бы вашу ссылку на donate.stripe.com и напечатал бы на кнопке Пожертвовать. Это другой бизнес, и такой, который Stripe проверяет заметно строже.

Ключ: считайте, что он утечёт, — и сделайте это скучным

Не кладите секретный ключ (sk_live_…) на устройство, которое стоит на сцене. Возьмите ограниченный ключ (rk_live_…): вы выбираете право для каждого ресурса, а всё, что не выбрали, остаётся на None.

Для сборки выше полный список — пять строк:

Ресурс Право Что это даёт
Products Write создать Product
Prices Write создать Price «плати сколько хочешь»
Payment Links Write создать ссылку
Checkout Sessions Read видеть пришедшие чаевые
Events Read живая лента (следующий раздел)

Всё остальное — Balance, Payouts, Refunds, Customers, PaymentIntents, весь Connect — остаётся на None.

А теперь сделайте упражнение, ради которого всё это и затевалось. В час ночи ваш планшет уводят со столика с мерчем. Что вор сделает с ключом в кейчейне? Прочитает историю чаевых и создаст ещё несколько ссылок на чаевые в вашем аккаунте. Это весь радиус поражения. Он не увидит баланс, не запустит выплату, не оформит возврат на свою карту, не прочитает список клиентов. Вы отзываете ключ с телефона в такси по дороге домой — и устройство гаснет. Из ваших денег не сдвинулось ничего.

Эта асимметрия — запись в банку для чаевых и ноль доступа к деньгам — единственная причина, по которой бессерверный дизайн со своим ключом вообще можно защищать. Она же — причина, почему «Login with Stripe» здесь не ответ: OAuth требует сервера разработчика приложения, чтобы держать ваш токен, — а сервер это ровно то, чего мы не строим.

(Странность, на которую вы наткнётесь: право Prices внутри называется plan_write, поэтому сообщение об ошибке Stripe называет scope, которого под таким именем в дашборде нет. Речь про Prices.)

Читать чаевые без вебхука

Здесь большинство руководств либо останавливается, либо хватается за вебхук — и здесь сцена по-настоящему отличается от веб-приложения.

Вебхук — это входящий HTTP-запрос. Планшет за микрофонной стойкой не может его принять. Он сидит в гостевом Wi-Fi заведения за NAT, у него нет публичного адреса и TLS-сертификата — и незачем им быть. Если пойти путём вебхука, придётся поднять сервер, который ловит события, и сокет, который пропихивает их на устройство: бэкенд, эксплуатационная нагрузка и место, где теперь живут имена ваших фанатов. Вы только что заново построили ту самую платформу, которой хотели избежать.

Поэтому тяните, а не ждите, пока толкнут. Эндпойнт Stripe List all events публичный, задокументированный и возвращает события, начиная с самых новых:

curl -G https://api.stripe.com/v1/events \
  -u "$RK:" \
  -d "types[]"=checkout.session.completed \
  -d "types[]"=checkout.session.async_payment_succeeded \
  -d ending_before=evt_ПОСЛЕДНЕЕ_ВИДЕННОЕ \
  -d limit=100

ending_before — это весь дизайн. Храните id самого свежего обработанного события; каждый опрос просит всё строго более новое, а вы двигаете курсор. Никаких таймстемпов, никакого рассинхрона часов, никакой дедупликации по сумме. На первом опросе сета попросите limit=1 без курсора, чтобы зацепиться за то, что уже есть, — иначе на саундчеке переиграете чаевые сегодняшнего утра.

Дальше фильтруйте то, что вернулось. Оба типа событий могут выстрелить на один платёж, поэтому дедуплицируйте по id Checkout Session. Проверяйте payment_status == "paid" — завершённая сессия не обязательно оплаченная. И проверяйте, что payment_link совпадает с вашей ссылкой, потому что /v1/events работает на уровне всего аккаунта и с радостью подсунет вам трафик от всего остального, что этот аккаунт Stripe делает.

Будьте честны насчёт компромиссов, они настоящие:

  • Stripe рекомендует вебхуки. Опрос — не благословенный путь; это задокументированный эндпойнт, который применяют осознанно. Напишите об этом в README и идите дальше.
  • События уходят на 30 дней назад. Слова самого Stripe: «List events, going back up to 30 days.» Это живая лента, а не ваша бухгалтерская книга. Ваша книга — Checkout Sessions, а настоящая книга — дашборд Stripe.
  • Следите за квотой на чтение. Все смотрят на лимит в секунду (rate limits: 100 запросов/с в live) и никто — на другой: Stripe выделяет примерно 500 запросов на чтение на одну транзакцию за скользящие 30 дней, с полом в 10 000 чтений в месяц. Опрашивайте раз в 4 секунды — и трёхчасовой сет это ~2 700 чтений. Четыре длинных концерта за месяц, и вы на полу. Чаевые докупают запас по мере поступления, но тот, кто опрашивает раз в секунду, потому что «так бодрее», найдёт потолок. Четыре секунды — не ленивое число; это то самое число.

Вот честная форма дела: опрос стоит вам пары тысяч GET-запросов и покупает удаление целого бэкенда.

Арифметика комиссий, сделанная как следует

Платформа, рекламирующая 0 %, не бесплатна — и это тоже. Собственная комиссия Stripe за обработку применяется к каждому чаевому, и Stripe выставляет её вам напрямую. Сегодня, по евро-прайсу Stripe, стандартная карта ЕЭЗ стоит 1,5 % + 0,25 €. Премиальные карты ЕЭЗ — 1,9 % + 0,25 €, британские — 2,5 % + 0,25 €, всё остальное — 3,25 % + 0,25 € плюс ещё 2 %, если нужна конвертация валюты. (В США это 2,9 % + 0,30 $, что хуже ровно по причине ниже.)

Проблема не в проценте. Проблема в двадцати пяти центах.

Чаевые Stripe забирает Артисту остаётся Реальная доля
2 € 0,28 € 1,72 € 14,0 %
5 € 0,33 € 4,67 € 6,5 %
10 € 0,40 € 9,60 € 4,0 %
20 € 0,55 € 19,45 € 2,8 %
50 € 1,00 € 49,00 € 2,0 %

Фиксированная комиссия — это процент в маскировке, и на малых деньгах маскировка сползает. Те же 0,25 €, невидимые на чаевых в 50 €, съедают восьмую часть чаевых в 2 €. Чаевые малы по своей природе — именно это и делает их чаевыми — так что это не краевой случай, а медианный.

Именно поэтому вы ставите custom_unit_amount[minimum]. Где-то около 2 € транзакция перестаёт быть достойной обработки; карточные чаевые в 0,50 € придут как 0,24 € и обойдутся Stripe в переводе дороже, чем стоят. Выбирайте пол осознанно, а не обнаруживайте его при первой выплате.

И заметьте, что это делает со сравнением, с которого вы начали. Платформа, берущая 0 % поверх Stripe, берёт с вас 0 % поверх вот этого. Их 0 % настоящие — и это 0 % от того, что оставил процессинг. Ничья карточная рельса не бесплатна: честная формулировка — «никакой доли сверх той, что берёт процессинг», а кто утверждает больше, тот либо врёт, либо не использует карты.

Что у вас теперь есть — и чего нет

Три вызова API и QR-код — и настоящая банка для чаевых: хостируемая, PCI-совместимая, Apple Pay, Google Pay, чаевые падают на ваш собственный баланс Stripe по вашему собственному графику выплат, и никакого сервера по дороге. Для многих это искренне конец проекта, и вы вполне можете остановиться здесь и выпустить это.

Чего у вас нет — так это сцены. У вас есть страница оплаты. Между ними стоят скучные вещи: цикл опроса с курсором и бэкоффом; экран, который видит публика, с целью и последним сообщением; место для ключа, которое не называется localStorage; блокировка, чтобы чужой не тыкал в планшет между сетами; и слой из тысячи мелких решений о том, что делать, когда Wi-Fi заведения падает посреди сета.

Именно это и есть live.tips — ровно эта архитектура, доведённая до конца, под лицензией MIT. Ограниченный ключ с теми пятью правами, курсорный цикл по /v1/events, создание Product/Price/Payment Link — всё работает на устройстве артиста, против его собственного аккаунта. В пути к Stripe нет сервера live.tips и нигде нет баланса live.tips — об этом мы написали отдельно в как live.tips обращается с деньгами.

Читайте исходники, забирайте нужные куски или просто пользуйтесь. Смысл этого поста в том, что архитектура — не секрет и не сложность: Stripe захостит вашу банку для чаевых бесплатно, а ограниченный ключ плюс цикл опроса — это всё, что стоит между артистом и его собственными деньгами. Нам важнее, чтобы вы это знали, чем чтобы вы где-то регистрировались.

Все статьи