Add a convenience fee to a payment
Overview
In some markets, merchants can charge a convenience fee for using an alternative payment channel (for example, paying online instead of in person or by bank transfer).
In Payments AI, a convenience fee is modeled as an additional amount that is:
- Calculated on our server based on your fee rules.
- Displayed clearly to the customer before they confirm the payment.
- Passed to Payments AI as structured data (metadata) together with the transaction, and reconciled to the related invoice.
This tutorial shows how to:
- Enable convenience fees on your Payments AI account.
- Prepare products, plans, and a one-time order.
- Calculate and display a convenience fee in your UI.
- Send the fee to Payments AI when creating a transaction.
Important: This guide is not legal advice. You are responsible for ensuring that your use of convenience fees is compliant with local laws, card network rules, and your acquiring bank.
Prerequisites & enablement
Before you can start charging a convenience fee, you need both business and technical prerequisites in place.
Business and compliance checklist
Make sure you:
- Describe the convenience fee in your Terms of Service (ToS), including:
- That a convenience fee will be charged.
- When it applies (for example, “for online card payments only”).
- How the fee amount is determined (for example, fixed amount or defined range).
- Define your usage plan, including:
- The fee amount or range you plan to apply.
- The countries or regions where you will charge the fee.
- The payment channels where the fee applies (for example, online vs. in-person).
- Confirm that you offer a standard fee-free payment channel, such as ACH, in-person cash, check, or mail.
- Design your checkout UI so the customer:
- Sees the base amount, the convenience fee, and the total before confirming.
- Sees the convenience fee as a clearly labeled, separate line item.
Contact support to enable convenience fees
Convenience fees are disabled by default for Payments AI accounts.
To enable them:
- Contact Payments AI support or your account manager.
- Provide the following information:
- Confirmation that your ToS includes convenience fee disclosures.
- Your planned fee amount or range.
- The countries/regions and payment channels where you will charge the fee.
- Any other relevant compliance documentation requested by Payments AI.
- Wait for confirmation that your organization has been toggled for convenience fees.
Payments AI may review your disclosures and implementation plan before enabling this feature.
High-level flow
At a high level, the flow looks like this:
- Your server prepares product and plan data.
- Your server creates an order (one-time sale) in Payments AI.
- The browser requests a calculated convenience fee from your server.
- Your server calls the Payments AI preview endpoint to compute the fee amount and returns it to the browser.
- The browser displays the fee and updated total to the customer.
- The browser updates Framepay with the final amount and collects a payment token.
- Your server creates a transaction in Payments AI using the token, including:
- The convenience fee in
metadata. - The related invoice in
invoiceIds.
- The convenience fee in
The next sections walk through this flow step by step.
Step 1 – Prepare product, plan, and one-time order
Payments AI uses products, plans, and orders to describe what the customer is paying for.
- A product is what you sell (for example, a physical good or a service).
- A plan is a template that describes pricing and terms (for example, a one-time order vs. a subscription).
- An order groups the items that will be billed on an invoice.
For more background, see Products and plans.
In this tutorial, we focus on a one-time-order plan. You can either:
- Look up existing products and plans by name, or
- Use IDs that you already know.
Example: Look up product and plan
curl --location --request GET 'https://staging-api.payments.ai/v1/public-api/organizations/{{ORGANIZATION_ID}}/products?name=Online%20Service%20Fee' \
--header 'Accept: application/json' \
--header 'Authorization: {{YOUR_API_KEY}}'
curl --location --request GET 'https://staging-api.payments.ai/v1/public-api/organizations/{{ORGANIZATION_ID}}/plans?name=One-time%20order' \
--header 'Accept: application/json' \
--header 'Authorization: {{YOUR_API_KEY}}'
Example: Create a one-time order
Once you have a product and plan, create an order that contains the base amount (without the convenience fee).
curl --location --request POST 'https://staging-api.payments.ai/v1/public-api/organizations/{{ORGANIZATION_ID}}/orders' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: {{YOUR_API_KEY}}' \
--data '{
"type": "one-time-order",
"items": [
{
"productId": "{{PRODUCT_ID}}",
"planId": "{{PLAN_ID}}",
"quantity": 1,
"unitPrice": 100.00,
"currency": "USD",
"description": "Base payment"
}
]
}'
The response will include an orderId and an invoiceId. You will use the invoiceId later when you create the transaction.
Note: You can also create the fee as a separate line item on the same invoice. This tutorial focuses on calculating the fee dynamically and passing it through metadata during the transaction.
Step 2 – Calculate the convenience fee on your server
To keep your implementation consistent and auditable, calculate the convenience fee on your server, not in the browser.
Define a server endpoint, for example:
GET /convenience-fee?invoiceId={invoiceId}
This endpoint should:
- Accept the
invoiceId(or base amount context) from the browser. - Call the Payments AI preview endpoint:
GET /v1/public-api/organizations/{organizationId}/convenience-fee-preview - Return the values from Payments AI to the browser.
Example: Call the Payments AI preview endpoint
From your server, you can call the preview endpoint using the invoice context:
curl --location --request GET 'https://staging-api.payments.ai/v1/public-api/organizations/{{ORGANIZATION_ID}}/convenience-fee-preview?invoiceId=inv_123456' \
--header 'Accept: application/json' \
--header 'Authorization: {{YOUR_API_KEY}}'
The response includes the fee amount you will return to the browser.
Example: Server endpoint response
Your server could respond with JSON like:
{
"invoiceId": "inv_123456",
"netSettlementTarget": 100.00,
"convenienceFeeAmount": 3.30,
"grossAmount": 103.30,
"currency": "USD"
}
Recommendation: Keep the fee logic encapsulated in a single service on your backend. This makes it easier to maintain compliance and adjust your fee policy over time.
Step 3 – Display the fee and update Framepay
On the client side, you will:
- Call your
/convenience-feeendpoint. - Display the base amount, fee, and total.
- Update Framepay with the final amount before tokenization.
Example: Fetch and display the fee (browser)
async function fetchConvenienceFee(invoiceId) {
const response = await fetch(`/convenience-fee?invoiceId=${encodeURIComponent(invoiceId)}`);
if (!response.ok) {
throw new Error('Unable to retrieve convenience fee');
}
return response.json();
}
async function updateTotals(invoiceId) {
const {
netSettlementTarget,
convenienceFeeAmount,
grossAmount,
currency
} = await fetchConvenienceFee(invoiceId);
document.getElementById('base-amount').textContent =
`${netSettlementTarget.toFixed(2)} ${currency}`;
document.getElementById('convenience-fee').textContent =
`${convenienceFeeAmount.toFixed(2)} ${currency}`;
document.getElementById('total-amount').textContent =
`${grossAmount.toFixed(2)} ${currency}`;
}
Your HTML might include:
<div>
<div>Base amount: <span id="base-amount"></span></div>
<div>Convenience fee: <span id="convenience-fee"></span></div>
<div><strong>Total: <span id="total-amount"></span></strong></div>
</div>
Example: Update Framepay with the final amount
After you have the final total, update Framepay configuration before you create a token. For full setup instructions, see the Framepay client integration.
// Assume Framepay is already loaded and initialized
let framepayInstance;
async function initFramepay() {
framepayInstance = await loadFramepay();
await framepayInstance.configure({
publishableKey: 'pk_sandbox_YOUR_KEY_HERE',
organizationId: 'org_YOUR_ORG_HERE',
websiteId: 'web_YOUR_WEBSITE_HERE',
amount: 0 // will be updated after fee calculation
});
}
async function onCheckout(invoiceId) {
const { grossAmount, currency } = await fetchConvenienceFee(invoiceId);
// Update the displayed totals
document.getElementById('total-amount').textContent =
`${grossAmount.toFixed(2)} ${currency}`;
// Update Framepay with the final amount
await framepayInstance.update({
amount: grossAmount,
currency,
// optionally pass invoiceId or other metadata used by your backend
metadata: { invoiceId }
});
}
When the customer confirms the payment, you create a token with Framepay as usual.
Step 4 – Create the transaction with fee metadata
After Framepay returns a payment token to the browser, send it to your server. Then your server will create a transaction in Payments AI.
In addition to the normal transaction fields, include:
- The invoice ID in
invoiceIds[]. - A
convenienceFeeentry inmetadata.
When the transaction is created successfully, Payments AI will automatically append a corresponding convenience fee item to the related invoice so that:
- The fee appears as a separate line item on the invoice.
- Your reporting and reconciliation can distinguish between the base amount and the convenience fee.
Example: Server-side transaction request
curl --location --request POST 'https://staging-api.payments.ai/v1/public-api/organizations/{{ORGANIZATION_ID}}/transactions' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: {{YOUR_API_KEY}}' \
--data '{
"amount": 103.30,
"currency": "USD",
"paymentInstrument": {
"method": "payment-card",
"token": "{{FRAMEPAY_TOKEN}}"
},
"invoiceIds": [
"inv_123456"
],
"metadata": {
"convenienceFee": 3.30,
"baseAmount": 100.00
}
}'
Payments AI associates the transaction with the provided invoice(s). Your internal systems can use the metadata to reconcile the convenience fee and report on it separately from the base amount.
The API response will include the transaction status and any additional details required by your integration.
Refunds and post-transaction behavior
The behavior of fees during refunds and disputes depends on your agreements with your payment partners. In many setups:
- You may choose whether to refund the convenience fee back to the customer.
Best practices:
- Define and document a refund policy for convenience fees (for example, “fees are non-refundable” or “fees are refunded only in specific cases”).
- Ensure receipts and invoices show the fee as a separate line item so it is clear to the customer.
- Keep evidence of disclosure (for example, screenshots of your checkout and ToS text) to help defend against disputes about unauthorized fees.
Checklist & next steps
Before going live, verify that:
- Support has enabled convenience fees for your organization.
- Your ToS and UI clearly disclose the convenience fee and when it applies.
- Your backend endpoint correctly calculates and returns the fee.
- Your frontend displays the base amount, fee, and total before the customer confirms.
- Your transaction requests include both
metadata.convenienceFeeandinvoiceIds.
Next, you can:
- Extend your fee logic to support different products, countries, or channels.
- Build monitoring and reporting around convenience fee revenue.
- Combine convenience fees with other risk and pricing rules as needed.