medipass / react-payment-inputs

A React Hook & Container to help with payment card input fields.
https://medipass.github.io/react-payment-inputs
341 stars 62 forks source link

Ant Design compatibility #70

Open EncryptedCurse opened 3 years ago

EncryptedCurse commented 3 years ago

How can I use this library with Ant Design?

Attempting a minimal working example as follows...

<Input {...getCardNumberProps()} />

...produces this error:

Uncaught TypeError: Cannot read property 'match' of undefined
    at Object.formatCardNumber (formatter-b0b2372d.js:6)
    at usePaymentInputs.js:518
    at commitHookEffectListMount (react-dom.development.js:20573)
    at commitLifeCycles (react-dom.development.js:20634)
    at commitLayoutEffects (react-dom.development.js:23426)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at commitRootImpl (react-dom.development.js:23151)
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at commitRoot (react-dom.development.js:22990)
    at performSyncWorkOnRoot (react-dom.development.js:22329)
    at react-dom.development.js:11327
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at flushSyncCallbackQueueImpl (react-dom.development.js:11322)
    at flushSyncCallbackQueue (react-dom.development.js:11309)
    at discreteUpdates$1 (react-dom.development.js:22420)
    at discreteUpdates (react-dom.development.js:3756)
    at dispatchDiscreteEvent (react-dom.development.js:5889)
odragora commented 3 years ago

Would be awesome to have Ant Design compatibility.

EncryptedCurse commented 3 years ago

@odragora I managed to come up with my own solution that may interest you. This is not a minimal working example and there's probably a better way to do it, but it works for my purpose. I used the card-validator library and my own custom icons (though you could swap them for something like Font Awesome without too much trouble).

Do note that it's definitely not as full-featured as react-payment-inputs, but it's capable of dynamically changing the card type icon as the number is typed and performing validation on it using the aforementioned library and the Form component built into Ant Design.

import { useState } from "react";
import * as cardValidator from "card-validator";

import visaIcon from "./payment/visa.png";
import mastercardInputIcon from "./payment/mastercard.png";
import maestroIcon from "./payment/maestro.png";
import amexIcon from "./payment/amex.png";

const cardIcons = {
    "visa": visaIcon,
    "american-express": amexIcon,
    "mastercard": mastercardInputIcon,
    "maestro": maestroIcon,
};

const cardExpirationDropdown = (type, text) => (
    <Tooltip title={text} placement="right" trigger={["hover"]}>
        <Form.Item name={["card", "expiration", type]} initialValue={text} rules={[{ required: true }]}>
            <Select>
                {cardExpirationData[type].map((value) => (
                    <Select.Option key={value} value={value}>
                        {value}
                    </Select.Option>
                ))}
            </Select>
        </Form.Item>
    </Tooltip>
);

export default function Example() {
    const [cardNumberSuffix, setCardNumberSuffix] = useState(null);

    ...

    const cardFormGroup = () => {
        function validateCard(_, value) {
            const { card, isPotentiallyValid, isValid } = cardValidator.number(value);
            if (value) {
                setCardNumberSuffix(
                    card && card in cardIcons ? (
                        <img src={cardIcons[card.type]} title={card.niceType} width="32" />
                    ) : null
                );
                return (value.length >= 15 ? isValid : isPotentiallyValid)
                    ? Promise.resolve()
                    : Promise.reject(new Error("Invalid"));
            } else {
                setCardNumberSuffix(null);
                return Promise.reject(new Error("Required"));
            }
        }

        const normalize = (value) => value.replace(/\D+/g, "");

        return (
            <>
                <Form.Item label="Name" name={["card", "name"]}>
                    <Input />
                </Form.Item>
                <Form.Item
                    label="Number"
                    name={["card", "number"]}
                    rules={[{ validator: validateCard }]}
                    normalize={normalize}
                >
                    <Input maxLength={16} suffix={<>{cardNumberSuffix}</>} />
                </Form.Item>
                <Form.Item
                    label="CVV"
                    name={["card", "cvv"]}
                    rules={[{ required: true }]}
                    normalize={normalize}
                >
                    <Input maxLength={4} />
                </Form.Item>
                <Form.Item label="Expiration" required>
                    <Row gutter={6}>
                        <Col span={12}>{cardExpirationDropdown("month", "Month")}</Col>
                        <Col span={12}>{cardExpirationDropdown("year", "Year")}</Col>
                    </Row>
                </Form.Item>
            </>
        );
    };

    ...

}
odragora commented 3 years ago

Thank you very much @EncryptedCurse ! I think your code can save a lot of time for me.