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 1210249f54 Normalize database column and table names across entire codebase
Update all SQL queries, query result references, and ColdFusion code to match
the renamed database schema. Tables use plural CamelCase, PKs are all `ID`,
column prefixes stripped (e.g. BusinessName→Name, UserFirstName→FirstName).

Key changes:
- Strip table-name prefixes from all column references (Businesses, Users,
  Addresses, Hours, Menus, Categories, Items, Stations, Orders,
  OrderLineItems, Tasks, TaskCategories, TaskRatings, QuickTaskTemplates,
  ScheduledTaskDefinitions, ChatMessages, Beacons, ServicePoints, Employees,
  VisitorTrackings, ApiPerfLogs, tt_States, tt_Days, tt_AddressTypes,
  tt_OrderTypes, tt_TaskTypes)
- Rename PK references from {TableName}ID to ID in all queries
- Rewrite 7 admin beacon files to use ServicePoints.BeaconID instead of
  dropped lt_Beacon_Businesses_ServicePoints link table
- Rewrite beacon assignment files (list, save, delete) for new schema
- Fix FK references incorrectly changed to ID (OrderLineItems.OrderID,
  Categories.MenuID, Tasks.CategoryID, ServicePoints.BeaconID)
- Update Addresses: AddressLat→Latitude, AddressLng→Longitude
- Update Users: UserPassword→Password, UserIsEmailVerified→IsEmailVerified,
  UserIsActive→IsActive, UserBalance→Balance, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:39:12 -08:00

182 lines
6.4 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 StripeAccountID, StripeOnboardingComplete, Name
FROM Businesses
WHERE ID = :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 DeliveryFee, OrderTypeID
FROM Orders
WHERE ID = :orderID
", { orderID: orderID }, { datasource: "payfrit" });
deliveryFee = 0;
if (qOrder.recordCount > 0 && qOrder.OrderTypeID == 3) {
deliveryFee = val(qOrder.DeliveryFee);
}
// For testing, allow orders even without Stripe Connect setup
hasStripeConnect = qBusiness.StripeOnboardingComplete == 1 && len(trim(qBusiness.StripeAccountID)) > 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.StripeAccountID);
}
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.Name#");
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.TaskID = 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>