import { isEmpty, head, map } from 'lodash';
...
const TabbyPaymentForm = () => {
const { error, onSubmit } = useHandleSubmit();
const [preScoringResponse, setPreScoringResponse] =
useState<TabbyPreScoringResponse>();
const { executePreScoring, error: preScoringError } = useExecutePreScoringRequest();
const getTabbyPayment = useGetTabbyPayment();
const TabbyCard = window['TabbyCard'];
useEffect(() => {
if (!preScoringResponse && cart && !resolvingCart && TabbyCard) {
const tabbyPayment = getTabbyPayment(cart);
const paymentAmount = getPaymentAmount(cart);
const request = {
payment: {
amount: toString(paymentAmount.amount),
currency: paymentAmount.currency,
...tabbyPayment,
},
lang: 'en',
} as TabbyCheckoutSessionRequest;
executePreScoring(request).then(response => {
if (response?.status === 'created' && TabbyCard) {
// Create Tabby Checkout Snippet
new TabbyCard({
selector: '#tabbyCard',
currency: request.payment.currency,
lang: 'en',
price: request.payment.amount,
size: 'wide',
theme: 'black',
header: true,
});
}
setPreScoringResponse(response);
});
}
}, [
cart,
executePreScoring,
getPaymentAmount,
getTabbyPayment,
preScoringResponse,
resolvingCart,
setPreScoringResponse,
TabbyCard,
]);
if (!preScoringResponse && !preScoringError) {
return null;
}
let preScoringRejectedMessage;
if (
preScoringResponse?.status !== 'created' &&
preScoringResponse?.configuration?.products?.installments?.rejection_reason
) {
const rejectionReason =
preScoringResponse.configuration?.products?.installments
?.rejection_reason;
if (rejectionReason === 'order_amount_too_high') {
preScoringRejectedMessage = 'This purchase is above your current spending limit with Tabby, try a smaller cart or use another payment method.';
} else if (rejectionReason === 'order_amount_too_low') {
preScoringRejectedMessage = 'The purchase amount is below the minimum amount required to use Tabby, try adding more items or use another payment method.';
} else {
preScoringRejectedMessage = 'Sorry, Tabby is unable to approve this purchase. Please use an alternative payment method for your order.';
}
}
return (
<Formik
onSubmit={onSubmit}
validateOnBlur
validateOnChange={false}
>
{() => (
<FormikForm>
<div>
<img
className="mr-2"
src="/tabby-badge.png"
alt="Tabby payment"
role="img"
width={60}
/>
<span>Pay in 4. No interest, no fees.</span>
</div>
<div id="tabbyCard"></div>
{(error || preScoringError || preScoringRejectedMessage) && (
<strong className="block my-4 text-red-600 text-lg font-normal">
{error ? 'There was an error processing your request. Please check your info and try again.' : undefined}
{preScoringError
? 'There was an unexpected error. Please check your info and try again our use different payment method.'
: undefined}
{preScoringRejectedMessage
? preScoringRejectedMessage
: undefined}
</strong>
)}
<button type="submit">Submit</button>
</FormikForm>
)}
</Formik>
)
};
const useGetTabbyPayment = () => {
return useCallback((cart: Cart, billingAddress?: Address) => {
const shippingAddress = cart?.fulfillmentGroups[0]?.address;
if (isEmpty(billingAddress)) {
billingAddress = shippingAddress;
}
const orderItems = map(cart.cartItems, cartItem => {
return {
title: cartItem.name,
quantity: cartItem.quantity,
unit_price: toString(cartItem.unitPrice.amount),
reference_id: cartItem.productId,
image_url: cartItem.imageAsset?.contentUrl,
category: head(cartItem.attributes?.categoryNames),
} as TabbyOrderItem;
});
return {
buyer: {
phone: billingAddress.phonePrimary.phoneNumber,
email: cart.emailAddress,
name: billingAddress.fullName,
},
shipping_address: {
city: shippingAddress?.city,
address: shippingAddress?.addressLine1,
zip: shippingAddress?.postalCode,
},
order: {
reference_id: cart?.id,
items: orderItems,
},
} as TabbyPayment;
}, []);
};
const useHandleSubmit = () => {
const { cart } = useCartContext();
const { payments } = usePaymentsContext();
const paymentClient = usePaymentClient();
const authState = usePaymentAuthState();
const { handleSubmitPaymentInfo } = useSubmitPaymentRequest({
authState,
payments,
ownerId: cart?.id,
owningUserEmailAddress: cart.emailAddress,
paymentClient,
multiplePaymentsAllowed: true,
rejectOnError: true,
});
const redirectCallbackUrl = 'https://{storefrontUrl}/api/cart-operations/checkout/${cartId}/payment-callback/TABBY?tenantId=${tenantId}&applicationId=${applicationId}';
const getTabbyPayment = useGetTabbyPayment();
const [error, setError] = useState<ApiError>();
const onSubmit = async (
data,
actions
): Promise<void> => {
const { ...billingAddress } = data;
const tabbyPayment = getTabbyPayment(cart, billingAddress);
const amount = getPaymentAmount(cart);
const paymentRequest = {
name: 'TABBY',
type: 'BNPL',
gatewayType: 'TABBY',
amount,
billingAddress,
paymentMethodProperties: {
bnpl_type: 'PAY_IN_4',
payment: JSON.stringify(tabbyPayment),
lang: 'en',
merchant_urls: JSON.stringify({
success: redirectCallbackUrl,
cancel: redirectCallbackUrl,
failure: redirectCallbackUrl,
}),
},
} as PaymentRequest;
const existingPayment = payments?.content?.filter(
p => p.gatewayType === 'TABBY'
)[0];
let paymentSummary;
try {
setError(null);
paymentSummary = await handleSubmitPaymentInfo(
paymentRequest,
existingPayment?.paymentId
);
} catch (err) {
console.error('There was an error adding payment information', err);
setError(err);
} finally {
actions.setSubmitting(false);
...
}
};
return { error, onSubmit };
};