Using PHP to set up and use STRIPE Elements to create a payment form and the request payment

This is a walk through of the PHP & Javascript code required for collecting a card payment method using to save the card details ready for collecting payments later.

warning WARNING: This code is not 100% working code and is still being worked out how to implement fully - handle with care

The code here is using the STRIPE developers test environment and will not collect real funds.

As a pre-requisit for your own coding, you will need to have created a STRIPE account and acquired the public and secret keys from your STRIPE dashboard.
You will also have used composer to install the STRIPE API into your application vendor folders using the command line composer require stripe/stripe-php

The code is broken down into a few steps to set-up a customer on the STRIPE environment and then create a form to collect the credit card details that will be used by a background billing process later; use the STRIPE test cards listed below - or found on https://stripe.com/docs/testing#cards.

STRIPE fees in the UK are: 1.4% + 20p for UK cards (+1.1% for European Economic Area) / 2.9% + 20p for international cards.

Written: July-2022 PHP: 7+ STRIPE.js: v3

Step 1 Code:

Set up your STRIPE keys; these are provided to you from your STRIPE dashboard see https://dashboard.stripe.com/test/apikeys for your own.
NOTE: These are best stored in config files that are different between dev and live environments.

stripe_elements_checkout.php
require 'autoload.php'; // (prerequisite: composer require stripe/stripe-php)

$stripe_secretkey = "sk_test_.....";
$stripe_publickey = "pk_test_.....";

$stripe = new \Stripe\StripeClient($stripe_secretkey);

Step 2 Code:

Search STRIPE for the customer

stripe_elements_checkout.php
$json = $stripe->customers->search(['query' => 'name:\'' . $customerNm . '\'']);

foreach ($json->data as $key) {
    echo "<LI>id:[" . $key->id . "], name:[".$key->name, "] email:[" . $key->email . "]<br>";
    if (strcmp($key->name, $customerNm) == 0) {
        $customerId = $key->id;
        $email = $key->email;
        break;
    }
}

echo "<UL>Found [$customerId] on STRIPE; attached email is [$email]</UL>";

Step 2 Result:

  • checkpoint 1 : Searching STRIPE for name='customer X'.
  • id:[cus_MCvud3Hgg79qau], name:[customer X] email:[abc@customerX.com]
      Found [cus_MCvud3Hgg79qau] on STRIPE; attached email is [abc@customerX.com]
  • Step 3 Code:

    Create the STRIPE customer if it does not already have one.

    In this case we are using customer name as the Unique Identifier, but you would need to consider this when implementing your own solution.

    stripe_elements_checkout.php
    <?php
    
    
    include_once("class.MasterForm.php");
    
    class NoteForm extends MasterForm {
        //----------------------------------------------------------------------------------
        public function getCardHelperTable() {
    
            $see="https://stripe.com/docs/testing#cards.";
            if (1 == 1) {
                $workingCards = [
                    ["type" => "Visa", "num" => "4242424242424242", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Visa (debit)", "num" => "4000056655665556", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Mastercard", "num" => "5555555555554444", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Mastercard (2-series)", "num" => "2223003122003222", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Mastercard (debit)", "num" => "5200828282828210", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Mastercard (prepaid)", "num" => "5105105105105100", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "American Express", "num" => "378282246310005", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "American Express", "num" => "371449635398431", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Discover", "num" => "6011111111111117", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Discover", "num" => "6011000990139424", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Diners Club", "num" => "3056930009020004", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "Diners Club (14-digit card)", "num" => "36227206271667", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "JCB", "num" => "3566002020360505", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                    , ["type" => "UnionPay", "num" => "6200000000000005", "cvc" => "Any 3 digits", "expiry" => "Any future date"]
                ];
    
                $declinedCards = [
                    ["type" => "Generic decline", "num" => "4000000000000002", "error-code" => "card_declined", "decline-code" => "generic_decline"]
                    , ["type" => "Insufficient funds decline", "num" => "4000000000009995", "error-code" => "card_declined", "decline-code" => "Any future date"]
                    , ["type" => "Lost card decline", "num" => "4000000000009987", "error-code" => "card_declined", "decline-code" => "lost_card"]
                    , ["type" => "Stolen card decline", "num" => "4000000000009979", "error-code" => "card_declined", "decline-code" => "stolen_card"]
                    , ["type" => "Expired card decline", "num" => "4000000000000069", "error-code" => "expired_card", "decline-code" => "n/a"]
                    , ["type" => "Incorrect CVC decline", "num" => "4000000000000127", "error-code" => "incorrect_cvc", "decline-code" => "n/a"]
                    , ["type" => "Processing error decline", "num" => "4000000000000119", "error-code" => "processing_error", "decline-code" => "n/a"]
                    , ["type" => "Incorrect number decline", "num" => "4242424242424241", "error-code" => "incorrect_number", "decline-code" => "n/a"]
                ];
    
                $authCards = [
                    ["type" => "Authenticate unless set up", "num" => "4000002500003155", "notes" => "This card requires authentication for every payment unless you set it up for future payments. After you set it up, it no longer requires authentication."]
                    , ["type" => "Always authenticate", "num" => "4000002760003184", "notes" => "This card requires authentication on all transactions, regardless of how the card is set up."]
                    , ["type" => "Already set up", "num" => "4000003800000446", "notes" => "This card is already set up for off-session use. It requires authentication for one-off and other on-session payments. However, all off-session payments succeed as if the card has been previously set up."]
                    , ["type" => "Insufficient funds", "num" => "4000008260003178", "notes" => "This card requires authentication for one-time payments. All payments are declined with an insufficient_funds failure code even after being successfully authenticated or previously set up."]
    
                ];
            }
    
            $but="<a class='collapser material-icons handcursor' style='vertical-align:bottom'>expand_more</a>";
            echo "<BR><BR><HR><h2>$but Test card options:</h2>";
            echo "<LI>See <a href='$see' target='_blank'>$see</a></LI>";
            echo "<div class='collapsed hidden' style='height:0; overflow:scroll; padding:10px; max-height:300px'>";
            if (1 == 1) {
                echo "<LI>Working Test cards:</LI>";
                echo "<UL><TABLE>";
                echo "<TR><TH>Card</TH><TH>Num</TH><TH>CVC</TH><TH>Expiry Date</TH></TR>";
                foreach ($workingCards as $card) {
                    echo "<TR><TD>" . $card['type'] . "</TD><TD>" . $card["num"] . "</TD><TD>" . $card["cvc"] . "</TD><TD>" . $card["expiry"] . "</TD></TR>";
                }
                echo "</TABLE></UL>";
    
    
                echo "<LI>Declined Test cards:</LI>";
                echo "<UL><TABLE>";
                echo "<TR><TH>Card</TH><TH>Num</TH><TH>Error code</TH><TH>Decline code</TH></TR>";
                foreach ($declinedCards as $card) {
                    echo "<TR><TD>" . $card['type'] . "</TD><TD>" . $card["num"] . "</TD><TD>" . $card["error-code"] . "</TD><TD>" . $card["decline-code"] . "</TD></TR>";
                }
                echo "</TABLE></UL>";
    
    
                echo "<LI>Authentication Test cards</LI>";
                echo "<UL><TABLE>";
                echo "<TR><TH>Card</TH><TH>Num</TH><TH>Notes</TH></TR>";
                foreach ($authCards as $card) {
                    echo "<TR><TD>" . $card['type'] . "</TD><TD>" . $card["num"] . "</TD><TD>" . $card["notes"] . "</TD></TR>";
                }
                echo "</TABLE></UL>";
            }
            echo "</div>";
    
        }
    
    
        // --------------------------------------------------------------------------------------
    
        public function getHTMLAddHeadCSSstyles() {
            return /**@lang CSS */ "
            
            ";
        }
    
        // --------------------------------------------------------------------------------------
    
        public function getHTMLAddHeadJavascript() {
            return 
                /**@lang Javascript*/"
            function doPayment(elem) {
                var args=elem.getAttribute('args');
                var iframe=document.getElementById('payment-later-frame');
                iframe.className=iframe.className.replace(/hidden/, '');
                iframe.src='make_stripe_elements_payment.php?'+args;
            }
            ";
            
        }
        //----------------------------------------------------------------------------------
    
        function getVisibleHTML($html) {
            return str_replace("", ""
                , str_replace("", ""
                    , str_replace("<", "<"
                        , str_replace(">", ">", $html))));
        }
    
        //----------------------------------------------------------------------------------
    
        public function getHTMLHead() {
            $description="PHP and Javascript code walk through of STRIPE.com calls to set up payments using a built-in payment form";
            $keywords="PHP, coding, stripe.com, payment gateway, credit card, payment, curl, Javascript";
            return $this->getHTMLHeadInner("STRIPE.com PHP API calls to set up payments", $description, $keywords);
        }
    
        //----------------------------------------------------------------------------------
        function _showContent() {
    
            $hms=date("YmdHis");
            $sn=$_SERVER['SCRIPT_NAME'];// #hide-code-block
            $href="$sn?t=$hms";// #hide-code-block
    
            $logo = "<img src='https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg' style='width:100px; vertical-align:middle'>";
            echo /**@lang HTML*/"<div class='headlines info'>
            <h1>Using PHP to set up and use STRIPE Elements to create a payment form and the request payment</h1>
            <p>
            This is a walk through of the PHP & Javascript code required for collecting a card payment method using <a target='_blank' href='https://stripe.com' title='stripe'>$logo</a>
            to save the card details ready for collecting payments later.
            </p>
            <p><i class='material-icons'>warning</i> WARNING: This code is not 100% working code and is still being worked out how to implement fully - handle with care</p>
            <p>
            The code here is using the STRIPE developers test environment and will not collect real funds.  
            </p>
            <p>As a pre-requisit for your own coding, you will need to have created a STRIPE account and acquired the public and secret keys from your STRIPE dashboard.
            <BR>
            You will also have used composer to install the STRIPE API into your application vendor folders using the command line <code>composer require stripe/stripe-php</code>
            </p>
            <p>
            The code is broken down into a few steps to set-up a customer on the STRIPE environment and then create a form
            to collect the credit card details that will be used by a background billing process later; use the STRIPE test 
            cards listed below - or found on <a href='https://stripe.com/docs/testing#cards' target='_blank'>https://stripe.com/docs/testing#cards</a>.
            </p>
            <p>STRIPE <a href='https://stripe.com/gb/pricing' target='_blank'>fees in the UK</a> are: 1.4% + 20p for UK cards (+1.1% for European Economic Area) /  2.9% + 20p for international cards.</p>
            <p>Written: <span class='curvy colPaleGreyBg'>July-2022</span> <span class='curvy colPaleGreyBg'>PHP: 7+</span> <span class='curvy colPaleGreyBg'>STRIPE.js: v3</span></p>
            </div>";
    
    
            echo $this->getAlsoSee();
    
    
            $customerNm = "customer X";
            $customerId = "";
            $email = "";
            $step = 1;
    
            $linkicon = "<span class='material-icons handcursor invisible link' title='clipboard this hypertext link point'>insert_link</span>";
    
    
            echo "<div class='table'>";
            if (1 == 1) {
    
                echo "<div class='table-row'>";
                if (1 == 1) {
    
                    echo "<div class='code codewidth'><h2><a name='step$step' class='hyperlink'>Step $step Code:$linkicon</a></h2>";
                    if (1 == 1) {
                        echo "
                        <p>
                        Set up your STRIPE keys; these are provided to you from your STRIPE dashboard 
                        see <a href='https://dashboard.stripe.com/test/apikeys'>https://dashboard.stripe.com/test/apikeys</a> for your own.
                        <br>
                        NOTE: These are best stored in config files that are different between dev and live environments.
                        </p>
                        ";
    
                        //echo "<b>".basename($_SERVER['SCRIPT_NAME'])."</b>";
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep$step");
                    }
                    echo "</div>";
    
                    echo "<div class='result'><h2><a name='step$step-result' class='hyperlink'>Step $step Result:$linkicon</a></h2>";
                    if (1 == 1) {
    
                        
                        require 'autoload.php'; // (prerequisite: composer require stripe/stripe-php)
    
                        $stripe_secretkey = "sk_test_.....";
                        $stripe_publickey = "pk_test_.....";
                        $stripe_secretkey = config::stripeSecretKey; // #hide-code-block
                        $stripe_publickey = config::stripePublicKey; // #hide-code-block
    
                        $stripe = new \Stripe\StripeClient($stripe_secretkey);
                        
    
                    }
                    echo "</div>";
                }
                echo "</div>";
    
    
    
                // Search STRIPE for the customer
                echo "<div class='table-row'>";
                if (1 == 1) {
                    $step++;
                    echo "<div class='code codewidth'><h2><a name='step$step' class='hyperlink'>Step $step Code:$linkicon</a></h2>";
                    if (1 == 1) {
                        echo "
                        <p>
                        Search STRIPE for the customer
                        </p>
                        ";
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep$step");
                    }
                    echo "</div>";
    
    
                    echo "<div class='result'><h2><a name='step$step-result' class='hyperlink'>Step $step Result:$linkicon</a></h2>";
                    if (1 == 1) {
                        echo "<LI>checkpoint 1 : Searching STRIPE for name='$customerNm'. ";
    
                        
                        $json = $stripe->customers->search(['query' => 'name:\'' . $customerNm . '\'']);
    
                        foreach ($json->data as $key) {
                            echo "<LI>id:[" . $key->id . "], name:[".$key->name, "] email:[" . $key->email . "]<br>";
                            if (strcmp($key->name, $customerNm) == 0) {
                                $customerId = $key->id;
                                $email = $key->email;
                                break;
                            }
                        }
    
                        echo "<UL>Found [$customerId] on STRIPE; attached email is [$email]</UL>";
                        
                    }
                    echo "</div>";
                }
                echo "</div>";
    
    
                // Create the STRIPE customer if it does not already have one.
                echo "<div class='table-row'>";
                if (1 == 1) {
                    $step++;
                    echo "<div class='code codewidth'><h2><a name='step$step' class='hyperlink'>Step $step Code:$linkicon</a></h2>";
                    if (1 == 1) {
                        echo "
                        <p>
                        Create the STRIPE customer if it does not already have one.
                        </p>
                        <p>
                        In this case we are using customer name as the Unique Identifier, but you would need to 
                        consider this when implementing your own solution.
                        </p>
                        ";
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME']);
                    }
                    echo "</div>";
    
    
                    // if tenant 1 does not exist - create it
                    echo "<div class='result'><h2><a name='step$step-result' class='hyperlink'>Step $step Result:$linkicon</a></h2>";
                    if (1 == 1) {
                        
                        if (strcmp($customerId, "") == 0) {
    
                            // I have disabled adding any new customers after the first run.
                            echo "<LI>ERROR: Did not find a customer id;";
                            exit;
    
                            echo "<LI>checkpoint 2</LI>";
                            $email = str_replace(" ", "", "abc@$customerNm.com");
                            $json = $stripe->customers->create(
                                [
                                    'email' => $email,
                                    'name'  => $customerNm,
                                ]
                            );
                            //echo "<LI><PRE>" . print_r($json, true) . "</PRE>";
                            $customerId = $json->id;
                            echo "<UL>Created New STRIPE customer id: [$customerId]</UL>";
                        }
                        else echo "<LI>checkpoint 2: customer found</LI>";
                        
                    }
                    echo "</div>";
                }
                echo "</div>";
    
    
                // Create a STRIPE customer intent
                echo "<div class='table-row'>";
                if (1 == 1) {
                    $step++;
                    echo "<div class='code codewidth'><h2><a name='step$step' class='hyperlink'>Step $step Code:$linkicon</a></h2>";
                    if (1 == 1) {
                        echo "
                         <p>Create a STRIPE customer intent by creating the <form> using the client-secret
                         created using <span class='pre'>setupIntents->create</span>, and then using this in the javascript that follows,
                         this will tell stripe.js to populate the empty form with some input fields.
                         </p>
                        ";
    
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep$step");
                    }
                    echo "</div>";
    
    
                    echo "<div class='result'><h2><a name='step$step-result' class='hyperlink'>Step $step Result:$linkicon</a></h2>";
                    if (1 == 1) {
                        
                        echo "<LI>checkpoint 3: creating a setupintent</LI>";
                        try {
    
                            // See https://stripe.com/docs/payments/save-and-reuse?platform=web#web-create-intent
                            $json = $stripe->setupIntents->create(
                                ['customer'               => $customerId
                                 , 'payment_method_types' => ['card'] // ,'bacs_debit'
                                ]);
    
    
                            $intentId = $json->id;
                            $clientSecret = $json->client_secret;
    
                            echo /**@lang HTML */"
                            
                                <LI>setupIntent Id=$intentId</LI>
                                <HR>
                                
                                <p class='info colPaleGreyBg'>HINT: Use the Test card options listed below (e.g. 4242424242424242 - expires 12/33, CVC 123</p>
                                <p style='color:red'><i class='material-icons' style='vertical-align: middle; color: red'>warning</i> Never use real cards on this test page.</p><!-- #hide-code-block -->
    
                                <form id='payment_form' data-secret='$clientSecret'>
                                    <div id='payment-element'>
                                      <!-- Elements will create form elements here-->
                                    </div>
                                    
                                    <div id='error-message'></div>
                                    <button id='submit'>Save</button>
                                </form>
                                
                                ";
    
                            echo "<hr><button type='button' onClick='window.location.href=\"$href#step4-result\"'>Restart</button>"; // #hide-code-block
                            echo $this->getCardHelperTable(); // #hide-code-block
    
                            // See https://stripe.com/docs/payments/save-and-reuse?platform=web#web-collect-payment-details
                            echo /**@lang Javascript */"
                                
                                <script src='https://js.stripe.com/v3/'></script>
                                <script>
        
                                const stripe = Stripe('$stripe_publickey');
                                const options = {
                                      clientSecret: '$clientSecret'
                                    };
                                  
                                // Use options to customize the input form if required, e.g.  
                                //  const options = {
                                //  	clientSecret: '$clientSecret',
                                //	    appearance: {
                                //		  variables: {
                                //			fontLineHeight: '1.4',
                                //			fontSizeBase : '19px',
                                //			colorText: '#5c574d',
                                //			colorPrimary: '#27bff1',
                                //			colorDanger: '#e68282',
                                //			focusBoxShadow: '0 0 0 2px #92def7',
                                //			borderColor: '#cccccc'
                                //		  },
                                //		  rules:{
                                //			'.Input' : {
                                //				border: '1px solid #ccc',
                                //				boxShadow: 'none',
                                //		  }
                                //		}
                                //	}
                                //}
                                    
                                // Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in step 2
                                const elements = stripe.elements(options);
                                
                                // Create and mount the Payment Element
                                const paymentElement = elements.create('payment');
                                paymentElement.mount('#payment-element');
                                
                                
                                // See https://stripe.com/docs/payments/save-and-reuse?platform=web#web-submit-payment-details
                                const form = document.getElementById('payment_form');
        
                                form.addEventListener('submit', async (event) => {
                                    event.preventDefault();
                                    
                                    var target=document.getElementById('submit');
                                    target.disabled=true;
                                    target.innerHTML='Checking...';
                                    
                                    document.getElementById('step5-result').innerHTML='';
                                    document.getElementById('error-message').innerHTML='';
                                    
                                    const {error} = await stripe.confirmSetup({
                                            //`Elements` instance that was used to create the Payment Element
                                            elements,
                                            confirmParams: {
                                                return_url: window.location.origin+window.location.pathname+'?step4resultMsg=Y#step5-result',
                                            }
                                        });
                                    
                                    if (error) {
                                        // This point will only be reached if there is an immediate error when
                                        // confirming the payment. Show error to your customer (for example, payment
                                        // details incomplete)
                                        const messageContainer = document.querySelector('#error-message');
                                        messageContainer.textContent = error.message;
                                        alert('stripe.confirmSetup result = '+error.message);
                                        target.disabled=false;
                                        target.innerHTML='Save';
                                    } 
                                    else {
                                        // Your customer will be redirected to your `return_url`. For some payment
                                        // methods like iDEAL, your customer will be redirected to an intermediate
                                        // site first to authorize the payment, then redirected to the `return_url`.
                                        alert('This never happens');
                                    }
                                })
                                ;
                                </script>
                            ";
                        }
                        catch (Exception $e) {
                            echo "<LI>Payment method creation failed " . $e->getMessage();
                        }
                        
                    }
                    echo "</div>";
                }
                echo "</div>";
    
    
                echo "<div class='table-row'>";
                if (1 == 1) {
                    $step++;
                    echo "<div class='code codewidth'><h2><a name='step$step' class='hyperlink'>Step $step Code:$linkicon</a></h2>";
                    if (1 == 1) {
                        echo /**@lang HTML */"<p>
                            After this - the user will have submitted card details from the STRIPE <form> to STRIPE.com 
                            via our <span class='pre'>confirmSetup()</span> call in the embedded javascript code above; if successful, this will respond with 
                            redirect to this URL point. 
                            Now we want to charge the customer using a background process some time later, 
                            here demonstrated with the make_stripe_elements_payment.php script which can be triggered using the [Make Payment now...] button.
                            </p>
                            <p>(The make_stripe_elements_payment.php code will potentially pick up multiple intents from STRIPE and will only 
                            charge the last one it found; to resolve this problem, the code above should really have removed any previous card declarations
                            before saving a new one). </p>
                            <p>The next problem we now see is the response of status=requires_action, which the make_stripe_elements_payment.php
                            script will detect and jump into javascript to call the <span class='pre'>stripe.handleCardAction()</span> function (step 5a).
                            <br>
                            This then creates a stripe modal that prompts for authentication, the result of which is then passed to handleServerResponse
                            function which will be called via a POST request (step 5b).                        
                            </p>
                            ";
    
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep5JS", "", "JS function to show the output result iframe");
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep5", "", "script GET response handler (for Step 4 response call)");
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep1", "make_stripe_elements_payment.php", "make payment script processor");
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep2", "make_stripe_elements_payment.php", "generateResponse function");
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep3", "make_stripe_elements_payment.php", "script POST request handler");
                        echo $this->getCodeBlock($_SERVER['SCRIPT_FILENAME'], "CodeStep4", "make_stripe_elements_payment.php", "handleServerResponse function");
                    }
                    echo "</div>";
    
    
                    echo "<div class='result'><h2><a name='step$step-result'  id='step5-result' class='hyperlink'>Step $step Result:$linkicon</a></h2>";
                    if (1 == 1) {
    
    
                        
                        if (isset($_GET['step4resultMsg'])) {
    
                            echo /**@lang HTML */"
                                <p>GET/POST parameters detected:</p>
                                <div style='background-color:red; color:white; padding:10px; margin-bottom:10px; overflow:auto'>";
    
                            if (1) {
    
                                foreach ($_GET as $k => $v) {
                                    echo "<LI style='white-space:nowrap'>GET " . $this->getSafeTx("[$k] = [$v]")."</LI>";
                                }
    
                                foreach ($_POST as $k => $v) {
                                    echo "<LI style='white-space:nowrap'>POST " . $this->getSafeTx("[$k] = [$v]")."</LI>";
                                }
    
                            }
                            echo "</div>";
    
    
                            if (strcmp($_GET['redirect_status'], 'processing')==0) {
                                echo "<h3>Processing payment details. We'll update you when processing is complete.</h3>";
                            }
    
                            if (strcmp($_GET['redirect_status'], 'requires_payment_method')==0) {
                                echo "<h3>Failed to process payment details. Please try another payment method.</h3>";
                            }
    
                            if (strcmp($_GET['redirect_status'], 'succeeded')==0) {
                                $si = $_GET['setup_intent'];
                                $cs = $_GET['setup_intent_client_secret'];
    
                                echo /**@lang HTML */ "
                                    <div style='background-color:white; border:solid 1px grey; padding:10px;'>
                                        <p>Card has been saved ready for later payment</p>
                                        <a class='button' args='si=$si&cs=$cs&ci=$customerId' onClick='doPayment(this)'>Make Payment now...</a>
                                    </div>
                                    <br>
                                    <iframe id='payment-later-frame' style='min-height:900px' class='hidden'></iframe>
                                ";
                            }
    
                        }
                        else {
                            echo "<div class='iframe'>Waiting for card details</div>";
                        }
                        
    
                        echo "<button type='button' onClick='window.location.href=\"$href\"'>Restart</button>"; // #hide-code-block
                    }
                    echo "</div>";
                }
                echo "</div>";
            }
            echo "</div>";
    
            echo "<p>
            For an all in one card collection & payment solution, see <a href='stripe_elements_checkout_x.php'>striped down code</a>.
            </p>";
    
            if (1 == 2) {
                $stripe->paymentMethods->all(
                    ['customer' => '{{CUSTOMER_ID}}', 'type' => 'card']
                );
            }
        }
    }
    
    try {
        $form = new NoteForm();
        $form->showContent(false);
    }
    catch (Exception $e) {
        echo "<BR>Error:".$e->getMessage();
    }
    ?>
    

    Step 3 Result:

  • checkpoint 2: customer found
  • Step 4 Code:

    Create a STRIPE customer intent by creating the <form> using the client-secret created using setupIntents->create, and then using this in the javascript that follows, this will tell stripe.js to populate the empty form with some input fields.

    stripe_elements_checkout.php
    echo "<LI>checkpoint 3: creating a setupintent</LI>";
    try {
    
        // See https://stripe.com/docs/payments/save-and-reuse?platform=web#web-create-intent
        $json = $stripe->setupIntents->create(
            ['customer'               => $customerId
             , 'payment_method_types' => ['card'] // ,'bacs_debit'
            ]);
    
    
        $intentId = $json->id;
        $clientSecret = $json->client_secret;
    
        echo /**@lang HTML */"
        
            <LI>setupIntent Id=$intentId</LI>
            <HR>
            
            <p class='info colPaleGreyBg'>HINT: Use the Test card options listed below (e.g. 4242424242424242 - expires 12/33, CVC 123</p>
    
            <form id='payment_form' data-secret='$clientSecret'>
                <div id='payment-element'>
                  <!-- Elements will create form elements here-->
                </div>
                
                <div id='error-message'></div>
                <button id='submit'>Save</button>
            </form>
            
            ";
    
    
        // See https://stripe.com/docs/payments/save-and-reuse?platform=web#web-collect-payment-details
        echo /**@lang Javascript */"
            
            <script src='https://js.stripe.com/v3/'></script>
            <script>
    
            const stripe = Stripe('$stripe_publickey');
            const options = {
                  clientSecret: '$clientSecret'
                };
              
            // Use options to customize the input form if required, e.g.  
            //  const options = {
            //  	clientSecret: '$clientSecret',
            //	    appearance: {
            //		  variables: {
            //			fontLineHeight: '1.4',
            //			fontSizeBase : '19px',
            //			colorText: '#5c574d',
            //			colorPrimary: '#27bff1',
            //			colorDanger: '#e68282',
            //			focusBoxShadow: '0 0 0 2px #92def7',
            //			borderColor: '#cccccc'
            //		  },
            //		  rules:{
            //			'.Input' : {
            //				border: '1px solid #ccc',
            //				boxShadow: 'none',
            //		  }
            //		}
            //	}
            //}
                
            // Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in step 2
            const elements = stripe.elements(options);
            
            // Create and mount the Payment Element
            const paymentElement = elements.create('payment');
            paymentElement.mount('#payment-element');
            
            
            // See https://stripe.com/docs/payments/save-and-reuse?platform=web#web-submit-payment-details
            const form = document.getElementById('payment_form');
    
            form.addEventListener('submit', async (event) => {
                event.preventDefault();
                
                var target=document.getElementById('submit');
                target.disabled=true;
                target.innerHTML='Checking...';
                
                document.getElementById('step5-result').innerHTML='';
                document.getElementById('error-message').innerHTML='';
                
                const {error} = await stripe.confirmSetup({
                        //`Elements` instance that was used to create the Payment Element
                        elements,
                        confirmParams: {
                            return_url: window.location.origin+window.location.pathname+'?step4resultMsg=Y#step5-result',
                        }
                    });
                
                if (error) {
                    // This point will only be reached if there is an immediate error when
                    // confirming the payment. Show error to your customer (for example, payment
                    // details incomplete)
                    const messageContainer = document.querySelector('#error-message');
                    messageContainer.textContent = error.message;
                    alert('stripe.confirmSetup result = '+error.message);
                    target.disabled=false;
                    target.innerHTML='Save';
                } 
                else {
                    // Your customer will be redirected to your `return_url`. For some payment
                    // methods like iDEAL, your customer will be redirected to an intermediate
                    // site first to authorize the payment, then redirected to the `return_url`.
                    alert('This never happens');
                }
            })
            ;
            </script>
        ";
    }
    catch (Exception $e) {
        echo "<LI>Payment method creation failed " . $e->getMessage();
    }
    

    Step 4 Result:

  • checkpoint 3: creating a setupintent
  • setupIntent Id=seti_1Lly7WE8srzgGJZyfxUYgbWo

  • HINT: Use the Test card options listed below (e.g. 4242424242424242 - expires 12/33, CVC 123

    warning Never use real cards on this test page.





    expand_more Test card options:

  • See https://stripe.com/docs/testing#cards.
  • Step 5 Code:

    After this - the user will have submitted card details from the STRIPE <form> to STRIPE.com via our confirmSetup() call in the embedded javascript code above; if successful, this will respond with redirect to this URL point. Now we want to charge the customer using a background process some time later, here demonstrated with the make_stripe_elements_payment.php script which can be triggered using the [Make Payment now...] button.

    (The make_stripe_elements_payment.php code will potentially pick up multiple intents from STRIPE and will only charge the last one it found; to resolve this problem, the code above should really have removed any previous card declarations before saving a new one).

    The next problem we now see is the response of status=requires_action, which the make_stripe_elements_payment.php script will detect and jump into javascript to call the stripe.handleCardAction() function (step 5a).
    This then creates a stripe modal that prompts for authentication, the result of which is then passed to handleServerResponse function which will be called via a POST request (step 5b).

    stripe_elements_checkout.php :: JS function to show the output result iframe
    /**@lang Javascript*/"
    function doPayment(elem) {
        var args=elem.getAttribute('args');
        var iframe=document.getElementById('payment-later-frame');
        iframe.className=iframe.className.replace(/hidden/, '');
        iframe.src='make_stripe_elements_payment.php?'+args;
    }
    ";
    
    stripe_elements_checkout.php :: script GET response handler (for Step 4 response call)
    if (isset($_GET['step4resultMsg'])) {
    
        echo /**@lang HTML */"
            <p>GET/POST parameters detected:</p>
            <div style='background-color:red; color:white; padding:10px; margin-bottom:10px; overflow:auto'>";
    
        if (1) {
    
            foreach ($_GET as $k => $v) {
                echo "<LI style='white-space:nowrap'>GET " . $this->getSafeTx("[$k] = [$v]")."</LI>";
            }
    
            foreach ($_POST as $k => $v) {
                echo "<LI style='white-space:nowrap'>POST " . $this->getSafeTx("[$k] = [$v]")."</LI>";
            }
    
        }
        echo "</div>";
    
    
        if (strcmp($_GET['redirect_status'], 'processing')==0) {
            echo "<h3>Processing payment details. We'll update you when processing is complete.</h3>";
        }
    
        if (strcmp($_GET['redirect_status'], 'requires_payment_method')==0) {
            echo "<h3>Failed to process payment details. Please try another payment method.</h3>";
        }
    
        if (strcmp($_GET['redirect_status'], 'succeeded')==0) {
            $si = $_GET['setup_intent'];
            $cs = $_GET['setup_intent_client_secret'];
    
            echo /**@lang HTML */ "
                <div style='background-color:white; border:solid 1px grey; padding:10px;'>
                    <p>Card has been saved ready for later payment</p>
                    <a class='button' args='si=$si&cs=$cs&ci=$customerId' onClick='doPayment(this)'>Make Payment now...</a>
                </div>
                <br>
                <iframe id='payment-later-frame' style='min-height:900px' class='hidden'></iframe>
            ";
        }
    
    }
    else {
        echo "<div class='iframe'>Waiting for card details</div>";
    }
    
    make_stripe_elements_payment.php :: make payment script processor
    // see https://stripe.com/docs/payments/save-and-reuse?platform=web#charge-saved-payment-method
    
    require 'autoload.php'; // (prerequisite: composer require stripe/stripe-php)
    
    $stripe_secretkey = "sk_test_.....";
    $stripe_publickey = "pk_test_.....";
    
    $stripe = new \Stripe\StripeClient($stripe_secretkey);
    
    $customerId = $this->getSafeTx($_GET['ci']);
    
    echo /**@lang Javascript*/"
    <script>
    console.log('step 1 : customer=$customerId');
    </script>
    ";
    
    $json = $stripe->paymentMethods->all(
        ['customer' => "$customerId", 'type' => 'card']
    );
    
    echo "<BR>paymentMethods response:<PRE>$json</PRE>";
    
    $pmid = null;
    foreach ($json->data as $key) {
        echo "<LI style='white-space:nowrap'>id:[" . $key->id . "], card:" . $key->card->brand . " **** **** **** " . $key->card->last4;
        $pmid = $key->id; // This may be set several times if there are multiple payment methods
    }
    
    if ($pmid) {
    
        try {
            echo "<LI>Calling for payment...";
    
            $response = $stripe->paymentIntents->create([
                                                            'amount'              => 100,
                                                            'currency'            => 'gbp',
                                                            'description'         => 'product ABC',
                                                            'customer'            => "$customerId",
                                                            'payment_method'      => "$pmid",
                                                            'confirm'             => true,
                                                            'confirmation_method' => 'manual'
                                                        ]);
    
            $this->generateResponse($response, $stripe_publickey);
    
        }
        catch (\Stripe\Exception\CardException $e) {
            // Error code will be authentication_required if authentication is needed
            echo "<LI>PaymentIntents create Exception: Error code is:<span class='warningBg'>" . $e->getError()->code . "</span></LI>";
            $payment_intent_id = $e->getError()->payment_intent->id;
            $payment_intent = \Stripe\PaymentIntent::retrieve($payment_intent_id);
        }
    }
    
    make_stripe_elements_payment.php :: generateResponse function
    public function generateResponse($intent, $stripe_publickey) {
        // See https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions?platform=web#update-server
        // and https://stripe.com/docs/payments/accept-a-payment-synchronously?platform=web#create-payment-intent
    
        if ($intent['status'] == 'succeeded') {
            // Handle post-payment fulfillment
            echo json_encode(['success' => true]);
        }
        elseif ($intent['status'] == 'requires_action') {
    
            # Tell the client to handle the action
            $jse=json_encode([
                                 'requiresAction' => true,
                                 'clientSecret' => $intent['client_secret']
                             ]);
    
    
            echo /**@lang Javascript*/"
                <script src='https://js.stripe.com/v3/'></script>
                
                <script>
                    function handleServerResponseJS(responseJson) {
                    
                        console.log('handleServerResponseJS.step5: '+responseJson.requiresAction);
                        
                        if (responseJson.error) {
                            // Show error from server on payment form
                        } 
                        else if (responseJson.requiresAction) {
                        
                            console.log('handleServerResponseJS.step5a');
                            
                            // Use Stripe.js to handle required card action
                            stripe.handleCardAction( //                                       <------------------------- step 5a
                                responseJson.clientSecret
                            ).then(function(result) {
                                if (result.error) {
                                    // Show `result.error.message` in payment form
                                } else {
                                    // The card action has been handled
                                    // The PaymentIntent can be confirmed again on the server
                                    
                                    console.log('handleServerResponseJS.step5b');
                                    fetch('make_stripe_elements_payment.php', { //            <------------------------- step 5b
                                        method: 'POST',
                                        headers: { 'Content-Type': 'application/json' },
                                        body: JSON.stringify({ payment_intent_id: result.paymentIntent.id })
                                    }).then(function(confirmResult) {
                                        return confirmResult.json();
                                    }).then(handleServerResponseJS);
                                }
                            });
                        } else {
                            // Show success message
                        }
                    }
                    
                    const stripe = Stripe('$stripe_publickey');
                    handleServerResponseJS(JSON.parse('$jse'));
                </script>
            ";
        }
        else {
            // Any other status would be unexpected, so error
            echo json_encode(['error' => 'Invalid PaymentIntent status']);
        }
    }
    
    make_stripe_elements_payment.php :: script POST request handler
    if ( strcmp($_SERVER['REQUEST_METHOD'],"POST")==0 ) {
        $this->handleServerResponse();
        exit;
    }
    
    make_stripe_elements_payment.php :: handleServerResponse function
    public function handleServerResponse() {
        // See https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions?platform=web#update-server-second-confirm
    
    
        // Set your secret key. Remember to switch to your live secret key in production!
        // See your keys here: https://dashboard.stripe.com/apikeys
        $stripe = new \Stripe\StripeClient($stripe_secretkey);
    
    
        header('Content-Type: application/json');
    
        # retrieve json from POST body
        $json_str = file_get_contents('php://input');
        $json_obj = json_decode($json_str);
    
        try {
            $intent=null;
            if (isset($json_obj->payment_method_id)) {
                # Create the PaymentIntent
                $intent = $stripe->paymentIntents->create([
                                                            'amount' => 100,
                                                            'currency' => 'gbp',
                                                            'confirm' => true,
                                                            'description'         => 'product ABC',
                                                            'payment_method' => $json_obj->payment_method_id,
                                                            'confirmation_method' => 'manual',
                                                            'use_stripe_sdk' => true,
                                                        ]);
            }
            else {
                //echo "<LI>No payment_method_id</LI>";
            }
            if (isset($json_obj->payment_intent_id)) {
                $intent = $stripe->paymentIntents->retrieve(
                    $json_obj->payment_intent_id
                );
                $intent->confirm();
            }
            $this->generateResponse($intent);
        } catch (\Stripe\Exception\ApiErrorException $e) {
            # Display error on client
            echo json_encode([ 'error' => $e->getMessage() ]);
        }
    }
    

    Step 5 Result:

    Waiting for card details

    For an all in one card collection & payment solution, see striped down code.

    Comments


    New Comment

    NOTE: (Put code blocks in [[[ and ]]] markup to be formatted.)

    (Posted comments will be checked by the administrator before being published)



    Sun Sep 25, 22 17:55:57