react-paypal-js: [BUG]: React skipped hostedField

Is there an existing issue for this?

  • I have searched the existing issues.

🐞 Describe the Bug

Referring to my other bug report on #304 , I tried another way using code example as a base. I did not get any error but it seems like React skipped hostedfield.cardFields.submit()

I was unable to find any solutions for this issue but I found a few recent posts on stackoverflow to address the same issue.

My code:

App.js

import { useState, useEffect, useRef } from "react";
import {
	PayPalScriptProvider,
	PayPalHostedFieldsProvider,
	PayPalHostedField,
	usePayPalHostedFields,
} from "@paypal/react-paypal-js";

const CUSTOM_FIELD_STYLE = {"border":"1px solid #606060","boxShadow":"2px 2px 10px 2px rgba(0,0,0,0.1)"};
const INVALID_COLOR = {
	color: "#dc3545",
};

// Example of custom component to handle form submit
const SubmitPayment = ({ customStyle, mr, sh }) => {
	const [paying, setPaying] = useState(false);
	const cardHolderName = useRef(null);
	const hostedField = usePayPalHostedFields();

  	const baseUrl = "http://local.payment.web"
  
	const handleClick = () => {
	           if (!hostedField?.cardFields) {
                       const childErrorMessage = 'Unable to find any child components in the <PayPalHostedFieldsProvider />';
          
                       // eslint-disable-next-line no-undef
                       action(ERROR)(childErrorMessage);
                       throw new Error(childErrorMessage);
                  }
		  const isFormInvalid =
		 	Object.values(hostedField.cardFields.getState().fields).some(
		 		(field) => !field.isValid
		 	) || !cardHolderName?.current?.value;
		
		  if (isFormInvalid) {
		 	return alert(
		 		"The payment form is invalid"
		 	);
		}

		setPaying(true);
		
		hostedField.cardFields
			.submit({
				cardholderName: cardHolderName?.current?.value,
			})
			.then(async () => {
				return await fetch(`${baseUrl}/api/Ppal/ReactApi/CaptureOrder`, {
					mode: "cors",
					method: "post",
					headers: {
						'content-type': 'application/json'
					},
					body: JSON.stringify({
						merchantRef: mr,
						sessionHash: sh
					})
				})
				.then(res => res.json())
				.then((data) => {
					console.log("data from pay button : ", data);
					if(data.isApproved) alert("Payment successful")
					if(!data.isApproved) alert("Payment declined")
					if(data.isExpired) alert("Payment expired")
				})
				.catch(err => console.log("err : ", err))
				.finally(() => setPaying(false))
      		})
      		.catch(err => console.log("Error catch : ", err))
	};

	return (
		<>
      		<label title="This represents the full name as shown in the card">
				Card Holder Name
				<input
					id="card-holder"
					ref={cardHolderName}
					className="card-field"
					style={{ ...customStyle, outline: "none" }}
					type="text"
					placeholder="Full name"
				/>
			</label>
			<button
				className={`btn${paying ? "" : " btn-primary"}`}
				style={{ float: "right" }}
				onClick={handleClick}
			>
				{paying ? <div className="spinner tiny" /> : "Pay"}
			</button>
		</>
	);
};

export default function App() {
	const [clientId, setClientId] = useState(null)
  	const [clientToken, setClientToken] = useState(null)
	const [merchantRef, setMerchantRef] = useState(null)
	const [sessionHash, setSessionHash] = useState(null)
 	const [orderId, setOrderId] = useState(null)

  	const baseUrl = "http://local.payment.web"

	useEffect(() => {
		(async () => {
			return await fetch(`${baseUrl}/api/Ppal/ReactApi/PrepareForPayment`, {
				mode: "cors",
				method: "post",
				headers: {
					'content-type': 'application/json',
					'Access-Control-Allow-Origin': '*'
	
				},
				body: JSON.stringify({
					"curCode": "USD",
					"orderAmount": 500
				})
			}).then(res => {
				console.log("fetch Data : ", res);
				return res.json()
			}).then(data => {
				console.log("fetch Data : ", data);
				setClientId(data.ClientId)
				setClientToken(data.ClientToken)
	
				if (data.prepareForPayment) {
					setMerchantRef(data.merchantRef)
					setSessionHash(data.sessionHash)
				}
			}).catch(err => console.log(err))
		})();
	}, []);

	return (
		<>
			{clientToken ? (
				<PayPalScriptProvider
					options={{
						"client-id": clientId,
						components: "buttons,hosted-fields",
						"data-client-token": clientToken,
						intent: "capture",
						vault: false,
					}}
				>
					<PayPalHostedFieldsProvider
						styles={{".valid":{"color":"#28a745"},".invalid":{"color":"#dc3545"},"input":{"font-family":"monospace","font-size":"16px"}}}
						createOrder={async() => {
							return await fetch(`${baseUrl}/api/Ppal/ReactApi/CreateOrder2`, {
								method: 'post',
								headers: {
									'content-type': 'application/json'
								},
								body: JSON.stringify({
									merchantRef: merchantRef,
									sessionHash: sessionHash
								})
							}).then(res => {
								console.log("res from createOrder", res);
								return res.json()
							}).then(data => {
								console.log("orderId from button : ", data?.orderId);
								if (data?.createOrder) return setOrderId(data?.orderId)
							})
					}}
					>
						<label htmlFor="card-number">
							Card Number
							<span style={INVALID_COLOR}>*</span>
						</label>
						<PayPalHostedField
							id="card-number"
							className="card-field"
							style={CUSTOM_FIELD_STYLE}
							hostedFieldType="number"
							options={{
								selector: "#card-number",
								placeholder: "4111 1111 1111 1111",
							}}
						/>
						<label htmlFor="cvv">
							CVV<span style={INVALID_COLOR}>*</span>
						</label>
						<PayPalHostedField
							id="cvv"
							className="card-field"
							style={CUSTOM_FIELD_STYLE}
							hostedFieldType="cvv"
							options={{
								selector: "#cvv",
								placeholder: "123",
								maskInput: true,
							}}
						/>
						<label htmlFor="expiration-date">
							Expiration Date
							<span style={INVALID_COLOR}>*</span>
						</label>
						<PayPalHostedField
							id="expiration-date"
							className="card-field"
							style={CUSTOM_FIELD_STYLE}
							hostedFieldType="expirationDate"
							options={{
								selector: "#expiration-date",
								placeholder: "MM/YYYY",
							}}
						/>
						<SubmitPayment customStyle={{"border":"1px solid #606060","boxShadow":"2px 2px 10px 2px rgba(0,0,0,0.1)"}} />
					</PayPalHostedFieldsProvider>
				</PayPalScriptProvider>
			) : (
				<h1>Loading token...</h1>
			)}
		</>
	);
}

πŸ˜• Current Behavior

No error and Alert tag render but on Network tab, it did not fetch capture api as screenshot below

Uploading Screenshot 2022-09-12 160040.png…

πŸ€” Expected Behavior

It should call capture api and render alert tag whether the payment was successful or declined or log any errors

πŸ”¬ Minimal Reproduction

No response

🌍 Environment

| Software         | Version(s) |
| ---------------- | ---------- |
| react-paypal-js  |    ^7.8.1  |
| Browser          |   Chrome   |
| Operating System |  Window10  |

Relevant log output

No log or output, React skipped `Hostedfield.cardfield.submit()`

Code of Conduct

  • I agree to follow this project’s Code of Conduct

βž• Anything else?

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 39 (8 by maintainers)

Most upvoted comments

Hey @gregjopa Thank you for your input. It didn’t even cross my mind to use useRef. I will try and get back to you soon if it works