Payments SDK displaying Success or Failure details in the DOM

I am able to get the card to charge successfully, and I confirm it in my square app but nothing shows up on the DOM. My view (using Laravel and Blade)

in the HEAD:

<!-- Initialize Web Payments SDK -->
        <script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
        <script>
            {{-- Initialize Web Payments SDK variables --}}
            const appId = 'MY_APP_ID';
            const locationId = 'MY_LOCATION_ID';

            // Initializes the Web Payments SDK by calling the app and location id
            // also initializes card payment method by calling card.attach
            async function initializeCard(payments) {
                const card = await payments.card();
                await card.attach('#card-container');
                return card;
            }

            // Call this function to send a payment token, buyer name, and other details
            // to the project server code so that a payment can be created with
            // Payments API
            async function createPayment(token) {
                const body = JSON.stringify({
                    locationId,
                    sourceId: token,
                    idempotency_key: {{ $payment->id }},
                    patient_id: {{ $payment->patient_id }},
                    amount: @isset($payment->due_amt){{ str_replace('.', '',$payment->due_amt) ?? 0.00 }}@endisset
                });

                const paymentResponse = await fetch('{{$payment->id}}', {
                    method: 'POST',
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json',
                        'X-CSRF-TOKEN': '{{ csrf_token() }}'
                    },
                    body,
                });


                if (paymentResponse.ok) {

                    console.log(paymentResponse.then(result => result.data));

                    return paymentResponse.json();
                }

                const errorBody = await paymentResponse.text();
                // console.log(errorBody);
                throw new Error(errorBody);

            }

            function buildPaymentRequest(payments) {
                const req = payments.paymentRequest({
                    countryCode: 'US',
                    currencyCode: 'USD',
                    total: {
                        amount: '@isset($payment->due_amt){{ str_replace('.', '',$payment->due_amt) ?? 0.00 }}@endisset',
                        label: 'Total',
                    },
                    requestShippingContact: false,
                });

                req.addEventListener('afterpay_shippingaddresschanged', function () {
                    return {
                        shippingOptions: [
                            {
                                amount: '0.00',
                                id: 'store_pickup',
                                label: 'Free',
                                taxLineItems: [
                                    {
                                        amount: '@isset($total){{ round(($total * .05),2) ?? 0.00 }}@endisset',
                                        label: 'Tax',
                                    },
                                ],
                                total: {
                                    amount: '@isset($total){{ $total ?? 0.00 }}@endisset',
                                    label: 'total',
                                },
                            },
                        ],
                    };
                });

                req.addEventListener(
                    'afterpay_shippingoptionchanged',
                    function (_option) {
                        // This event listener is for information purposes only.
                        // Changes here (or values returned) will not affect the Afterpay/Clearpay PaymentRequest.
                    }
                );

                return req;
            }

            // async function initializeAfterpay(payments) {
            //     const paymentRequest = buildPaymentRequest(payments);
            //     const afterpay = await payments.afterpayClearpay(paymentRequest);
            //     await afterpay.attach('#afterpay-button');
            //
            //     return afterpay;
            // }

            // async function initializeApplePay(payments) {
            //     const paymentRequest = buildPaymentRequest(payments);
            //     const applePay = await payments.applePay(paymentRequest);
            //     // Note: You do not need to `attach` applePay.
            //     return applePay;
            // }

            // async function initializeGooglePay(payments) {
            //     const paymentRequest = buildPaymentRequest(payments);
            //     const googlePay = await payments.googlePay(paymentRequest);
            //     await googlePay.attach('#google-pay-button');
            //
            //     return googlePay;
            // }

            // async function initializeGiftCard(payments) {
            //     const giftCard = await payments.giftCard();
            //     await giftCard.attach('#gift-card-container');
            //
            //     return giftCard;
            // }

            // This function tokenizes a payment method.
            // The ‘error’ thrown from this async function denotes a failed tokenization,
            // which is due to buyer error (such as an expired card). It is up to the
            // developer to handle the error and provide the buyer the chance to fix
            // their mistakes.
            async function tokenize(paymentMethod) {
                // console.log('tokenize', paymentMethod);
                const tokenResult = await paymentMethod.tokenize();
                // console.log('tokenResult', tokenResult);
                if (tokenResult.status === 'OK') {
                    // console.log('tokenResult.token', tokenResult.token);
                    return tokenResult.token;
                } else {
                    let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;

                    if (tokenResult.errors) {
                        errorMessage += ` and errors: ${JSON.stringify(
                            tokenResult.errors
                        )}`;
                    }
                    // console.log('errorMessage', errorMessage);
                    throw new Error(errorMessage);
                }
            }

            // Helper method for displaying the Payment Status on the screen.
            // status is either SUCCESS or FAILURE;
            function displayPaymentResults(status) {
                // console.log('displayPaymentResults', status);
                const statusContainer = document.getElementById(
                    'payment-status-container'
                );
                if (status === 'SUCCESS') {
                    statusContainer.classList.remove('is-failure');
                    statusContainer.classList.add('is-success');
                } else {
                    statusContainer.classList.remove('is-success');
                    statusContainer.classList.add('is-failure');
                }

                statusContainer.style.visibility = 'visible';
            }

            // DOMContentLoaded Event Listener OK
            document.addEventListener('DOMContentLoaded', async function () {
                if (!window.Square) {
                    throw new Error('Square.js failed to load properly');
                }
                const payments = window.Square.payments(appId, locationId);
                let card;
                try {
                    card = await initializeCard(payments);
                    // console.log('card', card);
                } catch (e) {
                    console.error('Initializing Card failed', e);
                    return;
                }

                // Checkpoint 2
                async function handlePaymentMethodSubmission(event, paymentMethod) {
                    // console.log('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);
                        // console.log('token', token);
                        const paymentResults = await createPayment(token);
                        // console.log('paymentResults', paymentResults);
                        displayPaymentResults('SUCCESS');

                        console.debug('Payment Success', paymentResults);
                    } catch (e) {
                        cardButton.disabled = false;
                        displayPaymentResults('FAILURE');
                        // console.log(e);
                        console.error(e.message);
                    }
                }

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


            });

        </script>

In the BODY:

<!-- Begin Payment Form -->
        <form id="payment-form">
            <div id="card-container"></div>
            <button id="card-button" type="button" class="btn bg-blue-600 text-white">Pay ${{ number_format($payment->due_amt, 2) }}</button>
        </form>
        <div id="payment-status-container"></div>
        <!-- End Payment Form -->

My code on the backend (I understood it that I could use the same Payments API create payment method I used for the old payment form but I changed the nonce to sourceId):

public function checkmeout2(Request $request, $id) {

        $payment = Payment::whereId($id)->first();
        $patient = Patient::whereId($payment->patient_id)->first();
        $family = Family::whereId($patient->family_id)->first();
        $plan = Plan::whereId($family->plan_id)->first();
        $amount_money = new Money();
        $amount_money->setAmount($payment->due_amt * 100);
        $amount_money->setCurrency('USD');

        $body = new CreatePaymentRequest(
            $request->sourceId,
            $payment->id,
            $amount_money
        );
        $body->setOrderId($plan->square_catalog_id);
        $body->setReferenceId($payment->id);
        $body->setNote($plan->plan . " ". $patient->nickname);
        $body->setStatementDescriptionIdentifier('Inner Healer Chiro');

        try {
            $result = $this->client->getPaymentsApi()->createPayment($body);
            $res = json_decode($result->getBody());
            $body->setAutocomplete(true);
            $cardDetails = $res->payment->source_type == 'CARD' ? $res->payment->card_details->card->card_brand . ' '. $res->payment->card_details->card->last_4 . ' Auth Code: '. $res->payment->card_details->auth_result_code : null;
            $bankInfo = $res->payment->source_type == 'BANK_ACCOUNT' ? $res->payment->bank_account_details->bank_name  : null;
            $achDetails = $res->payment->source_type == 'BANK_ACCOUNT' && $res->payment->bank_account_details->transfer_type == 'ACH' ?  $res->payment->bank_account_details->ach_details->account_type . ' ' . $res->payment->bank_account_details->ach_details->account_number_suffix : null;
            $sourceDetails = $res->payment->source_type == 'CARD' ? $cardDetails : ($res->payment->source_type == 'BANK_ACCOUNT' ? $bankInfo . ' '. $achDetails : null);
            $pmt = Payment::whereId($payment->id)->first();
            $pmt->transactionId = $res->payment->id;
            $pmt->receiptUrl = $res->payment->receipt_url;
            $pmt->referenceId = $payment->id;
            $pmt->orderId = $plan->square_catalog_id;
            $pmt->pmt_note = $sourceDetails;
            $pmt->pmt_amt = $res->payment->amount_money->amount / 100;
            $pmt->pmt_paid = true;
            $pmt->pmt_type = $res->payment->source_type;
            $pmt->save();
            // return receipt -> square receipt url
            // return auth code -> display in SUCCESS alert
           // return url -> redirect to url for IHC receipt pdf
            return response()->json(['receipt'=> $res->payment->receipt_url, 'auth_code'=>$res->payment->card_details->status . ': '. $res->payment->card_details->auth_result_code, 'url'=> route('payment.show', ['id' => $pmt->id ])]);

        } catch (ApiException $e) {
            echo "Exception when calling PaymentsApi->createPayment:";
            var_dump($e->getResponseBody());
        }
    }

Can someone please help with this part? I have 1 other issue migrating over from SqPaymentForm (my old one redirected to the url I was sending back in the response - which was a custom receipt on my letterhead that also emailed them a PDF copy of it), but this issue would be more important.

With a successful payment is it showing Payment Success? :slightly_smiling_face:

No. Here is what I’m getting:

This code:

 console.log(paymentResponse);
                console.log(await paymentResponse.json());
                console.log(paymentResponse.ok);
                if (paymentResponse.ok) {
                    console.log(paymentResponse);

                    return paymentResponse.json().clone();
                }

                const errorBody = await paymentResponse.text();
                // console.log(errorBody);
                throw new Error(errorBody);

            }

Produces this in the console:

[Info] Successfuly preconnected to https://t.ssl.ak.tiles.virtualearth.net/
[Info] Successfuly preconnected to https://t.ssl.ak.dynamic.tiles.virtualearth.net/
[Error] Error: Could not load image. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported. — kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:43028
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:154456)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:154416)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:154416)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:538795)
	(anonymous function)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:497674)
[Log] Response {type: "basic", url: "http://localhost:8000/checkout2/6326", redirected: false, status: 200, ok: true, …} (square.js, line 1)
[Log] Object (square.js, line 1)

auth_code: "CAPTURED: 697019"

receipt: "https://squareup.com/receipt/preview/TNIrO78HevrOU4w1mKGFm0Dp7hWZY"

result: Object

payment: Object

amount_money: {amount: 12, currency: "USD"}

application_details: {square_product: "ECOMMERCE_API", application_id: "sq0idp-_2UzsmQPzQxHT4pj-Fmuag"}

approved_money: {amount: 12, currency: "USD"}

card_details: {status: "CAPTURED", card: Object, entry_method: "KEYED", cvv_status: "CVV_ACCEPTED", avs_status: "AVS_ACCEPTED", …}

created_at: "2022-05-20T22:44:37.330Z"

delay_action: "CANCEL"

delay_duration: "PT168H"

delayed_until: "2022-05-27T22:44:37.330Z"

id: "TNIrO78HevrOU4w1mKGFm0Dp7hWZY"

location_id: "7MKHH12YXEE1C"

note: "Professional Courtesy DOC"

order_id: "OBNvOrmwSbaNWDBdyEZxrdQEYCTZY"

receipt_number: "TNIr"

receipt_url: "https://squareup.com/receipt/preview/TNIrO78HevrOU4w1mKGFm0Dp7hWZY"

reference_id: "6326"

risk_evaluation: {created_at: "2022-05-20T22:44:38.300Z", risk_level: "NORMAL"}

source_type: "CARD"

statement_description_identifier: "Inner Healer Chiro"

status: "COMPLETED"

total_money: {amount: 12, currency: "USD"}

updated_at: "2022-05-20T22:44:38.522Z"

version_token: "rn0GKAc1VDJ05C3Mue9MOEQtWaXpeS7BirMYW0oxDPM6o"

Object Prototype

Object Prototype

url: "http://localhost:8000/checkout/6326/receipt"

Object Prototype

[Log] true (square.js, line 1)
[Log] Response {type: "basic", url: "http://localhost:8000/checkout2/6326", redirected: false, status: 200, ok: true, …} (square.js, line 1)
[Log] displayPaymentResults – "FAILURE" (square.js, line 1)
[Error] paymentResponse.json().clone is not a function. (In 'paymentResponse.json().clone()', 'paymentResponse.json().clone' is undefined)
	(anonymous function) (square.js:1:41352)
	(anonymous function) (6326:260)
	asyncFunctionResume
	(anonymous function)
	promiseReactionJobWithoutPromise
	promiseReactionJob
[Error] Unhandled Promise Rejection: TypeError: Body is disturbed or locked
	(anonymous function)
	rejectPromise
	json
	(anonymous function) (6326:99)
	asyncFunctionResume
	(anonymous function)
	promiseReactionJobWithoutPromise
	promiseReactionJob

As you can see, I’m sending an object back to the frontend,

{
 'result'=> the api response, 
'receipt'=> the url for square, 
'auth_code' => the auth code,
 'url' => the url for redirecting
}

I need at the very least url and result to go to the frontend because I need that info to redirect to the next step.

I tried logging paymentResponse.json() and it said pending, so when I added await to that I could see the object I’m returning from the server. It’s there but I cannot access it and it says FAILED when it did not fail.

I feel so dang dumb, but Javascript is my weakest link.

Ok, I realized I may not need the extra data, as I’m using the payment ID in my system as the idempotency key and that’s in the response, along with the other data.

Now I’m getting the following in the console, but it is NOT displaying anything on the page that tells the customer it was a success. :

[Info] Successfuly preconnected to https://t.ssl.ak.dynamic.tiles.virtualearth.net/
[Info] Successfuly preconnected to https://t.ssl.ak.tiles.virtualearth.net/
[Error] Error: Could not load image. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported. — kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:43028
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:154456)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:154416)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:154416)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:538795)
	(anonymous function)
	(anonymous function) (kUzVOG5O3DPMP5Aks2gK1LfNjGM.br.js:54:497674)
[Log] displayPaymentResults – "SUCCESS" (square.js, line 1)
[Debug] Payment Success (square.js, line 1)
Object

payment: Object

amount_money: {amount: 17, currency: "USD"}

application_details: {square_product: "ECOMMERCE_API", application_id: "sq0idp-_2UzsmQPzQxHT4pj-Fmuag"}

approved_money: {amount: 17, currency: "USD"}

card_details: {status: "CAPTURED", card: Object, entry_method: "KEYED", cvv_status: "CVV_ACCEPTED", avs_status: "AVS_ACCEPTED", …}

created_at: "2022-05-20T23:09:52.663Z"

delay_action: "CANCEL"

delay_duration: "PT168H"

delayed_until: "2022-05-27T23:09:52.663Z"

id: "dqGwwnESJnEvtr7LYoPSH8DXEAAZY"

location_id: "7MKHH12YXEE1C"

note: "Professional Courtesy DOC"

order_id: "Q0EEPIyQZX8lVtQtNaBiBA78JHMZY"

receipt_number: "dqGw"

receipt_url: "https://squareup.com/receipt/preview/dqGwwnESJnEvtr7LYoPSH8DXEAAZY"

reference_id: "6331"

risk_evaluation: {created_at: "2022-05-20T23:09:53.712Z", risk_level: "NORMAL"}

source_type: "CARD"

statement_description_identifier: "Inner Healer Chiro"

status: "COMPLETED"

total_money: {amount: 17, currency: "USD"}

updated_at: "2022-05-20T23:09:53.935Z"

version_token: "R3QPvoSSSoffxsxiBDxYO7JbytYUA0SoZ4ss9orCTWR6o"

Object Prototype

Object Prototype

I don’t know why they even have the sections for displaying outcomes of payments when it doesn’t show anything, but I found a way around it.

I’m returning the apiResponse->getResults() and then using window.href to go directly to my custom receipt page if no errors happened, and displaying the error text on alert if there was one. It’s not pretty, but it’s working OK.

Glad to hear you found a workaround. :slightly_smiling_face: