Примечание. Если вы предпочитаете этот контент в виде видео, вы можете увидеть выступление, написанное мной и Сьюзен Голдблатт здесь.

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

Козий ландшафтный дизайн

Какая отличная идея!

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

У меня есть надежный план на успех!

У меня уже есть это отличное приложение, в котором вы можете арендовать наших коз, чтобы они постригли ваш газон. Я справился с большинством самых сложных частей приложения, такими как авторизация и управление расписанием. Осталось только одно последнее препятствие, и это то, что я собираюсь продемонстрировать сегодня: управление платежами. Управление платежами для вашего приложения может быть пугающим. Как создать безопасный и простой в использовании процесс? Один из способов, которым вы можете обрабатывать платежи безопасным и беспроблемным способом для вас и ваших клиентов, - это использование Stripe API с облачными функциями для Firebase.

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

Почему облако работает с Stripe?

Есть много разных способов обработки платежей. Быстрый поиск в Google обнаружил Stripe, Payoneer, Square, Paypal, Skrill, WePay, Google Payments и многие другие. Так почему я выбрал Stripe для этого примера? Честно говоря, выбор довольно случайный и в первую очередь основан на том факте, что у нас был существующий образец с использованием Stripe и базы данных Realtime, которые я настроил для использования Cloud Firestore. Разработчикам, похоже, нравится Stripe, и здесь он хорошо сработал для меня, но это не одобрение Stripe по сравнению с какой-либо другой платежной платформой. Методы, которые я использую здесь, могут быть изменены для работы на других платежных платформах.

Stripe имеет клиентский API, который позволяет вам делать практически все, что вам нужно, с точки зрения платежей. Он даже включает в себя пользовательский интерфейс по умолчанию, который вы можете включить в свое приложение для iOS или Android. Итак, почему я предпочитаю использовать облачные функции? На это есть несколько причин:

1. Возможность делиться соответствующей информацией в пользовательском интерфейсе клиента

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

2. Возможность добавить несколько способов оплаты одному клиенту Stripe

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

Покончив с этим объяснением, давайте вернемся к коду!

Создать клиента Stripe

Stripe использует идентификаторы клиентов, чтобы однозначно идентифицировать каждого клиента. В Goat Landscaping каждый пользователь Firebase связан с идентификатором клиента Stripe. Я достигаю этого с помощью облачной функции, которая запускается, когда новый пользователь Firebase входит в систему в первый раз.

Когда пользователь впервые входит в Goat Landscaping, запускается облачная функция Firebase Auth. Функция взаимодействует с API сервера Stripe для создания нового клиента. Идентификатор клиента, возвращенный из Stripe, затем записывается в Cloud Firestore в документе, идентификатор документа которого является идентификатором пользователя Firebase. Тогда структура базы данных будет выглядеть примерно так:

stripe_customers/{firebase-uid}: {
    customer_id: “cus_DSLH6qtxN3lMdL”
}

Приведенная ниже функция также доступна для просмотра в полном примере Cloud Functions with Stripe.

exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
    const customer = await stripe.customers.create({email: user.email});
    return admin.firestore().collection(‘stripe_customers’)
        .doc(user.uid).set({customer_id: customer.id});
});

Создать метод оплаты Stripe

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

К клиентам Stripe можно прикрепить способы оплаты для использования в будущем. Один из способов сделать это - создать покупателя. Это ограничивает вас привязкой к клиенту одного метода оплаты. В моем случае я уже создал клиента Stripe и хочу иметь возможность привязать к нему несколько способов оплаты.

Я использую SDK клиента Stripe для создания токена на основе способа оплаты пользователя. Вы можете найти i Руководство по ОС ​​и Руководство по Android в документации Stripe. Затем я записываю этот токен в Cloud Firestore в подколлекцию пользовательского документа под названием tokens.

stripe_customers/{firebase-uid}: {
    customer_id: “cus_DSLH6qtxN3lMdL”
    [tokens/{pushId}: {
    }]
}

Это вызывает функцию облака. Затем в этой функции мы создаем Источник, который можно связать с клиентом Stripe с помощью stripe.customers.createSource метода.

exports.addPaymentSource = functions.firestore
    .document(‘/stripe_customers/{userId}/tokens/{pushId}’)
    .onWrite(async (change, context) => {
    const source = change.after.data();
    const token = source.token;
    if (source === null) {
        return null;
    }
    try {
    const snapshot = await
    admin.firestore()
    .collection(‘stripe_customers’)
    .doc(context.params.userId)
    .get();
    const customer = snapshot.data().customer_id;
    const response = await stripe.customers
        .createSource(customer, {source: token});
    return admin.firestore()
    .collection(‘stripe_customers’)
    .doc(context.params.userId)
    .collection(“sources”)
    .doc(response.fingerprint)
    .set(response, {merge: true});
    } catch (error) {
    await change.after.ref
        .set({‘error’:userFacingMessage(error)},{merge:true});
    }
});

Приведенный выше код также можно найти в репозитории Cloud Functions на GitHub.

Производить платеж

Stripe дает вам возможность производить оплату напрямую от клиента. Однако я хотел бы сохранить некоторую информацию о статусе платежа в Cloud Firestore. Это облегчает мне и клиенту информацию о статусе платежей. Когда клиент производит платеж, сумма записывается в Cloud Firestore. Это запускает облачную функцию, которая запускается всякий раз, когда данные создаются в указанном месте платежа. Данные из записи используются для обработки платежа с помощью Stripe SDK. Затем результат платежа записывается обратно в базу данных, чтобы клиент мог быть проинформирован о результате.

Идемпотентность

Прежде чем я углублюсь в код для создания начисления Stripe, нужно отметить одну важную вещь: я не хочу, чтобы пользователи случайно получали оплату более одного раза. Чтобы гарантировать, что функция запускается хотя бы один раз, существуют условия, при которых облачная функция может запускаться более одного раза. Если вы включите повторные попытки в своих облачных функциях, это важно отметить. Поэтому необходимо писать функции таким образом, чтобы эффект не изменялся в зависимости от того, сколько раз функция запускалась. Например, вам нужна проверка, чтобы уведомление было отправлено только один раз. Или, в случае платежей, вам нужен способ идентифицировать уникальный платежный запрос, чтобы он не выплачивался более одного раза. Концепция многократного запуска функции или программы без изменения результата за пределами первоначального вызова называется идемпотентностью. Я достигаю идемпотентности в своей функции, используя уникальный идентификатор push, созданный Cloud Firestore, когда я записываю запрос на оплату в базу данных. Stripe API позволяет вам передавать ключ идемпотентности при выполнении платежного запроса. Если эта функция запускается более одного раза, у нее будет один и тот же ключ идемпотентности при каждом вызове. Когда этот ключ будет отправлен в Stripe API, он будет знать, что этот платеж уже обработан, и не запускать его снова.

Код для функции ниже также можно найти в репозитории Примеры облачных функций на GitHub.

exports.createStripeCharge = functions.firestore
    .document(‘stripe_customers/{userId}/charges/{id}’)
    .onCreate(async (snap, context) => {
    const val = snap.data();
    try {
    // Look up the Stripe customer id written in createStripeCustomer
    const snapshot = await admin.firestore()
    .collection(`stripe_customers`)
    .doc(context.params.userId).get();
    
    const snapval = snapshot.data();
    const customer = snapval.customer_id;
    // Create a charge using the pushId as the idempotency key
    // protecting against double charges
    const amount = val.amount;
    const idempotencyKey = context.params.id;
    const charge = {amount, currency, customer};
    if (val.source !== null) {
       charge.source = val.source;
    }
    const response = await stripe.charges
        .create(charge, {idempotency_key: idempotencyKey});
    // If the result is successful, write it back to the database
    return snap.ref.set(response, { merge: true });
    } catch(error) {
        await snap.ref.set({error: userFacingMessage(error)}, { merge: true });
    }
});

Заключение

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