Fix cash payment fee: use real Payfrit platform fee, not 2.25% cash handling fee

submitCash.cfm: Calculate platform fee from subtotal * PayfritFee,
store in Orders.PlatformFee and Payments.PaymentPayfritsCut on submission.

complete.cfm: Replace bogus 2.25% cash transaction fee with the real
platform fee (customer fee + business fee = 2 × PayfritFee × subtotal).
Credit full Payfrit revenue to User 0. Record business fee in
PaymentPayfritNetworkFees.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-03-02 12:59:22 -08:00
parent cb7e3b7fc6
commit 96c2ed3fc1
2 changed files with 51 additions and 23 deletions

View file

@ -40,10 +40,13 @@
</cfif> </cfif>
<cftry> <cftry>
<!--- Get order details ---> <!--- Get order details with business fee rate --->
<cfset qOrder = queryExecute( <cfset qOrder = queryExecute(
"SELECT ID, StatusID, UserID, BusinessID, ServicePointID, PaymentID "SELECT o.ID, o.StatusID, o.UserID, o.BusinessID, o.ServicePointID, o.PaymentID,
FROM Orders WHERE ID = ? LIMIT 1", b.PayfritFee
FROM Orders o
INNER JOIN Businesses b ON b.ID = o.BusinessID
WHERE o.ID = ? LIMIT 1",
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ], [ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" } { datasource = "payfrit" }
)> )>
@ -56,6 +59,21 @@
<cfset apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Order is not in cart state." })> <cfset apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Order is not in cart state." })>
</cfif> </cfif>
<!--- Calculate platform fee (customer portion — shown in cart) --->
<cfset feeRate = (isNumeric(qOrder.PayfritFee) AND val(qOrder.PayfritFee) GT 0) ? val(qOrder.PayfritFee) : 0>
<cfset customerFee = CashAmount * feeRate / (1 + feeRate)>
<!--- CashAmount already includes the customer fee (subtotal+tax+fee), back it out:
Actually CashAmount is what the Android app sends as the order total including fee.
The fee was calculated on subtotal, so we need the actual subtotal from line items. --->
<cfset qSubtotal = queryExecute(
"SELECT COALESCE(SUM(Price * Quantity), 0) AS Subtotal
FROM OrderLineItems WHERE OrderID = ? AND IsDeleted = b'0'",
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfset subtotal = val(qSubtotal.Subtotal)>
<cfset platformFee = subtotal * feeRate>
<!--- Create Payment record with expected cash amount ---> <!--- Create Payment record with expected cash amount --->
<cfset CashAmountCents = round(CashAmount * 100)> <cfset CashAmountCents = round(CashAmount * 100)>
<cfset qInsertPayment = queryExecute( <cfset qInsertPayment = queryExecute(
@ -65,30 +83,34 @@
PaymentSentByUserID, PaymentSentByUserID,
PaymentReceivedByUserID, PaymentReceivedByUserID,
PaymentOrderID, PaymentOrderID,
PaymentPayfritsCut,
PaymentAddedOn PaymentAddedOn
) VALUES (?, 0, ?, 0, ?, NOW())", ) VALUES (?, 0, ?, 0, ?, ?, NOW())",
[ [
{ value = CashAmount, cfsqltype = "cf_sql_decimal" }, { value = CashAmount, cfsqltype = "cf_sql_decimal" },
{ value = qOrder.UserID, cfsqltype = "cf_sql_integer" }, { value = qOrder.UserID, cfsqltype = "cf_sql_integer" },
{ value = OrderID, cfsqltype = "cf_sql_integer" } { value = OrderID, cfsqltype = "cf_sql_integer" },
{ value = platformFee, cfsqltype = "cf_sql_decimal" }
], ],
{ datasource = "payfrit", result = "insertResult" } { datasource = "payfrit", result = "insertResult" }
)> )>
<cfset PaymentID = insertResult.generatedKey> <cfset PaymentID = insertResult.generatedKey>
<!--- Update order: link payment, set status to submitted, payment pending ---> <!--- Update order: link payment, set status to submitted, store platform fee --->
<cfset queryExecute( <cfset queryExecute(
"UPDATE Orders "UPDATE Orders
SET StatusID = 1, SET StatusID = 1,
PaymentID = ?, PaymentID = ?,
PaymentStatus = 'pending', PaymentStatus = 'pending',
PlatformFee = ?,
TipAmount = ?, TipAmount = ?,
SubmittedOn = NOW(), SubmittedOn = NOW(),
LastEditedOn = NOW() LastEditedOn = NOW()
WHERE ID = ?", WHERE ID = ?",
[ [
{ value = PaymentID, cfsqltype = "cf_sql_integer" }, { value = PaymentID, cfsqltype = "cf_sql_integer" },
{ value = platformFee, cfsqltype = "cf_sql_decimal" },
{ value = Tip, cfsqltype = "cf_sql_decimal" }, { value = Tip, cfsqltype = "cf_sql_decimal" },
{ value = OrderID, cfsqltype = "cf_sql_integer" } { value = OrderID, cfsqltype = "cf_sql_integer" }
], ],

View file

@ -161,28 +161,31 @@
<cfset cashTip = val(qOrderTotal.TipAmount)> <cfset cashTip = val(qOrderTotal.TipAmount)>
<cfset cashDeliveryFee = (val(qOrderTotal.OrderTypeID) EQ 3) ? val(qOrderTotal.DeliveryFee) : 0> <cfset cashDeliveryFee = (val(qOrderTotal.OrderTypeID) EQ 3) ? val(qOrderTotal.DeliveryFee) : 0>
<cfset cashPayfritFee = (isNumeric(qOrderTotal.PayfritFee) AND val(qOrderTotal.PayfritFee) GT 0) ? val(qOrderTotal.PayfritFee) : 0.05> <cfset cashPayfritFee = (isNumeric(qOrderTotal.PayfritFee) AND val(qOrderTotal.PayfritFee) GT 0) ? val(qOrderTotal.PayfritFee) : 0.05>
<cfset cashPlatformFee = cashSubtotal * cashPayfritFee>
<cfset orderTotalCents = round((cashSubtotal + cashTax + cashTip + cashDeliveryFee + cashPlatformFee) * 100)> <!--- Customer fee: added to their total (visible in cart) --->
<cfset customerFeeDollars = cashSubtotal * cashPayfritFee>
<!--- Business fee: deducted when cash is distributed --->
<cfset businessFeeDollars = cashSubtotal * cashPayfritFee>
<!--- Total Payfrit revenue = customer fee + business fee --->
<cfset payfritRevenueDollars = customerFeeDollars + businessFeeDollars>
<cfset orderTotalCents = round((cashSubtotal + cashTax + cashTip + cashDeliveryFee + customerFeeDollars) * 100)>
<cfif CashReceivedCents LT orderTotalCents> <cfif CashReceivedCents LT orderTotalCents>
<cfset apiAbort({ "OK": false, "ERROR": "insufficient_cash", "MESSAGE": "Cash received ($#numberFormat(CashReceivedCents/100, '0.00')#) is less than order total ($#numberFormat(orderTotalCents/100, '0.00')#)." })> <cfset apiAbort({ "OK": false, "ERROR": "insufficient_cash", "MESSAGE": "Cash received ($#numberFormat(CashReceivedCents/100, '0.00')#) is less than order total ($#numberFormat(orderTotalCents/100, '0.00')#)." })>
</cfif> </cfif>
<!--- Calculate cash transaction fee (on order total) ---> <cfset payfritRevenueCents = round(payfritRevenueDollars * 100)>
<cfif orderTotalCents LT 1000>
<cfset feeCents = round(orderTotalCents * 0.0225)>
<cfelse>
<cfset feeCents = 22 + round(orderTotalCents * 0.0005)>
</cfif>
<cfset changeCents = CashReceivedCents - orderTotalCents> <cfset changeCents = CashReceivedCents - orderTotalCents>
<cfset businessReceivesCents = orderTotalCents - feeCents> <cfset businessReceivesCents = orderTotalCents - payfritRevenueCents>
<cfset cashResult = { <cfset cashResult = {
"orderTotalCents": orderTotalCents, "orderTotalCents": orderTotalCents,
"cashReceivedCents": CashReceivedCents, "cashReceivedCents": CashReceivedCents,
"changeCents": changeCents, "changeCents": changeCents,
"feeCents": feeCents, "customerFeeCents": round(customerFeeDollars * 100),
"businessFeeCents": round(businessFeeDollars * 100),
"payfritRevenueCents": payfritRevenueCents,
"businessReceivesCents": businessReceivesCents "businessReceivesCents": businessReceivesCents
}> }>
</cfif> </cfif>
@ -296,12 +299,12 @@
}, { datasource = "payfrit" })> }, { datasource = "payfrit" })>
</cfif> </cfif>
<!--- Credit Payfrit fee to User 0 (Payfrit Network) ---> <!--- Credit Payfrit revenue (customer fee + business fee) to User 0 (Payfrit Network) --->
<cfif feeCents GT 0> <cfif payfritRevenueCents GT 0>
<cfset queryTimed(" <cfset queryTimed("
UPDATE Users SET Balance = Balance + :fee WHERE ID = 0 UPDATE Users SET Balance = Balance + :fee WHERE ID = 0
", { ", {
fee: feeCents / 100 fee: payfritRevenueCents / 100
}, { datasource = "payfrit" })> }, { datasource = "payfrit" })>
</cfif> </cfif>
@ -333,7 +336,7 @@
) VALUES ( ) VALUES (
:sentBy, :receivedBy, :orderID, :sentBy, :receivedBy, :orderID,
0, 0, :cashAmount, 0, 0, :cashAmount,
:payfritCut, 0, 0, :payfritCut, 0, :bizFee,
'Cash payment', NOW() 'Cash payment', NOW()
) )
", { ", {
@ -341,7 +344,8 @@
receivedBy: businessOwnerUserID, receivedBy: businessOwnerUserID,
orderID: qTask.OrderID, orderID: qTask.OrderID,
cashAmount: CashReceivedCents / 100, cashAmount: CashReceivedCents / 100,
payfritCut: feeCents / 100 payfritCut: payfritRevenueCents / 100,
bizFee: round(businessFeeDollars * 100) / 100
}, { datasource = "payfrit" })> }, { datasource = "payfrit" })>
<cfset cashProcessed = true> <cfset cashProcessed = true>
@ -410,7 +414,9 @@
<cfset response["CashReceived"] = numberFormat(CashReceivedCents / 100, "0.00")> <cfset response["CashReceived"] = numberFormat(CashReceivedCents / 100, "0.00")>
<cfset response["OrderTotal"] = numberFormat(orderTotalCents / 100, "0.00")> <cfset response["OrderTotal"] = numberFormat(orderTotalCents / 100, "0.00")>
<cfset response["Change"] = numberFormat(changeCents / 100, "0.00")> <cfset response["Change"] = numberFormat(changeCents / 100, "0.00")>
<cfset response["Fee"] = numberFormat(feeCents / 100, "0.00")> <cfset response["CustomerFee"] = numberFormat(round(customerFeeDollars * 100) / 100, "0.00")>
<cfset response["BusinessFee"] = numberFormat(round(businessFeeDollars * 100) / 100, "0.00")>
<cfset response["PayfritRevenue"] = numberFormat(payfritRevenueCents / 100, "0.00")>
<cfset response["BusinessReceives"] = numberFormat(businessReceivesCents / 100, "0.00")> <cfset response["BusinessReceives"] = numberFormat(businessReceivesCents / 100, "0.00")>
</cfif> </cfif>