简体   繁体   中英

Uncaught (in promise) IntegrationError: Invalid value for stripe.confirmCardPayment intent secret

I get this error when trying to checkout with stripe:

Uncaught (in promise) IntegrationError: Invalid value for stripe.confirmCardPayment intent secret: value should be a client secret of the form ${id} secret ${secret}. You specified: .....

I use stripe on my website and I've implemented it with firebase functions. When I run my website and firebase functions locally I do not get this error, but when I have it on my firebase hosting it does not work and I get that error. Locally I would run these commands: npm start to start the website and then I cd inside the functions folder and then run npm run serve . How can I fix this? Here is the index.js file that is being run with firebase functions: index.js

const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const stripe = require('stripe')(secret_key)
const app = express();
app.use(cors({
    origin: true
}));
app.use(express.json());

app.post('/payments/create', async (req, res) => {
    try {
        const { amount, shipping } = req.body;
        const paymentIntent = await stripe.paymentIntents.create({
            shipping,
            amount,
            currency: 'eur'
        });

        res
        .status(200)
        .send(paymentIntent.client_secret);
    }catch(err) {
        res
        .status(500)
        .json({
            statusCode: 500,
            message: err.message
        });
    }
})

app.get('*', (req, res) => {
    res
    .status(404)
    .send('404, Not Found');
});

exports.api = functions.https.onRequest(app);

Here's the package.json

  {
"name": "evelinas-art-store",
"version": "0.1.0",
"private": true,
"dependencies": {
  "@material-ui/core": "^4.11.2",
  "@stripe/react-stripe-js": "^1.1.2",
  "@stripe/stripe-js": "^1.11.0",
  "@testing-library/jest-dom": "^5.11.6",
  "@testing-library/react": "^11.2.2",
  "@testing-library/user-event": "^12.6.0",
  "axios": "^0.21.1",
  "ckeditor4-react": "^1.3.0",
  "firebase": "^8.2.1",
  "moment": "^2.29.1",
  "node-sass": "^4.14.1",
  "react": "^17.0.1",
  "react-country-region-selector": "^3.0.1",
  "react-dom": "^17.0.1",
  "react-redux": "^7.2.2",
  "react-router-dom": "^5.2.0",
  "react-scripts": "4.0.1",
  "redux": "^4.0.5",
  "redux-logger": "^3.0.6",
  "redux-persist": "^6.0.0",
  "redux-saga": "^1.1.3",
  "redux-thunk": "^2.3.0",
  "reselect": "^4.0.0",
  "web-vitals": "^0.2.4"
},
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
},
"eslintConfig": {
  "extends": [
    "react-app",
    "react-app/jest"
  ]
},
"browserslist": {
  "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ],
  "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
  ]
 }
}

File that has stripe payment

  import React, { useState, useEffect } from 'react';
  import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
  import FormInput from './../forms/FormInput';
  import Button from './../forms/Button';
  import { CountryDropdown } from 'react-country-region-selector';
  import { apiInstance } from './../../Utils';
  import { selectCartTotal, selectCartItemsCount, selectCartItems } from './../../redux/Cart/cart.selectors';
  import { saveOrderHistory } from './../../redux/Orders/orders.actions';
  import { createStructuredSelector } from 'reselect';
  import { useSelector, useDispatch } from 'react-redux';
  import { useHistory } from 'react-router-dom';
  import './styles.scss';

  const initialAddressState = {
    line1: '',
    line2: '',
    city: '',
    state: '',
    postal_code: '',
    country: '',
  };

  const mapState = createStructuredSelector({
    total: selectCartTotal,
    itemCount: selectCartItemsCount,
    cartItems: selectCartItems,
  });

  const PaymentDetails = () => {
    const stripe = useStripe();
    const elements = useElements();
    const history = useHistory();
    const { total, itemCount, cartItems } = useSelector(mapState);
    const dispatch = useDispatch();
    const [billingAddress, setBillingAddress] = useState({ ...initialAddressState });
    const [shippingAddress, setShippingAddress] = useState({ ...initialAddressState });
    const [recipientName, setRecipientName] = useState('');
     const [nameOnCard, setNameOnCard] = useState('');

    useEffect(() => {
      if (itemCount < 1) {
        history.push('/dashboard');
      }

    }, [itemCount]);

    const handleShipping = evt => {
      const { name, value } = evt.target;
      setShippingAddress({
        ...shippingAddress,
        [name]: value
      });
    };

    const handleBilling = evt => {
      const { name, value } = evt.target;
      setBillingAddress({
        ...billingAddress,
        [name]: value
      });
    }

    const handleFormSubmit = async evt => {
      evt.preventDefault();
      const cardElement = elements.getElement('card');

      if (
        !shippingAddress.line1 || !shippingAddress.city ||
        !shippingAddress.state || !shippingAddress.postal_code ||
        !shippingAddress.country || !billingAddress.line1 ||
        !billingAddress.city || !billingAddress.state ||
        !billingAddress.postal_code || !billingAddress.country ||
        !recipientName || !nameOnCard
      ) {
        return;
      }

      apiInstance.post('/payments/create', {
        amount: total * 100,
        shipping: {
          name: recipientName,
          address: {
            ...shippingAddress
          }
        }
      }).then(({ data: clientSecret }) => {

        stripe.createPaymentMethod({
          type: 'card',
          card: cardElement,
          billing_details: {
            name: nameOnCard,
            address: {
              ...billingAddress
            }
          }
        }).then(({ paymentMethod }) => {

          stripe.confirmCardPayment(clientSecret, {
            payment_method: paymentMethod.id
          })
          .then(({ paymentIntent }) => {

            const configOrder = {
              orderTotal: total,
              orderItems: cartItems.map(item => {
                const { documentID, productThumbnail, productName,
                  productPrice, quantity } = item;

                return {
                  documentID,
                  productThumbnail,
                  productName,
                  productPrice,
                  quantity
                };
              })
            }

            dispatch(
              saveOrderHistory(configOrder)
            );
          });

        })


      });

    };

    const configCardElement = {
      iconStyle: 'solid',
      style: {
        base: {
          fontSize: '16px'
        }
      },
      hidePostalCode: true
    };

    return (
      <div className="paymentDetails">
        <form onSubmit={handleFormSubmit}>

          <div className="group">
            <h2>
              Shipping Address
            </h2>

            <FormInput
              required
              placeholder="Recipient Name"
              name="recipientName"
              handleChange={evt => setRecipientName(evt.target.value)}
              value={recipientName}
              type="text"
            />

            <FormInput
              required
              placeholder="Line 1"
              name="line1"
              handleChange={evt => handleShipping(evt)}
              value={shippingAddress.line1}
              type="text"
            />

            <FormInput
              placeholder="Line 2"
              name="line2"
              handleChange={evt => handleShipping(evt)}
              value={shippingAddress.line2}
              type="text"
            />

            <FormInput
              required
              placeholder="City"
              name="city"
              handleChange={evt => handleShipping(evt)}
              value={shippingAddress.city}
              type="text"
            />

            <FormInput
              required
              placeholder="State"
              name="state"
              handleChange={evt => handleShipping(evt)}
              value={shippingAddress.state}
              type="text"
            />

            <FormInput
              required
              placeholder="Postal Code"
              name="postal_code"
              handleChange={evt => handleShipping(evt)}
              value={shippingAddress.postal_code}
              type="text"
            />

            <div className="formRow checkoutInput">
              <CountryDropdown
                required
                onChange={val => handleShipping({
                  target: {
                    name: 'country',
                    value: val
                  }
                })}
                value={shippingAddress.country}
                valueType="short"
              />
            </div>

          </div>

          <div className="group">
            <h2>
              Billing Address
            </h2>

            <FormInput
              required
              placeholder="Name on Card"
              name="nameOnCard"
              handleChange={evt => setNameOnCard(evt.target.value)}
              value={nameOnCard}
              type="text"
            />

            <FormInput
              required
              placeholder="Line 1"
              name="line1"
              handleChange={evt => handleBilling(evt)}
              value={billingAddress.line1}
              type="text"
            />

            <FormInput
              placeholder="Line 2"
              name="line2"
              handleChange={evt => handleBilling(evt)}
              value={billingAddress.line2}
              type="text"
            />

            <FormInput
              required
              placeholder="City"
              name="city"
              handleChange={evt => handleBilling(evt)}
              value={billingAddress.city}
              type="text"
            />

            <FormInput
              required
              placeholder="State"
              name="state"
              handleChange={evt => handleBilling(evt)}
              value={billingAddress.state}
              type="text"
            />

            <FormInput
              required
              placeholder="Postal Code"
              name="postal_code"
              handleChange={evt => handleBilling(evt)}
              value={billingAddress.postal_code}
              type="text"
            />

            <div className="formRow checkoutInput">
              <CountryDropdown
                required
                onChange={val => handleBilling({
                  target: {
                    name: 'country',
                    value: val
                  }
                })}
                value={billingAddress.country}
                valueType="short"
              />
            </div>

          </div>

          <div className="group">
            <h2>
              Card Details
            </h2>

            <CardElement
              options={configCardElement}
            />
          </div>

          <Button
            type="submit"
          >
            Pay Now
          </Button>

        </form>
      </div>
    );
  }

  export default PaymentDetails;```

According to the documentation https://stripe.com/docs/api/payment_intents/create for stripe.PaymentIntent.create()

You need to pass this:

import stripe
stripe.api_key = "sk_test_51I5EU6DbwDQYqmKoHRVYU2jw4jtzB8aQa6byuVIMyfDvYl3lxHOzmIRUZ6SabMmk1TV0jNu4w9akIgPY4E3krUbj00ewcroCvC"

  const PaymentIntentVar =  stripe.PaymentIntent.create(
  amount=2000,
  currency="usd",
  payment_method_types=["card"],
)

You have a typo, I guess? PaymentIntents? After please try to:

console.log(PaymentIntentVar)

in index.js To see if you are getting the correct response? Would you please share that !

Also in " File that has stripe payment":

instead of:

const cardElement = elements.getElement('card');

and this:

stripe.createPaymentMethod({
          type: 'card',
          card: cardElement,
          billing_details: {
            name: nameOnCard,
            address: {
              ...billingAddress
            }
          }
        })

do this:

stripe.createPaymentMethod({
          type: 'card',
          card: elements.getElement(CardElement),
          billing_details: {
            name: nameOnCard,
            address: {
              ...billingAddress
            }
          }
        })

also check you are passing the correct public and secret keys on your front and backend by console.log() on front and backend

Also instead useStripe, try this

import {loadStripe} from '@stripe/stripe-js';

const stripe = loadStripe('secret_key');

Here are the possible scenarios and how I understand the configuration.

  1. pass
  2. requires 3d secure
  3. fail - insufficient funds

https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=checkout#additional-testing-resources

So first you attempt a payment from the front end

      const { error, paymentMethod } = await stripe.createPaymentMethod({
        type: 'card',
        card: cardElement,
        billing_details: {
          name: `${stateProps.userProfile.firstName} ${stateProps.userProfile.lastName}`,
          email: stateProps.user.email
        }
      });

from that paymentMethod, you retrieve paymentMethod.Id and send that to your server to make a payment - at this point we are assuming the payment will work and not require 3d secure.

In my case, I need is that paymentMethod.Id because I have put all the billing details in a different object.

In the backend, here is what I have using C#

    try
    {
        if (request.PaymentMethodId != null)
        {
            var createOptions = new PaymentIntentCreateOptions
            {
                PaymentMethod = request.PaymentMethodId,
                Amount = request.total,
                Currency = "gbp",
                ConfirmationMethod = "manual",
                Confirm = true,
                //ReturnUrl = "",
                Metadata = new Dictionary<string, string>
                {
                    { "OrderId", request.orderId.ToString() },
                }
            };

            paymentIntent = paymentIntentService.Create(createOptions);
        }

        if (request.PaymentIntentId != null)
        {
            var confirmOptions = new PaymentIntentConfirmOptions { };
            paymentIntent = paymentIntentService.Confirm(
                request.PaymentIntentId,
                confirmOptions
            );
        }
    }
    catch (StripeException e)
    {
        return new ViewModels.ResponseObject
        {
            Success = false,
            Title = "An Error Occcured.",
            Message = e.Message,
            Data = e.Message
        };
    }

    if (paymentIntent.Status == "requires_action" && paymentIntent.NextAction.Type == "use_stripe_sdk")
    {
        return new ViewModels.ResponseObject
        {
            Success = false,
            Data = new
            {
                requires_action = true,
                payment_intent_client_secret = paymentIntent.ClientSecret
            },
            Title = "Requires Action",
            Message = "Requires Action"
        };
    }
    else if (paymentIntent.Status == "succeeded")
    {
        return new ViewModels.ResponseObject
        {
            Success = true,
            Title = "Payment Successful.",
            Message = "Thank you. Your payment has been successful. A confirmation message has been sent to you by email.",
            Data = null
        };
    }
    else
    {
        return new ViewModels.ResponseObject
        {
            Success = false,
            Title = "An Error Occcured.",
            Message = "An error has occured with your payment. A representative from our store has been notified and will be in touch with you to resolve the issue.",
            Data = null
        };
    }

So if you read the code, you will see that it tries to make a payment and if that fails, THEN it gives you the clientSecret and you can return that to the client.

This is what you can now use on the front end to construct a payment intent (which we already know will fail due to 3d secure) that will automatically pull up the 3d secure authentication window.

In my opinion it seems a waste to do the roundtrip twice but that's how I managed to beat it into submission.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM