- New endpoint: api/tasks/getDetails.cfm - Returns task info, customer info, service point, order line items - Joins Tasks, Orders, Users, ServicePoints, OrderLineItems tables - Add getDetails.cfm to public endpoints allowlist in Application.cfm 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
152 lines
5.4 KiB
Text
152 lines
5.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 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;
|
|
}
|
|
|
|
// 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 + 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
|
|
|
|
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,
|
|
"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>
|