stripe-payment

最近接收项目遇到一个bug, 使用stripe的payment相关支付的时候页面展示的元素重复,见下图

相关代码如下

  const paymentElementOptions: any = {
    layout: 'tabs'
  }

<form id="payment-form" onSubmit={handleSubmit} className="text-cap">
      <LinkAuthenticationElement id="link-authentication-element" />
      <AddressElement options={{ mode: 'billing' }} />
      <PaymentElement id="payment-element" options={paymentElementOptions} />
      <Center>
        <Button my="4" colorScheme="green" type="submit" disabled={isLoading || !stripe || !elements} id="submit">
          <span id="button-text">{isLoading ? <div className="spinner" id="spinner"></div> : 'Pay Now'}</span>
        </Button>
      </Center>
      {/* Show any error or success messages */}
      <Center>{message && <div id="payment-message">{message}</div>}</Center>
    </form>

产品的诉求是需要用户填写相关address的信息
看代码 猜测应该是相关配置缺失导致的,
第一次接触stripe这个支付的SDK,翻看了一下相关文档,https://stripe.com/docs/stripe-js/react
咋一看没发现有什么不对,也没找到相关配置信息,耐着性子一个一个翻,

终于在payment_element_create-customized_fieldss的文档中找到了一些配置信息

paymane_element

billingDetails: 'never' | 'auto' | object Specify never to avoid collecting all billing details in the Payment Element. If you would like to disable only certain billing details, pass an object specifying which fields you would like to disable collection for. The default setting for each field or object is auto.

大意就是可以配置相关支付选项的配置信息,有这些配置 默认是auto


name:
'never' | 'auto'
email:
'never' | 'auto'
phone:
'never' | 'auto'
address:
'never' | 'auto' | object
// 使用方法
var paymentElement = elements.create('payment', {
  fields: {
    billingDetails: {
      name: 'never',
      email: 'never',
    }
  }
});
有一段注意事项:

By default, the Payment Element will collect all necessary details to complete a payment.

For some payment methods, this means that the Payment Element will collect details like name or email that you may have already collected from the user. If this is the case, you can prevent the Payment Element from collecting these data by using the fields option.

If you disable the collection of a certain field with the fields option, you must pass that same data to stripe.confirmPayment or the payment will be rejected.

See below for details.

大意就说:默认情况下,付款元素将收集完成付款所需的所有详细信息。

对于某些付款方式,这意味着付款元素将收集您可能已从用户那里收集的详细信息,例如姓名或电子邮件。如果是这种情况,您可以使用该选项阻止付款元素收集这些数据fields。

如果您使用该选项禁用某个字段的收集fields,则必须将相同的数据传递给stripe.confirmPayment ,否则付款将被拒绝

下面找到 address_element 就好了

address_element

注意事项

使用这种方式监听地址变化是有bug的
当你使用chrome的自动填充地址信息的时候,你会发现并没有触发这个onChange的事件


  const handleAddressChange = (event) => {
    if (event.complete) {
      // Extract potentially complete address
      const address = event.value.address
      console.log(address)
      setAddress(address)
    }
  }
<AddressElement options={{ mode: 'billing' }} onChange={handleAddressChange} />

不过它提供了另外一种方式

import { PaymentElement, LinkAuthenticationElement, useStripe, AddressElement, useElements } from '@stripe/react-stripe-js'


 const elements = useElements()

const getAddressValue = async () => {
    if (elements) {
      const addressElement = elements.getElement('address')
      if (addressElement) {
        const { complete, value } = await addressElement?.getValue()
        if (complete) {
          // Allow user to proceed to the next step
          // Optionally, use value to store the address details
          console.log(value)
          return value
        }
      }
    }
  }

另外需要注意address的value的返回值不仅仅是地址,还有name等字段

{
    name,
    email,
    phone,
    address:{
        line1,
        line2,
        city,
        state,
        country,
        postalCode,
    }
}

有点矛盾的地方
https://stripe.com/docs/js/elements_object/create_address_element#address_element_create-options-mode


Specify which mode you would like to use Address Element for.

When shipping mode is used with the Payment Element and Link Authentication Element, it will automatically pass shipping information when confirming Payment Intent or Setup Intent.

When billing mode is used with the Payment Element, it will automatically pass the billing information when confirming Payment Intent or Setup Intent.

大意是说当 Address Element 与Payment Element联合使用的时候会自动把相关信息带入到支付信息里,实测也确实如此,不过保险起见,也可以自己手动传入

最终的解决


import React, { useState, useImperativeHandle, forwardRef } from 'react'
import { PaymentElement, LinkAuthenticationElement, useStripe, AddressElement, useElements } from '@stripe/react-stripe-js'
 
 
export const CheckoutForm = forwardRef(function MyInput(props, ref) {
  const stripe = useStripe()
  const elements = useElements()

 
  const [email, setEmail] = useState('')
 
  const paymentElementOptions: any = {
    layout: 'tabs',
    fields: {
      billingDetails: {
        name: 'never',
        email: 'never',
        address: 'never',
      },
    },
  }
  const getAddressValue = async () => {
    if (elements) {
      const addressElement = elements.getElement('address')
      if (addressElement) {
        const { complete, value } = await addressElement?.getValue()
        if (complete) {
          // Allow user to proceed to the next step
          // Optionally, use value to store the address details
          console.log(value)
          return value
        }
      }
    }
  }
  const handleSubmit = async (e: any) => {
    e.preventDefault()

    if (!stripe || !elements) {
      return
    }
    try {
      const addressValue = (await getAddressValue()) as any
      console.log('===addressValue==', addressValue)
      const { error }: any = await stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: `${window.location.origin}/payment/completion`,
          payment_method_data: {
            billing_details: {
              email: email,
              ...addressValue,
            },
          },
        },
      })
      if (error.type === 'card_error' || error.type === 'validation_error') {
        setMessage(error.message)
      } else {
        setMessage('An unexpected error occurred.')
      }
    } catch (error) {
      console.log(error)
    }
  
  }

 
 
  const handleLinkChange = (event) => {
    const email = event.value.email
    setEmail(email)
  }

  return (
    <form id="payment-form" onSubmit={handleSubmit} className="text-cap">
      <LinkAuthenticationElement id="link-authentication-element" onChange={handleLinkChange} />
      <AddressElement options={{ mode: 'billing' }}   />
      <PaymentElement id="payment-element" options={paymentElementOptions} />
      <Center>
        <Button my="4" colorScheme="green" type="submit" disabled={isLoading || !stripe || !elements} id="submit">
          <span id="button-text">{isLoading ? <div className="spinner" id="spinner"></div> : 'Pay Now'}</span>
        </Button>
      </Center>
      {/* Show any error or success messages */}
      <Center>{message && <div id="payment-message">{message}</div>}</Center>
    </form>
  )
})

本文作者:番茄炒蛋
本文地址: https://www.noway.pub/2023/10/09/stripe-payment/
版权声明:转载请注明出处!