自定义样式条纹支付元素布局

问题描述 投票:0回答:2

有人知道我们如何定制 Stripe 支付布局吗?我的目标是更改布局,例如第一个字段应为全宽,其余 3 个字段应对齐。

来自 stripe 的样式和 iframe https://js.stripe.com/v3/

Stripe grid-layout element style change

jquery css stripe-payments
2个回答
1
投票

这不是您在使用 PaymentElement 时可以控制的。通过这种集成,Stripe 控制 UI,但不允许您控制内部的确切布局。您可以控制父级的宽度/高度并通过各种选项进行样式设置,但没有一个可以通过这种方式控制每个字段。

需要注意的是,虽然您的屏幕截图仅显示卡详细信息,但 Stripe 的 PaymentElement 针对收集各种付款方式类型的付款方式详细信息进行了优化。沿着流程,预先收集的信息量各不相同,例如全页重定向到合作伙伴与同步成功或失败。

如果您确实想控制布局本身并且只关注卡支付,您可以使用不同的集成,为每条信息(号码、到期日期和 CVC)创建元素,并使用您自己的信息在页面上显示这些元素布局。这些字段仍然由 Stripe 控制,以降低您的 PCI 合规成本。您可以在此处查看有关此内容的文档。


0
投票

正如 koopajah 所说,这不是你可以用 PaymentElement 完成的事情。

为了实现你想要的,我们必须单独渲染每个元素。建议的方法可以使用

NextJs
TailwindCSS
来实现,如下所示:

StripeFormWrapper.tsx(这是我们自定义的条纹形式)

"use client"

import React, { useEffect, useState } from "react"
import { CardNumberElement, Elements, useElements, useStripe } from '@stripe/react-stripe-js'
import {
    loadStripe, 
    StripeCardCvcElementOptions,
    StripeCardExpiryElementOptions,
    StripeCardNumberElement,
    StripeCardNumberElementOptions
} from '@stripe/stripe-js'

import ButtonPrimary from "@/shared/ButtonPrimary"
import Checkbox from "@/shared/Checkbox"
import InputField from "@/shared/InputField"

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string)

function onVisible(element: HTMLElement, callback: Function): void {
    new IntersectionObserver((entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
        entries.forEach(entry => {
            if(entry.intersectionRatio > 0) {
                callback(element)
                observer.disconnect()
            }
        })
    }).observe(element)
}

const StripeForm = () => {
    const stripe = useStripe()
    const elements = useElements()

    const [cardholderName, setCardholderName] = useState<string>("")
    const [cardNumberReady, setCardNumberReady] = useState<boolean>(false)
    const [cardExpiryReady, setCardExpiryReady] = useState<boolean>(false)
    const [cardCvcReady, setCardCvcReady] = useState<boolean>(false)

    useEffect(() => {
        let cardNumber
        let cardExpiry
        let cardCvc

        if (elements) {
            cardNumber = elements.create(
                "cardNumber",
                {
                    classes: {
                        base: "table-cell align-middle w-[600px] h-[50px] pl-4 pr-2 py-2 rounded-full border border-solid border-zinc-400 bg-zinc-100"
                    },
                    showIcon: true,
                } as StripeCardNumberElementOptions
            )
            cardNumber.mount("#cardNumber-element")
            cardNumber.on("ready", () => setCardNumberReady(true))

            cardExpiry = elements.create(
                "cardExpiry",
                {
                    classes: {
                        base: "table-cell align-middle w-[600px] h-[50px] pl-4 pr-2 py-2 rounded-full border border-solid border-zinc-400 bg-zinc-100"
                    },
                } as StripeCardExpiryElementOptions
            )
            cardExpiry.mount("#cardExpiry-element")
            cardExpiry.on("ready", () => setCardExpiryReady(true))

            cardCvc = elements.create(
                "cardCvc",
                {
                    classes: {
                        base: "table-cell align-middle w-[600px] h-[50px] pl-4 pr-2 py-2 rounded-full border border-solid border-zinc-400 bg-zinc-100"
                    },
                } as StripeCardCvcElementOptions
            )
            cardCvc.mount("#cardCvc-element")
            cardCvc.on("ready", () => setCardCvcReady(true))
        }
    }, [elements])

    const handleSubmit = async (e: React.FormEvent) => { // tweak the content according to your needs
        e.preventDefault()

        const cardNumberElement = elements!.getElement(CardNumberElement)

        const response = await stripe!.confirmCardSetup(
            "client_secret", // from intent
            {
                payment_method: {
                    card: cardNumberElement!,
                    billing_details: {
                        name: cardholderName,
                        address: {
                            city: "Denver",
                            country: "US",
                            line1: "US, Denver, Colorado",
                            line2: "",
                            state: "Colorado",
                            postal_code: "80221"
                        },
                        email: "[email protected]", // customer email in stripe
                    }
                },
            }
        )

        console.log("response", response)
    }

    // for presentation purposes
    const cardNumberRef = React.useRef<HTMLDivElement>(null)
    const [showCardNumber, setShowCardNumber] = useState<boolean>(false)
    useEffect(() => {
        if (cardNumberRef.current) {
            onVisible(cardNumberRef.current, () => {
                setShowCardNumber(true)
            })
        }
    }, [cardNumberRef.current])

    const cardExpiryRef = React.useRef<HTMLDivElement>(null)
    const [showCardExpiry, setShowCardExpiry] = useState<boolean>(false)
    useEffect(() => {
        if (cardExpiryRef.current) {
            onVisible(cardExpiryRef.current, () => {
                setShowCardExpiry(true)
            })
        }
    }, [cardExpiryRef.current])

    const cardCvcRef = React.useRef<HTMLDivElement>(null)
    const [showCardCvc, setShowCardCvc] = useState<boolean>(false)
    useEffect(() => {
        if (cardCvcRef.current) {
            onVisible(cardCvcRef.current, () => {
                setShowCardCvc(true)
            })
        }
    }, [cardCvcRef.current])

    return (
        <React.Fragment>
            <form
                className="w-full grid grid-cols-1 gap-6"
                onSubmit={handleSubmit}
            >
                <label className="block">
                    <InputField
                        name="name"
                        label={(() => (
                            <div className="mb-2">
                                <h2 className="text-sm font-medium leading-normal">
                                    <span className="text-zinc-700">Name of Cardholder</span>
                                </h2>
                            </div>
                        ))()}
                        borderClassName="rounded-3xl"
                        type="text"
                        placeholder="Cardholder Name"
                        value={cardholderName}
                        onChange={(event) => setCardholderName(event.target.value)}
                    />
                </label>
                <label className="inline-block">
                    {showCardNumber && (
                        <div className={`mb-2 ${cardNumberReady ? `opacity-100` : `opacity-25`}`}>
                            <h2 className="text-sm font-medium leading-normal">
                                <span className="text-zinc-700">Card Number</span>
                            </h2>
                        </div>
                    )}
                    <div
                        id="cardNumber-element"
                        style={{ opacity: `${cardNumberReady ? `1` : `0.25`}` }}
                        ref={cardNumberRef}
                    />
                </label>
                <label className="inline-block">
                    <div className="flex space-x-3">
                        <div className="basis-1/2">
                            {showCardExpiry && (
                                <div className={`mb-2 ${cardExpiryReady ? `opacity-100` : `opacity-25`}`}>
                                    <h2 className="text-sm font-medium leading-normal">
                                        <span className="text-zinc-700">Expiry Date</span>
                                    </h2>
                                </div>
                            )}
                            <div
                                id="cardExpiry-element"
                                style={{ opacity: `${cardExpiryReady ? `1` : `0.25`}` }}
                                ref={cardExpiryRef}
                            />
                        </div>
                        <div className="basis-1/2">
                            {showCardCvc && (
                                <div className={`mb-2 ${cardCvcReady ? `opacity-100` : `opacity-25`}`}>
                                    <h2 className="text-sm font-medium leading-normal">
                                        <span className="text-zinc-700">Security Code (CVV)</span>
                                    </h2>
                                </div>
                            )}
                            <div
                                id="cardCvc-element"
                                style={{ opacity: `${cardCvcReady ? `1` : `0.25`}` }}
                                ref={cardCvcRef}
                            />
                        </div>
                    </div>
                </label>
                <label className="inline-block">
                    <Checkbox
                        name="agreed"
                        className="!text-xs h-6 items-center"
                        inputClassName="cursor-pointer focus:ring-0 dark:focus:ring-0 h-[20px] w-[20px] rounded bg-zinc-50 dark:bg-zinc-700 checked:bg-[url('../images/checked.svg')] checked:bg-contain dark:checked:bg-current checked:bg-[length:9px_7px] border-zinc-400 dark:border-zinc-200 text-zinc-700"
                        labelClassName="!ml-1"
                        label={(() => {
                            return (
                                <h2 className="text-sm font-normal leading-[20px]">
                                    <span className="text-zinc-700">Save Card for Future Transactions</span>
                                </h2>
                            )
                        })()}
                        defaultChecked={saveCard}
                        onChange={(checked) => setSaveCard(checked)}
                    />
                </label>
                <label className="inline-block">
                    <ButtonPrimary
                        containerClassName="w-full"
                        className="w-full mx-auto !py-1.5 !pr-1.5 !pl-[14px] justify-between"
                        type="submit"
                        showIcon
                    >
                        Save
                    </ButtonPrimary>
                </label>
            </form>
        </React.Fragment>
    )
}

const StripeFormWrapper = () => (
    <Elements
        stripe={stripePromise}
    >
        <StripeForm />
    </Elements>
)

export default StripeFormWrapper

page.tsx(这是页面)

import React, { FC } from "react"

import StripeFormWrapper from "./StripeFormWrapper"

export interface AddPaymentMethodPageProps {}

const AddPaymentMethodPage: FC<AddPaymentMethodPageProps> = ({}) => {
    return (
        <div className={`nc-AddPaymentMethodPage overflow-hidden`}>
            <div className="container relative mb-24 lg:mb-28">
                <div className="pt-5 lg:pt-8 pb-8">
                    <div className="flex flex-col items-center max-w-[600px] mx-auto space-y-6">
                        <h2 className="w-full text-4xl sm:text-5xl text-center font-normal leading-normal tracking-[-0.48px]">
                            <span className="text-zinc-500">Add&nbsp;</span>
                            <span className="text-zinc-700">Payment Method</span>
                        </h2>
                        <div className="flex flex-col items-start self-stretch">
                            <StripeFormWrapper />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}

结果

Result

© www.soinside.com 2019 - 2024. All rights reserved.