How To Request a New Nonce?

I’m using Drupal’s Commerce Square Connect Module to process credit card payments on my website. When I customer attempts to submit a payment with some error, such as an invalid CVV code, the error is detected and the request is rejected. The problem is that when the error is corrected by the customer and the payment request is submitted a second time, the request is again rejected with this error:

{"errors": [{"code": "CARD_TOKEN_USED","detail": "Card nonce already used with a different idempotency key; please request new nonce.","category": "INVALID_REQUEST_ERROR"}]}

I submitted a bug report some time ago. There’s been no response from the project maintainers, so I’d like to suggest a patch for them to consider. Here’s the code:

  $orders_api_request = $orders_api->createOrder($order_request);
  if ($orders_api_request->isSuccess()) {
    $order_response = $orders_api_request->getResult();
    // Create payment request.
    $payment_request = new CreatePaymentRequest(
      $payment_method->getRemoteId(),
      uniqid('', TRUE)
    );
    $payment_request->setAmountMoney($square_total_money);
    $payment_request->setOrderId($order_response->getOrder()->getId());
    $payment_request->setAutocomplete($capture);
    $payment_request->setIdempotencyKey(uniqid('', TRUE));
    $payment_request->setBuyerEmailAddress($payment->getOrder()->getEmail());
    $payment_request->setBillingAddress($billing_address);
    // Create payment.
    $payment_api = $api_client->getPaymentsApi();
    $payment_api_request = $payment_api->createPayment($payment_request);
    if ($payment_api_request->isSuccess()) {
      $payment_response = $payment_api_request->getResult();
      $next_state = $capture ? 'completed' : 'authorization';
      $payment->setState($next_state);
      $payment->setRemoteId($payment_response->getPayment()->getId());
      if (!$capture) {
        $expires = $this->time->getRequestTime() + (3600 * 24 * 6) - 5;
        $payment->setExpiresTime($expires);
      }
      $payment->save();
    }
    else {
      throw ErrorHelper::convertException(
        new ApiException(
          $payment_api_request->getBody(),
          $payment_api_request->getRequest(),
          NULL
        ),
        $payment,
      );
    }

Where in the code above is the correct place to request a new nonce, and what’s the correct API call to make?

No server-side API call can request a new nonce, so none belongs anywhere in this code block. A card nonce (the source_id you’re passing as $payment_method->getRemoteId()) is single-use. It’s consumed by the first CreatePayment attempt even when that attempt is declined. The only way to get a new one is a fresh card.tokenize() call in the browser through the Web Payments SDK.

To fix the module: in the else branch where createPayment fails, delete or invalidate the stored payment method so its remote ID can’t be reused. Then send the customer back to the payment form so the corrected card details are tokenized again, and a new nonce reaches createPayment on the next submit. Generating a new idempotency key with uniqid() on each attempt is correct. Reusing the old nonce is what triggers CARD_TOKEN_USED.

Is there an API call that can be used to delete or invalidate the stored payment method?

You’d be using Drupal’s API to delete the stored payment methods. It looks like their Commerce Square already implements deletePaymentMethod(). Alternatively, you can also utilize setExpiresTime() which is a method on payment method entities used to set the expiration timestamp for the payment method or card nonce

Please feel free to reach out to Drupal if you need more details on how to make use of these 2 methods.