Verification fail on larger transactions

Good evening

I’m obviously getting something wrong here with the sequence of requests and responses. When I do a £1 test translation (production), it works. Any amount in sandbox works. When customers try to purchase for larger amounts I get “verification required” from the python sdk which, as I’ve currently got things set up, stops the transaction and communicates the failure clientside.

Step 1: Client asks for a serverside response

      async function createPayment(token) {
        const body = JSON.stringify({
          locationId,
          sourceId: token,
        });

        const paymentResponse = await fetch('/payment-req', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body,
        });
        if (paymentResponse.ok) {
          return paymentResponse.json();
        }
        
        const errorBody = await paymentResponse.text();
        throw new Error(errorBody);
      }

Step 2: Serverside branches on is_error in the response:

@csrf_exempt
def payment_req(request):
    bsk = Basket.objects.get(id=get_session_id(request, Basket))
    client = Client(
        access_token=access_token_live,
        environment='production')
    idempotency_key = str(uuid.uuid4())
    r = json.loads(request.body.decode())
    pyt = {
        'idempotency_key': idempotency_key,
        'location_id': r['locationId'],
        'source_id': r["sourceId"],
        'amount_money': {
            'amount': int(bsk.total * 100),
            'currency': 'GBP'
        }
    }
    a = client.payments.create_payment(body=pyt)
    if a.is_error():
        return HttpResponse(json.dumps({'success': False,
                                        'payment': {"code": a.errors[0]['code'],
                                                    "detail": a.errors[0]['detail']}}),
                            status=500)
    return HttpResponse(json.dumps({'success': True,
                         'payment': {'id': a.body['payment']['id'],
                                    'status': a.status_code,
                                    'receipt_url': a.body['payment']['receipt_url'],
                                    'order_id': a.body['payment']['order_id']
                                    }}))

If is_error is False only then do I send the verification and transaction completes.

But on larger amounts ‘is_error’ returns True with the following:

{'code': 'CARD_DECLINED_VERIFICATION_REQUIRED', 'detail': "Authorization error: 'CARD_DECLINED_VERIFICATION_REQUIRED'", 'category': 'PAYMENT_METHOD_ERROR'}

As it’s set up, I communicate “payment failure” to the user and it’s game over. How should I handle this response?

Hello! It sounds like you’ll need to add SCA to verify your customers. You can find our docs on the subject here: Verify the Buyer When Using a Payment Token

Hi Josh

Thanks a million for coming back to me so quickly. I implemented it, worked perfectly first time.

Fyi, the docs you shared links a github repo with examples but it 404s:

https://github.com/square/web-payments-quickstart/blob/main/public/examples/card-payment-with-verification.html

Also, your docs list a three char region code, eg: region: 'LND' for London. I skipped it, seemed to work anyway. Don’t know if it’s following an ISO / if there’s an object that returns it / if I have to send another req for it.

Just a little feedback: I think your docs need an “overview”, just to give an high level view of the client-server-req-resp flow and to bring to our attention important options like SCA. At the moment - while each individual page of your docs are very good - very clear indeed - navigating the whole docs site is a little like trying to untangle a Picasso what with all the language options and everything. Of course, if there already is such a page and I missed it just ignore.

2 Likes

Awesome, happy to hear that worked for you!

Thanks for the docs feedback, and for flagging that dead link. Taking a look at the example snippet, it looks like that’ll also need to be updated — I believe the documented field now is state, instead of region. I’ll notify our docs team.

There is a Web Payments SDK overview page here, but I understand that it might not contain all the information you’d hope for. It’s definitely a delicate balance between being comprehensive with an overview and overloading it with information, so we appreciate hearing from you on what you’d like to see!

Following up here — @david12345 the docs team has been discussing your feedback and we’re figuring out ways we can revamp docs on this topic for a better experience.

In the mean time, we also have this page which discusses frontend and backend development for Square’s APIs in general. You may find it helpful as far as providing an overview goes, although I understand that the core of your feedback was that we make this type of high level information accessible from one location instead of split across the docs site.

Thanks again for providing your feedback! We’re always looking for ways to improve the developer experience, so we appreciate it.

We recently updated the quickstart to include verify_buyer in the card-payment example. There isn’t a separate example anymore for SCA which is why that link didn’t work. :slightly_smiling_face:

Oh apologies, my email was spam foldering your replies so I only just saw them. That’s really great job, thank you so much for taking the time to do that. It all looks very clear.

Hi Bryan, I’m so sorry, I spoke too soon, it’s still not right. I’m still sometimes getting the same 'CARD_DECLINED_VERIFICATION_REQUIRED':

Clientside I’m calling your payments object:

  const verificationResults = await payments.verifyBuyer(token, verificationDetails);

verificationDetails contains:

{"amount":"1.00","billingContact":{"addressLines":["[redacted]","[redacted]"],"familyName":"[redacted]","givenName":"[redacted]","email":"[redacted]","country":"GB","phone":"[redacted]","city":"[redacted]"},"currencyCode":"GBP","intent":"CHARGE"}

This spits out a perfectly fine looking verificationToken which I send serverside. There, I add it to the create_payment req:

    pyt = {
        'idempotency_key': idempotency_key,
        'location_id': r['locationId'],
        'source_id': r["sourceId"],
        'amount_money': {
            'amount': int(bsk.total * 100),
            'currency': 'GBP'
        }
    }
    if r.get('verificationToken'):
        pyt['verificationToken'] = r['verificationToken']
    resp = client.payments.create_payment(body=pyt)

The create_payment body is sent the following:

{'idempotency_key': '[redacted]', 'location_id': '[redacted]', 'source_id': 'cnon:[redacted]', 'amount_money': {'amount': 100, 'currency': 'GBP'}, 'verificationToken': 'verf:[redacted]'}

And there the object returns the verification error. You wouldn’t mind having a quick look at the logs would you on my declined transactions?

Thanks so much and sorry to disturb again.

Do you have the payment_id of the failed payment? You can still get verification errors even when a verification token is created passed in the payment request. Banks may still decline payments if information isn’t correct. Did the customer retry the payment or contact there bank? :slightly_smiling_face:

I have only been saving the payment_id on successful transactions as only they go on to create an Order object. I’m now logging it but I’ll need to wait until the next decline. I also asked your customer service if they have the one on an existing transaction as you can’t see it in the console.

There’s definitely an issue. There’s a whole page full of declined payments with customers trying repeatedly. Customers are calling frustrated to say they can’t buy and when we run phone purchases for any value they’re frequently rejected.

Odd though, there’s nothing obvious wrong with the code. It is sending the verificationToken and correctly grabbing the details. I’ll come back to you with a payment_id as soon as I’m able.

Thanks again for your help, much appreciated.

Okay, were happy to take a look if the customer contacted their bank and the bank says that they didn’t block any payments. Also did the customers also reach out to there banks? It’s ultimately the Banks decision on whether or not the funds are released. Having a verification_token never fully guarantees that a payment will go through. :slightly_smiling_face:

We’re having way too many transactions declined for there not to be an issue here. Take a small £150 sale yesterday as an example. Cux tried to purchase six times online, then called us, we ran it on the terminal and it went through fine first time.

Would you mind having a look at this example payment_id please?

DNemhqBXC5guTwlliFSgUeSGbECZY

Thanks again.

Looks like the payment request verification_token was passed in as camelCase verificationToken instead of snake_case which is why the verification failed. :slightly_smiling_face:

Ugh… I was thinking of it as JSON encoding so lowerCamelCase is the standard. Your devs obviously saw it as a dictionary parameter input to a python sdk object, so snake_case. Meh, you can argue the toss either way*.

Anyway, fixed in 5 seconds flat.

I have to say, my interaction with your firm has shown a level of professionalism and dedication I don’t recall seeing elsewhere any time recently.

  • Without exception, your responses have been prompt, expert and extremely helpful.
  • Your dev team’s code base is gold standard in terms of quality. I was recently working with a Google Shopping Content Api and… Well, I had to rewrite large sections of it. Yours however; faultless.
  • Implementing my docs suggestions in full and reviewing is simply amazing. No other firm would react to feedback so pro-actively and positively.
  • I’ve spoken to two customer service reps who have equally bent over backwards to assist. I can’t think of another company where someone non-technical would have come back to me with that payment_id token after listening to me explain what it was for fifteen minutes in Spanish**.
  • Checking the logs for me was really going out of your way. Any other tech support team would have shrugged, assumed it was bank rejection once we settled that the logic was sound and left me stranded.

You carry on as you are; you’re killing it.

*=I was right. ;))
**I’m not above showing off as you can see. :wink:

Glad to hear that it’s working as expected and thank for taking the time to leave the feedback. We really appreciate it. Feel free to reach out anytime with questions. We’re happy to help. :slightly_smiling_face: