# Make Payments Source: https://fragment.dev/guides/make-payments [Documentation index](https://fragment.dev/llms.txt) Fragment enables you to make payments that automatically reconcile to your Ledger. [Visual: PaymentDiagram. View on the HTML page.](https://fragment.dev/guides/make-payments) ## Payment Type A Payment Type is a reusable definition of one type of money movement in your Ledger. Define them in your Schema. ```json expandable=true title="Payment Type definition" { "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}}" } ] } } } ``` ## Embed Checkout Embed Fragment's checkout in your product to begin processing payments. [Visual: EmbedCheckoutSequenceDiagram. View on the HTML page.](https://fragment.dev/guides/make-payments) ### Create a Payment Call `createPayment` from your backend with a Payment Type. Pass the returned `Payment` to your frontend: ```typescript title="backend.ts" 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 }; ``` ```python title="backend.py" response = await client.create_payment( ik=order_id, ledger_ik="main", type_="card_payin", type_version=1, parameters={"amount": "4999"}, ) result = response.create_payment if result.typename__ != "CreatePaymentResult": raise Exception(result.message) return {"payment": result.payment} ``` ```go title="backend.go" parameters := json.RawMessage(`{"amount": "4999"}`) response, err := queries.CreatePayment( context.Background(), graphqlClient, orderID, "main", "card_payin", 1, // typeVersion ¶meters, nil, // paymentMethod ) if err != nil { return nil, err } return response.GetCreatePayment(), nil ``` ```ruby title="backend.rb" response = fragment.create_payment({ ik: order_id, ledgerIk: 'main', type: 'card_payin', typeVersion: 1, parameters: { amount: '4999' } }) result = response.data.create_payment raise result.message unless result.__typename == 'CreatePaymentResult' { payment: result.payment } ``` The Payment starts in `needs_payment_method`. No money moves until the customer confirms the card. ### Render the checkout form Use Fragment's [frontend SDK](https://fragment.dev/markdown/api-reference/ledger-sdks.md#frontend) to render a checkout form. [Visual: checkoutHero. View on the HTML page.](https://fragment.dev/guides/make-payments) Pass the `Payment` and the customer's email in `billingDetails.email` as props to the component. Mount `Checkout` within `PaymentElements`: ```typescript title="CheckoutPage.tsx" 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; }) => ( console.error('Payment failed:', message)} onConfirmed={onSuccess} > ); ``` You control the Pay button. Call `confirm()` to charge the card when `canConfirm` is `true`: ```typescript title="PayButton.tsx" import { usePayment } from '@fragment-dev/payment-elements'; export const PayButton = () => { const { confirm, canConfirm, isConfirming, isLoading, error } = usePayment(); return ( <> {error &&

{error}

} ); }; ``` The SDK calls `onConfirmed` after it charges the card. On a failure, it calls `onError`. ## Manage saved cards Save a user's cards during checkout or onboarding to initiate payments from your backend. ### Save a card at checkout Enable `savePaymentMethod` on the `Checkout` component to save a user's card when the payment confirms: ```typescript title="CheckoutPage.tsx" const CheckoutPage = ({ payment, email }: { payment: Payment; email: string }) => { const [saveCard, setSaveCard] = useState(false); return ( ); }; ``` Render a checkbox for the payer to choose during checkout: [Visual: checkoutCardOnFile. View on the HTML page.](https://fragment.dev/guides/make-payments) Saved cards are tied to the user's email provided in `billingDetails.email`. ### Save a card Save a user's card during onboarding to charge them later. [Visual: SavePaymentMethodSequenceDiagram. View on the HTML page.](https://fragment.dev/guides/make-payments) Call `createPaymentMethodSetup` from your backend. Pass the returned `PaymentMethodSetup` to your frontend: ```typescript title="backend.ts" const { createPaymentMethodSetup } = await fragment.createPaymentMethodSetup({ ik: requestId, ledgerIk: 'main', email, }); if (createPaymentMethodSetup.__typename !== 'CreatePaymentMethodSetupResult') { throw new Error(createPaymentMethodSetup.message); } return { paymentMethodSetup: createPaymentMethodSetup.paymentMethodSetup }; ``` ```python title="backend.py" response = await client.create_payment_method_setup( ik=request_id, ledger_ik="main", email=email, ) result = response.create_payment_method_setup if result.typename__ != "CreatePaymentMethodSetupResult": raise Exception(result.message) return {"payment_method_setup": result.payment_method_setup} ``` ```go title="backend.go" response, err := queries.CreatePaymentMethodSetup( context.Background(), graphqlClient, requestID, "main", email, ) if err != nil { return nil, err } return response.GetCreatePaymentMethodSetup(), nil ``` ```ruby title="backend.rb" response = fragment.create_payment_method_setup({ ik: request_id, ledgerIk: 'main', email: email }) result = response.data.create_payment_method_setup raise result.message unless result.__typename == 'CreatePaymentMethodSetupResult' { paymentMethodSetup: result.payment_method_setup } ``` Render `SavePaymentMethod` from Fragment's [frontend SDK](https://fragment.dev/markdown/api-reference/ledger-sdks.md#frontend) inside `PaymentElements` to show the card form: [Visual: savePaymentMethodHero. View on the HTML page.](https://fragment.dev/guides/make-payments) Pass the `PaymentMethodSetup` to `SavePaymentMethod`. You control the Save button. Call `submit()` from `usePaymentMethodSetup()` when `canSubmit` is `true`: ```typescript expandable=true title="SaveCardPage.tsx" import { PaymentElements, SavePaymentMethod, usePaymentMethodSetup, type PaymentMethodSetup, } from '@fragment-dev/payment-elements'; const SaveButton = () => { const { submit, canSubmit, state, error } = usePaymentMethodSetup(); return ( <> {state.status === 'declined' &&

{state.message}

} {error &&

{error.message}

} ); }; export const SaveCardPage = ({ setup }: { setup: PaymentMethodSetup }) => ( ); ``` The SDK calls `onPaymentMethodSaved` after it saves the card, and `onPaymentMethodDeclined` when the card is declined. Any other failure calls `onError`. ### Charge a saved card Call `createPayment` with the saved card's `id` in `paymentMethod` to make a payment from your backend: ```typescript title="backend.ts" const { createPayment } = await fragment.createPayment({ ik: renewalId, ledgerIk: 'main', type: 'card_payin', typeVersion: 1, parameters: { amount: '4999', }, paymentMethod: { id: savedCardId }, }); ``` ```python title="backend.py" response = await client.create_payment( ik=renewal_id, ledger_ik="main", type_="card_payin", type_version=1, parameters={"amount": "4999"}, payment_method={"id": saved_card_id}, ) ``` ```go title="backend.go" parameters := json.RawMessage(`{"amount": "4999"}`) response, err := queries.CreatePayment( context.Background(), graphqlClient, renewalID, "main", "card_payin", 1, // typeVersion ¶meters, &queries.PaymentMethodMatchInput{Id: savedCardID}, ) ``` ```ruby title="backend.rb" response = fragment.create_payment({ ik: renewal_id, ledgerIk: 'main', type: 'card_payin', typeVersion: 1, parameters: { amount: '4999' }, paymentMethod: { id: saved_card_id } }) ``` The Payment starts in `processing` and follows the same lifecycle as a checkout Payment. ### Delete a saved card Call `deletePaymentMethod` with the saved card's `id`. After the call, the card can no longer be charged: ```typescript title="backend.ts" await fragment.deletePaymentMethod({ id: savedCardId, }); ``` ```python title="backend.py" await client.delete_payment_method( id=saved_card_id, ) ``` ```go title="backend.go" queries.DeletePaymentMethod( context.Background(), graphqlClient, savedCardID, ) ``` ```ruby title="backend.rb" fragment.delete_payment_method({ id: saved_card_id }) ``` ## Refund a Payment To refund a `settled` Payment, call `refundPayment` with the Payment's `ik`: ```typescript const { payment, refund } = await fragment.refundPayment({ ik: refundRequestIk, paymentId: settledPayment.ik, }); console.log(refund.status); // pending console.log(refund.amount); // 4999 ``` Pass `amount` to refund part of the Payment, up to its captured amount: ```typescript 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. ## Testing 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.