# 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.