stripe embedded and other fixes
This commit is contained in:
+101
-52
@@ -25,10 +25,23 @@ const STRIPE_SUCCESS_URL =
|
||||
"https://dashboard.stripe.com/test/billing/starter-guide/checkout-success";
|
||||
const STRIPE_CANCEL_URL = STRIPE_SUCCESS_URL;
|
||||
|
||||
const isStripeTesting = false;
|
||||
const stripePromise = loadStripe(
|
||||
isStripeTesting ? STRIPE_PUBLISHABLE_KEY_TEST : STRIPE_PUBLISHABLE_KEY_LIVE,
|
||||
);
|
||||
const isStripeTesting = true;
|
||||
const rawStripePublishableKey = isStripeTesting
|
||||
? STRIPE_PUBLISHABLE_KEY_TEST
|
||||
: STRIPE_PUBLISHABLE_KEY_LIVE;
|
||||
const stripePublishableKey =
|
||||
typeof rawStripePublishableKey === "string"
|
||||
? rawStripePublishableKey.trim()
|
||||
: "";
|
||||
const stripePromise = stripePublishableKey
|
||||
? loadStripe(stripePublishableKey)
|
||||
: null;
|
||||
|
||||
if (!stripePublishableKey) {
|
||||
console.warn(
|
||||
"[StripeProvider] Aucune clé publique Stripe fournie, le checkout intégré sera désactivé.",
|
||||
);
|
||||
}
|
||||
|
||||
const isSafariBrowser = () => {
|
||||
if (
|
||||
@@ -86,6 +99,7 @@ export const useStripe = () => {
|
||||
const StripeProvider = ({ children }) => {
|
||||
const { isMobile } = useLayoutType();
|
||||
const { currentUserData } = useUserData() || {};
|
||||
const canUseEmbeddedCheckout = isWeb && Boolean(stripePromise);
|
||||
|
||||
const [clientSecret, setClientSecret] = React.useState(null);
|
||||
const [subscriptions, setSubscriptions] = React.useState({
|
||||
@@ -118,33 +132,12 @@ const StripeProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
if (isWeb) {
|
||||
if (safariWindow && !safariWindow.closed) {
|
||||
try {
|
||||
safariWindow.location.replace(checkoutUrl);
|
||||
} catch (navigationError) {
|
||||
safariWindow.location.href = checkoutUrl;
|
||||
}
|
||||
safariWindow.focus?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const openedTab = window.open(
|
||||
checkoutUrl,
|
||||
"_blank",
|
||||
"noopener,noreferrer",
|
||||
);
|
||||
if (openedTab) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await Linking.openURL(checkoutUrl);
|
||||
return;
|
||||
throw new Error(
|
||||
"Impossible d'ouvrir le paiement Stripe sans checkout intégré.",
|
||||
);
|
||||
}
|
||||
|
||||
const isMobileApp =
|
||||
Platform.OS === "ios" || Platform.OS === "android";
|
||||
const isMobileApp = Platform.OS === "ios" || Platform.OS === "android";
|
||||
|
||||
if (isMobileApp) {
|
||||
try {
|
||||
@@ -173,17 +166,37 @@ const StripeProvider = ({ children }) => {
|
||||
);
|
||||
|
||||
const runCheckoutSession = React.useCallback(
|
||||
async ({ callableName, payload, logTag }) => {
|
||||
const safariWindow = openSafariCheckoutWindow();
|
||||
try {
|
||||
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable(
|
||||
callableName,
|
||||
async ({ callableName, payload, logTag, useEmbeddedFlow = false }) => {
|
||||
if (useEmbeddedFlow && !canUseEmbeddedCheckout) {
|
||||
throw new Error(
|
||||
"Le paiement intégré Stripe est indisponible sur cette plateforme.",
|
||||
);
|
||||
}
|
||||
|
||||
const wantsEmbeddedCheckout = useEmbeddedFlow && canUseEmbeddedCheckout;
|
||||
const safariWindow = wantsEmbeddedCheckout
|
||||
? null
|
||||
: openSafariCheckoutWindow();
|
||||
try {
|
||||
const callable =
|
||||
getFunctionsClient(FUNCTIONS_REGION).httpsCallable(callableName);
|
||||
const { data } = await callable(payload);
|
||||
const checkoutUrl = data?.url;
|
||||
const clientSecret = data?.client_secret || data?.clientSecret;
|
||||
|
||||
if (wantsEmbeddedCheckout) {
|
||||
if (!clientSecret) {
|
||||
throw new Error("Session Stripe introuvable.");
|
||||
}
|
||||
setClientSecret(clientSecret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkoutUrl) {
|
||||
throw new Error("Session Stripe introuvable.");
|
||||
}
|
||||
|
||||
console.log("[StripeProvider] Stripe checkout URL", checkoutUrl);
|
||||
await redirectToCheckout(checkoutUrl, { safariWindow });
|
||||
} catch (error) {
|
||||
if (safariWindow && !safariWindow.closed) {
|
||||
@@ -196,7 +209,7 @@ const StripeProvider = ({ children }) => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[redirectToCheckout],
|
||||
[canUseEmbeddedCheckout, redirectToCheckout, setClientSecret],
|
||||
);
|
||||
|
||||
const createSubscriptionCheckout = React.useCallback(
|
||||
@@ -204,6 +217,19 @@ const StripeProvider = ({ children }) => {
|
||||
if (!priceId) {
|
||||
throw new Error("Aucun abonnement sélectionné.");
|
||||
}
|
||||
if (isWeb && !canUseEmbeddedCheckout) {
|
||||
console.error(
|
||||
"[StripeProvider] checkout blocked (missing embedded support)",
|
||||
{
|
||||
hasStripeKey: Boolean(stripePublishableKey),
|
||||
isClientSecretReady: false,
|
||||
},
|
||||
);
|
||||
throw new Error(
|
||||
"Le paiement intégré Stripe est indisponible pour le moment (clé Stripe ou client secret absent).",
|
||||
);
|
||||
}
|
||||
const shouldUseEmbeddedCheckout = canUseEmbeddedCheckout;
|
||||
await runCheckoutSession({
|
||||
callableName: "subscription-createSubscriptionCheckoutSession",
|
||||
payload: {
|
||||
@@ -212,11 +238,13 @@ const StripeProvider = ({ children }) => {
|
||||
successUrl: STRIPE_SUCCESS_URL,
|
||||
cancelUrl: STRIPE_CANCEL_URL,
|
||||
},
|
||||
uiMode: shouldUseEmbeddedCheckout ? "embedded" : "hosted",
|
||||
},
|
||||
logTag: "subscription checkout error",
|
||||
useEmbeddedFlow: shouldUseEmbeddedCheckout,
|
||||
});
|
||||
},
|
||||
[runCheckoutSession],
|
||||
[canUseEmbeddedCheckout, runCheckoutSession],
|
||||
);
|
||||
|
||||
const createCoinPackCheckout = React.useCallback(
|
||||
@@ -224,6 +252,19 @@ const StripeProvider = ({ children }) => {
|
||||
if (!productId) {
|
||||
throw new Error("Aucun pack sélectionné.");
|
||||
}
|
||||
if (isWeb && !canUseEmbeddedCheckout) {
|
||||
console.error(
|
||||
"[StripeProvider] checkout blocked (missing embedded support)",
|
||||
{
|
||||
hasStripeKey: Boolean(stripePublishableKey),
|
||||
isClientSecretReady: false,
|
||||
},
|
||||
);
|
||||
throw new Error(
|
||||
"Le paiement intégré Stripe est indisponible pour le moment (clé Stripe ou client secret absent).",
|
||||
);
|
||||
}
|
||||
const shouldUseEmbeddedCheckout = canUseEmbeddedCheckout;
|
||||
await runCheckoutSession({
|
||||
callableName: "subscription-createCoinPackCheckoutSession",
|
||||
payload: {
|
||||
@@ -232,11 +273,13 @@ const StripeProvider = ({ children }) => {
|
||||
successUrl: STRIPE_SUCCESS_URL,
|
||||
cancelUrl: STRIPE_CANCEL_URL,
|
||||
},
|
||||
uiMode: shouldUseEmbeddedCheckout ? "embedded" : "hosted",
|
||||
},
|
||||
logTag: "coin pack checkout error",
|
||||
useEmbeddedFlow: shouldUseEmbeddedCheckout,
|
||||
});
|
||||
},
|
||||
[runCheckoutSession],
|
||||
[canUseEmbeddedCheckout, runCheckoutSession],
|
||||
);
|
||||
|
||||
const fetchActiveSubscription = React.useCallback(async () => {
|
||||
@@ -252,9 +295,9 @@ const StripeProvider = ({ children }) => {
|
||||
setIsActiveSubscriptionLoading(true);
|
||||
setActiveSubscriptionError(null);
|
||||
try {
|
||||
const callable = getFunctionsClient(
|
||||
FUNCTIONS_REGION,
|
||||
).httpsCallable("subscription-getActiveSubscription");
|
||||
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable(
|
||||
"subscription-getActiveSubscription",
|
||||
);
|
||||
const { data } = await callable();
|
||||
setActiveSubscription(data?.subscription || null);
|
||||
} catch (error) {
|
||||
@@ -276,8 +319,9 @@ const StripeProvider = ({ children }) => {
|
||||
let lastError = null;
|
||||
|
||||
try {
|
||||
const callable =
|
||||
functionsClient.httpsCallable("subscription-listSubscriptionPlans");
|
||||
const callable = functionsClient.httpsCallable(
|
||||
"subscription-listSubscriptionPlans",
|
||||
);
|
||||
const { data } = await callable();
|
||||
const nextPlans = data?.plans || {};
|
||||
setSubscriptions({
|
||||
@@ -290,8 +334,9 @@ const StripeProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const callable =
|
||||
functionsClient.httpsCallable("subscription-listCoinPacks");
|
||||
const callable = functionsClient.httpsCallable(
|
||||
"subscription-listCoinPacks",
|
||||
);
|
||||
const { data } = await callable();
|
||||
setCoinPacks(Array.isArray(data?.packs) ? data.packs : []);
|
||||
} catch (error) {
|
||||
@@ -382,23 +427,28 @@ const StripeProvider = ({ children }) => {
|
||||
isVisible={clientSecret !== null}
|
||||
setIsVisible={closeEmbeddedCheckout}
|
||||
>
|
||||
<View
|
||||
style={[StyleSheet.absoluteFillObject, styles.overlayContent]}
|
||||
>
|
||||
<View style={[StyleSheet.absoluteFillObject, styles.overlayContent]}>
|
||||
<View
|
||||
style={[
|
||||
styles.embeddedWrapper,
|
||||
{
|
||||
width: isMobile ? "90%" : "80%",
|
||||
width: isMobile ? "99%" : "95%",
|
||||
height: isMobile ? "92vh" : "96vh",
|
||||
maxWidth: isMobile ? "100%" : "1400px",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{clientSecret ? (
|
||||
{clientSecret && stripePromise ? (
|
||||
<EmbeddedCheckoutProvider
|
||||
stripe={stripePromise}
|
||||
options={{ clientSecret }}
|
||||
>
|
||||
<EmbeddedCheckout />
|
||||
<EmbeddedCheckout
|
||||
onComplete={() => {
|
||||
closeEmbeddedCheckout();
|
||||
fetchActiveSubscription();
|
||||
}}
|
||||
/>
|
||||
</EmbeddedCheckoutProvider>
|
||||
) : null}
|
||||
</View>
|
||||
@@ -422,9 +472,8 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
embeddedWrapper: {
|
||||
alignSelf: "center",
|
||||
height: "70vh",
|
||||
borderRadius: mainBorderRadius,
|
||||
overflow: "scroll",
|
||||
overflow: "hidden",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user