Inject Scripts to Intercept Buy Now and Checkout Actions
This page explains the concepts and patterns used to intercept and handle the AddToCart mutation flow in a generic, reusable way. This covers the optional approach for intercepting Buy Now button from the PDP. For the mandatory Checkout button on the Cart page, use Storefront Bindings instead.
After implementing script injection and the buy-now endpoint, proceed to Payment Handler to handle payment responses from the payment gateway.
Script injection is the process of automatically injecting client-side JavaScript code into storefront pages. This enables extensions to add custom functionality without modifying the core storefront code. This is achieved through injectable Tags that allow extensions to inject scripts at specific positions in HTML documents. The injected scripts intercept mutations, access state, and enhance functionality. Once injected, the script executes on every page load. Use this approach to intercept storefront GraphQL mutations directly when a customer clicks Buy Now on the PDP.
Your injected script should:
- Register interceptors using
fpi.mutations.apply(...). Refer to FPI Mutation page for more details. - Detect Buy Now / Checkout intent
- Route into your One-click checkout experience
Inject your interception script into the storefront using Create HTML tag API. Injectable tags are content blocks managed by the platform's content system that can be injected into storefront pages at specific body-bottom positions Before closing </body> tag (recommended for checkout scripts).
When creating the injectable tag, set the defer attribute to true to ensure the script executes after the HTML document has been parsed. The script should be base64 encoded before injection to handle special characters safely.
Script Encoding
Scripts are typically base64 encoded before injection to handle special characters safely, ensure proper transmission through APIs, and prevent encoding issues in content systems.
Example: Buy Now Interceptor (Framework)
async function buyNow(request) {
const shouldUseCustomCheckout = request.buyNow === true;
// CASE 1: Intercept - Return error structure + magic-checkout flag
// Theme side handles "magic-checkout": true flag for error handling
if (shouldUseCustomCheckout) {
return {
response: {
data: { AddItemsToCart: { "magic-checkout": true } },
},
};
}
// CASE 2: Forward to GraphQL server
return { requestParam: { ...request } };
}
Fynd exposes GraphQL mutations that can be intercepted. Mutations are GraphQL operations that modify platform data. The AddItemsToCart mutation is typically used when users add products to their cart or click Buy Now. When building platform extensions that need to intercept the AddToCart flow (e.g., Buy Now button), you need to create a route handler. By intercepting this mutation, you can:
- Receives the intercepted request from the frontend
- Calls the platform's GraphQL API to create/update the cart
- Transforms cart data into your payment gateway's format
- Creates an order with the payment gateway
- Saves order details to your database
- Returns configuration for the frontend to proceed with payment
Request Flow
The extension route acts as a middleware layer that receives the original mutation request, calls the platform's GraphQL API with modified parameters, processes the response, creates external resources (payment orders), and returns a custom response to the frontend.
Frontend (FPI) → Extension Route → Platform GraphQL API → Payment Gateway → Database → Response
Header Forwarding
When calling platform APIs from your extension backend, you must forward authentication headers from the original request. This ensures that user context is preserved, that authorization works correctly, and that cookies and session data are maintained.
Steps
The following steps walk through implementing the buy-now endpoint that intercepts requests, creates payment gateway orders, and returns modal configuration. This endpoint is called by your injected script when a user clicks Buy Now. The implementation details for these steps are also covered in the Controllers page, which provides reusable patterns and helper functions.
Receive Intercepted Request
When the frontend intercepts a mutation, it sends the request to your extension endpoint. The route handler receives all necessary data to recreate the platform API call. Headers are critical for authentication and context. The request includes:
- Mutation parameters: Original GraphQL variables (buyNow, areaCode, addCartRequestInput)
- Headers: Authorization, cookies, origin (for building API URLs)
- User data: Extracted from FPI state (name, email, contact)
- Application data: Theme, logo, configuration
The buyNow flag from the request should be saved in the order meta for later use during checkout. When calling platform's AddItemsToCart, always set buyNow: false to ensure proper cart creation, but save the original buyNow value for the checkout step.
// Example: Route handler receives request
router.post("/buy-now-add-to-cart", async (req, res) => {
const { buyNow, areaCode, addCartRequestInput, variables } = req.body;
const authorization =
req.headers["authorization"] || req.headers["Authorization"];
const cookie = req.headers["cookie"] || "";
const origin = req.headers["origin"];
// Process request...
});
Build Storefront GraphQL URL
Storefront APIs are typically accessed via GraphQL endpoints. The URL is usually constructed from the request origin to ensure it points to the correct storefront instance. Using the request origin ensures your extension works across different storefront domains without hardcoding URLs. This is essential for multi-tenant platforms.
// Example: Building dynamic GraphQL URL
const baseUrl = origin || req.protocol + "://" + req.get("host");
const graphqlUrl = `${baseUrl}/api/service/application/graphql/`;
Call Platform GraphQL API
Call the platform's AddItemsToCart mutation with the original parameters, but modify buyNow to false to ensure a proper cart is created (even if the frontend sent buyNow: true). Always set buyNow: false when calling the platform API to ensure cart data is fully populated. Request comprehensive cart fields (items, prices, breakup values) needed for payment gateway integration. Forward all authentication headers to maintain user context.
// Refer addItemsToCart GraphQL API for the full structure, mutation, and variable requirements:
// Link: https://docs.fynd.com/partners/commerce/sdk/latest/graphql/application/cart/mutations/additemstocart#addItemsToCart
// Example usage:
const graphqlQuery = {
query: `mutation AddItemsToCart($buyNow: Boolean, $areaCode: String!, $addCartRequestInput: AddCartRequestInput) {
addItemsToCart(
buyNow: $buyNow
areaCode: $areaCode
addCartRequestInput: $addCartRequestInput
) {
success
cart {
id
items { quantity }
breakup_values { raw { total } }
}
}
}`,
variables: {
buyNow: false,
areaCode,
addCartRequestInput,
},
};
// Call platform API
const response = await axios.post(graphqlUrl, graphqlQuery, {
headers: {
authorization: authorization,
cookie: cookie,
origin: origin,
"content-type": "application/json",
},
});
Extract and Validate Cart Data
After receiving the cart response from the platform GraphQL API (called in the previous step), extract and validate the data needed for payment gateway integration. Cart data structure varies by platform. Extract values safely using optional chaining and provide defaults. The cart ID is crucial for later checkout operations and must be saved in the order meta (covered in Save Order to Database).
// Example: Extract cart data
const cart = response.data.data?.addItemsToCart?.cart;
if (!cart) {
return res.status(400).json({
success: false,
message: "No cart data received",
});
}
// Extract key values
const cartId = cart.id; // MongoDB cart ID
const items = cart.items || [];
const breakup = cart.breakup_values?.raw || {};
const subtotal = breakup.subtotal || 0;
const deliveryCharge = breakup.delivery_charge || 0;
const totalAmount = breakup.total || 0;
Get Application Context
Extract application and company identifiers from request headers or platform state. Multi-tenant platforms include application context in headers or frontend state. Always validate these values before proceeding with operations that require tenant isolation. These identifiers are required for the next step (Fetch Payment Gateway Credentials) and must be saved with the order for later use during payment verification.
The application context can come from:
- Request headers (
x-application-data,x-user-data) - Frontend state (FPI store) when called from frontend
- Database order records when processing verification
// Example: Extract application context from headers
const appDataHeader = req.headers["x-application-data"];
const userDataHeader = req.headers["x-user-data"];
let applicationId, companyId;
if (appDataHeader) {
const appData = JSON.parse(appDataHeader);
applicationId = appData.id;
companyId = appData.company_id;
} else if (userDataHeader) {
const userData = JSON.parse(userDataHeader);
applicationId = userData.application_id;
companyId = userData.company_id;
}
if (!applicationId || !companyId) {
return res.status(400).json({
success: false,
message: "Missing application context",
});
}
Fetch Payment Gateway Credentials
Retrieve encrypted credentials from your database. Credentials are typically stored encrypted and need to be decrypted before use.
// Example: Fetch and decrypt credentials
async function getPaymentGatewayCredentials(appId) {
const secret = await Secret.findOne({ app_id: appId });
if (!secret) {
throw new Error(`No credentials found for app_id: ${appId}`);
}
const credentials = EncryptHelper.decrypt(
config.extension.encrypt_secret,
secret.secrets,
);
return {
key_id: credentials.key_id,
key_secret: credentials.key_secret,
};
}
const credentials = await getPaymentGatewayCredentials(applicationId);
Transform Cart Data to Payment Gateway Format
Payment gateways require specific data structures. Transform platform cart data (extracted in Extract and Validate Cart Data) into the gateway's expected format. Payment gateways often require amounts in smallest currency units (paise for INR). Extract unit prices carefully (use dedicated fields when available). Include metadata (product IDs, sizes) in notes for later reference. Calculate subtotals from line items for validation. The transformed data is used in the next step to Create Payment Gateway Order.
// Example: Transform cart items to payment gateway line items
const lineItems = cart.items.map((item) => {
const quantity = item.quantity || 1;
// Extract unit price (prefer price_per_unit, fallback to calculated)
let unitPrice = 0;
if (item.price_per_unit?.base?.effective !== undefined) {
unitPrice = item.price_per_unit.base.effective;
} else if (item.price?.base?.effective && quantity > 0) {
unitPrice = item.price.base.effective / quantity;
}
return {
sku: item.article?.seller_identifier || item.product?.item_code || "",
variant_id: item.article?.uid || "",
name: item.product?.name || "",
price: Math.round(unitPrice * 100), // Convert to smallest currency unit (paise)
quantity: quantity,
description: item.product?.name || "",
image_url: item.product?.images?.[0]?.secure_url || "",
// Additional metadata
notes: {
size: item.article?.size || "OS",
product_uid: item.product?.uid?.toString() || "",
},
};
});
// Calculate totals
const lineItemsSubtotal = lineItems.reduce(
(sum, item) => sum + item.price * item.quantity,
0,
);
Handle Coupons and Discounts
Coupons may be applied in the cart or passed from the frontend. Extract coupon information for payment gateway integration. Coupons can come from cart state or frontend request. Payment gateways may need order amount before coupon (they apply coupon separately). Store coupon code for later application in payment gateway. Calculate final amount considering coupon discount.
// Example: Extract coupon data
const cartCouponData = cart.breakup_values?.coupon || {};
const frontendCouponData = req.body.coupon_data || null;
// Prefer frontend coupon data (for Buy Now flows)
const couponData =
frontendCouponData ||
(cartCouponData.is_applied && cartCouponData.code ? cartCouponData : null);
const couponCode = couponData?.code || null;
const couponDiscount = Math.abs(breakup.coupon || 0); // Coupon is negative in breakup
// Calculate amount before coupon (for order creation)
const amountBeforeCoupon = subtotal + deliveryCharge + codCharge;
Create Payment Gateway Order
Create an order with the payment gateway using transformed cart data (from Transform Cart Data to Payment Gateway Format) and credentials (from Fetch Payment Gateway Credentials). Generate unique order IDs (receipt) for tracking. Include platform cart ID in notes for later reference. Set order amount before coupon (gateway applies coupon separately). Store all relevant metadata in notes field. The gateway order ID returned here is used in Save Order to Database and included in the modal configuration.
// Example: Create payment gateway order
const paymentGateway = new PaymentGateway({
key_id: credentials.key_id,
key_secret: credentials.key_secret,
});
const orderOptions = {
amount: Math.round(amountBeforeCoupon * 100), // Amount before coupon
currency: cart.breakup_values?.display?.[0]?.currency_code || "INR",
receipt: `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
notes: {
app_id: applicationId,
company_id: companyId,
cart_id: cartId,
fynd_cart_id: cart.id,
},
line_items: lineItems,
line_items_total: lineItemsSubtotal,
};
const gatewayOrder = await paymentGateway.orders.create(orderOptions);
Save Order to Database
Persist order details in your database for later reference during payment verification and checkout. This order record is critical - it's used during payment verification to look up the order and retrieve the cart ID needed for checkoutCart. Store both your internal order ID and payment gateway order ID (from Create Payment Gateway Order). Save full cart data in meta for later checkout operations. Include serviceability data (product IDs, sizes) for shipping calculations. Link order to application and company (from Get Application Context) for multi-tenant support. Important: Store app_id and company_id at both top level AND in meta for reliable lookups.
// Example: Save order to database
const orderData = {
gid: internalOrderId, // Your internal order ID
app_id: applicationId, // Required at top level
company_id: companyId, // Required at top level (for applyCoupon, getCoupons, getShippingInfo lookups)
amount: amountInPaise,
currency: gatewayOrder.currency,
aggregator_order_id: gatewayOrder.id, // Payment gateway order ID
receipt: internalOrderId,
payment_methods: [
{
mode: "payment_gateway",
name: "Payment Gateway Name",
payment: "required",
},
],
meta: {
app_id: applicationId, // Also save in meta as backup
company_id: companyId, // Also save in meta as backup (for reliable lookup)
cart_id: cartId,
fynd_cart_id: cart.id,
line_items: lineItems,
fynd_cart: cart, // Store full cart for reference
gateway_order: gatewayOrder,
buyNow: req.body.buyNow || false, // Save buyNow flag for checkout
cart_items_for_serviceability: cartItemsForServiceability, // For serviceability API
},
};
await Order.create(orderData);
Create Transaction Record
Create a transaction record to track payment status throughout the lifecycle. Track payment status transitions in status array. Store gateway order details for verification. Use journey_type to distinguish forward payments from refunds. Update aggregator_payment_id after successful payment.
// Example: Create transaction record
const transactionData = {
gid: magicOrderId,
aggregator_order_id: gatewayOrder.id,
aggregator_payment_id: null, // Will be set after payment
amount: amountInPaise,
current_status: "initiated",
journey_type: "forward",
status: [
{
status: "initiated",
source: "buy_now_flow",
meta: {
gateway_order: gatewayOrder,
order_creation_time: new Date(),
},
},
],
};
await Transaction.create(transactionData);
Build Frontend Configuration
Create a configuration object that the frontend uses to initialize the payment gateway modal. Include only public data in modal config (never secrets). Provide prefill data for better UX. Include coupon code in prefill if applicable. Return both internal and gateway order IDs for tracking. Payment gateway SDKs may require specific field names (e.g., order_id, amount).
After returning this modal configuration, your frontend script opens the payment gateway modal. When the user completes payment, the payment gateway SDK calls your Payment Handler function with the payment response. If your payment gateway supports features like address validation or coupon management, it may also call your Payment Gateway Integration Endpoints during the modal interaction.
// Example: Build popup configuration
const popupConfig = {
key: credentials.key_id, // Public key for frontend (never secret)
amount: gatewayOrder.amount, // Amount in smallest currency unit (paise for INR)
currency: gatewayOrder.currency,
name: applicationName,
description: `Order ${internalOrderId}`,
order_id: gatewayOrder.id, // Payment gateway order ID (gateway-specific field name)
image: logoUrl,
// Payment gateway specific fields (adjust based on your gateway)
line_items: lineItems,
line_items_total: lineItemsSubtotal,
prefill: {
name: userName,
email: userEmail,
contact: userContact,
...(couponCode ? { coupon_code: couponCode } : {}),
},
notes: {
order_id: internalOrderId, // Your internal order ID for verification
cart_id: cartId,
},
theme: {
color: themeColor,
},
// Some payment gateways support cart object for coupon handling
cart: {
total: Math.round(amountBeforeCoupon * 100), // Amount before coupon
...(couponDiscount > 0
? {
discount: Math.round(couponDiscount * 100), // Coupon discount in smallest currency unit
}
: {}),
items: lineItems,
},
};
return res.json({
success: true,
popup_config: popupConfig,
checkout_id: internalOrderId,
order_id: internalOrderId,
gateway_order_id: gatewayOrder.id,
});
Transaction Management
Save order and transaction atomically to maintain data consistency. Consider database transactions for atomicity:
// If using transactions
await db.transaction(async (trx) => {
await Order.create(orderData, { transaction: trx });
await Transaction.create(transactionData, { transaction: trx });
});
Next Steps
After implementing script injection and the buy-now endpoint:
- Test the flow: Verify that your script intercepts Buy Now actions and creates payment gateway orders
- Implement Payment Handler: Proceed to Payment Handler to handle payment responses from the payment gateway
- Implement Verify Endpoint: The payment handler calls your Verify Endpoint to verify payments and complete checkout
- Implement Redirection: After verification, implement Redirection to send users to the success page
For detailed controller implementation patterns, see the Controllers page.