paypal / paypal-js

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

[Bug] PayPalCardFieldsProvider does not render component #533

Open mcdado opened 2 weeks ago

mcdado commented 2 weeks 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 2 weeks 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