FRAGMENT enables you to make payments that automatically reconcile to your Ledger.
A Payment Type is a reusable definition of one type of money movement in your Ledger. Define them in your Schema.
{
"type": "card_payin",
"typeVersion": 1,
"status": "active",
"payment": {
"amount": "{{order_amount}}",
"direction": "payin"
},
"accounting": {
"settled": {
"description": "Order {{order_id}} settled",
"lines": [
{
"key": "settlement",
"system": "payment_settlement_line",
"account": {
"path": "assets-root/payments"
},
"amount": "{{settled_amount}}"
},
{
"key": "fee",
"system": "payment_fee_line",
"account": {
"path": "assets-root/payments"
},
"amount": "-{{fragment_fee_amount}}"
},
{
"key": "receivable",
"account": {
"path": "assets-root/receivable:{{order_id}}"
},
"amount": "-{{settled_amount}}"
},
{
"key": "fee-expense",
"account": {
"path": "expense-root/payment-fees"
},
"amount": "{{fragment_fee_amount}}"
}
]
}
}
}Add checkout to your product in two steps:
Checkout formCall createPayment from your backend with a Payment Type:
const { createPayment } = await fragment.createPayment({
ik: orderId,
ledgerIk: 'main',
type: 'card_payin',
typeVersion: 1,
parameters: {
amount: '4999',
},
});
if (createPayment.__typename !== 'CreatePaymentResult') {
throw new Error(createPayment.message);
}
return { payment: createPayment.payment };The Payment starts in needs_payment_method. No money moves until the customer confirms the card.
Use FRAGMENT's frontend SDK to render a checkout form.
Pass the Payment and the customer's email in billingDetails.email as props to the component. Mount Checkout within PaymentElements:
import {
Checkout,
PaymentElements,
type Payment,
} from '@fragment-dev/payment-elements';
import { PayButton } from './PayButton';
const CheckoutPage = ({
payment,
email,
onSuccess,
}: {
payment: Payment;
email: string;
onSuccess: () => void;
}) => (
<PaymentElements>
<Checkout
payment={payment}
billingDetails={{ email }}
onError={(message) => console.error('Payment failed:', message)}
onConfirmed={onSuccess}
>
<PayButton />
</Checkout>
</PaymentElements>
);Implement a Pay button. Call confirm() to charge the card when canConfirm is true:
import { usePayment } from '@fragment-dev/payment-elements';
export const PayButton = () => {
const { confirm, canConfirm, isConfirming, isLoading, error } = usePayment();
return (
<>
<button
type="button"
disabled={isLoading || !canConfirm || isConfirming}
onClick={() => canConfirm && confirm()}
>
Pay
</button>
{error && <p role="alert">{error}</p>}
</>
);
};The SDK calls onConfirmed after it charges the card. On a failure, it calls onError.
Save a user's cards during checkout or onboarding to initiate payments from your backend.
Enable savePaymentMethod on the Checkout component to save a user's card when the payment confirms:
const CheckoutPage = ({ payment, email }: { payment: Payment; email: string }) => {
const [saveCard, setSaveCard] = useState(false);
return (
<Checkout
payment={payment}
billingDetails={{ email }}
savePaymentMethod={saveCard}
onError={logError}
onConfirmed={showReceipt}
>
<SaveCardCheckbox checked={saveCard} onChange={setSaveCard} />
<PayButton />
</Checkout>
);
};Render a checkbox for the payer to choose during checkout:
Saved cards are tied to the user's email provided in billingDetails.email.
Save a user's card during onboarding to charge them later in a couple of steps:
PaymentMethodSetup via the APISavePaymentMethod componentCall createPaymentMethodSetup from your backend:
const { createPaymentMethodSetup } = await fragment.createPaymentMethodSetup({
ik: requestId,
ledgerIk: 'main',
email,
});
if (createPaymentMethodSetup.__typename !== 'CreatePaymentMethodSetupResult') {
throw new Error(createPaymentMethodSetup.message);
}
return { paymentMethodSetup: createPaymentMethodSetup.paymentMethodSetup };Pass the returned PaymentMethodSetup to your frontend. Render SavePaymentMethod from FRAGMENT's frontend SDK inside PaymentElements to display the card form:
Implement a Save button. Call submit() from usePaymentMethodSetup() when canSubmit is true:
import {
PaymentElements,
SavePaymentMethod,
usePaymentMethodSetup,
type PaymentMethodSetup,
} from '@fragment-dev/payment-elements';
const SaveButton = () => {
const { submit, canSubmit, state, error } = usePaymentMethodSetup();
return (
<>
<button type="button" disabled={!canSubmit} onClick={() => void submit()}>
{state.status === 'processing' ? 'Saving…' : 'Save card'}
</button>
{state.status === 'declined' && <p role="alert">{state.message}</p>}
{error && <p role="alert">{error.message}</p>}
</>
);
};
export const SaveCardPage = ({ setup }: { setup: PaymentMethodSetup }) => (
<PaymentElements>
<SavePaymentMethod
paymentMethodSetup={setup}
onPaymentMethodSaved={showConfirmation}
onPaymentMethodDeclined={showDecline}
onError={logError}
>
<SaveButton />
</SavePaymentMethod>
</PaymentElements>
);The SDK calls onPaymentMethodSaved after it saves the card, and onPaymentMethodDeclined when the card is declined. Any other failure calls onError.
Call createPayment with the saved card's id in paymentMethod to make a payment from your backend:
const { createPayment } = await fragment.createPayment({
ik: renewalId,
ledgerIk: 'main',
type: 'card_payin',
typeVersion: 1,
parameters: {
amount: '4999',
},
paymentMethod: { id: savedCardId },
});The Payment starts in processing and follows the same lifecycle as a checkout Payment.
Call deletePaymentMethod with the saved card's id. After the call, the card can no longer be charged:
await fragment.deletePaymentMethod({
id: savedCardId,
});To refund a settled Payment, call refundPayment with the Payment's ik:
const { payment, refund } = await fragment.refundPayment({
ik: refundRequestIk,
paymentId: settledPayment.ik,
});
console.log(refund.status); // pending
console.log(refund.amount); // 4999Pass amount to refund part of the Payment, up to its captured amount:
const { payment, refund } = await fragment.refundPayment({
ik: refundRequestId,
paymentId: settledPayment.id,
amount: '2500',
});The funds will immediately move out of your Payment Account and the refund will move to a pending status. Refunds will move to the succeeded status after a few days. In the event of a failure, the refund will be marked as failed.
FRAGMENT keeps its processing fee from the original Payment when you refund it.
Use the following cards in sandbox mode for your testing:
| Card number | Outcome |
|---|---|
4242 4242 4242 4242 | The payment succeeds. A saved card's later charges also succeed. |
4000 0000 0000 0002 | The payment is declined. |
4000 0000 0000 0341 | The card saves, but a later charge against it is declined. |
5385 3083 6013 5181 | 3D Secure is required. Enter Checkout1! on the 3DS screen. |
The expiry date must be in the future, and use any three-digit CVC.