1- import React , { useEffect } from 'react' ;
1+ import React , { useState , useEffect } from 'react' ;
22import { useLocation } from 'react-router-dom' ;
3- import { useStripe } from '@stripe/react-stripe-js' ;
3+ import { Elements , PaymentElement , useStripe , useElements } from '@stripe/react-stripe-js' ;
4+ import { loadStripe } from '@stripe/stripe-js' ;
45import StatusMessages , { useMessages } from './StatusMessages' ;
56
7+ // Inner form component that uses PaymentElement
68const IdealForm = ( ) => {
79 const stripe = useStripe ( ) ;
10+ const elements = useElements ( ) ;
811 const [ messages , addMessage ] = useMessages ( ) ;
912
1013 const handleSubmit = async ( e ) => {
11- // We don't want to let default form submission happen here,
12- // which would refresh the page.
1314 e . preventDefault ( ) ;
1415
15- if ( ! stripe ) {
16- // Stripe.js has not yet loaded.
17- // Make sure to disable form submission until Stripe.js has loaded.
16+ if ( ! stripe || ! elements ) {
1817 addMessage ( 'Stripe.js has not yet loaded.' ) ;
1918 return ;
2019 }
2120
22- const { error : backendError , clientSecret} = await fetch (
23- '/api/create-payment-intent' ,
24- {
25- method : 'POST' ,
26- headers : {
27- 'Content-Type' : 'application/json' ,
28- } ,
29- body : JSON . stringify ( {
30- paymentMethodType : 'ideal' ,
31- currency : 'eur' ,
32- } ) ,
33- }
34- ) . then ( ( r ) => r . json ( ) ) ;
35-
36- if ( backendError ) {
37- addMessage ( backendError . message ) ;
38- return ;
39- }
40-
41- addMessage ( 'Client secret returned' ) ;
21+ addMessage ( 'Processing payment...' ) ;
4222
43- const { error : stripeError , paymentIntent} =
44- await stripe . confirmIdealPayment ( clientSecret , {
45- payment_method : {
46- billing_details : {
47- name : 'Jenny Rosen' ,
48- } ,
49- } ,
23+ const { error, paymentIntent} = await stripe . confirmPayment ( {
24+ elements,
25+ confirmParams : {
5026 return_url : `${ window . location . origin } /ideal?return=true` ,
51- } ) ;
27+ } ,
28+ redirect : 'if_required' ,
29+ } ) ;
5230
53- if ( stripeError ) {
54- // Show error to your customer (e.g., insufficient funds)
55- addMessage ( stripeError . message ) ;
31+ if ( error ) {
32+ addMessage ( error . message ) ;
5633 return ;
5734 }
5835
59- // Show a success message to your customer
60- // There's a risk of the customer closing the window before callback
61- // execution. Set up a webhook or plugin to listen for the
62- // payment_intent.succeeded event that handles any business critical
63- // post-payment actions.
6436 addMessage ( `Payment ${ paymentIntent . status } : ${ paymentIntent . id } ` ) ;
6537 } ;
6638
6739 return (
68- < >
69- < h1 > iDEAL</ h1 >
70- < form id = "payment-form" onSubmit = { handleSubmit } >
71- < button type = "submit" > Pay</ button >
72- </ form >
40+ < form id = "payment-form" onSubmit = { handleSubmit } >
41+ < PaymentElement />
42+ < button type = "submit" disabled = { ! stripe || ! elements } > Pay</ button >
7343 < StatusMessages messages = { messages } />
74- </ >
44+ </ form >
7545 ) ;
7646} ;
7747
78- // Component for displaying results after returning from
79- // iDEAL redirect flow.
48+ // Component for displaying results after returning from redirect flow.
8049const IdealReturn = ( ) => {
8150 const stripe = useStripe ( ) ;
8251 const [ messages , addMessage ] = useMessages ( ) ;
@@ -85,7 +54,7 @@ const IdealReturn = () => {
8554 const clientSecret = query . get ( 'payment_intent_client_secret' ) ;
8655
8756 useEffect ( ( ) => {
88- if ( ! stripe ) {
57+ if ( ! stripe || ! clientSecret ) {
8958 return ;
9059 }
9160 const fetchPaymentIntent = async ( ) => {
@@ -102,18 +71,75 @@ const IdealReturn = () => {
10271
10372 return (
10473 < >
105- < h1 > Ideal Return</ h1 >
74+ < h1 > iDEAL Return</ h1 >
10675 < StatusMessages messages = { messages } />
10776 </ >
10877 ) ;
10978} ;
11079
80+ // Wrapper component that fetches clientSecret and sets up Elements
81+ const IdealWrapper = ( ) => {
82+ const [ clientSecret , setClientSecret ] = useState ( '' ) ;
83+ const [ stripePromise , setStripePromise ] = useState ( null ) ;
84+ const [ error , setError ] = useState ( null ) ;
85+
86+ useEffect ( ( ) => {
87+ const init = async ( ) => {
88+ try {
89+ const { publishableKey} = await fetch ( '/api/config' ) . then ( ( r ) => r . json ( ) ) ;
90+ setStripePromise ( loadStripe ( publishableKey ) ) ;
91+
92+ const response = await fetch ( '/api/create-payment-intent' , {
93+ method : 'POST' ,
94+ headers : { 'Content-Type' : 'application/json' } ,
95+ body : JSON . stringify ( {
96+ paymentMethodType : 'ideal' ,
97+ currency : 'eur' ,
98+ } ) ,
99+ } ) ;
100+ const data = await response . json ( ) ;
101+
102+ if ( data . error ) {
103+ setError ( data . error . message ) ;
104+ } else {
105+ setClientSecret ( data . clientSecret ) ;
106+ }
107+ } catch ( err ) {
108+ setError ( err . message ) ;
109+ }
110+ } ;
111+ init ( ) ;
112+ } , [ ] ) ;
113+
114+ return (
115+ < >
116+ < h1 > iDEAL</ h1 >
117+
118+ { error && < div className = "error" > { error } </ div > }
119+
120+ { clientSecret && stripePromise && (
121+ < Elements
122+ stripe = { stripePromise }
123+ options = { {
124+ clientSecret,
125+ appearance : { theme : 'stripe' } ,
126+ } }
127+ >
128+ < IdealForm />
129+ </ Elements >
130+ ) }
131+
132+ { ! clientSecret && ! error && < div > Loading...</ div > }
133+ </ >
134+ ) ;
135+ } ;
136+
111137const Ideal = ( ) => {
112138 const query = new URLSearchParams ( useLocation ( ) . search ) ;
113139 if ( query . get ( 'return' ) ) {
114140 return < IdealReturn /> ;
115141 } else {
116- return < IdealForm /> ;
142+ return < IdealWrapper /> ;
117143 }
118144} ;
119145
0 commit comments