В настоящее время Medusa проводит Хакатон, где разработчики могут создавать удивительные проекты с Medusa и выигрывать призы и подарки. Примером проекта хакатона является создание приложения для электронной коммерции с помощью React Native, которое рассматривается в этой статье.
Это вторая часть серии статей Создание приложения электронной коммерции React Native с помощью Medusa. В предыдущей статье вы узнали, как настроить сервер Medusa и администратора, а также как создать базовое приложение для электронной коммерции.
В этой части вы узнаете, как реализовать функции корзины и оформления заказа. Вы также можете найти исходный код приложения React Native на GitHub.
Предпосылки
- Вам нужна учетная запись Stripe, вы можете зарегистрироваться здесь.
Клонировать стартовый код
Если вы пропустили часть 1, вы можете клонировать код с GitHub. Для этого просто запустите следующую команду:
git clone -b part-1 https://github.com/suhailkakar/react-native-medusajs
После того, как вы клонировали репозиторий, перейдите во вновь созданный каталог react-native-medusajs
, запустив cd react-native-medusajs
. Запустите следующую команду, чтобы установить модули узла:
yarn install
Приведенная выше команда установит необходимые пакеты и зависимости из реестра NPM.
После успешной установки зависимостей запустите expo start
, чтобы запустить приложение. Вы можете отсканировать QR-код с помощью своего устройства или запустить приложение на симуляторе Android/iOS. Вы должны увидеть аналогичный экран, как только приложение откроется на вашем устройстве/эмуляторе.
Убедитесь, что сервер Medusa также работает.
Возможно, вам придется изменить URL-адрес в constants/urls.js
, если вы не видите продукты. Вы можете обновить базовый URL-адрес до IP-адреса, на котором работает внутренний сервер.
Создание корзины
Первым шагом является реализация функциональности корзины. Установите async-storage
, чтобы сохранить идентификатор корзины в локальном хранилище устройства. Запустите следующую команду, чтобы установить @react-native-async-storage/async-storage
в проект:
expo install @react-native-async-storage/async-storage
После установки пакета откройте проект в любом редакторе кода и вставьте приведенный ниже код в App.js
перед функцией возврата:
const getCartId = () => {
axios.post(`${baseURL}/store/carts`).then((res) => {
AsyncStorage.setItem("cart_id", res.data.cart.id);
});
};
В приведенном выше коде вы создали функцию с именем getCartId
, и внутри этой функции вы запрашиваете у внутреннего API-интерфейса получение идентификатора корзины с помощью Axios, а затем сохраняете его в локальном хранилище устройства с помощью async-storage
.
Обязательно импортируйте async-storage, useEffect, Axios и baseURL в файл
App.js
:
import axios from "axios";
import baseURL from "./constants/url";
import { useEffect } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
Вышеупомянутая функция (getCartId
) не будет запущена, так как вы еще не вызвали ее. Однако вы хотите запускать эту функцию только в том случае, если в локальном хранилище устройства нет сохраненного идентификатора корзины.
Теперь напишите еще одну функцию, чтобы проверить, есть ли уже идентификатор корзины в локальном хранилище устройства. Если нет, функция должна вызвать getCartId
. Наконец, вызовите checkCartId
внутри useEffect:
// Check cart_id const checkCartId = async () => { const cartId = await AsyncStorage.getItem("cart_id"); if (!cartId) { getCartId(); } };
useEffect(() => { checkCartId(); }, []);
Добавление товаров в корзину
Во-первых, давайте обновим компонент кнопки и добавим свойство для обновления макета кнопки в зависимости от предоставленного размера. Вы можете заменить код внутри components/Button.js
кодом ниже:
import { View, Text, StyleSheet } from "react-native"; import React from "react"; import { widthToDp } from "rn-responsive-screen";
export default function Button({ title, onPress, style, textSize, large }) { return ( <View style={[styles.container, style, large && styles.large]}> <Text style={[ styles.text, { fontSize: textSize ? textSize : widthToDp(3.5) }, , ]} onPress={onPress} > {title} </Text> </View> ); }
const styles = StyleSheet.create({ container: { backgroundColor: "#C37AFF", padding: 5, width: widthToDp(20), alignItems: "center", justifyContent: "center", borderRadius: 59, }, large: { width: "100%", marginTop: 10, height: widthToDp(12), }, text: { color: "#fff", fontWeight: "bold", }, });
Вспомните, что к концу части 1 вы создали компонент components/ProductInfo/MetaInfo.js
для отображения описания, размера и другой информации о товаре. Идем дальше и добавляем компонент кнопки после описания:
<Button title="Add to Cart" onPress={addToCart} large={true} />
Также не забудьте импортировать компонент кнопки из components/Button
:
import Button from "../Button";
Как видите, вы вызываете функцию addToCart
, когда пользователь нажимает кнопку, поэтому объявите эту функцию.
Поверх компонента MetaInfo.js
перед возвратом объявите новую функцию с именем addToCart и добавьте в нее следующий код:
const addToCart = async () => {
const cartId = await AsyncStorage.getItem("cart_id");
axios
.post(`${baseURL}/store/carts/${cartId}/line-items`, {
variant_id: product.variants[0].id,
quantity: 1,
})
.then(({ data }) => {
alert(`Item ${product.title} added to cart`);
})
.catch((err) => {
console.log(err);
});
};
В приведенном выше коде сначала вы получаете cart_id
из асинхронного хранилища, а затем публикуете первый вариант продукта с 1 количеством в API корзины. Затем вы направляете пользователей на экран корзины.
Вам также необходимо импортировать AsyncStorage, axios и baseURL в файл MetaInfo.js
:
import axios from "axios";
import baseURL from "../../constants/url";
import AsyncStorage from "@react-native-async-storage/async-storage";
Экран корзины
Это экран, на котором пользователь увидит товары, добавленные в корзину. Создайте новый файл с именем screens/Cart.js
и используйте его для рендеринга простого компонента Text
:
import { View, Text } from "react-native"; import React from "react";
export default function Cart() { return ( <View> <Text>Cart Screen</Text> </View> ); }
Затем импортируйте экран корзины вверху App.js
:
import Cart from "./screens/Cart";
Добавьте новый компонент Scene
под существующим компонентом Scene
в возвращенном JSX:
<Scene key="cart" component={Cart} hideNavBar />
В каталоге components
создайте новый файл с именем CartItem.js
и добавьте следующее содержимое:
import { View, Text, StyleSheet, Image } from "react-native"; import React from "react"; import { heightToDp, width, widthToDp } from "rn-responsive-screen";
export default function CartItem({ product }) { return ( <View style={styles.container}> <Image source={{ uri: product.thumbnail }} style={styles.image} /> <View style={styles.info}> <View> <Text style={styles.title}>{product.title}</Text> <Text style={styles.description}> {product.description} • ${product.unit_price / 100} </Text> </View> <View style={styles.footer}> <Text style={styles.price}>${product.total / 100}</Text> <Text style={styles.quantity}>x{product.quantity}</Text> </View> </View> </View> ); }
const styles = StyleSheet.create({ container: { marginTop: 20, flexDirection: "row", borderBottomWidth: 1, paddingBottom: 10, borderColor: "#e6e6e6", width: widthToDp("90%"), }, image: { width: widthToDp(30), height: heightToDp(30), borderRadius: 10, }, title: { fontSize: widthToDp(4), fontWeight: "bold", }, footer: { flexDirection: "row", justifyContent: "space-between", }, info: { marginLeft: widthToDp(3), flexDirection: "column", justifyContent: "space-between", marginVertical: heightToDp(2), width: widthToDp(50), }, description: { fontSize: widthToDp(3.5), color: "#8e8e93", marginTop: heightToDp(2), },
price: { fontSize: widthToDp(4), }, quantity: { fontSize: widthToDp(4), }, });
Приведенный выше код представляет собой простой компонент корзины React Native, который будет отображаться на экране корзины. Вы передаете реквизиты компоненту из родительского компонента. Это компонент, который будет представлять товар в корзине.
Затем в файле screens/Cart.js
добавьте приведенную ниже функцию и useState перед оператором return, чтобы получить продукты из API корзины и сохранить их в состоянии:
const [cart, setCart] = useState([]);
const fetchCart = async () => { // Get the cart id from the device storage const cartId = await AsyncStorage.getItem("cart_id"); // Fetch the products from the cart API using the cart id axios.get(`${baseURL}/store/carts/${cartId}`).then(({ data }) => { // Set the cart state to the products in the cart setCart(data.cart); }); };
useEffect(() => { // Calling the fetchCart function when the component mounts fetchCart(); }, []);
В приведенном выше коде вы получаете идентификатор корзины из хранилища устройства, извлекаете продукты из API корзины и сохраняете данные в состояние. Затем вы вызываете функцию внутри useEffect. Замените импорт в верхней части файла screens/Cart.js
кодом ниже:
import { View, Text, StyleSheet } from "react-native";
import React, { useEffect, useState } from "react";
import Header from "../components/Header";
import axios from "axios";
import baseURL from "../constants/url";
import CartItem from "../components/CartItem";
import { ScrollView } from "react-native-gesture-handler";
import { SafeAreaView } from "react-native-safe-area-context";
import { width, widthToDp } from "rn-responsive-screen";
import Button from "../components/Button";
import { Actions } from "react-native-router-flux";
import AsyncStorage from "@react-native-async-storage/async-storage";
Теперь, когда вы получили данные и сохранили их в состоянии, пришло время отобразить продукты на экране корзины. Замените все ниже функции useEffect на следующий код:
return ( // SafeAreaView is used to avoid the notch on the phone <SafeAreaView style={[styles.container]}> {/* SchrollView is used in order to scroll the content */} <ScrollView contentContainerStyle={styles.container}> {/* Using the reusable header component */} <Header title="My Cart" />
{/* Mapping the products into the Cart component */} {cart?.items?.map((product) => ( <CartItem product={product} /> ))} </ScrollView> {/* Creating a seperate view to show the total amount and checkout button */} <View> <View style={styles.row}> <Text style={styles.cartTotalText}>Items</Text>
{/* Showing Cart Total */} <Text style={[ styles.cartTotalText, { color: "#4C4C4C", }, ]} > {/* Dividing the total by 100 because Medusa doesn't store numbers in decimal */} ${cart?.total / 100} </Text> </View> <View style={styles.row}> {/* Showing the discount (if any) */} <Text style={styles.cartTotalText}>Discount</Text> <Text style={[ styles.cartTotalText, { color: "#4C4C4C", }, ]} > - ${cart?.discount_total / 100} </Text> </View> <View style={[styles.row, styles.total]}> <Text style={styles.cartTotalText}>Total</Text> <Text style={[ styles.cartTotalText, { color: "#4C4C4C", }, ]} > {/* Calculating the total */}$ {cart?.total / 100 - cart?.discount_total / 100} </Text> </View> <View> {/* A button to navigate to checkout screen */} <Button large={true} onPress={() => { Actions.checkout({ cart, }); }} title={cart?.items?.length > 0 ? "Checkout" : "Empty Cart"} /> </View> </View> </SafeAreaView> ); }
// Styles.... const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", }, row: { flexDirection: "row", justifyContent: "space-between", width: widthToDp(90), marginTop: 10, }, total: { borderTopWidth: 1, paddingTop: 10, borderTopColor: "#E5E5E5", marginBottom: 10, }, cartTotalText: { fontSize: widthToDp(4.5), color: "#989899", }, });
Затем вы добавите базовый и простой компонент кнопки в screens/Products.js
, чтобы перейти к экрану корзины.
Добавьте приведенный ниже код после компонента ScrollView
на экране screens/Products.js
:
<View style={styles.addToCart}>
<Feather
name="shopping-cart"
size={24}
color="white"
onPress={() => Actions.cart()}
/>
</View>
Не забудьте также добавить объект стиля внутри функции StyleSheet.create
:
addToCart: {
position: "absolute",
bottom: 30,
right: 10,
backgroundColor: "#C37AFF",
width: widthToDp(12),
height: widthToDp(12),
borderRadius: widthToDp(10),
alignItems: "center",
padding: widthToDp(2),
justifyContent: "center",
},
Вам также необходимо импортировать значки перьев из @expo/vector-icons
вверху файла:
import { Feather } from "@expo/vector-icons";
Экран тестовой корзины
Экран корзины готов. Убедитесь, что сервер Medusa запущен, затем перезапустите приложение React Native.
Попробуйте добавить несколько товаров в корзину, а затем просмотреть корзину. Экран вашей корзины должен выглядеть так:
Оформление заказа и оплата
Теперь, когда мы завершили процесс оформления корзины, пришло время оформления заказа.
Настройте плагин Stripe
Перейдите в каталог сервера Medusa и установите плагин Stripe, выполнив следующую команду:
npm install medusa-payment-stripe
Добавьте следующий код в конец массива плагинов в файле medusa-config.js
.
const plugins = [
...,
{
resolve: `medusa-payment-stripe`,
options: {
api_key: process.env.STRIPE_API_KEY,
},
},
];
В этом уроке я использовал Stripe для обработки платежей. Перейдите на панель управления Stripe и включите переключатель Тестовый режим в правом верхнем углу. Нажмите кнопку разработчиков рядом с тестовым режимом. Слева выберите Ключ API, и вы увидите Ключ для публикации и Секретный ключ. Скопируйте два ключа, так как они понадобятся вам позже.
В файле .env
вставьте свой секретный ключ, где написано STRIPE_API_KEY
.
Включить Stripe в качестве поставщика платежей
Убедитесь, что сервер Medusa и панель администратора запущены. Теперь откройте панель администратора Medusa и выберите Настройки на боковой панели. Затем выберите Регионы.
Затем выберите регионы, в которые вы хотите добавить Stripe в качестве поставщика платежей. В настройках справа нажмите на значок с тремя точками и выберите «Редактировать сведения о регионе».
В открывшемся новом окне выберите Stripe в поле поставщиков платежных услуг. Закончив, нажмите кнопку Сохранить и закрыть.
Экран оформления заказа
Вернувшись в приложение React Native, создайте новый файл screens/Checkout.js
и добавьте в него простой компонент Text:
import { View, Text } from "react-native"; import React from "react";
export default function Checkout() { return ( <View> <Text>Checkout</Text> </View> ); }
Затем импортируйте экран оформления заказа в верхней части App.js
:
import Checkout from "./screens/Checkout";
Добавьте новый компонент Scene
под существующими компонентами Scene
в возвращенном JSX:
<Scene key="checkout" component={Checkout} hideNavBar />
В папке компонентов создайте новый файл с именем ShippingAddress.js
и добавьте в него следующий код:
// Importing a few package and components import { View, StyleSheet, Text, TextInput } from "react-native"; import React, { useState } from "react"; import { heightToDp } from "rn-responsive-screen";
export default function ShippingAddress({ onChange }) { // Passing onChange as a prop
// Declaring a few states to store the user's input const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [AddressLine1, setAddressLine1] = useState(""); const [AddressLine2, setAddressLine2] = useState(""); const [city, setCity] = useState(""); const [country, setCountry] = useState(""); const [province, setProvince] = useState(""); const [postalCode, setPostalCode] = useState(""); const [phone, setPhone] = useState(""); const [company, setCompany] = useState(""); const [email, setEmail] = useState("");
// Function to handle the user's input const handleChange = () => { // Creating an object to store the user's input let address = { first_name: firstName, last_name: lastName, email, address_1: AddressLine1, address_2: AddressLine2, city, province, postal_code: postalCode, phone, company, country }; // Calling the onChange function and passing the address object as an argument onChange(address); };
return ( // Creating a view to hold the user's input <View style={styles.container}> {/* Creating a text input for the user's first name */} <TextInput onChangeText={(e) => { // Setting the user's input to the firstName state setFirstName(e); // Calling the handleChange function handleChange(); }} placeholder="First Name" style={styles.input} /> <TextInput onChangeText={(e) => { setLastName(e); handleChange(); }} placeholder="Last Name" style={styles.input} /> <TextInput onChangeText={(e) => { setEmail(e); handleChange(); }} placeholder="Email" style={styles.input} /> <TextInput onChangeText={(e) => { setAddressLine1(e); handleChange(); }} placeholder="Address Line 1" style={styles.input} /> <TextInput onChangeText={(e) => { setAddressLine2(e); handleChange(); }} placeholder="Address Line 2" style={styles.input} /> <TextInput onChangeText={(e) => { setCity(e); handleChange(); }} placeholder="City" style={styles.input} /> <TextInput onChangeText={(e) => { setCountry(e); handleChange(); }} placeholder="Country" style={styles.input} /> <TextInput onChangeText={(e) => { setProvince(e); handleChange(); }} placeholder="Province" style={styles.input} /> <TextInput onChangeText={(e) => { setPostalCode(e); handleChange(); }} placeholder="Postal Code" style={styles.input} /> <TextInput onChangeText={(e) => { setPhone(e); handleChange(); }} placeholder="Phone" style={styles.input} /> <TextInput onChangeText={(e) => { setCompany(e); handleChange(); }} placeholder="Company" style={styles.input} /> </View> ); }
// Creating a stylesheet to style the view const styles = StyleSheet.create({ container: { marginTop: heightToDp(2), }, input: { borderWidth: 1, padding: 12, borderColor: "#E5E5E5", borderRadius: 5, marginTop: 10.2, }, });
В приведенном выше коде вы импортировали несколько компонентов и создали функцию, которая обрабатывает событие onChange
для входных данных. Вы также создали несколько TextInputs
для адреса доставки.
Создайте новый файл с именем RadioButton.js
в папке компонентов и добавьте в него приведенный ниже код. Это очень простой компонент, который будет отображать переключатели:
import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import React from "react";
const RadioButton = ({ onPress, selected, children }) => { return ( <View style={styles.radioButtonContainer}> <TouchableOpacity onPress={onPress} style={styles.radioButton}> {selected ? <View style={styles.radioButtonIcon} /> : null} </TouchableOpacity> <TouchableOpacity onPress={onPress}> <Text style={styles.radioButtonText}>{children}</Text> </TouchableOpacity> </View> ); }; const styles = StyleSheet.create({ radioButtonContainer: { flexDirection: "row", alignItems: "center", marginRight: 45, }, radioButton: { height: 20, width: 20, backgroundColor: "#F8F8F8", borderRadius: 10, borderWidth: 1, borderColor: "#E6E6E6", alignItems: "center", justifyContent: "center", }, radioButtonIcon: { height: 14, width: 14, borderRadius: 7, backgroundColor: "#C37AFF", }, radioButtonText: { fontSize: 16, marginLeft: 16, }, }); export default RadioButton;
Создайте еще один файл с именем stripe.js
в папке с константами и добавьте приведенный ниже код. Обязательно обновите YOUR_STRIPE_PUBLISHABLE_KEY
своим публикуемым ключом:
const publishable_key = "YOUR_STRIPE_PUBLISHABLE_KEY";
export { publishable_key };
Затем установите Stripe SDK для React Native, используя следующую команду:
npm install @stripe/stripe-react-native
Теперь обновите экран проверки (screens/Checkout.js
), заменив импорт следующими компонентами и зависимостями:
import { View, Text, StyleSheet } from "react-native";
import React, { useEffect, useState } from "react";
import Header from "../components/Header";
import axios from "axios";
import baseURL from "../constants/url";
import { ScrollView } from "react-native-gesture-handler";
import { SafeAreaView } from "react-native-safe-area-context";
import { heightToDp, widthToDp } from "rn-responsive-screen";
import Button from "../components/Button";
import ShippingAddress from "../components/ShippingAddress";
import Payment from "../components/Payment";
import { publishable_key } from "../constants/stripe";
import RadioButton from "../components/RadioButton";
import { CardField, useStripe } from "@stripe/stripe-react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { StripeProvider } from "@stripe/stripe-react-native";
Затем создайте несколько useStates
, которые будут использоваться для захвата входных значений в компоненте Checkout
:
const [paymentInfo, setPaymentInfo] = useState({}); const [shippingAddress, setShippingAddress] = useState({}); const [shippingOptions, setShippingOptions] = useState([]); const [selectedShippingOption, setSelectedShippingOption] = useState(""); const [paymentSession, setPaymentSession] = useState({});
const { confirmPayment } = useStripe();
Создайте две функции, которые будут использоваться для захвата входных значений, которые мы объявим позже.
// const [selectedShippingOption, setSelectedShippingOption] = useState(""); // ......
const handlePaymentInputChange = (card) => { setPaymentInfo(card.values); };
const handleAddressInputChange = (address) => { setShippingAddress(address); };
При открытии страницы и до того, как платежные системы будут показаны клиенту на выбор, необходимо инициализировать платежные сессии. Для этого мы создаем функцию с именем InitializePaymentSessions
и вызываем ее в функции useEffect
. Вот как должен выглядеть код:
const InitializePaymentSessions = async () => { // Getting cart id from async storage let cart_id = await AsyncStorage.getItem("cart_id"); // Intializing payment session axios .post(`${baseURL}/store/carts/${cart_id}/payment-sessions`) .then(({ data }) => { axios .post(`${baseURL}/store/carts/${cart_id}/payment-session`, { provider_id: "stripe", }) .then(({ data }) => { setPaymentSession(data.cart.payment_session); }); }); };
useEffect(() => { // Calling the function to fetch the payment options when the component mounts fetchPaymentOption(); }, []);
Затем создайте другие функции для обработки платежей. Добавьте следующие функции Checkout.js
:
const handlePayment = async () => {
// Getting client secret from the payment session state const clientSecret = paymentSession.data ? paymentSession.data.client_secret : paymentSession.client_secret
const billingDetails = { email: shippingAddress.email, phone: shippingAddress.phone, addressCity: shippingAddress.city, addressCountry: shippingAddress.country, addressLine1: shippingAddress.address_1, addressLine2: shippingAddress.address_2, addressPostalCode: shippingAddress.postalCode, }; const { error, paymentIntent } = await confirmPayment(clientSecret, { type: "Card", billingDetails, }); if (error) { alert("Payment failed", error); } if (paymentIntent) { alert("Payment successful");
// Calling the complete cart function to empty the cart and redirect to the home screen completeCart(); }
};
const completeCart = async () => { const cartId = await AsyncStorage.getItem("cart_id");
// Sending a request to the server to empty the cart axios .post(`${baseURL}/store/carts/${cartId}/complete`) .then(async (res) => { // Removing the cart_id from the local storage await AsyncStorage.removeItem("cart_id"); // Redirecting to the home screen Actions.push("products"); }); };
// Calling the API when user presses the "Place Order" button const placeOrder = async () => { // Getting cart id from async storage let cart_id = await AsyncStorage.getItem("cart_id"); // Post shipping address to server axios .post(`${baseURL}/store/carts/${cart_id}`, { shipping_address: shippingAddress, }) .then(({ data }) => { // Post shipping method to server axios .post(`${baseURL}/store/carts/${cart_id}/shipping-methods`, { option_id: selectedShippingOption, }) .then(({ data }) => { // Calling the handle Payment API handlePayment(); }); }); };
const fetchPaymentOption = async () => { // Getting cart id from async storage let cart_id = await AsyncStorage.getItem("cart_id");
// Fetch shipping options from server axios .get(`${baseURL}/store/shipping-options/${cart_id}`) .then(({ data }) => { setShippingOptions(data.shipping_options); // Initializing payment session InitializePaymentSessions(); }); };
Эти функции позволяют получать варианты оплаты и доставки от Medusa. Он также обрабатывает платеж через Stripe, а затем обрабатывает оплату в Medusa.
Вы также можете заменить функцию возврата кодом ниже:
return ( <StripeProvider publishableKey={publishable_key}> <SafeAreaView style={styles.container}> <ScrollView> <Header title="Checkout" /> <View style={styles.address}> <Text style={styles.title}>Shipping Address</Text> <ShippingAddress onChange={handleAddressInputChange} /> </View>
<View style={styles.payment}> <Text style={styles.title}>Payment</Text> <CardField postalCodeEnabled={false} placeholders={{ number: "4242 4242 4242 4242", }} cardStyle={{ backgroundColor: "#FFFFFF", textColor: "#000000", }} style={{ width: "100%", height: 50, marginVertical: 30, }} onCardChange={(cardDetails) => { handlePaymentInputChange(cardDetails); }} onFocus={(focusedField) => { console.log("focusField", focusedField); }} /> </View> <View style={styles.shipping}> <Text style={styles.title}>Shipping Options</Text> {shippingOptions.map((option) => ( <View style={styles.shippingOption}> <RadioButton onPress={() => setSelectedShippingOption(option.id)} key={option.id} selected={selectedShippingOption === option.id} children={option.name} /> </View> ))}
<Button onPress={placeOrder} large title="Place Order" /> </View> </ScrollView> </SafeAreaView> </StripeProvider> );
Наконец, добавьте следующий объект стиля в конец файла:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
address: {
marginHorizontal: widthToDp(5),
},
payment: {
marginHorizontal: widthToDp(5),
marginTop: heightToDp(4),
},
shipping: {
marginHorizontal: widthToDp(5),
},
title: {
fontSize: widthToDp(4.5),
},
shippingOption: {
marginTop: heightToDp(2),
},
});
Вот и все! Теперь вы сможете совершать платежи и покупать товары в магазине.
Тестовый экран проверки
Теперь попробуйте добавить несколько товаров в корзину и перейти к экрану оформления заказа, вы сможете добавить адрес и произвести оплату. Вот как должен выглядеть экран оформления заказа:
Что дальше?
Эта статья является частью 2 статьи Создание приложения для электронной коммерции с помощью Medusa и React Native. Вы также можете добавить дополнительные функции с помощью Medusa:
- Добавьте поисковую систему с помощью MeiliSearch.
- Ознакомьтесь с документацией, чтобы узнать, что еще можно сделать с Medusa.
Если у вас есть какие-либо проблемы или вопросы, связанные с Medusa, не стесняйтесь обращаться к команде Medusa через Discord.