paypal / paypal-js

Loading wrapper and TypeScript types for the PayPal JS SDK
Apache License 2.0
238 stars 83 forks source link

[Bug] PayPalCardFieldsProvider does not render component #533

Closed mcdado closed 1 month ago

mcdado commented 4 months ago

Library used

react-paypal-js

๐Ÿž Describe the Bug

I'm trying to use the PayPalCardFieldsProvider and PayPalCardFieldsForm to have more control than simply using PayPalButtons. I don't want to collect the billing and shipping information in the checkout component, because when a user signs up, they already provide all the information ahead of time and this information is available in the shopping cart. This is the main problem I have with PayPalButtons, I found no way to disable the billing address form when selecting Credit/debit card in the checkout. While PayPalButtons works in so far as rendering the checkout options, the CardFields component doesn't: it renders an empty <div>:

๐Ÿ”ฌ Minimal Reproduction

I'm writing a Next.js app, here is the client component that should render the checkout section below the shopping cart. My goal is to show both the PayPal button, optionally processors like MyBank and Giropay, but also credit card.

"use client";

import { PayPalCardFieldsComponentOptions } from "@paypal/paypal-js";
import {
  PayPalCardFieldsForm,
  PayPalCardFieldsProvider,
  PayPalNameField,
  PayPalNumberField,
  PayPalCVVField,
  PayPalExpiryField,
  PayPalScriptProvider,
} from "@paypal/react-paypal-js";

/// * hooks
import useTranslation from "@/translation/useTranslation";
import { usePayPalCardFields } from "@paypal/react-paypal-js";

/// * types
import { IProjectClient } from "@/db/models/project.model";

/// * config
import config from "@/config";

const SubmitPayment = () => {
  const { cardFieldsForm, fields } = usePayPalCardFields();

  function submitHandler() {
    if (!cardFieldsForm) {
      return; // validate that `submit()` exists before using it
    }

    cardFieldsForm
      .submit()
      .then(() => {
        // submit successful
      })
      .catch(() => {
        // submission error
      });
  }

  return <button onClick={submitHandler}>Pay</button>;
};

interface IPayPalCheckout {
  cart: IProjectClient[];
  clientToken: string;
  deliveryAddressId: string;
}

const PayPalCheckout = ({
  cart,
  clientToken,
  deliveryAddressId,
}: IPayPalCheckout) => {
  /// y *********************************************************************
  const { l } = useTranslation();

  /// g *********************************************************************
  const createOrder: PayPalCardFieldsComponentOptions["createOrder"] = () => {
    console.log("createOrder!");
    const body = {
      // paymentSource: data.paymentSource,
      deliveryAddressId,
      cart,
    };

    return fetch("/api/checkout/paypal-order-create", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    })
      .then((response) => response.json())
      .then((order) => order.id)
      .catch((err) => {
        console.error(err);
      });
  };

  /// g *********************************************************************
  const approveOrder: PayPalCardFieldsComponentOptions["onApprove"] = (
    data
  ) => {
    console.log("onApprove!", data);
    return fetch("/api/checkout/paypal-order-capture", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        orderID: data,
      }),
    })
      .then((response) => response.json())
      .then((orderData) => {
        debugger;
        // TODO: redirect to /thank-you?qualcosa...
        /**
            {
              orderID: {
                orderID: "3HU55761....",
                payerID: "CNMSVSAVJ...",
                paymentID: "3HU5576....",
                billingToken: null,
                facilitatorAccessToken: "A21AAKq...",
                paymentSource: "paypal",
              },
            }
         */
      })
      .catch((err) => {
        console.error(err);
      });
  };

  /// g *********************************************************************
  const orderError: PayPalCardFieldsComponentOptions["onError"] = (err) => {
    console.log("orderError!", err);
    debugger;

    // return fetch("/api/checkout/paypal-order-cancel", {
    //   method: "POST",
    //   headers: {
    //     "Content-Type": "application/json",
    //   },
    //   body: JSON.stringify({
    //     orderID: data,
    //   }),
    // })
    //   .then((response) => response.json())
    //   .then((orderData) => {
    //     // TODO: redirect to "/cart"
    //   });
  };

  /// m *********************************************************************
  return (
    <div className="flex justify-center mt-5">
      <div className="w-[350px] paypal-payment">
        <PayPalScriptProvider
          options={{
            clientId: config.paypal.clientId,
            components: "card-fields",
            dataClientToken: clientToken,
            // currency: "EUR",
            // intent: "capture",
          }}
        >
          <PayPalCardFieldsProvider
            createOrder={createOrder}
            onApprove={approveOrder}
            onError={orderError}
            style={{ input: { color: "black" } }}
          >
            <PayPalCardFieldsForm />
            <SubmitPayment />
          </PayPalCardFieldsProvider>
        </PayPalScriptProvider>
      </div>
    </div>
  );
};

export default PayPalCheckout;

๐Ÿ˜• Actual Behavior

When using PayPalCardFieldsProvider and PayPalCardFieldsForm, the Checkout form is not rendered (see below). I tried using PayPalCardFieldsForm and the individual form fields (PayPalNameField, PayPalNumberField, ...) but the result is the same.

Screenshot 2024-07-05 at 14 48 18

๐Ÿค” Expected Behavior

The Checkout form should be rendered.

๐ŸŒ Environment

โž• Additional Context

โ”œโ”€โ”€ @paypal/paypal-js@8.1.0 โ”œโ”€โ”€ @paypal/react-paypal-js@8.5.0 โ”œโ”€โ”€ next@14.1.1 โ”œโ”€โ”€ react@18.2.0

mcdado commented 4 months ago

I enabled the first option below in the Sandbox app but it still doesn't work.

Screenshot 2024-07-05 at 15 12 47

rajatbedi commented 3 months ago

Hi @mcdado, I faced same issue in my project. Have you found any solution?

mcdado commented 3 months ago

I did, but sincerely I don't know how or why. I remember trying again the next day and it now worked.

qkrooz commented 1 month ago

Same here, tried everything and still does not works.

Hint: PayPalButtons does renders, PayPalCardFields does not.

mcdado commented 1 month ago

I enabled the first option below in the Sandbox app but it still doesn't work.

When we recently went to production with our integration, I had to enable then option Advanced Credit and Debit Card Payments in the Live environment as well. It then took a day for it to actually start working โ€” by itself.

The library should have a better error message when the option is not enabled and you try to render PayPalCardFieldsProvider with React and the PayPalCardFieldsComponent created by the plain JS paypal.CardFields()