简体   繁体   中英

The PaymentIntent requires a payment method — React, Django Rest

I have a React app and a Django Rest API.

My goal is to get the PaymentRequestButtonElement working.

In my Stripe dashboard (test mode) I get the following logs:

200 OK
POST    
/v1/payment_intents
12:22:55 PM

200 OK
POST    
/v1/payment_methods
12:22:54 PM

200 OK
POST    
/v1/tokens
12:22:53 PM

But in the Payments tab, I get the following:

The PaymentIntent requires a payment method

Here is my React component:

import React, { useState, useEffect } from 'react';
// import { useNavigate } from 'react-router-dom';
// import { useShoppingCart } from 'use-shopping-cart';


import {
  PaymentRequestButtonElement,
  useStripe,
} from '@stripe/react-stripe-js';

const PaymentRequest = () => {
  // const history = useNavigate();
  // const { totalPrice, cartDetails, cartCount } = useShoppingCart();
  const stripe = useStripe();
  const [paymentRequest, setPaymentRequest] = useState(null);
  const price = 350;

  const handleButtonClicked = (event) => {
    // if (!cartCount) {
    //   event.preventDefault();
    //   alert('Cart is empty!');
    //   return;
    // }
    paymentRequest.on('paymentmethod', handlePaymentMethodReceived);
    paymentRequest.on('cancel', () => {
      paymentRequest.off('paymentmethod');
    });
    return;
  };

  const handlePaymentMethodReceived = async (event) => {
    // Send the cart details and payment details to our function.
    const paymentDetails = {
      payment_method: event.paymentMethod.id,
      shipping: {
        name: event.shippingAddress.recipient,
        phone: event.shippingAddress.phone,
        address: {
          line1: event.shippingAddress.addressLine[0],
          city: event.shippingAddress.city,
          postal_code: event.shippingAddress.postalCode,
          state: event.shippingAddress.region,
          country: event.shippingAddress.country,
        },
      },
    };
    const response = await fetch('https://my-api/create-payment-intent/', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ 
        // cartDetails, 
        paymentDetails,
        amount: price,
        currency: 'usd',
        payment_method: 'card' 
        // automatic_payment_methods: true,
      }),
    }).then((res) => {
      return res.json();
    });
    if (response.error) {
      // Report to the browser that the payment failed.
      console.log(response.error);
      event.complete('fail');
    } else {
      // Report to the browser that the confirmation was successful, prompting
      // it to close the browser payment method collection interface.
      event.complete('success');
      // Let Stripe.js handle the rest of the payment flow, including 3D Secure if needed.
      const { error, paymentIntent } = await stripe.confirmCardPayment(
        response.paymentIntent.client_secret
      );
      if (error) {
        console.log(error);
        return;
      }
      if (paymentIntent.status === 'succeeded') {
        console.log('Payment succeeded!');
      } else {
        console.warn(
          `Unexpected status: ${paymentIntent.status} for ${paymentIntent}`
        );
      }
    }
  };

  useEffect(() => {
    if (stripe && paymentRequest === null) {
      const pr = stripe.paymentRequest({
        country: 'US',
        currency: 'usd',
        total: {
          label: 'Demo total',
          // 
          amount: price,
          pending: true,
        },
        requestPayerName: true,
        requestPayerEmail: true,
        requestShipping: true,
        shippingOptions: [
          {
            id: 'standard-global',
            label: 'Global shipping',
            detail: 'Handling and delivery fee',
            amount: 350,
          },
        ],
      });
      // Check the availability of the Payment Request API first.
      pr.canMakePayment().then((result) => {
        if (result) {
          setPaymentRequest(pr);
        }
      });
    }
  }, [stripe, 
      paymentRequest, 
      // totalPrice
    ]);

  useEffect(() => {
    if (paymentRequest) {
      paymentRequest.update({
        total: {
          label: 'Demo total',
          amount: 350,
          pending: false,
        },
      });
    }
  }, [
      // totalPrice, 
      paymentRequest
    ]);

  if (paymentRequest) {
    return (
      <div className="payment-request-button">
        <PaymentRequestButtonElement
          options={{ paymentRequest }}
          onClick={handleButtonClicked}
        />
        --- OR ---
      </div>
    );
  }

  return '';
};

export default PaymentRequest;

and here is my Django REST View

class PaymentIntentView(APIView):
    def post(self, request, *args, **kwargs):
        amount = request.data.get('amount')
        currency = request.data.get('currency')
        # automatic_payment_methods = request.data.get('automatic_payment_methods')

        try:
            intent = stripe.PaymentIntent.create(
                amount=amount,
                currency=currency,
                # automatic_payment_methods={
                #     'enabled': True,
                # },
                # You can also add other options like capture_method, setup_future_usage, etc.
            )

            return Response({'client_secret': intent.client_secret, 'id': intent.id})
        except Exception as e:
            return Response({'error': str(e)})

I've tried variations of passing automatic_payments as true and passing the payment_method as 'card', no joy

There's a couple of options that you can do in order to fix the problem here.

Option 1: Pass the PM in the backend

When you call fetch on https://my-api/create-payment-intent/ you are passing the paymentDetails that you're not using in your stripe.PaymentIntent.create method. For this to work, you need to first deserialize your request to get access to this information since it's nested (eg this guide ). Then you need to pass payment_method to the stripe.PaymentIntent.create method. In this option you don't have to change anything in your frontend code.

Option 2: Pass the PM in the frontend

When you call stripe.confirmCardPayment you can pass in the payment_method as explained here . In this option you don't have to change anything in your backend code but you can remove the paymentDetails from the request to your backend.

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