Falcon Payments Integration
We recommend using one of our example apps as a start place, these are already setup using Falcon Payments.
This guide will talk you though how those integrations work and how to create your own.
1. Getting and setting payment methods
Getting the available shipping methods in a component is simple.
We'll be using the PaymentMethodListQuery
query from @deity/falcon-shop-data
and SetPaymentMethod
from @deity/falcon-front-kit
.
First you'll need to ensure your shop API has a paymentMethodList
method that returns methods. If you're using one of our existing integrations this will exist already.
In your client side component the code is as simple as:
...
import { PaymentMethodListQuery } from '@deity/falcon-shop-data';
import { SetPaymentMethod } from '@deity/falcon-front-kit';
...
export const PaymentMethodSection = () => {
const [state, setState] = useState(values.paymentMethod || undefined);
<PaymentMethodListQuery fetchPolicy="network-only">
{({ data: { paymentMethodList } }) => {
if (paymentMethodList.length === 0) {
return (
<p>No Methods</p>
);
}
return (
<SetPaymentMethod>
{(setPayment, { error, loading }) => (
<>
{paymentMethodList.map(method => (
<PaymentMethodOption
key={getMethodID(method)}
onChange={() => setState(method)}
checked={state ? getMethodID(method) === getMethodID(state) : false}
option={method}
disabled={loading}
total={cart}
/>
))}
<NextStepButton
onClick={() => {
setPayment(state);
}}
disabled={!state || !state.provider}
loading={loading}
/>
</>
)}
</SetPaymentMethod>
);
}}
</PaymentMethodListQuery>
}
In this example we get the available methods from PaymentMethodListQuery
and set the payment method using the setPayment
method from SetPaymentMethod
.
2. Loading the payment UI
The next thing you'll want to do is to get the selected payment method and load the payment UI.
We use loadable
so only the selected method is loaded. As you can see from the code below we use the provider code and method (if available) to define which UI components to load.
const paymentCodeToPluginMap = {
cash: `SimplePayment`,
stripe: `Stripe`,
mollie: {
creditcard: `Mollie/CreditCard`,
paypal: 'Mollie/PayPal',
ideal: 'Mollie/Ideal',
klarnapaylater: 'Mollie/KlarnaPayLater',
giftcard: 'Mollie/Giftcard',
default: 'Mollie/Default'
}
};
paymentCodeToPluginMap.getFor = (providerCode, method) => {
const provider = providerCode in paymentCodeToPluginMap ? paymentCodeToPluginMap[providerCode] : undefined;
if (typeof provider === 'object') {
return provider[method] ? provider[method] : provider.default;
}
return provider;
};
const getPaymentUI = (provider) =>
loadable(() =>
import(/* webpackChunkName: "shop/checkout/payments/[request]" */ `../components/payments/${provider}`)
);
Now we render the component. We're able to get the selected method from useCheckout
import { useCheckout } from '@deity/falcon-front-kit';
export const PlaceOrderSection = () => {
const { values, setStep, result } = useCheckout();
const { paymentMethod } = values;
const [placeOrder, { loading, error }] = usePlaceOrder();
const paymentPlugin = paymentCodeToPluginMap.getFor(paymentMethod.provider, paymentMethod.method);
if (!paymentPlugin) {
if (process.env.NODE_ENV !== 'production') {
console.error(`No Payment Method Plugin found for ${paymentMethod.provider}`);
}
}
const Payment = useRef(getPaymentUI(paymentPlugin)).current;
return (
<>
<Payment {...paymentMethod}>
{(pay, { loading: paying }) => (
<>
{Payment.UI && <Payment.UI />}
<NextStepButton
onClick={() =>
pay().then((payResult) =>
placeOrder(
{
...values,
paymentMethod: {
...values.paymentMethod,
data: {
...values.paymentMethod.data,
...payResult
}
}
},
{
awaitRefetchQueries: false
}
)
)
}
loading={paying || loading}
>
<T id="checkout.placeOrder" />
</NextStepButton>
<ErrorSummary errors={error} mt="sm" />
</>
)}
</Payment>
{result && result.url && <TestAdditionalPaymentStep {...result} />}
{result && result.id && <Redirect to="/checkout/confirmation" />}
</>
);
};
You'll notice we have 2 additional components also loaded:
{
result && result.url && <TestAdditionalPaymentStep {...result} />;
}
{
result && result.id && <Redirect to="/checkout/confirmation" />;
}
TestAdditionalPaymentStep
is used to handle payment methods that require an additional step, e.g. 3D secure.
Redirect
simple redirects a user to your desired success page if result exists in useCheckout
.
3. Example UI Components
The UI components for each method can vary, some being very simple and some needing additional info (e.g. a credit card form).
Simple UI Example
The Payment.UI
can return null
if you don't need to display anything other than the name of the method.
In this example below we display a simple line of text informing the customer they'll be redirected to complete the payment.
It's important that the pay
method is passed to the children of Payment
.
import React, { useCallback } from 'react';
import { T } from '@deity/falcon-i18n';
import { Text, FlexLayout, withTheme } from '@deity/falcon-ui';
const Payment = withTheme(({ children }) => {
const pay = useCallback(() => Promise.resolve({ id: undefined }), []);
return children(pay, { loading: false });
});
Payment.UI = () => (
<FlexLayout as="section" my="xs" css={{ width: '100%' }}>
<Text mt="xs" color="secondaryText" fontWeight="regular">
<T id="payment.redirect.default" />
</Text>
</FlexLayout>
);
export default Payment;
Complex UI Example
This example is taken from our Stripe
credit card form. We wrap the entire form in StripePlugin
, this loads the various scripts that are needed to load the Stripe card fields. You'll notice we pass prop
to StripePlugin
. These props are passed from Falcon Payment
and depending on your method are likely to contain API keys needed to load the UI.
import React, { useState } from 'react';
import { StripePlugin, CardElement } from '@deity/falcon-stripe-plugin';
import { Box } from '@deity/falcon-ui';
const Payment = ({ children, ...props }) => {
const [loading, setLoading] = useState(false);
const fn = () => {
setLoading(true);
return Promise.resolve();
};
return (
<StripePlugin {...props}>
{(pay) =>
children(
() =>
fn()
.then(() => pay())
.then((x) => {
setLoading(false);
return x;
})
.catch((x) => {
setLoading(false);
return Promise.reject(x);
}),
{ loading }
)
}
</StripePlugin>
);
};
Payment.UI = () => (
<Box>
{/* https://stripe.com/docs/stripe-js/reference#element-options */}
<CardElement hidePostalCode />
</Box>
);
export default Payment;