Рубрики
Без рубрики

Монетизация вашего нативного приложения raction с полосой

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

Автор оригинала: Foysal.

Этот пост был спонсирован и изначально опубликован На блоге LOGOCKET Отказ

Поскольку вы решили прочитать этот пост, я уверен, что вы знакомы, как быстро развивается экосистема JavaScript. После завоевания в Интернете, теперь сейчас безудержно встроенная индустрия приложений, с такими инструментами, как actword ruated для мобильных приложений и электрон для настольных приложений.

В наши дни пробел между приложениями, построенными с реактивными нативными, а теми, построенные с истинными родными стеками, только более узкий. Одной из основных деталей, которые приносят пользу как разработчикам INDie, так и Big Corporate App, является возможность обрабатывать оплату в своих приложениях без много суеты.

Если у вас есть отличная идея для приложения, это всего лишь вопрос времени, прежде чем начать смотреть, как монетизировать его и получать платежи непосредственно от пользователей как можно меньше трения. Итак, в этом посте я буду проходить вас через построение целого приложения, с обработкой аутентификации и платежей, используя React Native (Via Expo ), Ui котенок , Полоса и Adonisjs Backend Node.js Framework.

Прежде чем начать следовать, пожалуйста, имейте в виду, что этот пост предполагает, что вы знакомы с основы Git, React Nature, React, Tysesscript и API-адресов отдыха в целом.

Если вы новичок в один или все из них, вы все еще можете прочитать вместе и узнать, что вы можете по пути. Однако для того, чтобы сохранить этот пост по теме, многие детали нижнего уровня не будут объяснены на протяжении всего поста. Кроме того, пожалуйста, убедитесь, что у вас есть ваша среда для РЕАКТАЦИОНАЛЬНОЕ РАЗВИТИЕ КОМАННОГО ПРИЛОЖЕНИЯ Отказ

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

Итак, во-первых, создайте новый каталог с именем Payme И навигация внутри него с вашего терминала:

mkdir payme
cd payme

Если вы нетерпеливы, как я, и хочу сначала увидеть код, прежде чем делать что-либо, вот Github Repo Содержащие всю кодовую базу – копайте! 🙂 Или, если вы просто хотите увидеть конечный результат, вот A Быстрое превью видео Отказ

Приложение EXPO

EXPO – это платформа/инструментарий, построенный на вершине реагирования на родом, чтобы сделать опыт разработчика намного проще и более гладко. Вы можете думать об этом как о рН на стероидах. У него есть некоторые ограничения, когда речь идет о нативной функциональности, но всегда есть обходной путь для такого рода вещей.

Первое, что нам нужно, это EXPO-CLI Установлен по всему миру и использование CLI, мы будем генерировать новое приложение для котельной:

Нажмите здесь, чтобы увидеть полную демонстрацию сетевых запросов

npm install -g expo-cli
expo init payme

Вторая команда попросит вас выбрать шаблон и использовать клавишу со стрелкой, выберите второй из списка, как показано на рисунке ниже. Это генерирует пустое приложение Tymdercript Expo.

Помните, когда я сказал, что есть некоторые ограничения с EXPO? Извините, что мы ударили одного из них – и так скоро в пост!

Если вам нужно сделать его работать с iOS, вам нужно будет использовать голый рабочий процесс вместо того, чтобы управлять, потому что EXPO не поддерживает модуль полоса на iOS. Для получения более подробной информации, пожалуйста, Прочитайте эту документацию из Экспо.

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

После завершения вы останетесь с новым каталогом с именем Payme Отказ Поскольку у нас будет приложение API на стороне сервера и приложение EXPO на стороне клиента, давайте переименовать сгенерированную папку приложения из Payme к приложение Отказ Затем установите несколько пакетов, которые мы будем использовать очень скоро:

mv payme app
cd app
expo install @react-native-community/async-storage @ui-kitten/eva-icons @ui-kitten/components @eva-design/eva react-native-svg expo-payments-stripe

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

Наконец, мы можем добраться до кодирования! Откройте App.tsx Файл и замените существующий код следующим:

import { StatusBar } from 'expo-status-bar';
import React, {useEffect, useState} from 'react';
import * as eva from '@eva-design/eva';
import { ApplicationProvider, IconRegistry } from '@ui-kitten/components';
import { default as theme } from './theme.json';
import { EvaIconsPack } from '@ui-kitten/eva-icons';
import { AuthPage } from "./AuthPage";
import { ProductPage } from "./ProductPage";
import { Auth } from "./auth";
import { Payment } from "./payment";

const auth = new Auth();
const payment = new Payment(auth);
export default function App() {
    const [isLoggedIn, setIsLoggedIn] = useState(false);
    const [isLoggingIn, setIsLoggingIn] = useState(true);

    useEffect(() => {
        auth.getToken().then((token) => {
            setIsLoggingIn(false);
            if (!!token) setIsLoggedIn(true);
        });
    });

    return (
        <>
            
            
                
                { isLoggedIn
                    ? 
                    : 
                }
            
        
    );
}

Некоторые из этого кода просто бойтерна, но давайте немного сломаемся. Чтобы настроить котенок UI с пользовательской темой, которую мы создали, мы импортируем Theme.json Файл и прохождение Тема объект к ApplicationProvider Отказ

Мы импортируем и создаем два класса, Auth и Оплата из их назначенных файлов. auth Объект вводится в Оплата класс как зависимость. Они инкапсулируют бизнес-логику и связываются с API сервера.

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

Изначально мы загружаем UI в режиме входа в систему, а на нагрузке мы проводим проверку существующей информации о аутентификации, используя auth.gettoken () Отказ Это необходимо, потому что если пользователь вступает в систему одновременно, впоследствии открытие приложения не должно каждый раз показывать экран входа в систему.

Затем мы регистрируем значок, установленный из котенка UI, настроив макет, показать строку состояния, YADA, YADA, YADA … Просто более бойтовые материалы, которые я копирую/вставляется из Официальная документация UI котенка Отказ Если вы читаете это долго после публикации этого поста, пожалуйста, убедитесь, что вы следуете и сопоставьте документ с помощью этой настройки.

Наконец, на основе состояния входа в систему, если пользователь не вошел в систему, мы видим Authpage составная часть; В противном случае мы видим Продуктная страница составная часть. Продуктная страница Компонент принимает только на объект платежа как [RP [ Отказ

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

По мере роста вашего приложения этот вид быстрого и грязного способа управляющего состояния может больше не сократить его, и вам придется забрать инструменты, такие как контекст React или более передовые государственные библиотеки управления, такие как Redux или отдачи, но для ограниченного объема этого поста это должно сделать просто хорошо.

Страница аутентификации

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

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

Во-первых, создайте новый файл с именем Authpage.tsx В корне приложения и падение в код ниже:

import {Layout, Icon, Button} from "@ui-kitten/components";
import { Layout, Card, Button, Input, Text } from "@ui-kitten/components";
import { StyleSheet, View } from "react-native";
import React, { useState } from "react";

import { Auth } from "./auth";

const styles = StyleSheet.create({
    page: {
        flex: 1,
        padding: 15,
        alignItems: 'center',
        justifyContent: 'center',
    },
    card: {
      alignSelf: 'stretch',
    },
    formInput: {
        marginTop: 16,
    },
    footer: {
        marginTop: 10,
        alignSelf: 'stretch',
        flexDirection: 'row',
        justifyContent: 'space-between',
    },
    statusContainer: {
        alignSelf: 'center',
    },
    actionsContainer: {
        flexDirection: 'row-reverse',
    },
    button: {
        marginLeft: 10,
    }
});

type AuthPageProps = {
    auth: Auth,
    isLoggingIn: boolean,
    setIsLoggedIn: (isLoggedIn: boolean) => any,
    setIsLoggingIn: (isLoggedIn: boolean) => any,
};

export const AuthPage = ({ auth, isLoggingIn, setIsLoggedIn, setIsLoggingIn }: AuthPageProps) => {
    const [password, setPassword] = useState();
    const [email, setEmail] = useState();
    const [errors, setErrors] = useState([]);

    const handlePrimaryButtonPress = async (action = 'signup') => {
        setIsLoggingIn(true);
        // when signing up, we want to use the signup method from auth class, otherwise, use the login method
        try {
            const { success, errors } = await auth.request(action, {email, password});
            setIsLoggedIn(success);
            setErrors(errors);
        } catch (err) {
            console.log(err);
        }

        setIsLoggingIn(false);
    };

    return (
        
            
                
                
                {errors.length > 0 && errors.map(message =>
                    {message}
                )}
            
            
                
                    {isLoggingIn ? 'Authenticating...' : ''}
                
                
                    
                    
                
            
        
    );
}

В идеале, в реальном мире приложений у вас будет ваша собственная структура архитектуры и папки, но для целей и объема этого поста мы просто сделаем все в корне папки приложения.

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

Во-первых, у нас есть определения таблицы стилей. Поскольку здание UI не является в первичном объеме этого поста, я не буду уходить в подробную информацию о том, почему и как это работает. Если вы совершенно новичок в этом, пожалуйста, Читайте на нем здесь Отказ Чтобы обобщить, мы просто добавляем некоторые стили, которые мы позже применим к нашим различным элементам, оказываемым Authpage составная часть.

Затем мы создаем тип, определяя все различные реквизиты, которые этот компонент ожидает получить от его рендерера.

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

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

Наконец, мы называем auth.request (действие, {электронная почта, пароль}) Способ с электронной почтой и паролем из состояния. Представьте себе, что это просто отправят данные на наш сервер API и получите ответный ответ.

Теперь давайте доберемся до того, что на самом деле увидит пользователь. Чтобы сделать это приятно и красиво, мы используем Макет и Карта Компоненты из котенка UI и поместите карту прямо в центр экрана с двумя входами внутри – один для электронной почты и один для пароля. Давайте проанализируем поле пароля, например:


Мы используем Формирование Стиль, который дает ему хорошее поле на вершине. Тогда этикетка опоры добавляет текст поверх входного поля. Securetextentry Опыт делает текст скрыть в виде типов пользователей в пароле. Как обработчик для Onchangetext Событие, мы проходим setwassword Функция, которая будет обновлять значение состояния. Это значение затем устанавливается в качестве значения самого входного поля.

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

Под ним мы будем проходить все строки сообщения об ошибках из состояния и визуализации Текст Компонент, показывающий само сообщение, если есть. Статус = «Опасность» Часть сделает текстовый цвет красным благодаря Ui котенка.

{errors.length > 0 && errors.map(message =>
                    {message}
                )}

Наконец, мы добираемся до нижнего колонтитула. Разделены на две горизонтальные секции справа, он показывает две кнопки: один для Войти а другой для Зарегистрируйтесь Отказ Слева, это пустая область по умолчанию, но когда мы связываемся с сервером, текст, говорящий Аутентификация … Будут отображаться там, чтобы пользователь узнал, что мы обрабатываем их запрос.

Обратите внимание, что две кнопки вызывают ту же функцию, но с разными параметрами. У них также есть немного разные реквизиты – например, у одного есть Внешний вид = «Наброски» , что дает ему границу и делает цвет фона немного исчез. Мы также проезжаем отключен = {isloggingin} , что гарантирует, что после того, как пользователь нажимает кнопку, и мы отправляем данные на сервер, обе кнопки будут отключены, чтобы избежать нескольких представлений.

Фу! Первый компонент приложения сделан – не плохо, верно? Ну, плохие новости – это то, что вы не можете видеть его в действии еще из-за пропущенных частей здесь, как auth Объект и Срок общение составная часть. Тем не менее, чтобы поблагодарить вас за вашу тяжелую работу до сих пор, я вознаграждаю вас с помощью скриншота того, как он будет выглядеть, как только приложение сделает страницу во всей своей славе! И вот!

Предварительный просмотр нашей страницы для входа/регистрации

ОК, извините, я, наверное, слишком сильно раскручивал его. Это не так удивительно, но эй, это тоже не так уж плохо, верно?

Страница продукта

Также как мы сделали для страницы авторизации, давайте создадим новый файл с именем ProductPage.tsx В корне приложения и удалите следующий код там:

import {Layout, Icon, Button} from "@ui-kitten/components";
import { StyleSheet } from "react-native";
import React, {useEffect, useState} from "react";
import {Payment, PaymentData} from "./payment";

const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'center',
    },
    button: {
        margin: 2
    }
});

const products = [{
    status: 'primary',
    text: 'Buy Video',
    product: 'video',
    icon: 'film',
    price: 50,
}, {
    price: 30,
    status: 'info',
    product: 'audio',
    text: 'Buy Audio',
    icon: 'headphones',
}];

type ProductPageProps = {
    payment: Payment,
};

export const ProductPage = ({ payment }: ProductPageProps) => {
    const [paymentReady, setPaymentReady] = useState(false);
    const [paymentHistory, setPaymentHistory] = useState([]);

    // Initialize the payment module, on android, this MUST be inside the useEffect hook
    // on iOS, the initialization can happen anywhere
    useEffect(() => {
        payment.init().then(() => {
            setPaymentReady(true);
            setPaymentHistory(payment.history);
        });
    });

    const handlePayment = async (price: number, product: string) => {
        await payment.request(price, product);
        setPaymentHistory(payment.history);
    };

    const hasPurchased = (product: string) => !!paymentHistory.find(item => item.product === product);

    return (
        
            {products.map(({product, status, icon, text, price}) => (
                
            ))}
        
    );
};

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

Тогда у нас есть массив, содержащий два записей продукта. Каждая запись имеет Статус , Цена , Продукт , Значок и текст характеристики. Кроме Цена и Продукт Свойства все в основном для пользовательского интерфейса, в то время как прежние двое будут позже использованы в связи API. В приложении Real-World вы, вероятно, получите этот список с сервера вместо того, чтобы убрать его в код приложения.

В определении самого компонента мы устанавливаем его для получения объекта платежа в качестве опоры, который мы передали от Приложение Компонент, помните? На нагрузке мы инициализируем платежный модуль, позвонив init () и сохранить интерфейс синхронизации пользовательского интерфейса с состоянием загрузки модуля платежей, мы введем переменную состояния, которая устанавливается на ложь Первоначально и при инициализации платежного модуля включается.

Кроме того, есть еще одна переменная государства, которая должна содержать все ранее выплаты пользователем. После звонка init () На объекте оплаты мы сбрасываем эту переменную состояния с История недвижимость от платежного объекта. Как будто после инициализации свойство истории будет содержать историю оплаты. Мы увидим, как это сделано позже.

У нас также есть функция обработчика событий, Ручка , что запускается с указанием цены и продукта и называет Запрос Способ из платежного модуля. После заканчивается звонок, мы переоцениваем СОБСТВЕННОСТЬ Состояние с История Недвижимость от Оплата Объект, как если бы, после получения оплаты, свойство истории будет содержать разные данные, а затем.

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

Затем наступает рендеринг пользовательского интерфейса. Внутри Макет Компонент из UI котенка, мы просто зацикливаем как на наших продуктах, так и делаем одну кнопку для каждого из них. Вот где вы видите свойства, такие как Статус , текст и т. Д. Набор в Продукт Объект пригодится.

Уи котенка позволяет нам легко манипулировать внешностью различных компонентов, таких как кнопки через простые свойства. Например, Статус = "Первичный" даст кнопку отчетливый стиль на основе основного цвета от определения вашей темы.

Мы также отключаем кнопку, если инициализация платежа не завершена, или уже есть запись в истории для того же продукта. Вы можете играть с внешним видом и чувствовать себя изменением/добавлением различных реквизитов Документировано здесь Отказ

Обратите внимание, что мы используем наши ранее созданные Ручка Функция как слушатель для onpress События, и мы также используем различные стили, которые мы создали в начале, чтобы добавить некоторое расстояние между кнопками и центрировать их вертикально на экране.

Чтобы дать вам проблеск того, как все это выглядит, вот скриншот:

Предварительный просмотр нашей страницы продукта

С этим мы возьмем короткий перерыв от боковой стороны вещей и построим API. Как только это готов, мы вернемся к этому, чтобы соединить два и обернуть вещи!

Adonis приложение

Так же, как EXPO, Adonisjs поставляется с удобной командой генератора Boiterplate. Чтобы запустить его, убедитесь, что вы внутри Payme/ каталог и запустить следующую команду:

yarn create adonis-ts-app payme

Вы получите несколько вопросов и предоставляете ответы, такие как изображение ниже, и вы останетесь с каталогом с именем Payme :

Adonis init вывод.

Adonis init выпуск

Как и раньше, мы перенесем это на более фитинговое имя для структуры Monorepo: MV Payme API Отказ Теперь вы можете перемещаться внутри каталога и запустить приложение:

cd api
yarn start

Вы должны увидеть выходной сигнал, который говорит вам, что приложение запущено и доступно от Localhost на порту 3333.

Аутентификация

Adonis делает аутентификацию пользователя в ветру прямо из коробки, с большой гибкостью. Мы будем начать, установив некоторые пакеты, которые мы будем использовать очень скоро: пряжа Добавить полоску @ adonisjs/auth @ alpha Отказ

Первый пакет, который мы установили, есть узел для полосы. Второй – пакет аутентирования от Adonis, который дает нам все, что нам нужно для аутентификации пользователя после запуска Узел ACE вызывает @ adonisjs/auth и предоставляя все входные данные требует.

Вот ответы, которые я предоставил, и вы можете увидеть все файлы, которые он генерирует для вас:

Создание котельной Auth.

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

Обратите внимание, что мы используем BuciD в качестве поставщика хранения данных, которые мы еще не настроены. Это официальная форма базы данных Adonis ORM. Давайте установим это, сначала установив сам пакет: пряжа Добавить @ adonisjs/lucid @ alpha Отказ Это попросит вас выбрать провайдер базы данных. Обычно вы бы выбрали что-то вроде MySQL или PostgreSQL, но для небольшого объема этого поста я просто придерживаюсь SQLite.

С этим мы сейчас готовы запускать миграции, созданные пакетом Adonis Auth: Узел Ace Migration: Run Отказ Это создаст необходимые таблицы с надлежащей схемой для поддержки аутентификации пользователей на основе JWT.

Примечание : На момент написания этого поста есть проблема с настроенными пакетами в Adonis, и вам может потребоваться вручную установить пакет PHC-ARGON2, запустив Moking пряжа Добавить PHC-ARGON2 команда.

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

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

import Route from '@ioc:Adonis/Core/Route';

Route.post('/login', 'AuthController.login');
Route.post('/signup', 'AuthController.signup');

Это зарегистрирует два конечных точка Post Chast, /Вход и /Регистрация И когда запросы отправляются на эти конечные точки, Adonis выполнит связанные методы от класса контроллера имени AuthController Отказ

Тем не менее, это еще не существует, поэтому давайте создадим новый файл (и необходимые каталоги, то есть, контроллеры и http ) в Приложение/Контроллеры/http/authcontroller.ts Отказ Или вы можете просто запустить команду Узел ACE сделать: контроллер Auth , что будет генерировать файлы и каталоги для вас. Теперь разместите в следующем коде в файле контроллера:

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User';

export default class AuthController {
  public async login ({ request, auth }: HttpContextContract) {
    const email = request.input('email');
    const password = request.input('password');

    const token = await auth.use('api').attempt(email, password);
    return token.toJSON();
  }
  public async signup ({ request, auth }: HttpContextContract) {
    const email = request.input('email');
    const password = request.input('password');

    const user = await User.create({email, password});
    const token = await auth.use('api').generate(user);
    return token.toJSON();
  }
}

Вход Метод ожидает электронного письма и пароль, который будет отправлен в корпусе запроса Post, и используя инъекционные auth Модуль, Adonis проверит, есть ли запись пользователя в базе данных, сопоставив эти учетные данные через попытка метод.

Если пользователь существует, он выдаст токен и отправят его в ответ. Ответ токена содержит два свойства, {токен: <Фактический токен JWT>, Тип: <Тип токена, Обычно носитель>} Отказ

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

Подумайте о вышеупомянутой установке как аналог для Authpage Компонент, который мы только что создали для нашего собственного приложения React. Так что теперь оставая вещь для нашего API – аналог для Срок общение составная часть. Давайте построим это, но мы возьмем более живописный маршрут для этого, так как Adonis не построит все для нас, как это сделано для модуля AUTHULE.

Структура данных

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

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

В значительной степени мы хотели бы назвать этот стол Платежи Отказ Как я уже сказал, вместо того, чтобы создавать этот стол вручную в нашей базе данных, мы запустим команду Узел ACE делает: Миграция Оплата генерировать миграцию таблицы платежей. Затем обновите сгенерированный файл миграции, который заканчивается _Payments.ts В API/База данных/миграции/ каталог со следующими новыми строками:

table.increments('id');
//--> new lines start
      table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE');
      table.string('charge_id').unique().notNullable();
      table.string('token_id').notNullable();
      table.string('product').notNullable();
      table.integer('price').notNullable();
//--> new lines end
      table.timestamps(true);

Теперь, прежде чем мы погрузиться в код, который мы просто написали, давайте запустим команду Узел Ace Migration: Run Чтобы создать новую таблицу со структурой, описанной в этом куске кода.

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

Также есть немного магии с Timestamps (True) , который в основном переводит: добавить два столбца с именем Create_at и updated_at , которые являются полями метки времени. Теперь для новых полей:

|. Поле |. Критерии |. Причина |. |. Цена |. Номер, на данный момент только целые числа (работа с центами – это другое дело) | Содержит цену продукта, который является суммой, которая будет взиматься на карту пользователя . Продукт |. Строка, не может быть пустой | Содержит название продукта, для которого была сделана платеж . Token_id |. Строка, не может быть пустой | Токенид Сгенерируется библиотекой полосы внутри приложения, когда пользователь вводит допустимые данные карты . Change_id |. Строка, не может быть пустой | Идентификатор записи заряда, что полоса возвращается после того, как карта пользователя была успешно заряжена

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

Запустите команду Узел ACE делает: модель оплаты Для генерации модели оплаты, которая создаст новый файл в Приложение/Модели/Оплата. Отказ Откройте этот файл и поместите следующий код там:

import { DateTime } from 'luxon'
import { BaseModel, column, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm'

import User from 'App/Models/User';

export default class Payment extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public price: number

  @column()
  public product: string

  @column()
  public tokenId: string

  @column()
  public chargeId: string

  @column()
  public userId: number

  @belongsTo(() => User)
  public user: BelongsTo

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime
}

Как видите, это в значительной степени зеркало того, что мы делали с миграцией, но более типографически-эски. Обратите внимание, что мы написаны по полям Timestamp на уровне модели, которое обрабатывается Timestamps (True) Вызов в миграции.

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

@belongsTo(() => User)
  public user: BelongsTo

Это то, что позволяет нам запускать всевозможные реляционные запросы, используя умно конструированные ясных ORM. Мы скоро увидим хотя бы один пример в контроллере оплаты. Это устанавливает соединение из модели оплаты для модели пользователя. Для модели пользователя, чтобы знать о своих отношениях с моделью оплаты, нам нужно пойти в Приложение/Модели/User.ts и добавьте следующие строки:

import {
  column,
  beforeSave,
  BaseModel,
  hasMany,
  HasMany,
} from '@ioc:Adonis/Lucid/Orm';

//....previously generated code
export default class User extends BaseModel {
//....previously generated code

  @hasMany(() => Payment)
  public payments: HasMany
}

Примечание : Если вы не знакомы с реляционной структурой данных, или хотели бы узнать больше о том, как Adonis снимается, пожалуйста, Читайте здесь Отказ

Теперь давайте прыгаем на еще один уровень и подвергайте управлению данными через HTTP-запросы. В этом шаге участвуют три вещи.

1.) маршрут

Маршруты – это то, что дают внешний мир способ общаться с нашим приложением Adonis через HTTP. Давайте начнем с создания новой конечной точки отдыха в Пуск/Route.ts Файл со следующим контентом:

Route.get('/payment', 'PaymentsController.list').middleware('auth');
Route.post('/payment', 'PaymentsController.charge').middleware('auth');

Мы добавляем один обработчик для получения запросов на /Оплата Конечная точка, и когда прибудет запрос, мы говорим Adonis к Список Метод от Оплатыscontroller класс. Другой обработчик для почтовых запросов на той же конечной точке и выполняет заряд Метод от класса контроллера. Есть также свисающая часть с промежуточное ПО («Auth») Что мы скоро будем демистифицировать.

2.) контроллер

Запустите команду Узел ACE Make: Контроллер Оплата Чтобы генерировать контроллер платежа, который создаст новый файл в Приложение/HTTP/Controllers/Payment.ts Отказ Откройте этот файл и место в следующем коде:

import { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";
import Env from '@ioc:Adonis/Core/Env'
import Payment from "App/Models/Payment";
import Stripe from 'stripe';

const stripeSecretKey = `${Env.get('STRIPE_SECRET_KEY')}`;
const stripe = new Stripe(stripeSecretKey, {
  apiVersion: '2020-08-27'
});

export default class PaymentsController {
  public async charge ({ request, auth }: HttpContextContract) {
    const payment = new Payment();
    payment.tokenId = request.input('tokenId');
    payment.price = request.input('price');

    const { id } = await stripe.charges.create({
      amount: payment.price * 100,
      source: payment.tokenId,
      currency: 'usd',
    });
    payment.chargeId = id;

    await auth.user?.related('payments').save(payment);
    return payment;
  }
  public async list ({ auth }: HttpContextContract) {
    const user = auth.user;
    if (!user) return [];
    await user?.preload('payments');
    return user.payments;
  }
}

Много, чтобы распаковать там. Давайте начнем с вершины:

import Stripe from 'stripe';

const stripeSecretKey = `${Env.get('STRIPE_SECRET_KEY')}`;
const stripe = new Stripe(stripeSecretKey, {
  apiVersion: '2020-08-27'
});

Помните, что мы установили полоску при установке библиотеки аутентификации? Это где мы его используем.

После импорта мы создали полосу с секретным ключом. Однако в интересах безопасности безопасности, а не жесткодируя ключ, мы используем Env Поставщик из Adonis, который позволяет нам легко читать из Env Переменные Отказ Вы бы обновили .env В корне API каталог и добавьте новую запись, как приведено ниже:

STRIPE_SECRET_KEY=

Чтобы получить секретный ключ, вам нужно будет войти на свою учетную запись на полоску, а затем перейти к Разработчики -> API Ключ Отказ Затем, переключаться на Просмотр тестовых данных с верхнего правого угла. Теперь вы можете создать новый секретный ключ, либо если у вас уже есть, не стесняйтесь использовать это.

Просмотр секретных ключевых данных

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

Далее, метод зарядки:

public async charge ({ request, auth }: HttpContextContract) {
    const payment = new Payment();
    payment.tokenId = request.input('tokenId');
    payment.product = request.input('product');
    payment.price = request.input('price');

    const { id } = await stripe.charges.create({
      amount: payment.price * 100,
      description: payment.product,
      source: payment.tokenId,
      currency: 'usd',
    });
    payment.chargeId = id;

    await auth.user?.related('payments').save(payment);
    return payment;
  }

Здесь мы ожидаем, что клиент отправит нам полосу Токенид , Цена и Продукт И с тем, мы создали создание нового объекта модели оплаты. Перед сохранением мы пытаемся зарядить карту пользователя, позвонив Заряды. Create () метод. Полосальные ручки суммы в центах, поэтому, если общая сумма, которую мы хотим взимать, – это 50 долларов, нам нужно отправить 5000 в качестве суммы за запрос на заряд.

Мы также жесткодируем валюту быть USD , но в приложении Real-World вы, вероятно, настроили его на основе карты пользователя или на основе какой-либо другой переменной на уровне приложения. Есть гораздо больше данных, которые вы можете отправить на полоску при зарядке карты пользователя. Чтобы узнать больше, пожалуйста, прочитайте их удивительно подробную Документация API здесь Отказ

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

Обратите внимание, что мы не обращаемся с любым случаем, в котором полоса не может заряжать карту – будь то, что информация о карте неверна, карта истекает или любую другую причину – и возвращает информацию об ошибке вместо Changid Отказ Я покидаю это как упражнение для читателя.

Затем, чтобы вставить строку, а не напрямую вставляющую использование модели оплаты, мы используем user.related («платежи»). Сохранить () Отказ Таким образом, Adonis автоматически связывает запись оплаты с помощью вошедшего в систему.

Обратите внимание, что мы ожидаем определенного auth Объект, который должен быть передан нам, из которого мы обращаемся к недвижимости пользователя. Это автоматически вводится по промежуточным программным обеспечением Auth и дает доступ к модели пользователя. От пользовательской модели, поскольку мы установили Hasmany Отношения, Adonis может автоматически заполнить детали для ассоциирования ребенка на родительскую запись.

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

public async list ({ auth }: HttpContextContract) {
    const user = auth.user;
    if (!user) return [];
    await user?.preload('payments');
    return user.payments;
  }

Как и метод заряда, имея доступ к модели пользователя, мы можем запустить Preload («Платежи») Для загрузки всех записей платежей, которые связаны с вошедшим пользователем.

Промежуточное программное обеспечение

В целях безопасности мы не хотели бы, чтобы кто-то, кто не вошел в систему, чтобы сделать запрос на оплату. Вручную проверяя каждый запрос – это утомительный и рискованный процесс, поэтому Adonis делает его очень легким благодаря использованию Adminwares.

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

Мы уже добавили .middleware («auth») к определению маршрута. Последнее, что мы должны сделать, чтобы он работал, это зарегистрировать промежуточное программное обеспечение. Откройте Start/kernel.ts файл и внизу, замените строку, которая выглядит как Server.middleware.registernamed ({}) со следующим:

Server.middleware.registerNamed({
  auth: 'App/Middleware/Auth',
});

Теперь Адонис знает, что мы подразумеваем под auth промежуточное программное обеспечение, и вся логика для предотвращения аутентифицированных запросов от достижения метода контроллера уже создана для нас генератором Auth.

Объединение двух приложений

Теперь, когда наш отдых API готов со всеми конечными точками, давайте перейдем к последнему этапу подключения двух. Давно прошли дни, когда вам понадобится целая библиотека NPM для связи между клиентом и сервером; в наши дни, все, что вам нужно, это извлекать Отказ Мы начинаем, сделав API.TS файл в корне нашего приложение/ Справочник и размещение в следующем коде:

const API_URL = 'http://192.168.1.205:3333';

export const postDataToApi = async ({endpoint = '', data = {}, headers = {}}) => {
    const response = await fetch(`${API_URL}/${endpoint}`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            ...headers
        },
        body: JSON.stringify(data)
    });
    return response.json();
};

export const getDataFromApi = async ({endpoint = '', headers = {}}) => {
    const response = await fetch(`${API_URL}/${endpoint}`, {
        headers: {
            'Content-Type': 'application/json',
            ...headers
        },
    });

    return response.json();
};

Здесь у нас есть конечная точка API HardCoded здесь и указала, но отметим, что IP-часть этого будет отличаться для вас. Чтобы получить это, вы можете Следуй что-то вроде этого Отказ Это необходимо, потому что наше приложение будет работать на мобильном устройстве, но наше приложение API работает на другом устройстве. Предполагая, что оба устройства подключены к той же сети, вы должны получать префикс контейнера IP-адресов 192.168.

Тогда у нас есть две функции: один для создания почтовых запросов и другой для получения запросов. Обе функции позволяют указывать конечную точку и дополнительные параметры заголовков. Для постов запросов также позволяет нам пройти дополнительные данные к телу запроса.

Это все, что нам нужно для общения с нашим API-сервером. Используя эти помощники, мы будем делать запросы на сервер в каждом конкретном случае.

Авторский класс

Первый случай, который мы обращаемся, это Auth модуль. Давайте создадим новый файл в корне для приложения/каталога с именем auth.ts и поместите следующий код внутри него:

import AsyncStorage from '@react-native-community/async-storage';
import { postDataToApi } from "./api";

interface AuthData {
    email: string | undefined;
    password: string | undefined;
}

export class Auth {
    tokenKey: string = '@authKey';

    async request(endpoint = 'login', data: AuthData) {
        const { token, errors } = await postDataToApi({ endpoint, data });
        if (token) {
            await AsyncStorage.setItem(this.tokenKey, token);
            return { success: true, errors: [] };
        }

        const errorMapper: (params: { message: string }) => string = ({ message }) => message;
        return { success: false, errors: errors.map(errorMapper) };
    }

    async getToken() {
        return AsyncStorage.getItem(this.tokenKey);
    }

    async getApiHeader() {
        const token = await this.getToken();
        return { 'Authorization': `Bearer ${token}` };
    }
}

Этот класс имеет Запрос Способ, который отправляет учетные данные электронной почты и пароля на наш сервер API, используя postdatatoapi Функция помощника, которая вернет объект с помощью A токен или Ошибки Массив с деталями того, что пошло не так. Если мы получим токен, мы храним его в локальном хранилище, используя Asyncstorage Пакет, который является самым быстрым способом сохранять данные локально на устройстве.

Если что-то не так с запросом входа в систему/регистрации, API ответит на массивы объектов, содержащих ошибки, и каждый объект будет содержать сообщение имущество. Чтобы упростить эти данные, мы рассмотрим через массив и преобразуем его на массив строк, которые мы можем попереться и отображать пользователю.

Тогда у нас есть Геттокен Метод для извлечения ранее сохраненного токена из хранения, который мы звоним, когда Приложение Компонент загружен в виду. Последний метод является помощником, который создает Авторизация С хранимым токеном, который можно отправить с HTTP-запросами на наш API-сервер.

Поскольку запросы аутентификации не должны использовать существующий токен, мы не используем его здесь нигде, но мы увидим его использование в Оплата класс.

Класс оплаты

Создайте новый файл в корне приложение/ каталог и поставить код ниже внутри него:

import { PaymentsStripe as Stripe } from 'expo-payments-stripe';
import {Auth} from "./auth";
import {getDataFromApi, postDataToApi} from "./api";

type PaymentData = {
    id: number,
    price: number,
    product: string,
};

export class Payment {
    auth: Auth;
    history: PaymentData[] = [];

    constructor(auth: Auth) {
        this.auth = auth;
    }

    async init() {
        await Stripe.setOptionsAsync({
            androidPayMode: 'test',
            publishableKey: 'pk_test_',
        });
        await this.getHistory();
        return true;
    }

    async getHistory() {
        const response = await getDataFromApi({endpoint: 'payment', headers: await this.auth.getApiHeader()});
        if (response.errors) {
            this.history = [];
            return false;
        }

        if (response.length > 0) {
            this.history = response;
            return true;
        }
    }

    async request(price: number, product: string) {
        try {
            const { tokenId } = await Stripe.paymentRequestWithCardFormAsync();
            const payment: PaymentData = await postDataToApi({
                endpoint: 'payment',
                data: {price, token: tokenId, product},
                headers: await this.auth.getApiHeader()
            });

            if (payment.id)
                this.history.push(payment);

            return payment;
        } catch(err) {
            console.log(err);
            return null;
        }
    }
}

Это делает немного больше, чем просто создание запросов API Server. Прежде всего, он ожидает экземпляра Auth класс, который должен быть введен в приведение введение. Тогда у нас есть init Метод, который инстанционирует библиотеку в полоску Экспо с publishablekey Отказ

Чтобы получить ключ публикуемого, выполните те же шаги, которые вы сделали, чтобы получить секретный ключ при настройке механизма зарядки полосы Server Side, и вместе с секретным ключом вы увидите ключ на одну и ту же страницу. После мгновенной полосы мы называем другой метод класса GetHistory Отказ

GetHistory Это простой запрос на наш сервер API для получения всех предыдущих платежей, которые текущий пользователь сделан. Чтобы определить текущий пользователь в HTTP-запросе, мы передаем заголовок токена Authk, используя this.auth.getapiHeader () Способ вызова. Этот запрос выходит на /Оплата конечная точка.

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

Запрос это последний метод этого класса. Это называется, когда пользователь нажимает Купить Кнопка на одной из продуктов и получит цену и название продукта. Первое, что мы делаем с этим методом, огневая STRIPE.PAYMENTREQUESTWITHCARDFORMASYSYNC () Вызов, который покажет хороший модал для пользователя для ввода информации о своей карте. Это все встроено в библиотеку выставки. Вы можете настроить внешний вид модал Прохождение различных вариантов конфигурации к этому звонку. Вот скриншот того, как выглядит модал:

Подробности карты модаль

Полоса делает тестирование карты Super Simple; Вам не нужна настоящая карта для проверки любой из его функций. Вы можете найти Данные тестовые карты здесь и используйте один оттуда, чтобы заполнить эту форму. Обратите внимание, что если вы вставите неправильную информацию о карте, UI автоматически позаботится о проверке и покажет вам ошибку.

После того, как вы вводите действительную/тестовую карточку и нажмите «Готово», полоса вернет различную информацию в качестве объекта, наиболее важным среди них являются Токенид имущество. Только потому, что вы введите действительную информацию о карте и нажатую, не означает, что ваша карта была заряжена. Чтобы сделать это безопасно и надежно, нам нужно пройти Токенид на наш сервер API в качестве почтового запроса на /Оплата конечная точка.

Мы уже установили это, чтобы позвонить в заряд () Способ использования библиотеки полосы, помните? Вместе с Токенид Мы передаем цену и название продукта. Помните, что цена проходит в долларах, но на сервере она будет преобразована в цента, а имя продукта будет добавлено к описанию заряда.

Кроме того, чтобы определить, какой пользователь делает платеж, мы снова, используя this.auth.getapiHeader () Способ добавить токен аутентификации в заголовке запроса.

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

Обратите внимание, что мы не обращаемся к чему по ошибке – снова я оставляю это как упражнение для читателя.

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

Взять его для спина

Теперь для выплаты: запустить Пряжа начать Команда из приложение/ каталог, и вам будет представлен QR-код в терминале. Я использую реальное устройство для тестирования этого, но вы можете легко проверить его на IOS или Android эмулятор, если вы хотите.

Если вы еще этого не установите, а затем откройте приложение EXPO на своем устройстве и сканируйте QR-код. Он автоматически загрузится и откроет приложение на вашем устройстве. Вы также должны убедиться, что сервер API все еще работает.

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

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

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

После того, как вы вкладываете данные о действите/тестируемой карточке и нажмите «Готово», вы должны увидеть, что кнопка, которую вы только что нажали перед появлением всплывающего окна, теперь отключен. Если вы теперь закроете приложение и открывайте его, вы увидите, что кнопка остается отключена для вас.

Но мы не можем просто доверять нашему UI слепо, когда вовлечены деньги! Итак, давайте проверим, что платеж фактически произошел, отправляясь на панель инструментов полосы.

Проверка оплаты на панели инструментов полосы

С левой боковой панели на приборной панели, переключитесь на Просмотр тестовых данных а потом иди к Платежи панель. Убедитесь, что вы видите Тестовые данные Этикетка на вершине списка платежей, а последняя запись там должна быть единственная приложение только созданное. Поздравляю! Вы только что сделали немного свободных денег!

Закрытие замечаний

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

  • Переместите список продуктов из статического массива в приложении на динамический, хранящийся в списке продуктов, полученных из API
  • Сделайте продукты выглядеть воображать, добавляя изображения, описания, названия и т. Д.
  • Использование конечной точки списка платежей, показать список продуктов, которые пользователь приобрел
  • Как только пользователь покупает продукт, показать индикатор на продукте, чтобы напомнить пользователю, что они уже приобрели его – но также позвольте пользователю снова приобретать тот же продукт, если они выберут, вместо того, чтобы просто отключить возможность покупать
  • Покажите всплывающее окно успеха, когда запрос на оплату успешен

Если вы сделали это так далеко, мне 110% уверены, что вы можете реализовать все вышеперечисленное самостоятельно, и я бы очень поощрял всем, чтобы дать ему выстрел. Пожалуйста, дайте мне знать, если вы заполните любые или все вышеперечисленные элементы Достигнув мне в Twitter С вашей Git Reppo или готовым приложением, опубликованным в магазине!