RootStudio / Root-Perch-Shop-Omnipay-v3

Ominpay update for perch shop to allow stripe 3DS2 payments
3 stars 0 forks source link

Make payment click - nothing happens #6

Closed stephenturvey closed 3 years ago

stephenturvey commented 3 years ago

Hi Byron, thanks for your work on this.

I've done my best to implement the new files and code to my project, but when clicking on Make Payment - alas - nothing happens.

In the console I've got the following errors:

*The publishable_key is showing in showall, although I can tell this is coming from the 'stripe' gateway in shop.php and not 'stripe_intents' (I have both copied in there having read one of the other issues someone had).

Which files would you like me to paste in here to help find the problem?

Thanks again.

byron-roots commented 3 years ago

Hi There,

If you could post your perch_stripe_checkout.js file that should be enough to get to the bottom of your issues

I think this is an error in your JS implementation for stripe elements. When calling the Stripe SDK you need to supply your publishable key.

var stripe = new Stripe('pk_test_xxxxx');

From the error in the console I guess you are calling it without the key.

As for the second error, are you loading the JS file directly or are you using a bundler such as webpack? If you are using the JS example file it requires a bundler for it to work. As a few others have mentioned they are struggling with the JS as they aren't using bundlers, I'll stick another example up that you can just drop in.

Working on the publishable key being pulled in from the wrong config now as well.

stephenturvey commented 3 years ago

Hi, thanks for getting back to me.

On my checkout page I'm just linking to the js file that was included in the download like so:

<script src="/js/perch_stripe_checkout.js"></script>

I haven't edited that file at all so it is as provided (didn't see anything in readme about editing this). Not using a bundler so guess that's where it's going wrong?

I searched all the code for your example above:

var stripe = new Stripe('pk_test_xxxxx');

But the closest I could find was within stripe_intents_payment_form.html:

var stripe = Stripe('<perch:shop id="publishable_key" escape="true">');

byron-roots commented 3 years ago

The two pull requests should help address your above issues. The correct gateway config should be pulled in correctly now.

I've added a new JS example that should work in the browser without needing to be changed. The file creates a function you can call anywhere once it's included, and the function call is included at the bottom of the example, which you'll need to remove if you want to call it elsewhere. You may want to update/edit the createPayment method to include/remove any fields to be passed to stripe (billing details e.t.c) or to add extra error handling.

stephenturvey commented 3 years ago

Hi Byron,

Well the good news is that the correct gateway config is now pulled in.

Unfortunately I've still got no luck with anything else, I don't know much about js at all so I suspect I've got a mish-mash of my old code, this new code and the stuff I found on the other issues pages, not knowing what is what. I'll paste my code below and hopefully it's something simple I've missed:

stripe_intents_payment_form.html

<form id="payment-form" action="/checkout" method="post">
  <div id="card-element"></div>
  <!-- Hidden fields used to send to stripe checkout -->
  <input type="hidden" value="<perch:member id="email" escape="true" />" name="email"/>
  <input type="hidden" value="<perch:member id="first_name" escape="true" /> <perch:member id="last_name" escape="true" />" name="cardholderName"/>
      <!-- Address Fields if passed through -->
      <input type="hidden" value="<perch:member id="addressLine1" escape="true" />" name="address"/>
      <input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
      <input type="hidden" value="<perch:member id="postcode" escape="true" />" name="postcode"/>
      <!-- ./Address Fields if passed through -->
  <input type="hidden" id="paymentMethodID" name="paymentMethodID"/>
  <!-- ./Hidden fields used to send to stripe checkout -->
  <!-- Regular button as we want to submit with JS -->
  <button type="button" class="button button--primary">Make Payment</button>
</form>

<script src="https://js.stripe.com/v3/"></script>

<!-- copied from https://github.com/RootStudio/Root-Perch-Shop-Omnipay-v3/issues/4 -->
<script>
    var stripe = Stripe('<perch:shop id="publishable_key" escape="true">');
    var elements = stripe.elements();

    var card = elements.create('card', {
      hidePostalCode: true
    });

    card.mount('#card-element');

    // The form is submitted
    var form = document.getElementById('payment-form');
    var cardholderName = document.querySelector( '[name="cardholderName"]' );
    var email = document.querySelector( '[name="email"]' );
    var paymentMethodID = document.querySelector( '[name="paymentMethodID"]' );

    function stripeTokenHandler(token) {
      // Insert the token ID into the form so it gets submitted to the server
      var form = document.getElementById('payment-form');
      var hiddenInput = document.createElement('input');
      hiddenInput.setAttribute('type', 'hidden');
      hiddenInput.setAttribute('name', 'stripeToken');
      hiddenInput.setAttribute('value', token.id);
      form.appendChild(hiddenInput);
      stripe.createPaymentMethod({
      type: 'card',
      card: card,
      billing_details: {
        name: cardholderName.value,
        email: email.value,
      },
    })
    .then(function(result) {
      if (result.error) {
        // Inform the user if there was an error
        var errorElement = document.getElementById('card-errors');
        errorElement.textContent = result.error.message;
      } else {
        // Send the token to your server
        paymentMethodID.value = result.paymentMethod.id;
        //console.log(result.paymentMethod);
        form.submit();
      }
    });

    }

    function createToken() {
      stripe.createToken(card).then(function(result) {
        if (result.error) {
          // Inform the user if there was an error
          var errorElement = document.getElementById('card-errors');
          errorElement.textContent = result.error.message;
        } else {
          // Send the token to your server
          stripeTokenHandler(result.token);
        }
      });
    };

    // Create a token when the form is submitted.
    var form = document.getElementById('payment-form');
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      createToken();
    });

    </script>

    <perch:showall>

js/perch_stripe_checkout_es5.js


    //Check if stripe has been loaded
    if ( typeof Stripe != 'undefined' ) {
        var stripe = Stripe( key );

        var elements = this.stripe.elements();
    }
    // Get the card element container
    if ( document.querySelector( '#card-element' ) ) {

        // Create stripe card elements
        var cardElement = this.elements.create( 'card', {
            hidePostalCode: true
        } );

        cardElement.mount( '#card-element' );

        // Get hidden fields for validation
        var cardholderName = document.querySelector( '[name="cardholderName"]' );
        var email = document.querySelector( '[name="email"]' );
        var address = document.querySelector( '[name="address"]' );
        var city = document.querySelector( '[name="city"]' );
        var postcode = document.querySelector( '[name="postcode"]' );
        var cardButton = document.getElementById( 'card-button' );
        var paymentMethodID = document.querySelector( '[name="paymentMethodID"]' );
        var form = document.querySelector( '#stripeForm' );

        // Add listener for the form button
        cardButton.addEventListener( 'click', function( ev ) {
            // Get payment intent from stripe
            stripe.createPaymentMethod( 'card', this.cardElement, {
                billing_details: {
                    name: cardholderName.value,
                    email: email.value,
                    address: {
                        line1: address.value,
                        postal_code: postcode.value,
                        city: city.value,
                        country: 'GB'
                    }
                }
            } ).then( function( result ) {

                paymentMethodID.value = result.paymentMethod.id;
                // Submit form for next step
                form.submit();
            } );
        } );
    }

}

// Call function with
PerchStripeCheckout('pk_test_xxxxxx');

confirm.php


    include($_SERVER['DOCUMENT_ROOT'].'/admin/runtime.php');

    // hide the address/payment options with css
    if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {

        $status = 'loggedin';

        // pass to stripe basics
        $customer_firstname = perch_member_get('first_name');
        $customer_surname = perch_member_get('last_name');
        $customer_name = $customer_firstname . ' ' . $customer_surname;
        $customer_email = perch_member_get('email');

        // pass to stripe cart titles, skus
        $cart = perch_shop_cart(['skip-template'=>true]);
        $cart_items = $cart['items'];

        foreach($cart_items as $item) {
          $product_titles[] = $item['title'];
          $product_skus[] = $item['sku'];
        }

        $product_title = implode(', ', $product_titles);
        $product_sku = implode(', ', $product_skus);

    }

    else {
        $status = 'loggedout';
    }

    // stripe urls
    if (perch_member_logged_in() && perch_post('stripeToken')) {

        $return_url = '/pay/success';
        $cancel_url = '/pay/error';

        // stripe data
        perch_shop_checkout('stripe_intents', [
        'return_url' => $return_url,
        'cancel_url' => $cancel_url,
        //'token' => perch_post('stripeToken'),
        'payment_method' => perch_post('paymentMethodID'), // Pass the payment method to the gateway
        'receipt_email' => $customer_email,
        'metadata' => [
            'email' => $customer_email,
            'name' => $customer_name,
            'product(s)' => $product_title,
            'sku(s)' => $product_sku,
            ],
        'confirm' => true, // Tell the gateway to confirm the gateway
        ]);

    }

    // paypal urls
    if (perch_member_logged_in() && isset($_POST["paypal"])) {

    $return_url = 'http://esl.local/pay/result-paypal';
    $cancel_url = 'http://esl.local/pay/error';

    perch_shop_checkout('paypal-express', [
    'return_url' => $return_url,
    'cancel_url' => $cancel_url,
    ]);

    }

    perch_layout('header', array(
        'title' => "Confirm your order",
    ));

?>

<div class="clear feature-block ta-c mb-0">

    <div class="wrap wrap--large">

        <h1 class="mb-0">Confirm and pay</h1>

    </div>

</div>

<div class="clear">

    <div class="wrap wrap--small">

        <?php perch_content('Intro text'); ?>

        <?php

            // Show the cart 
            perch_shop_cart([
                'template'=>'cart/cart.html'
            ]);

        ?>

        <hr>

        <style>.loggedout {display: none;}</style>

        <div class="grid mb-0 <?php echo $status ?>">

            <div class="grid__6"><?php perch_shop_order_addresses(); ?></div>

            <div class="grid__6">

                <div class="tabs">

                    <h2>Pay here</h2>

                    <input id="tab-1" type="radio" name="type" value="1" checked="checked" aria-selected="true" aria-controls="card-tab" class="tabs__state">
                    <label for="tab-1"> Pay direct with card</label>

                    <input id="tab-2" type="radio" name="type" value="2" aria-controls="paypal-panel" class="tabs__state">
                    <label for="tab-2"> Pay with Paypal</label>

                    <div class="tab tab-1" role="tabpanel" aria-labelledby="card-tab">
                        <?php
                        // Show the payment form if logged in and something in cart
                        if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
                        perch_shop_payment_form('stripe_intents');
                        }
                        else echo "There is nothing in the cart";
                        ?>
                        <br>
                        <p class="text-colour-grey">(Quick and easy Stripe payment - 100% secure)</p>
                    </div>

                    <div class="tab tab-2" role="tabpanel" aria-labelledby="paypal-tab">
                        <?php
                        // Show the paypal button if logged in and something in cart
                        if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
                        echo '<form action="" method="post"><button name="paypal" class="button button--primary" value="true">Pay with Paypal</button></form>
                        <br>
                        <p class="text-colour-grey">(This will take you off to the Paypal website)</p>
                        ';
                        }

                        else echo "There is nothing in the cart";
                        ?>
                    </div>

                </div>

            </div>

        </div>

    <?php
    // Show the cancel link if logged in and something in cart
    if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
    echo '<p><a href="logout" style="color:red;">Cancel order</a></p>';
    }
    ?>

    </div>

</div>

<div class="clear">
    <div class="wrap wrap--small">
        <?php perch_content('Shop footer'); ?>
    </div>
</div>

<script src="/js/perch_stripe_checkout_es5.js"></script>

<?php 
    perch_layout('footer', array(
        'hide-social' => true,
    )); 
?>
byron-roots commented 3 years ago

There were some issue with my examples, but I've tried it on site locally now and fixed the issues. All you need to change is the stripe_intents_payment_form.html. The below should work for you

<form id="stripeForm" action="/checkout" method="post">
    <div id="card-element"></div>
    <!-- Hidden fields used to send to stripe checkout -->
    <input type="hidden" value="<perch:member id="email" escape="true" />" name="email"/>
    <input type="hidden" value="<perch:member id="first_name" escape="true" /> <perch:member id="last_name" escape="true" />" name="cardholderName"/>
    <!-- Address Fields if passed through -->
    <input type="hidden" value="<perch:member id="addressLine1" escape="true" />" name="address"/>
    <input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
    <input type="hidden" value="<perch:member id="postcode" escape="true" />" name="postcode"/>
    <!-- ./Address Fields if passed through -->
    <input type="hidden" id="paymentMethodID" name="paymentMethodID"/>
    <!-- ./Hidden fields used to send to stripe checkout -->
    <!-- Regular button as we want to submit with JS -->
    <button id="card-button" type="button" class="button button--primary">Make Payment</button>
</form>

<script src="https://js.stripe.com/v3/"></script>

<script>
    function PerchStripeCheckout ( key = '' ) {

        //Check if stripe has been loaded
        if ( typeof Stripe != 'undefined' ) {
            var stripe = Stripe( key );

            var elements = stripe.elements();
        }
        // Get the card element container
        if ( document.querySelector( '#card-element' ) ) {

            // Create stripe card elements
            var cardElement = elements.create( 'card', {
                hidePostalCode: true
            } );

            cardElement.mount( '#card-element' );

            // Get hidden fields for validation
            var cardholderName = document.querySelector( '[name="cardholderName"]' );
            var email = document.querySelector( '[name="email"]' );
            var address = document.querySelector( '[name="address"]' );
            var city = document.querySelector( '[name="city"]' );
            var postcode = document.querySelector( '[name="postcode"]' );
            var cardButton = document.getElementById( 'card-button' );
            var paymentMethodID = document.querySelector( '[name="paymentMethodID"]' );
            var form = document.querySelector( '#stripeForm' );

            // Add listener for the form button
            cardButton.addEventListener( 'click', function( ev ) {
                // Get payment intent from stripe
                stripe.createPaymentMethod( 'card', cardElement, {
                    billing_details: {
                        name: cardholderName.value,
                        email: email.value,
                        address: {
                            line1: address.value,
                            postal_code: postcode.value,
                            city: city.value,
                            country: 'GB'
                        }
                    }
                } ).then( function( result ) {

                    paymentMethodID.value = result.paymentMethod.id;
                    // Submit form for next step
                    form.submit();
                } );
            } );
        }

    }
</script>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        PerchStripeCheckout('<perch:shop id="publishable_key" escape="true">');
    });
</script>

The main script tag block can be moved into a seperate JS file and included separately. You'll need to update this somewhat if you want to add/remove fields to send to stripe or if you want to edit the stripe elements stylings. You can see all the options in their docs. For styling options see the docs here

stephenturvey commented 3 years ago

Thanks Byron, we're getting somewhere now!

Quick question - are all these vendor files in the project src vital and need copying across/replacing my current files?

braintree/braintree_php clue/stream-filter composer guzzlehttp league/csv moneyphp/money omnipay php-http psr ralouphie/getallheaders stripe/stripe-php symfony autoload.php

(https://github.com/RootStudio/Root-Perch-Shop-Omnipay-v3/tree/master/perch_shop/lib/vendor)

byron-roots commented 3 years ago

Yeah they'll need to be copied over, as this includes the updated omnipay package for stripe to use the intents API

stephenturvey commented 3 years ago

Thanks. Make Payment button is now having an effect - but the payment is not successful so it just redirects to the confirmation page and displays the unsuccessful message. Wondering how I can debug this?

Relevant files if helpful:

confirm.php

<?php 

    include($_SERVER['DOCUMENT_ROOT'].'/admin/runtime.php');

    // hide the address/payment options with css
    if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {

        $status = 'loggedin';

        // pass to stripe basics
        $customer_firstname = perch_member_get('first_name');
        $customer_surname = perch_member_get('last_name');
        $customer_name = $customer_firstname . ' ' . $customer_surname;
        $customer_email = perch_member_get('email');

        // pass to stripe cart titles, skus
        $cart = perch_shop_cart(['skip-template'=>true]);
        $cart_items = $cart['items'];

        foreach($cart_items as $item) {
          $product_titles[] = $item['title'];
          $product_skus[] = $item['sku'];
        }

        $product_title = implode(', ', $product_titles);
        $product_sku = implode(', ', $product_skus);

    }

    else {
        $status = 'loggedout';
    }

    // stripe urls
    if (perch_member_logged_in() && perch_post('paymentMethodID')) {

        $return_url = '/pay/result-stripe';
        $cancel_url = '/pay/result-stripe';

        // stripe data
        perch_shop_checkout('stripe_intents', [
        'return_url' => $return_url,
        'cancel_url' => $cancel_url,
        //'token' => perch_post('stripeToken'),
        'payment_method' => perch_post('paymentMethodID'), // Pass the payment method to the gateway
        'receipt_email' => $customer_email,
        'metadata' => [
            'email' => $customer_email,
            'name' => $customer_name,
            'product(s)' => $product_title,
            'sku(s)' => $product_sku,
            ],
        'confirm' => true, // Tell the gateway to confirm the gateway
        ]);

    }

    // paypal urls
    if (perch_member_logged_in() && isset($_POST["paypal"])) {

    $return_url = 'https://www.eslstarter.com/pay/result-paypal';
    $cancel_url = 'https://www.eslstarter.com/pay/error';

    perch_shop_checkout('paypal-express', [
    'return_url' => $return_url,
    'cancel_url' => $cancel_url,
    ]);

    }

    perch_layout('header', array(
        'title' => "Confirm your order",
    ));

?>

<div class="clear feature-block ta-c mb-0">
        <h1 class="mb-0">Confirm and pay</h1>
</div>

<div class="clear">

    <div class="wrap wrap--small">

        <?php perch_content('Intro text'); ?>

        <?php

            // Show the cart 
            perch_shop_cart([
                'template'=>'cart/cart.html'
            ]);

        ?>

        <hr>

        <style>.loggedout {display: none;}</style>

        <div class="grid mb-0 <?php echo $status ?>">

            <div class="grid__6"><?php perch_shop_order_addresses(); ?></div>
            <div class="grid__6">
                <div class="tabs">

                    <h2>Pay here</h2>

                    <input id="tab-1" type="radio" name="type" value="1" checked="checked" aria-selected="true" aria-controls="card-tab" class="tabs__state">
                    <label for="tab-1"> Pay direct with card</label>

                    <input id="tab-2" type="radio" name="type" value="2" aria-controls="paypal-panel" class="tabs__state">
                    <label for="tab-2"> Pay with Paypal</label>

                    <div class="tab tab-1" role="tabpanel" aria-labelledby="card-tab">

                        <?php
                        // Show the payment form if logged in and something in cart
                        if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
                        perch_shop_payment_form('stripe_intents');
                        }
                        else echo "There is nothing in the cart";
                        ?>

                        <br>
                        <p class="text-colour-grey">(Quick and easy Stripe payment - 100% secure)</p>
                    </div>

                    <div class="tab tab-2" role="tabpanel" aria-labelledby="paypal-tab">
                        <?php
                        // Show the paypal button if logged in and something in cart
                        if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
                        echo '<form action="" method="post"><button name="paypal" class="button button--primary" value="true">Pay with Paypal</button></form>
                        <br>
                        <p class="text-colour-grey">(This will take you off to the Paypal website)</p>
                        ';
                        }

                        else echo "There is nothing in the cart";
                        ?>
                    </div>
                </div>
            </div>
        </div>

    <?php
    // Show the cancel link if logged in and something in cart
    if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
    echo '<p><a href="logout" style="color:red;">Cancel order</a></p>';
    }
    ?>

    </div>

</div>

<div class="clear">
    <div class="wrap wrap--small">
        <?php perch_content('Shop footer'); ?>
    </div>
</div>

<?php 

    perch_layout('footer', array(
        'hide-social' => true,
    )); 

?>

result-stripe.php

<?php 
include($_SERVER['DOCUMENT_ROOT'].'/admin/runtime.php');

/**
 * Here we check if the payment was redirected or not. If the payment_intent field exists we need to finish the payment
 */
if (PerchUtil::get('payment_intent')) {

  $payment_opts = [
      'paymentIntentReference' => PerchUtil::get('payment_intent'), // set the reference ID
      'returnUrl'              => 'http://esl/pay/result-stripe',
      'confirm'                => true, // We are confirming the payment so we want this true
  ];

  // New method to confirm the payment. Use the above options and the new intents gateway
  perch_shop_confirm_payment('stripe_intents', $payment_opts);
  }

    perch_layout('header', array(
        'title' => "Your order",
  ));

?>

<div class="clear feature-block ta-c mb-0">
    <h1 class="mb-0">Your order</h1>
</div>

<div class="clear">
    <div class="wrap wrap--small">

    <?php
    perch_shop_complete_payment('stripe_intents');

    if (perch_shop_order_successful()) {
    echo '<h2>Thank you for your order!</h2>
    <p>We have successfully received your payment from Stripe. You should receive a confirmation email shortly. Please take a moment to like us on Facebook and sign up for our newsletter (see footer below).</p>
    <p><a href="/">Back to homepage</a></p>';
    }

    else{
    echo '<h2>Important: It looks like the transaction was unsuccessful</h2>

    <p><strong>Please check your email</strong>. If you have received a confirmation email then your order was successful and there is nothing more you need to do.</p>
    <p>If no emails have arrived after 10 minutes please email us and one of our staff will be in touch as soon as possible. Include in the email your name, what you were trying to order and what happened. You can also call us on <strong>0800 368 2255</strong> (Mon-Fri 9am-5pm).</p>
    <p>If you accidentally cancelled the order you can go back to <a href="/pay/confirm">the cart</a> and try again.</p>';
    }

    ?>

    </div>
</div>

<?php 
  perch_layout('footer', array(
      'hide-social' => true,
  )); 
?>
byron-roots commented 3 years ago

Your current stripe URLs are using relative paths, you'll need to use am absolute path for these to work correctly

stephenturvey commented 3 years ago

Payments now going through, thanks Byron.

stephenturvey commented 3 years ago

Quick one - how do I pass these through to stripe_intents_payment_form.html? Name and email are both present so wondering why these ones aren't. Thank you

<!-- Address Fields if passed through -->
<input type="hidden" value="<perch:member id="addressLine1" escape="true" />" name="address"/>
<input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
<input type="hidden" value="<perch:member id="postcode" escape="true" />" name="postcode"/>
<!-- ./Address Fields if passed through -->
byron-roots commented 3 years ago

You'll need to set them as global perch variables using the PerchSystem::set_var(). You can use the perch address functions to get the address details.

It seemed to work fine as is without this on our first shop, but we must have had some other configurations that set the address else where

stephenturvey commented 3 years ago

I thought that PerchSystem::set_var() had to be used in conjunction with perch_content_custom()? I think I need to get the values passed into perch_shop_payment_form('stripe_intents')

byron-roots commented 3 years ago

PerchSystem::set_var() just sets a global variable. Whenever perch calls it's template renderer all these variables are passed through, not just in the content custom runtime function.

stephenturvey commented 3 years ago

Okay that's good to know, thanks!

I'm stuck now at the final hurdle. I can't figure out how to get, for example, the city out of perch_shop_customer_address

stripe_intents_payment_form.html <input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>

checkout.php PerchSystem::set_var('city', perch_shop_customer_address('??'));

I've tried a few things but no dice.

byron-roots commented 3 years ago

It looks like it could be a namespace issue. the perch:member namespace lets you retrieve member information outside of the current namespace, but only if it relates to the members app. Otherwise you'll need to be using the perch:shop namespace

stephenturvey commented 3 years ago

Hi Byron,

I seem to have got it working using the following:

stripe_intents_payment_form.html

<input type="hidden" value="<perch:shop id="addressLine1" escape="true" />" name="address"/>
<input type="hidden" value="<perch:shop id="city" escape="true" />" name="city"/>
<input type="hidden" value="<perch:shop id="postcode" escape="true" />" name="postcode"/>

checkout.php

$addresses = perch_shop_customer_addresses(['skip-template'=>true]);
PerchSystem::set_var('addressLine1', $addresses[0]["address_1"]);
PerchSystem::set_var('city', $addresses[0]["city"]);
PerchSystem::set_var('postcode', $addresses[0]["postcode"]);

Does that look right to you?

byron-roots commented 3 years ago

That looks fine to me.

If you wanted to clean it up a bit you could do PerchSystem::set_vars($addresses[0]);

stephenturvey commented 3 years ago

Thanks for all your help.