使用 React Native Expo 实施条带支付时出错
Error when implementing stripe payment with react native expo
我在尝试使用 React Native 和 Expo SDK 实现 Stripe 支付时遇到错误。
场景非常简单,我将商品添加到购物车,然后选择支付选项作为支付卡,但是当我点击卡时,出现错误。错误和代码如下。
import { StatusBar } from "expo-status-bar";
import React from "react";
import {
StyleSheet,
Text,
View,
Button,
Pressable,
Platform,
} from "react-native";
import { StripeProvider } from "@stripe/stripe-react-native";
import { initStripe, useStripe } from "@stripe/stripe-react-native";
import GooglePayMark from "./GooglePayMark";
import ApplePayMark from "./ApplePayMark";
const API_URL = "http://192.168.0.163:3000";
const ProductRow = ({ product, cart, setCart }) => {
const modifyCart = (delta) => {
setCart({ ...cart, [product.id]: cart[product.id] + delta });
};
return (
<View style={styles.productRow}>
<View style={{ flexDirection: "row" }}>
<Text style={{ fontSize: 17, flexGrow: 1 }}>
{product.name} - {product.price}$
</Text>
<Text style={{ fontSize: 17, fontWeight: "700" }}>
{cart[product.id]}
</Text>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 8,
}}
>
<Button
disabled={cart[product.id] <= 0}
title="Remove"
onPress={() => modifyCart(-1)}
/>
<Button title="Add" onPress={() => modifyCart(1)} />
</View>
</View>
);
};
const ProductsScreen = ({ products, navigateToCheckout }) => {
/**
* We will save the state of the cart here
* It will have the inital shape:
* {
* [product.id]: 0
* }
*/
const [cart, setCart] = React.useState(
Object.fromEntries(products.map((p) => [p.id, 0]))
);
const handleContinuePress = async () => {
/* Send the cart to the server */
const URL = `${API_URL}/create-payment-intent`;
const response = await fetch(URL, {
method: "POST",
headers: {
"Content-Type": "application-json",
},
body: JSON.stringify(cart),
});
/* Await the response */
const { publishableKey, clientSecret, merchantName } =
await response.json();
/* Navigate to the CheckoutScreen */
/* You can use navigation.navigate from react-navigation */
navigateToCheckout({
publishableKey,
clientSecret,
merchantName,
cart,
products,
});
};
return (
<View style={styles.screen}>
{products.map((p) => {
return (
<ProductRow key={p.id} product={p} cart={cart} setCart={setCart} />
);
})}
<View style={{ marginTop: 16 }}>
<Button title="Continue" onPress={handleContinuePress} />
</View>
</View>
);
};
/**
* CheckoutScreen related components
*/
const CartInfo = ({ products, cart }) => {
return (
<View>
{Object.keys(cart).map((productId) => {
const product = products.filter((p) => p.id === productId)[0];
const quantity = cart[productId];
return (
<View
key={productId}
style={[{ flexDirection: "row" }, styles.productRow]}
>
<Text style={{ flexGrow: 1, fontSize: 17 }}>
{quantity} x {product.name}
</Text>
<Text style={{ fontWeight: "700", fontSize: 17 }}>
{quantity * product.price}$
</Text>
</View>
);
})}
</View>
);
};
const MethodSelector = ({ onPress, paymentMethod }) => {
// ...
return (
<View style={{ marginVertical: 48, width: "75%" }}>
<Text
style={{
fontSize: 14,
letterSpacing: 1.5,
color: "black",
textTransform: "uppercase",
}}
>
Select payment method
</Text>
{/* If there's no paymentMethod selected, show the options */}
{!paymentMethod && (
<Pressable
onPress={onPress}
style={{
flexDirection: "row",
paddingVertical: 8,
alignItems: "center",
}}
>
{Platform.select({
ios: <ApplePayMark height={59} />,
android: <GooglePayMark height={59} />,
})}
<View style={[styles.selectButton, { marginLeft: 16 }]}>
<Text style={[styles.boldText, { color: "#007DFF" }]}>Card</Text>
</View>
</Pressable>
)}
{/* If there's a paymentMethod selected, show it */}
{!!paymentMethod && (
<Pressable
onPress={onPress}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 8,
}}
>
{paymentMethod.label.toLowerCase().includes("apple") && (
<ApplePayMark height={59} />
)}
{paymentMethod.label.toLowerCase().includes("google") && (
<GooglePayMark height={59} />
)}
{!paymentMethod.label.toLowerCase().includes("google") &&
!paymentMethod.label.toLowerCase().includes("apple") && (
<View style={[styles.selectButton, { marginRight: 16 }]}>
<Text style={[styles.boldText, { color: "#007DFF" }]}>
{paymentMethod.label}
</Text>
</View>
)}
<Text style={[styles.boldText, { color: "#007DFF", flex: 1 }]}>
Change payment method
</Text>
</Pressable>
)}
</View>
);
};
const CheckoutScreen = ({
products,
navigateBack,
publishableKey,
clientSecret,
merchantName,
cart,
}) => {
// We will store the selected paymentMethod
const [paymentMethod, setPaymentMethod] = React.useState();
// Import some stripe functions
const { initPaymentSheet, presentPaymentSheet, confirmPaymentSheetPayment } =
useStripe();
// Initialize stripe values upon mounting the screen
React.useEffect(() => {
(async () => {
await initStripe({
publishableKey,
// Only if implementing applePay
// Set the merchantIdentifier to the same
// value in the StripeProvider and
// striple plugin in app.json
merchantIdentifier: "yourMerchantIdentifier",
});
// Initialize the PaymentSheet with the paymentIntent data,
// we will later present and confirm this
await initializePaymentSheet();
})();
}, []);
const initializePaymentSheet = async () => {
const { error, paymentOption } = await initPaymentSheet({
paymentIntentClientSecret: clientSecret,
customFlow: true,
merchantDisplayName: merchantName,
style: "alwaysDark", // If darkMode
googlePay: true, // If implementing googlePay
applePay: true, // If implementing applePay
merchantCountryCode: "ES", // Countrycode of the merchant
testEnv: __DEV__, // Set this flag if it's a test environment
});
if (error) {
console.log(error);
} else {
// Upon initializing if there's a paymentOption
// of choice it will be filled by default
setPaymentMethod(paymentOption);
}
};
const handleSelectMethod = async () => {
const { error, paymentOption } = await presentPaymentSheet({
confirmPayment: false,
});
if (error) {
alert(`Error code: ${error.code}`, error.message);
}
setPaymentMethod(paymentOption);
};
const handleBuyPress = async () => {
if (paymentMethod) {
const response = await confirmPaymentSheetPayment();
if (response.error) {
alert(`Error ${response.error.code}`);
console.error(response.error.message);
} else {
alert("Purchase completed!");
}
}
};
return (
<View style={styles.screen}>
<CartInfo cart={cart} products={products} />
<MethodSelector
onPress={handleSelectMethod}
paymentMethod={paymentMethod}
/>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignSelf: "stretch",
marginHorizontal: 24,
}}
>
<Pressable onPress={navigateBack}>
<Text style={[styles.textButton, styles.boldText]}>Back</Text>
</Pressable>
<Pressable style={styles.buyButton} onPress={handleBuyPress}>
<Text style={[styles.boldText, { color: "white" }]}>Buy</Text>
</Pressable>
</View>
</View>
);
};
const AppContent = () => {
const products = [
{
price: 10,
name: "Pizza Pepperoni",
id: "pizza-pepperoni",
},
{
price: 12,
name: "Pizza 4 Fromaggi",
id: "pizza-fromaggi",
},
{
price: 8,
name: "Pizza BBQ",
id: "pizza-bbq",
},
];
const [screenProps, setScreenProps] = React.useState(null);
const navigateToCheckout = (screenProps) => {
setScreenProps(screenProps);
};
const navigateBack = () => {
setScreenProps(null);
};
return (
<View style={styles.container}>
{!screenProps && (
<ProductsScreen
products={products}
navigateToCheckout={navigateToCheckout}
/>
)}
{!!screenProps && (
<CheckoutScreen {...screenProps} navigateBack={navigateBack} />
)}
</View>
);
};
export default function App() {
return (
<StripeProvider>
<AppContent />
</StripeProvider>
);
}
所以有了这段代码,我就能够获得应用程序 运行 并且商品已添加到购物车,但是当我点击卡片选项时,出现错误。
我认为错误是在 CheckoutScreen 上产生的。
error showing
看起来是这样的:
- 您的代码调用了 presentPaymentSheet。
- 它在内部调用了 flowController?.presentPaymentOptions。
- 它检查并发现 FlowController 没有正确初始化并且 发出
错误.
FlowController 未正确初始化的原因是因为传入了 null or empty 客户端密码。您需要检查 clientSecret 变量是否来自 navigateToCheckout 实际上有一个值。
我在尝试使用 React Native 和 Expo SDK 实现 Stripe 支付时遇到错误。 场景非常简单,我将商品添加到购物车,然后选择支付选项作为支付卡,但是当我点击卡时,出现错误。错误和代码如下。
import { StatusBar } from "expo-status-bar";
import React from "react";
import {
StyleSheet,
Text,
View,
Button,
Pressable,
Platform,
} from "react-native";
import { StripeProvider } from "@stripe/stripe-react-native";
import { initStripe, useStripe } from "@stripe/stripe-react-native";
import GooglePayMark from "./GooglePayMark";
import ApplePayMark from "./ApplePayMark";
const API_URL = "http://192.168.0.163:3000";
const ProductRow = ({ product, cart, setCart }) => {
const modifyCart = (delta) => {
setCart({ ...cart, [product.id]: cart[product.id] + delta });
};
return (
<View style={styles.productRow}>
<View style={{ flexDirection: "row" }}>
<Text style={{ fontSize: 17, flexGrow: 1 }}>
{product.name} - {product.price}$
</Text>
<Text style={{ fontSize: 17, fontWeight: "700" }}>
{cart[product.id]}
</Text>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 8,
}}
>
<Button
disabled={cart[product.id] <= 0}
title="Remove"
onPress={() => modifyCart(-1)}
/>
<Button title="Add" onPress={() => modifyCart(1)} />
</View>
</View>
);
};
const ProductsScreen = ({ products, navigateToCheckout }) => {
/**
* We will save the state of the cart here
* It will have the inital shape:
* {
* [product.id]: 0
* }
*/
const [cart, setCart] = React.useState(
Object.fromEntries(products.map((p) => [p.id, 0]))
);
const handleContinuePress = async () => {
/* Send the cart to the server */
const URL = `${API_URL}/create-payment-intent`;
const response = await fetch(URL, {
method: "POST",
headers: {
"Content-Type": "application-json",
},
body: JSON.stringify(cart),
});
/* Await the response */
const { publishableKey, clientSecret, merchantName } =
await response.json();
/* Navigate to the CheckoutScreen */
/* You can use navigation.navigate from react-navigation */
navigateToCheckout({
publishableKey,
clientSecret,
merchantName,
cart,
products,
});
};
return (
<View style={styles.screen}>
{products.map((p) => {
return (
<ProductRow key={p.id} product={p} cart={cart} setCart={setCart} />
);
})}
<View style={{ marginTop: 16 }}>
<Button title="Continue" onPress={handleContinuePress} />
</View>
</View>
);
};
/**
* CheckoutScreen related components
*/
const CartInfo = ({ products, cart }) => {
return (
<View>
{Object.keys(cart).map((productId) => {
const product = products.filter((p) => p.id === productId)[0];
const quantity = cart[productId];
return (
<View
key={productId}
style={[{ flexDirection: "row" }, styles.productRow]}
>
<Text style={{ flexGrow: 1, fontSize: 17 }}>
{quantity} x {product.name}
</Text>
<Text style={{ fontWeight: "700", fontSize: 17 }}>
{quantity * product.price}$
</Text>
</View>
);
})}
</View>
);
};
const MethodSelector = ({ onPress, paymentMethod }) => {
// ...
return (
<View style={{ marginVertical: 48, width: "75%" }}>
<Text
style={{
fontSize: 14,
letterSpacing: 1.5,
color: "black",
textTransform: "uppercase",
}}
>
Select payment method
</Text>
{/* If there's no paymentMethod selected, show the options */}
{!paymentMethod && (
<Pressable
onPress={onPress}
style={{
flexDirection: "row",
paddingVertical: 8,
alignItems: "center",
}}
>
{Platform.select({
ios: <ApplePayMark height={59} />,
android: <GooglePayMark height={59} />,
})}
<View style={[styles.selectButton, { marginLeft: 16 }]}>
<Text style={[styles.boldText, { color: "#007DFF" }]}>Card</Text>
</View>
</Pressable>
)}
{/* If there's a paymentMethod selected, show it */}
{!!paymentMethod && (
<Pressable
onPress={onPress}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 8,
}}
>
{paymentMethod.label.toLowerCase().includes("apple") && (
<ApplePayMark height={59} />
)}
{paymentMethod.label.toLowerCase().includes("google") && (
<GooglePayMark height={59} />
)}
{!paymentMethod.label.toLowerCase().includes("google") &&
!paymentMethod.label.toLowerCase().includes("apple") && (
<View style={[styles.selectButton, { marginRight: 16 }]}>
<Text style={[styles.boldText, { color: "#007DFF" }]}>
{paymentMethod.label}
</Text>
</View>
)}
<Text style={[styles.boldText, { color: "#007DFF", flex: 1 }]}>
Change payment method
</Text>
</Pressable>
)}
</View>
);
};
const CheckoutScreen = ({
products,
navigateBack,
publishableKey,
clientSecret,
merchantName,
cart,
}) => {
// We will store the selected paymentMethod
const [paymentMethod, setPaymentMethod] = React.useState();
// Import some stripe functions
const { initPaymentSheet, presentPaymentSheet, confirmPaymentSheetPayment } =
useStripe();
// Initialize stripe values upon mounting the screen
React.useEffect(() => {
(async () => {
await initStripe({
publishableKey,
// Only if implementing applePay
// Set the merchantIdentifier to the same
// value in the StripeProvider and
// striple plugin in app.json
merchantIdentifier: "yourMerchantIdentifier",
});
// Initialize the PaymentSheet with the paymentIntent data,
// we will later present and confirm this
await initializePaymentSheet();
})();
}, []);
const initializePaymentSheet = async () => {
const { error, paymentOption } = await initPaymentSheet({
paymentIntentClientSecret: clientSecret,
customFlow: true,
merchantDisplayName: merchantName,
style: "alwaysDark", // If darkMode
googlePay: true, // If implementing googlePay
applePay: true, // If implementing applePay
merchantCountryCode: "ES", // Countrycode of the merchant
testEnv: __DEV__, // Set this flag if it's a test environment
});
if (error) {
console.log(error);
} else {
// Upon initializing if there's a paymentOption
// of choice it will be filled by default
setPaymentMethod(paymentOption);
}
};
const handleSelectMethod = async () => {
const { error, paymentOption } = await presentPaymentSheet({
confirmPayment: false,
});
if (error) {
alert(`Error code: ${error.code}`, error.message);
}
setPaymentMethod(paymentOption);
};
const handleBuyPress = async () => {
if (paymentMethod) {
const response = await confirmPaymentSheetPayment();
if (response.error) {
alert(`Error ${response.error.code}`);
console.error(response.error.message);
} else {
alert("Purchase completed!");
}
}
};
return (
<View style={styles.screen}>
<CartInfo cart={cart} products={products} />
<MethodSelector
onPress={handleSelectMethod}
paymentMethod={paymentMethod}
/>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignSelf: "stretch",
marginHorizontal: 24,
}}
>
<Pressable onPress={navigateBack}>
<Text style={[styles.textButton, styles.boldText]}>Back</Text>
</Pressable>
<Pressable style={styles.buyButton} onPress={handleBuyPress}>
<Text style={[styles.boldText, { color: "white" }]}>Buy</Text>
</Pressable>
</View>
</View>
);
};
const AppContent = () => {
const products = [
{
price: 10,
name: "Pizza Pepperoni",
id: "pizza-pepperoni",
},
{
price: 12,
name: "Pizza 4 Fromaggi",
id: "pizza-fromaggi",
},
{
price: 8,
name: "Pizza BBQ",
id: "pizza-bbq",
},
];
const [screenProps, setScreenProps] = React.useState(null);
const navigateToCheckout = (screenProps) => {
setScreenProps(screenProps);
};
const navigateBack = () => {
setScreenProps(null);
};
return (
<View style={styles.container}>
{!screenProps && (
<ProductsScreen
products={products}
navigateToCheckout={navigateToCheckout}
/>
)}
{!!screenProps && (
<CheckoutScreen {...screenProps} navigateBack={navigateBack} />
)}
</View>
);
};
export default function App() {
return (
<StripeProvider>
<AppContent />
</StripeProvider>
);
}
所以有了这段代码,我就能够获得应用程序 运行 并且商品已添加到购物车,但是当我点击卡片选项时,出现错误。
我认为错误是在 CheckoutScreen 上产生的。
error showing
看起来是这样的:
- 您的代码调用了 presentPaymentSheet。
- 它在内部调用了 flowController?.presentPaymentOptions。
- 它检查并发现 FlowController 没有正确初始化并且 发出 错误.
FlowController 未正确初始化的原因是因为传入了 null or empty 客户端密码。您需要检查 clientSecret 变量是否来自 navigateToCheckout 实际上有一个值。