This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/stripe/createPaymentIntent.cfm
John Mizerek 6b66d2cef8 Fix normalized DB column names across all API files
Sweep of 26 API files to use prefixed column names matching the
database schema (e.g. BusinessID not ID, BusinessName not Name,
BusinessDeliveryFlatFee not DeliveryFlatFee, ServicePointName not Name).

Files fixed: auth, beacons, businesses, menu, orders, setup, stripe,
tasks, and workers endpoints.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:56:41 -08:00

182 lines
6.5 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
/**
* Create Payment Intent for Order (v2 - with Payfrit fee structure)
*
* Fee Structure:
* - Customer pays: Subtotal + tax + tip + 5% Payfrit fee + card processing (2.9% + $0.30)
* - Restaurant receives: Subtotal - 5% Payfrit fee + tax + tip
* - Payfrit receives: 10% of subtotal (5% from customer + 5% from restaurant)
*
* POST: {
* BusinessID: int,
* OrderID: int,
* Subtotal: number (order subtotal in dollars, before fees),
* Tax: number (tax amount in dollars),
* Tip: number (optional, in dollars),
* CustomerEmail: string (optional)
* }
*/
response = { "OK": false };
try {
requestData = deserializeJSON(toString(getHttpRequestData().content));
businessID = val(requestData.BusinessID ?: 0);
orderID = val(requestData.OrderID ?: 0);
subtotal = val(requestData.Subtotal ?: 0);
tax = val(requestData.Tax ?: 0);
tip = val(requestData.Tip ?: 0);
customerEmail = requestData.CustomerEmail ?: "";
if (businessID == 0) {
response["ERROR"] = "BusinessID is required";
writeOutput(serializeJSON(response));
abort;
}
if (orderID == 0) {
response["ERROR"] = "OrderID is required";
writeOutput(serializeJSON(response));
abort;
}
if (subtotal <= 0) {
response["ERROR"] = "Invalid subtotal";
writeOutput(serializeJSON(response));
abort;
}
// Use test keys
stripeSecretKey = application.stripeSecretKey ?: "sk_test_LfbmDduJxTwbVZmvcByYmirw";
if (stripeSecretKey == "") {
response["ERROR"] = "Stripe is not configured";
writeOutput(serializeJSON(response));
abort;
}
// Get business Stripe account
qBusiness = queryExecute("
SELECT BusinessStripeAccountID, BusinessStripeOnboardingComplete, BusinessName
FROM Businesses
WHERE BusinessID = :businessID
", { businessID: businessID }, { datasource: "payfrit" });
if (qBusiness.recordCount == 0) {
response["ERROR"] = "Business not found";
writeOutput(serializeJSON(response));
abort;
}
// Get order's delivery fee (if delivery order)
qOrder = queryExecute("
SELECT OrderDeliveryFee, OrderTypeID
FROM Orders
WHERE OrderID = :orderID
", { orderID: orderID }, { datasource: "payfrit" });
deliveryFee = 0;
if (qOrder.recordCount > 0 && qOrder.OrderTypeID == 3) {
deliveryFee = val(qOrder.OrderDeliveryFee);
}
// For testing, allow orders even without Stripe Connect setup
hasStripeConnect = qBusiness.BusinessStripeOnboardingComplete == 1 && len(trim(qBusiness.BusinessStripeAccountID)) > 0;
// ============================================================
// FEE CALCULATION
// ============================================================
customerFeePercent = 0.05; // 5% customer pays to Payfrit
businessFeePercent = 0.05; // 5% business pays to Payfrit
cardFeePercent = 0.029; // 2.9% Stripe fee
cardFeeFixed = 0.30; // $0.30 Stripe fixed fee
payfritCustomerFee = subtotal * customerFeePercent;
payfritBusinessFee = subtotal * businessFeePercent;
totalBeforeCardFee = subtotal + tax + tip + deliveryFee + payfritCustomerFee;
cardFee = (totalBeforeCardFee * cardFeePercent) + cardFeeFixed;
totalCustomerPays = totalBeforeCardFee + cardFee;
// Convert to cents for Stripe
totalAmountCents = round(totalCustomerPays * 100);
totalPlatformFeeCents = round((payfritCustomerFee + payfritBusinessFee) * 100);
// Create PaymentIntent
httpService = new http();
httpService.setMethod("POST");
httpService.setUrl("https://api.stripe.com/v1/payment_intents");
httpService.setUsername(stripeSecretKey);
httpService.setPassword("");
httpService.addParam(type="formfield", name="amount", value=totalAmountCents);
httpService.addParam(type="formfield", name="currency", value="usd");
httpService.addParam(type="formfield", name="automatic_payment_methods[enabled]", value="true");
if (hasStripeConnect) {
httpService.addParam(type="formfield", name="application_fee_amount", value=totalPlatformFeeCents);
httpService.addParam(type="formfield", name="transfer_data[destination]", value=qBusiness.BusinessStripeAccountID);
}
httpService.addParam(type="formfield", name="metadata[order_id]", value=orderID);
httpService.addParam(type="formfield", name="metadata[business_id]", value=businessID);
httpService.addParam(type="formfield", name="description", value="Order ###orderID# at #qBusiness.BusinessName#");
if (customerEmail != "") {
httpService.addParam(type="formfield", name="receipt_email", value=customerEmail);
}
result = httpService.send().getPrefix();
piData = deserializeJSON(result.fileContent);
if (structKeyExists(piData, "error")) {
response["ERROR"] = piData.error.message;
writeOutput(serializeJSON(response));
abort;
}
// Fees are calculated dynamically, not stored in DB
// Link PaymentIntent to worker payout ledger (if a ledger row exists for this order's task)
try {
queryExecute("
UPDATE WorkPayoutLedgers wpl
INNER JOIN Tasks t ON t.ID = wpl.TaskID AND t.OrderID = :orderID
SET wpl.StripePaymentIntentID = :piID
WHERE wpl.Status = 'pending_charge'
AND wpl.StripePaymentIntentID IS NULL
", {
orderID: orderID,
piID: piData.id
}, { datasource: "payfrit" });
} catch (any e) {
// Non-fatal: ledger row may not exist yet (task not completed before payment)
writeLog(file="stripe_webhooks", text="Ledger link skipped for order #orderID#: #e.message#");
}
response["OK"] = true;
response["CLIENT_SECRET"] = piData.client_secret;
response["PAYMENT_INTENT_ID"] = piData.id;
response["PUBLISHABLE_KEY"] = application.stripePublishableKey ?: "pk_test_sPBNzSyJ9HcEPJGC7dSo8NqN";
response["FEE_BREAKDOWN"] = {
"SUBTOTAL": subtotal,
"TAX": tax,
"TIP": tip,
"DELIVERY_FEE": deliveryFee,
"PAYFRIT_FEE": payfritCustomerFee,
"CARD_FEE": cardFee,
"TOTAL": totalCustomerPays
};
response["STRIPE_CONNECT_ENABLED"] = hasStripeConnect;
} catch (any e) {
response["ERROR"] = e.message;
response["DETAIL"] = e.detail ?: "";
}
writeOutput(serializeJSON(response));
</cfscript>