Migrating from SqPaymentForm to Web Payments, with PHP for processing

Hello,

I’m trying to migrate from SqPaymentForm to Web Payments, but we want to keep the processing within PHP.

My frontend script looks like this:

const appId = "<?php echo $square_api[$square_mode]['application']; ?>";
const locationId = "<?php echo $square_api[$square_mode]['location']; ?>";

async function initializeCard(payments) {
  const card = await payments.card();
  await card.attach('#card-container');
  return card;
}

async function tokenize(paymentMethod) {
  const tokenResult = await paymentMethod.tokenize();
  if(tokenResult.status === 'OK') {
    return tokenResult.token;
  } else {
    let errorMessage = 'Tokenization failed with status: ' + $tokenResult.status;
    if(tokenResult.errors) {
      errorMessage += ' and errors: ' + $JSON.stringify(tokenResult.errors);
    }
    throw new Error(errorMessage);
  }
}

document.addEventListener('DOMContentLoaded', async function () {
  if(!window.Square) {
    throw new Error('Square.js failed to load properly');
  }

  let payments;
  try {
    payments = window.Square.payments(appId, locationId);
  } catch {
    const statusContainer = document.getElementById(
      'payment-status-container'
    );
    statusContainer.className = 'missing-credentials';
    statusContainer.style.visibility = 'visible';
    return;
  }

  let card;
  try {
    card = await initializeCard(payments);
  } catch (e) {
    console.error('Initializing Card failed', e);
    return;
  }

  // Checkpoint 2.
  async function handlePaymentMethodSubmission(event, paymentMethod) {
    event.preventDefault();
    try {
      // disable the submit button as we await tokenization and make a payment request.
      cardButton.disabled = true;
      const token = await tokenize(paymentMethod);
      **document.getElementById('payment-form').submit();**
    } catch (e) {
      cardButton.disabled = false;
    }
  }

  const cardButton = document.getElementById('card-button');
  cardButton.addEventListener('click', async function(event) {
    await handlePaymentMethodSubmission(event, card);
  });
});

I’ve taken this from the quickstart example found here: web-payments-quickstart/card.html at main · square/web-payments-quickstart · GitHub

I modified it to remove the actual processing of the payment, but keep the rendering of the credit card fields and tokenization, and added the line in BOLD to submit the form once that has taken place. Afterward, it goes to a PHP processing file that looks like this:

require('../square-php-sdk/vendor/autoload.php');
require($_SERVER['DOCUMENT_ROOT'] . '/include/config-square.php');
use Square\Environment;
use Square\SquareClient;
use Square\Models\Money;
use Square\Models\CreatePaymentRequest;
use Square\Exceptions\ApiException;
use Ramsey\Uuid\Uuid;

if($_POST) {
  if($_SERVER['REQUEST_METHOD'] != 'POST') {
    error_log('Received a non-POST request');
    echo 'Request not allowed';
    http_response_code(405);
    return;
  }

  $json = file_get_contents('php://input');
  $data = json_decode($json);
  $token = $data->token;

  $square_client = new SquareClient([
    'environment'       => $square_mode,
    'accessToken'       => $square_api[$square_mode]['access'],
    'userAgentDetail'   => 'sample_php_payment'
  ]);

  $payments_api = $square_client->getPaymentsApi();
  $money = new Money();
  $money->setAmount(100);
  $money->setCurrency($location_info->getCurrency());

  try {
    $create_payment_request = new CreatePaymentRequest($token, Uuid::uuid4(), $money);
    $create_payment_request->setLocationId($location_info->getId());
    $response = $payments_api->createPayment($create_payment_request);
    if($response->isSuccess()) {
      echo json_encode($response->getResult());
    } else {
      echo json_encode(array('errors' => $response->getErrors()));
    }
  } catch(ApiException $e) {
    echo json_encode(array('errors' => $e));
  }
}

The reason we want to keep it in PHP is because we also want to do other things during processing, such as saving data in a database/emailing people/etc… The process appears to work to a point, and it fails at the lines where it attempts to get the token data.

What’s happening is, instead of getting the “token”, I’m instead getting a value in POST that looks like this:

Array ( [nds-pmd] => {"jvqtrgQngn":{"oq":"1920:965:1936:1066:1920:1050","wfi":"flap-153472","oc":"2501pp0s72219oop","fe":"1080k1920 24","qvqgm":"300","jxe":359086,"syi":"snyfr","si":"si,btt,zc4,jroz","sn":"sn,zcrt,btt,jni","us":"s5p1ssn7p6p5189","cy":"Jva32","sg":"{\"zgc\":0,\"gf\":snyfr,\"gr\":snyfr}","sp":"{\"gp\":gehr,\"ap\":gehr}","sf":"gehr","jt":"598oop52s2oo35qp","sz":"87008s68s4196559","vce":"apvc,0,636q5s3r,2,1;fg,0,,0;zz,4p,66,27,svefg_anzr;zzf,3rn,0,n,16q 116,r97 1o09,orq,or6,-9sp6,13654,-130;zzf,3r8,3r8,n,54n 4o4,253o 36sn,9s5,9s8,-25134,297sp,p02;zzf,3rq,3rq,n,68 1ps,6rn 12s6,34n,357,-p9q6,n797,-p6r;zzf,3s7,3s7,n,ABC;zz,103,106,16r,;zzf,2s3,3s6,n,30 55,36q 394p,9n5,9p3,-20o1q,2384q,46q;zzf,3ro,3ro,n,q2 oo,662 33p5,554,56r,-1sn72,1s49q,-38;zzf,3rq,3rq,n,ABC;zzf,3ro,3rn,n,34 o7,r0n 1r05,n09,n0o,-o392,1318p,s3;zp,osp,3r,qp,pneq-ohggba;zz,76,415,32,;zzf,2s0,s63,n,9o 5p,p629 3n3o,271p,29o5,-3ssss,81164,6427;zzf,3rn,3r9,n,ABC;zz,11s4,18q,11r,;gf,0,44so;zzf,152o,2720,32,1oo 2r,911 217p,33n,205r,-6473,noo8,-r;zz,456,27p,o0,;zp,28op,37,q6,pneq-ohggba;gf,0,8738;","ns":"","qvg":"","vp":"","ji":""},"jg":"1.j-669810.1.2.so9leRzhf7PipbuDy780Dt,,.vKLmRdhrVrwdLyx-R_Hp1C5pI3Q-4Tsl6Sa_ysXbrQHNqT-zbysBMscEulxTr68-kSJKCmN-ujOp3ufqYxWaw7A25gcFKk8ivXf7DKcZbEPQ16XBNvtECp2XVJkzhAcaKSTn8czgHpA9MVnFArvGLEhKh8848YXkedaO9nb8ZrrBJFU2X2iVugYV0uDBuf9xIn-Qan5GyGbhfTExbo3-u2vUp33mnusvSrlEiTCmI3HiZKKmCrL1UFIYXsBpvjfp"} ) 

Given what I’m looking to accomplish here, am I going about it entirely the wrong way? Or, is there a way to get the token out of that “nds-pmd” field? I shyould mention that there is very little documentation related to what I’m trying to do, and this whole process has been very confusing and a little frustrating when it should be such an easy thing to do - I’ve searched around Google and these forums, as well as other places, but nothing seems to be quite what I’m looking for. I’ve already read through the quickstart and PHP SDK documentation and also didn’t find much beyond what I already have in place.

Is there anyone that can assist me? We have donation forms that have gone down since SqPaymentForm was deactivated, and we need to find a solution to get them back up and running.

Have you seen our PHP example that uses the Web Payments SDK? :slightly_smiling_face:

I mentioned I had seen the PHP example in the SDK documentation, but it doesn’t work for us because the “sq-payment-flow.js” file uses the “fetch” method to call the PHP processing. We have multiple forms using credit card payments, so we need to use the action on the form to get us to the processing file for each form individually.

How can we utilize the Web Payments SDK to give us the credit card inputs on the frontend, and then send that card data (and whatever token is needed as well) to our PHP backend for processing through an HTML form?

The Web Payments SDK uses fetch as well when calling the CreatePayment function on the server side. You can definitely do the Same with the Web Payments SDK. You can have all your other forms filed out before enabling the submission of the card information to tokenize. :slightly_smiling_face:

1 Like

I got that, I’m specifically trying to NOT use that though - not all of the processes are the same, so we can’t just use one processing file for all of them.

Take a hypothetical HTML donation form, for example. I want the credit card fields in my form, which has an action of “donate.php”, and when the form is submitted, the credit card information and token gets passed over to the processing WITHOUT needing JS “fetch” methods. I believe I’m doing this in the code I showed above, but the token doesn’t appear to be usable as it currently stands - how do I get access to that token within PHP?

The sample code I got from your documentation is as follows:

$json = file_get_contents('php://input');
$data = json_decode($json);
$token = $data->token;

However, this does not work, and instead gives an error about the “$token” variable being null. It’s not passing from the frontend to the backend correctly - how do I make the two work together, if at all?

Can you confirm if the token is being generated?

Debugging screenshot attached - the token is generated, but is not getting passed over to the PHP processing. Instead, the only additional POST data I have is the contents of the “nds-pmd” variable in my original post. Is there any native way to pass that token data along, or should I be writing JS to write that into a hidden form field?

Great, the token is being generated. Our example fetches it to process the payment. If you instead fetch it to then do all the other things your page needs to do. Then later POST it to your backend to process payment. :slightly_smiling_face:

The logic is such that the database work would depend on the payment being processed first, so something like:

if($payment_is_processed) {
  // Do database stuff
}

That doesn’t seem doable using what you’re recommending doing - it seems more like we’re doing our stuff first, then processing the payment afterward. What if we want to record the transaction ID along with our data? What if something happens and the payment doesn’t go through?

The thing I’m having trouble understanding here is the process of the migration itself - it’s not nearly as straightforward or “simple” as it’s being made out to be, there are major changes to the actual code requirements. For us, our functioning PHP forms are no longer going to be usable as they are, and the lack of fully functional examples (see any number of other recent forum posts about that, and which other companies have no problem providing) has caused our forms to be down for days at this point, still with no solutions (again, see other forum posts about developers not receiving notifications about these forms being discontinued).

You can definitely do that too. Are you following the Migration Guide or have you been referencing the getting started? :slightly_smiling_face:

I have, it basically leads us back to the quick start sample, which is what I’m working with - again, how do I get the token to pass through the form as an input into my PHP processing file so that I can send it through to the API to process the payment?

The Web Payments SDK returns a single object called TokenResult, which contains the payment token. It is returned by the tokenize function on a payment method.

The SqPaymentForm returns a payment token as a parameter of the cardNonceResponseReceived callback function. Unlike the Web Payments SDK, which returns the entire tokenize result in a single object, the SqPaymentForm splits the results over multiple objects.

The Web Payments SDK returns a token in a TokenResult. The object provides the same information returned by the SqPaymentForm but in a different structure. Structural changes include:

  • The billing address is part of the details.card object and represented by the card.billing field.
  • The SqPaymentForm cardData object is now the details.card object.
  • Shipping contact and shipping options objects are now part of the details.shipping object:
    • country and countryName are now contact.countryCode and shippingContact.countryCode. The name of the country is no longer provided.
    • The address field region is now state.
  • The SqPaymentForm nonce is now a token.
  • There is a new bankAccount object for ACH bank transfer payments.

For example:

{
  "details": {
    "bankAccount": {   
      "accountNumberSuffix": "",
      "accountType": "",
      "bankName": ""
    },
    "card": {
      "billing": {
        "addressLines": [
          "123 Main St",
          "Unit 5-234"
        ],
        "city": "Brisbane",
        "countryCode": "AU",
        "familyName": "Smith",
        "givenName": "Bill",
        "postalCode": "QLD 4001",
        "state": "Queensland"
      },
      "brand": "americanExpress",
      "cardType": "CREDIT",
      "expMonth": 12,
      "expYear": 2021,
      "lastFour": "1234",
      "prepaidType": "NOT_PREPAID"
    },
    "giftCard": {
      "type": ""
    },
    "method": "Apple Pay",
    "shipping": {
      "contact": {
        "addressLines": [
          "123 Main St",
          "Unit 5-234"
        ],
        "city": "Brisbane",
        "countryCode": "AU",
        "email": "[email protected]",
        "familyName": "Smith",
        "givenName": "Bill",
        "phone": "+61 0755 552 222",
        "postalCode": "QLD 4001",
        "state": "Queensland"
      },
      "option": {
        "amount": "12.00",
        "id": "123",
        "label": "Next Day Shipping"
      }
    }
  },
  "errors": {
    "field": "",
    "message": "",
    "type": ""
  },
  "status": "OK",
  "token": "cnon:card-nonce-ok"
}

You’ll take the "token": "cnon:card-nonce-ok" from the TokenResult. Then you can POST it to your backend for processing. :slightly_smiling_face:

1 Like

support11: I spent most of last weekend struggling to do pretty much what you’re trying to do. The quickstart example was pretty useless to me, but the php example in the github “examples” repo (connect-api-examples/README.md at master · square/connect-api-examples · GitHub) worked, with some modification.

For the js, I combined sq-card-pay.js and sq-payment-flow.js into one file. You don’t need anything of the other js files in the example. I had to mod it to use a non-standard return from the server that contained the order number for a js redirect that fires after a timeout. And, despite what the how-to video says, it doesn’t disable the pay button. My client just emailed me saying people are paying twice, so I need to fix that.

For the php, I pretty much used process_payment.php as is, combined with the existing stuff, which handles everything but the money – customer data, shipping data, discount, emailing, etc. I did have to mod the response from Square to add a field for the order number. I saved the response so I could get the transaction id and the “last 4” for my db.

I also hacked the errors response to provide something meaningful to the customer. I mean, really, “PAN_FAILURE” (bad cc#? Fortunately, there is a list of error codes on Square’s site, so you can change things.

Like you, I’m migrating from SqPaymentForm. All the business logic is handled by php, so the only thing I’m using it for is the payment. If I were writing an application from scratch, I might use the other stuff, but this has been working for at least 10 years (with updates, of course) and has used several different payment gateways through the years. It works fine.

There is one thing that I’m still working on: every example I’ve seen the credentials that you are urged to keep secret are hardcoded into the js. C’mon guys, anybody that knows how to “inspect” in a browser has access to them. I’m working on an Ajax call to the server to get them, but there are definite timing issues that I have to test/work out. Any suggestions?

Also, from a user-experience point of view, is there any way to pass the zip code to the cc form? The buyer has already entered it on the previous page, so it is available.

1 Like

Thanks for sharing all your findings. With credentials we definitely don’t want anyone to hardcode them. We recommend that you set them as environment variables.

As for setting the postal code you can pass in a preset placeholder. For example:

async function initializeCard(payments) {
        const card = await payments.card({
          "postalCode": "12345"
        });
        await card.attach('#card-container');

        return card;
      }
``` :slightly_smiling_face:
1 Like

As I understand it (I could be wrong), environment vars are a server-side thing and would normally be used in the server-side code or displayed in a template for use on the front end if needed. But putting them in your template means that anybody viewing the source has access to them

But the thing that makes this problem probably impossible to solve, is that you use the creds in global (window) variables. So no matter how they are passed – in a template, hard coded in the js, fetched, whatever – they are still visible in Chrome’s developer tools’ “sources” panel. I can hide them from everything but that.

Now, if there was a way to pass them directly to the appropriate function… (Or maybe encrypt them?)

The postal-code passing works great. Thanks!