Stripe 45 min
parent
742024ab35
commit
3565652962
4
.env
4
.env
|
@ -1,4 +1,6 @@
|
|||
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_bWVhc3VyZWQtcGFudGhlci04Mi5jbGVyay5hY2NvdW50cy5kZXYk
|
||||
DATABASE_URL=postgresql://jsm_uber_owner:npg_wsprVk4dJ8XW@ep-rapid-field-a1prd9ss-pooler.ap-southeast-1.aws.neon.tech/jsm_uber?sslmode=require
|
||||
EXPO_PUBLIC_GEOAPIFY_API_KEY=0e7ccfac62054b0f846032bff6bae5c9
|
||||
EXPO_PUBLIC_GOOGLE_API_KEY=AIzaSyD10tc4ec2FFVXQcWDXCT2CeBy-jwbRZB8
|
||||
EXPO_PUBLIC_GOOGLE_API_KEY=AIzaSyD10tc4ec2FFVXQcWDXCT2CeBy-jwbRZB8
|
||||
EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51RBCg0EEiRKTsjR3t4krSuirMpl6cf28QcyIpMlWD6eIZoU9qBCGBhoSwr9zlRLEQsoUwCusZupgnOMmOAyYG19U00S86heUPX
|
||||
STRIPE_SECRET_KEY=sk_test_51RBCg0EEiRKTsjR3wFKcVMiOhedbosVqKnl5fCYhTDsQkXZVx7UJqUiXRu6b9ghbP6yrqMnom1GePKtUAgVl9twg00dJzPIgBz
|
|
@ -5,21 +5,26 @@ import RideLayout from "@/components/RideLayout";
|
|||
import {icons} from "@/constants";
|
||||
import {formatTime} from "@/lib/utils";
|
||||
import {useDriverStore, useLocationStore} from "@/store";
|
||||
import Payment from "@/components/Payment";
|
||||
import { StripeProvider } from '@stripe/stripe-react-native';
|
||||
|
||||
const BookRide = () => {
|
||||
const {user} = useUser();
|
||||
const {userAddress, destinationAddress} = useLocationStore();
|
||||
const {drivers, selectedDriver} = useDriverStore();
|
||||
console.log(drivers);
|
||||
console.log(selectedDriver);
|
||||
const driverDetails = drivers?.filter(
|
||||
(driver) => +driver.id === selectedDriver,
|
||||
)[0];
|
||||
|
||||
return (
|
||||
<StripeProvider
|
||||
publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY!}
|
||||
merchantIdentifier="merchant.identifier" // required for Apple Pay
|
||||
urlScheme="your-url-scheme" // required for 3D Secure and bank redirects
|
||||
>
|
||||
<RideLayout title="Book Ride">
|
||||
<>
|
||||
<Text style={tw`text-xl font-JakartaSemiBold mb-3`}>
|
||||
<Text style={tw`text-xl font-JakartaSemiBold mb-3`} >
|
||||
Ride Information
|
||||
</Text>
|
||||
|
||||
|
@ -87,8 +92,16 @@ const BookRide = () => {
|
|||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Payment
|
||||
fullName={user?.fullName!}
|
||||
email={user?.emailAddresses[0].emailAddress!}
|
||||
amount={driverDetails?.price!}
|
||||
driverId={driverDetails?.id!}
|
||||
rideTime={driverDetails?.time!}
|
||||
/>
|
||||
</>
|
||||
</RideLayout>
|
||||
</StripeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import {Stripe} from "stripe";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SERCET_KEY!);
|
||||
export async function POST(request:Request) {
|
||||
const body = await request.json();
|
||||
const { name, email, amount} = body;
|
||||
if(!name || !email || !amount){
|
||||
return new Response (JSON.stringify({error:"please enter a valid email Address",status:400}))
|
||||
}
|
||||
let customer;
|
||||
const existingCustomer = await stripe.customers.list({email});
|
||||
if(existingCustomer.data.length > 0){
|
||||
customer = existingCustomer.data[0];
|
||||
}else{
|
||||
const newCustomer = await stripe.customers.create({
|
||||
name,
|
||||
email,
|
||||
});
|
||||
customer = newCustomer;
|
||||
}
|
||||
|
||||
const ephemeralKey = await stripe.ephemeralKeys.create(
|
||||
{customer: customer.id},
|
||||
{apiVersion: '2025-03-31.basil'}
|
||||
);
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: parseInt(amount) * 100,
|
||||
currency: 'usd',
|
||||
customer: customer.id,
|
||||
automatic_payment_methods: {
|
||||
enabled: true,
|
||||
allow_redirects:"never",
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
paymentIntent: paymentIntent.client_secret,
|
||||
ephemeralKey: ephemeralKey.secret,
|
||||
customer: customer.id,
|
||||
}),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import {Stripe} from "stripe";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SERCET_KEY!);
|
||||
export async function POST(request:Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { payment_method_id,payment_intent_id,customer_id} = body;
|
||||
if(!payment_method_id || !payment_intent_id || !customer_id) {
|
||||
return new Response (
|
||||
JSON.stringify({
|
||||
error:"Missing required payment infomation",
|
||||
status:400
|
||||
}),
|
||||
);
|
||||
}
|
||||
const paymentMethod = await stripe.paymentMethods.attach(payment_method_id,{customer:customer_id});
|
||||
const result = await stripe.paymentIntents.confirm(payment_intent_id,{payment_method:paymentMethod.id,});
|
||||
return new Response (
|
||||
JSON.stringify({
|
||||
success:true,
|
||||
message:"Payment confirmed successfully",result:result,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error:error,
|
||||
status:500,
|
||||
})
|
||||
)
|
||||
}
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
import { data } from "@/constants";
|
||||
import { neon } from "@neondatabase/serverless";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const sql = neon(`${process.env.DATABASE_URL}`);
|
||||
const response = await sql`SELECT * FROM drivers`;
|
||||
return Response.json({data:response})
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Response.json({error:error});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import {neon} from "@neondatabase/serverless";
|
||||
|
||||
export async function GET(request: Request, {id}: { id: string }) {
|
||||
if (!id)
|
||||
return Response.json({error: "Missing required fields"}, {status: 400});
|
||||
|
||||
try {
|
||||
const sql = neon(`${process.env.DATABASE_URL}`);
|
||||
const response = await sql`
|
||||
SELECT
|
||||
rides.ride_id,
|
||||
rides.origin_address,
|
||||
rides.destination_address,
|
||||
rides.origin_latitude,
|
||||
rides.origin_longitude,
|
||||
rides.destination_latitude,
|
||||
rides.destination_longitude,
|
||||
rides.ride_time,
|
||||
rides.fare_price,
|
||||
rides.payment_status,
|
||||
rides.created_at,
|
||||
'driver', json_build_object(
|
||||
'driver_id', drivers.id,
|
||||
'first_name', drivers.first_name,
|
||||
'last_name', drivers.last_name,
|
||||
'profile_image_url', drivers.profile_image_url,
|
||||
'car_image_url', drivers.car_image_url,
|
||||
'car_seats', drivers.car_seats,
|
||||
'rating', drivers.rating
|
||||
) AS driver
|
||||
FROM
|
||||
rides
|
||||
INNER JOIN
|
||||
drivers ON rides.driver_id = drivers.id
|
||||
WHERE
|
||||
rides.user_id = ${id}
|
||||
ORDER BY
|
||||
rides.created_at DESC;
|
||||
`;
|
||||
|
||||
return Response.json({data: response});
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent rides:", error);
|
||||
return Response.json({error: "Internal Server Error"}, {status: 500});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
import {neon} from "@neondatabase/serverless";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const {
|
||||
origin_address,
|
||||
destination_address,
|
||||
origin_latitude,
|
||||
origin_longitude,
|
||||
destination_latitude,
|
||||
destination_longitude,
|
||||
ride_time,
|
||||
fare_price,
|
||||
payment_status,
|
||||
driver_id,
|
||||
user_id,
|
||||
} = body;
|
||||
|
||||
if (
|
||||
!origin_address ||
|
||||
!destination_address ||
|
||||
!origin_latitude ||
|
||||
!origin_longitude ||
|
||||
!destination_latitude ||
|
||||
!destination_longitude ||
|
||||
!ride_time ||
|
||||
!fare_price ||
|
||||
!payment_status ||
|
||||
!driver_id ||
|
||||
!user_id
|
||||
) {
|
||||
return Response.json(
|
||||
{error: "Missing required fields"},
|
||||
{status: 400},
|
||||
);
|
||||
}
|
||||
|
||||
const sql = neon(`${process.env.DATABASE_URL}`);
|
||||
|
||||
const response = await sql`
|
||||
INSERT INTO rides (
|
||||
origin_address,
|
||||
destination_address,
|
||||
origin_latitude,
|
||||
origin_longitude,
|
||||
destination_latitude,
|
||||
destination_longitude,
|
||||
ride_time,
|
||||
fare_price,
|
||||
payment_status,
|
||||
driver_id,
|
||||
user_id
|
||||
) VALUES (
|
||||
${origin_address},
|
||||
${destination_address},
|
||||
${origin_latitude},
|
||||
${origin_longitude},
|
||||
${destination_latitude},
|
||||
${destination_longitude},
|
||||
${ride_time},
|
||||
${fare_price},
|
||||
${payment_status},
|
||||
${driver_id},
|
||||
${user_id}
|
||||
)
|
||||
RETURNING *;
|
||||
`;
|
||||
|
||||
return Response.json({data: response[0]}, {status: 201});
|
||||
} catch (error) {
|
||||
console.error("Error inserting data into recent_rides:", error);
|
||||
return Response.json({error: "Internal Server Error"}, {status: 500});
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ const DriverCard = ({item, selected, setSelected}: DriverCardProps) => {
|
|||
</Text>
|
||||
|
||||
<Text style={tw`text-sm font-JakartaRegular text-general-800`}>
|
||||
{formatTime(item.time!)}
|
||||
{formatTime(parseInt(`${item.time}`) || 5)}
|
||||
</Text>
|
||||
|
||||
<Text style={tw`text-sm font-JakartaRegular text-general-800 mx-1`}>
|
||||
|
|
|
@ -1,65 +1,49 @@
|
|||
import { useDriverStore, useLocationStore } from "@/store";
|
||||
import { View, Text } from "react-native";
|
||||
import { calculateRegion, generateMarkersFromData } from "@/lib/map";
|
||||
import { calculateDriverTimes, calculateRegion, generateMarkersFromData } from "@/lib/map";
|
||||
import MapView, { Marker, PROVIDER_DEFAULT } from "react-native-maps";
|
||||
import tw from "twrnc";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MarkerData } from "@/types/type";
|
||||
import { Driver, MarkerData } from "@/types/type";
|
||||
import { icons } from "@/constants";
|
||||
const drivers = [
|
||||
{
|
||||
"id": "1",
|
||||
"first_name": "James",
|
||||
"last_name": "Wilson",
|
||||
"profile_image_url": "https://ucarecdn.com/dae59f69-2c1f-48c3-a883-017bcf0f9950/-/preview/1000x666/",
|
||||
"car_image_url": "https://ucarecdn.com/a2dc52b2-8bf7-4e49-9a36-3ffb5229ed02/-/preview/465x466/",
|
||||
"car_seats": 4,
|
||||
"rating": "4.80"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"first_name": "David",
|
||||
"last_name": "Brown",
|
||||
"profile_image_url": "https://ucarecdn.com/6ea6d83d-ef1a-483f-9106-837a3a5b3f67/-/preview/1000x666/",
|
||||
"car_image_url": "https://ucarecdn.com/a3872f80-c094-409c-82f8-c9ff38429327/-/preview/930x932/",
|
||||
"car_seats": 5,
|
||||
"rating": "4.60"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"first_name": "Michael",
|
||||
"last_name": "Johnson",
|
||||
"profile_image_url": "https://ucarecdn.com/0330d85c-232e-4c30-bd04-e5e4d0e3d688/-/preview/826x822/",
|
||||
"car_image_url": "https://ucarecdn.com/289764fb-55b6-4427-b1d1-f655987b4a14/-/preview/930x932/",
|
||||
"car_seats": 4,
|
||||
"rating": "4.70"
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"first_name": "Robert",
|
||||
"last_name": "Green",
|
||||
"profile_image_url": "https://ucarecdn.com/fdfc54df-9d24-40f7-b7d3-6f391561c0db/-/preview/626x417/",
|
||||
"car_image_url": "https://ucarecdn.com/b6fb3b55-7676-4ff3-8484-fb115e268d32/-/preview/930x932/",
|
||||
"car_seats": 4,
|
||||
"rating": "4.90"
|
||||
}
|
||||
]
|
||||
import { useFetch } from "@/lib/fetch";
|
||||
import { ActivityIndicator, Text, View } from "react-native";
|
||||
|
||||
const Map = () => {
|
||||
const { data: drivers, loading, error } = useFetch<Driver[]>("/(api)/driver");
|
||||
const { userLongitude, userLatitude, destinationLatitude, destinationLongitude } = useLocationStore();
|
||||
const { selectedDriver, setDrivers } = useDriverStore();
|
||||
const [markers, setMarkers] = useState<MarkerData[]>();
|
||||
const [markers, setMarkers] = useState<MarkerData[]>([]);
|
||||
const region = calculateRegion({
|
||||
userLongitude, userLatitude, destinationLatitude, destinationLongitude,
|
||||
});
|
||||
useEffect(() => {
|
||||
// TODO:Remove
|
||||
setDrivers(drivers);
|
||||
if (Array.isArray(drivers)) {
|
||||
if (!userLatitude || !userLongitude) return;
|
||||
const newMarkers = generateMarkersFromData({ data : drivers, userLatitude, userLongitude, });
|
||||
setMarkers(newMarkers)
|
||||
const newMarkers = generateMarkersFromData({ data: drivers, userLatitude, userLongitude, });
|
||||
setMarkers(newMarkers);
|
||||
}
|
||||
}, [drivers])
|
||||
}, [drivers]);
|
||||
useEffect(() => {
|
||||
if (markers.length > 0 && destinationLatitude && destinationLongitude) {
|
||||
calculateDriverTimes({ markers, userLongitude, userLatitude, destinationLatitude, destinationLongitude }).then((drivers) => {
|
||||
setDrivers(drivers as MarkerData[]);
|
||||
});
|
||||
}
|
||||
}, [markers, destinationLatitude, destinationLongitude]);
|
||||
if (loading || !userLatitude || !userLongitude) {
|
||||
return (
|
||||
<View style={tw`flex justify-between items-center w-full`}>
|
||||
<ActivityIndicator size="small" color="#000" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<View style={tw`flex justify-between items-center w-full`}>
|
||||
<Text>Error:{error}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MapView provider={PROVIDER_DEFAULT} style={tw`w-full h-full rounded-2xl `} tintColor="black" mapType="standard" showsPointsOfInterest={false} initialRegion={region} showsUserLocation={true} userInterfaceStyle="light" >
|
||||
{markers?.map((marker) => (
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import { Alert, Text, View } from "react-native";
|
||||
import CustomButton from "./CustomButton";
|
||||
import tw from "twrnc";
|
||||
import { PaymentMethod, PaymentSheetError, useStripe } from "@stripe/stripe-react-native";
|
||||
import { useState, useEffect } from "react";
|
||||
import { fetchAPI } from "@/lib/fetch";
|
||||
import { PaymentProps } from "@/types/type";
|
||||
const Payment = ({fullName,email,amount,driverId,rideTime}:PaymentProps) => {
|
||||
const [success, setSuccess] = useState(false);
|
||||
const { initPaymentSheet, presentPaymentSheet } = useStripe();
|
||||
const confirmHandler = async (paymentMethod,_, intentCreationCallback) => {
|
||||
const {paymentIntent,customer} = await fetchAPI("/(api)/(stripe)/create",{
|
||||
method:"POST",
|
||||
headers:{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body:JSON.stringify({
|
||||
name:fullName || email.split("@")[0],
|
||||
email: email,
|
||||
amount: amount,
|
||||
paymentMethodId : paymentMethod.id,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if(paymentIntent.client_sercet){
|
||||
const {result} = await fetchAPI ("/(api)/(stripe)/pay",{
|
||||
method:"POST",
|
||||
headers:{
|
||||
"Content-Type":"application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
payment_method_id:paymentMethod.id,
|
||||
payment_intent_id:paymentIntent.id,
|
||||
customer_id:customer,
|
||||
}),
|
||||
});
|
||||
if (result.client_sercet) {
|
||||
//ride/create
|
||||
}
|
||||
}
|
||||
const { clientSercet, error } = await response.json();
|
||||
if(clientSercet){
|
||||
intentCreationCallback({clientSercet})
|
||||
}
|
||||
else{
|
||||
intentCreationCallback({error})
|
||||
}
|
||||
}
|
||||
const initializePaymentSheet = async () => {
|
||||
const { error } = await initPaymentSheet({
|
||||
merchantDisplayName: "Example,Inc.",
|
||||
intentConfiguration: {
|
||||
mode: {
|
||||
amount: 1099,
|
||||
currencyCode: "USD",
|
||||
},
|
||||
confirmHandler: confirmHandler
|
||||
}
|
||||
});
|
||||
if (error) {
|
||||
//handle error
|
||||
}
|
||||
};
|
||||
const openPaymentSheet = async () => {
|
||||
await initializePaymentSheet();
|
||||
|
||||
const { error } = await presentPaymentSheet();
|
||||
|
||||
if (error) {
|
||||
Alert.alert(`Error code: ${error.code}`, error.message);
|
||||
}
|
||||
else {
|
||||
setSuccess(true);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<CustomButton title="Confirm Ride" style={tw`my-10`} onPress={openPaymentSheet} />
|
||||
</>
|
||||
)
|
||||
};
|
||||
export default Payment;
|
|
@ -15,6 +15,7 @@
|
|||
"@neondatabase/serverless": "^0.10.4",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"@stripe/stripe-react-native": "0.38.6",
|
||||
"expo": "52.0.42",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.8",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"react-native-swiper": "^1.6.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.5",
|
||||
"stripe": "^18.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"twrnc": "^4.6.1",
|
||||
"zustand": "^5.0.3"
|
||||
|
@ -4976,6 +4978,22 @@
|
|||
"node": ">=12.16"
|
||||
}
|
||||
},
|
||||
"node_modules/@stripe/stripe-react-native": {
|
||||
"version": "0.38.6",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-react-native/-/stripe-react-native-0.38.6.tgz",
|
||||
"integrity": "sha512-U6yELoRr4h4x+p9an0MiDXZBbm/FYNayPXJP0PtsR3iFVAGpGQm6DzM+cye2PVlhbDK8htBA5ReZ1YEFxT/hJA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": ">=46.0.9",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
|
@ -6981,7 +6999,6 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
|
@ -14160,7 +14177,6 @@
|
|||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
@ -16984,7 +17000,6 @@
|
|||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
|
@ -17004,7 +17019,6 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
|
@ -17021,7 +17035,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
|
@ -17040,7 +17053,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
|
@ -17602,6 +17614,34 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stripe": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-18.0.0.tgz",
|
||||
"integrity": "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.*"
|
||||
}
|
||||
},
|
||||
"node_modules/stripe/node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/structured-headers": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"@neondatabase/serverless": "^0.10.4",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"@stripe/stripe-react-native": "0.38.6",
|
||||
"expo": "52.0.42",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.8",
|
||||
|
@ -55,6 +56,7 @@
|
|||
"react-native-swiper": "^1.6.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.5",
|
||||
"stripe": "^18.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"twrnc": "^4.6.1",
|
||||
"zustand": "^5.0.3"
|
||||
|
|
Loading…
Reference in New Issue