先决条件
在开始之前,请确保您对 Next.js 和 React 有基本的了解。
我们将创建一个与我们的 Geolocation API 交互的 Next.js API 路由。
在以下位置创建一个新文件:src/app/api/geolocation/route.ts
import { NextResponse } from "next/server"; import axios from "axios"; type IPGeolocation = { ip: string; version?: string; city?: string; region?: string; region_code?: string; country_code?: string; country_code_iso3?: string; country_fifa_code?: string; country_fips_code?: string; country_name?: string; country_capital?: string; country_tld?: string; country_emoji?: string; continent_code?: string; in_eu: boolean; land_locked: boolean; postal?: string; latitude?: number; longitude?: number; timezone?: string; utc_offset?: string; country_calling_code?: string; currency?: string; currency_name?: string; languages?: string; country_area?: number; asn?: string; // Append ?fields=asn to the URL isp?: string; // Append ?fields=isp to the URL } type IPGeolocationError = { code: string; error: string; } export async function GET() { // Retrieve IP address using the getClientIp function // For testing purposes, we'll use a fixed IP address // const clientIp = getClientIp(req.headers); const clientIp = "84.17.50.173"; if (!clientIp) { return NextResponse.json( { error: "Unable to determine IP address" }, { status: 400 } ); } const key = process.env.IPFLARE_API_KEY; if (!key) { return NextResponse.json( { error: "IPFlare API key is not set" }, { status: 500 } ); } try { const response = await axios.get<IPGeolocation | IPGeolocationError>( `https://api.ipflare.io/${clientIp}`, { headers: { "X-API-Key": key, }, } ); if ("error" in response.data) { return NextResponse.json({ error: response.data.error }, { status: 400 }); } return NextResponse.json(response.data); } catch { return NextResponse.json( { error: "Internal Server Error" }, { status: 500 } ); } }
我们将使用名为 IP Flare 的免费地理定位服务。访问 API 密钥页面:导航至 API 密钥页面。
访问:www.ipflare.io
从 API 密钥页面我们可以获得 API 密钥,并且可以使用快速复制将其作为环境变量存储在 .env 文件中。我们将用它来验证我们的请求。
我创建了这个一体化组件,其中包括提供程序和货币选择器。我正在使用 shadcn/ui 和一些我在网上找到的标志 SVG。
您需要将应用程序包装在
现在,在应用程序中我们想要访问货币的任何地方,我们都可以使用钩子 const {currency } = useCurrency();。
要将其与 Stripe 集成,当您创建结帐时,您只需发送货币并确保您已将多货币定价添加到您的 Stripe 产品中。
"use client"; import { useRouter } from "next/navigation"; import { createContext, type FC, type ReactNode, useContext, useEffect, useMemo, useState, } from "react"; import axios from "axios"; // 1) Import axios import { Flag } from "~/components/flag"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select"; import { cn } from "~/lib/utils"; import { type Currency } from "~/server/schemas/currency"; // -- [1] Create a local type for the data returned by /api/geolocation. type GeolocationData = { country_code?: string; continent_code?: string; currency?: string; }; type CurrencyContext = { currency: Currency; setCurrency: (currency: Currency) => void; }; const CurrencyContext = createContext<CurrencyContext | null>(null); export function useCurrency() { const context = useContext(CurrencyContext); if (!context) { throw new Error("useCurrency must be used within a CurrencyProvider."); } return context; } export const CurrencyProvider: FC<{ children: ReactNode }> = ({ children }) => { const router = useRouter(); // -- [2] Local state for geolocation data const [location, setLocation] = useState<GeolocationData | null>(null); const [isLoading, setIsLoading] = useState<boolean>(true); // -- [3] Fetch location once when the component mounts useEffect(() => { const fetchLocation = async () => { setIsLoading(true); try { const response = await axios.get("/api/geolocation"); setLocation(response.data); } catch (error) { console.error(error); } finally { setIsLoading(false); } }; void fetchLocation(); }, []); // -- [4] Extract currency from location if present (fallback to "usd") const geoCurrency = location?.currency; const getInitialCurrency = (): Currency => { if (typeof window !== "undefined") { const cookie = document.cookie .split("; ") .find((row) => row.startsWith("currency=")); if (cookie) { const value = cookie.split("=")[1]; if (value === "usd" || value === "eur" || value === "gbp") { return value; } } } return "usd"; }; const [currency, setCurrencyState] = useState<Currency>(getInitialCurrency); useEffect(() => { if (!isLoading && geoCurrency !== undefined) { const validatedCurrency = validateCurrency(geoCurrency, location); if (validatedCurrency) { setCurrency(validatedCurrency); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoading, location, geoCurrency]); // -- [5] Update currency & store cookie; no more tRPC invalidation const setCurrency = (newCurrency: Currency) => { setCurrencyState(newCurrency); if (typeof window !== "undefined") { document.cookie = `currency=${newCurrency}; path=/; max-age=${ 60 * 60 * 24 * 365 }`; // Expires in 1 year } // Removed tRPC invalidate since we are no longer using tRPC router.refresh(); }; const contextValue = useMemo<CurrencyContext>( () => ({ currency, setCurrency, }), [currency], ); return ( <CurrencyContext.Provider value={contextValue}> {children} </CurrencyContext.Provider> ); }; export const CurrencySelect = ({ className }: { className?: string }) => { const { currency, setCurrency } = useCurrency(); return ( <Select value={currency} onValueChange={setCurrency}> <SelectTrigger className={cn("w-[250px]", className)}> <SelectValue placeholder="Select a currency" /> </SelectTrigger> <SelectContent> <SelectGroup className="text-sm"> <SelectItem value="usd"> <div className="flex items-center gap-3"> <Flag code="US" className="h-4 w-4 rounded" /> <span>$ USD</span> </div> </SelectItem> <SelectItem value="eur"> <div className="flex items-center gap-3"> <Flag code="EU" className="h-4 w-4 rounded" /> <span>€ EUR</span> </div> </SelectItem> <SelectItem value="gbp"> <div className="flex items-center gap-3"> <Flag code="GB" className="h-4 w-4 rounded" /> <span>£ GBP</span> </div> </SelectItem> </SelectGroup> </SelectContent> </Select> ); }; // -- [6] Use our new GeolocationData type in place of RouterOutputs const validateCurrency = ( currency: string, location?: GeolocationData | null, ): Currency | null => { if (currency === "usd" || currency === "eur" || currency === "gbp") { return currency; } if (!location) { return null; } if (location.country_code === "GB") { return "gbp"; } // Check if they are in the EU if (location.continent_code === "EU") { return "eur"; } // North America if (location.continent_code === "NA") { return "usd"; } return null; };
以上是在 Next.js 中构建自动货币切换器的详细内容。更多信息请关注PHP中文网其他相关文章!