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_setupIntents.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_setupIntents.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_setupIntents.php
    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>";
    

    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_setupIntents.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>
            
            ";
    
        echo "".$this->getCardHelperTable()."";
    
    
        // 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'
                };
                
            // 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_1LlysHE8srzgGJZyP499anNj]

  • 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 - instead of immediately charging the customer while they are online, we want to charge the customer using a background process some time later, here demonstrated with the make_stripe_setupIntents_payment.php script.

    Once we get to this stage, the [Make Payment now...] button should be clickable multiple times - simulating the periodic charging that may occur with subscriptions.

    warning make_stripe_setupIntents_payment.php will request payment, but situations where it fails due to authorisation issues, lack of funds, or card-expiry are yet to be worked out.

    stripe_setupIntents.php
    echo /**@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_setupIntents_payment.php?'+args;
    }
    ";
    
    stripe_setupIntents.php
    if (isset($_GET['step4resultMsg'])) {
    
        echo /**@lang HTML */"
            <p>stripe_setupIntents 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]");
            }
    
            foreach ($_POST as $k => $v) {
                echo "<LI style='white-space:nowrap'>POST " . $this->getSafeTx("[$k] = [$v]");
            }
    
        }
        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_setupIntents_payment.php
    // 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_secretkey = config::stripeSecretKey;
    $stripe_publickey = config::stripePublicKey;
    
    $stripe = new \Stripe\StripeClient($stripe_secretkey);
    
    $customerId = getSafePostTx($_GET['ci']);
    
    $json = $stripe->paymentMethods->all(
        ['customer' => "$customerId", 'type' => 'card']
    );
    
    echo "<BR>paymentMethods response:<PRE style='max-height:150px; overflow:auto; background-color:lightgrey; padding:10px'>$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...";
            flush();
    
            $response = $stripe->paymentIntents->create([
                                                            'amount'         => 100,
                                                            'currency'       => 'gbp',
                                                            'description'    => 'product ABC',
                                                            'customer'       => "$customerId",
                                                            'payment_method' => "$pmid",
                                                            'off_session'    => true,
                                                            'confirm'        => true,
                                                        ]);
    
            echo "<LI>Response status=".$response['status'];
            if ( strcmp($response['status'], "succeeded")==0 ) {
                echo "<LI style='background-color:green; color:white; padding:10px'>Payment intent created</LI>";
            }
            echo "<p>Response:</p>";
            echo "<PRE>$response</PRE>";
        }
        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);
        }
    }
    

    Step 5 Result:

    Waiting for card details

    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 18:44:16