diff --git a/HOWTO_01_Payment_csharp/Program.cs b/HOWTO_01_Payment_csharp/Program.cs index b0fd0f6..89e610f 100644 --- a/HOWTO_01_Payment_csharp/Program.cs +++ b/HOWTO_01_Payment_csharp/Program.cs @@ -63,11 +63,12 @@ static async Task Main(string[] args) // - the terminal ID is not defined here (null) so the request will be processed by all available terminals for the cashbox // IMPORTANT: In a real setup you might want to define a specific terminal ID here to target a specific payment terminal device; especially when multiple payment terminals are registered for the same cashbox! // - we provide the operation ID to be able to retry in case of failure - ExecutedResult payResult = await ftPosAPI.Pay.PaymentAsync(new PayItemRequest + var payItemRequest = new PayItemRequest { Description = "Card", Amount = amount, - }, fiskaltrust.Payment.DTO.PaymentProtocol.use_auto, null, operationId); + }; + ExecutedResult payResult = await ftPosAPI.Pay.PaymentAsync(payItemRequest, fiskaltrust.Payment.DTO.PaymentProtocol.use_auto, null, operationId); ///////////////////////////////////////////////////////////////////////////////////////////////// // Check Result @@ -80,7 +81,7 @@ static async Task Main(string[] args) { // YES --> SUCCESS: Payment was successful PayResponse payResp = await payResult.Operation.GetResponseAsAsync(); - Utils.DumpToLogger(payResp); + Utils.DumpToLogger(payResp, payItemRequest); break; } else diff --git a/HOWTO_01_Payment_csharp/README.MD b/HOWTO_01_Payment_csharp/README.MD index ad207b4..0c7064c 100644 --- a/HOWTO_01_Payment_csharp/README.MD +++ b/HOWTO_01_Payment_csharp/README.MD @@ -25,3 +25,22 @@ Several critical error scenarios must be managed carefully to prevent double pay - The response is not received within the expected time frame (HTTP timeout), which can be simulated by a device losing internet connectivity during payment execution. In all of these cases, the solution is to resend the original request using the identical operation ID and request body. The backend will then return the final result of the operation. + +## Special case - payment vendor added TIP + +In many payment vendor apps it is possible to add a tip to the payment amount. +Flow example in a restaurant: +- Guest asks for the bill +- Waiter triggers to create the bill at the POS (e.g. mobile device) +- The POS on the mobile device pushes the amount (for example 10€) to be paid to the configured payment app (via POS System API and the fiskaltrust InStore App) +- The payment app opens and the waiter hands the mobile device over to the guest +- The guest sees a TIP entry screen and adds a tip of 2€ +- The guests now pays the full sum of 12€ +- The payment app reports a paid amount of 12€ (including 2€ of tip) +- The fiskaltrust InStore App / POS System API does report back the following in the payment response (see also example in the POS System API docs): + - 2 pay items + - Pay item 1: The fully paid amount with the receipt -> 12€ + - Pay item 2: The tip with negative amount -> -2€ + - sum of the 2 pay items is the original requested amount of 10€ + +NOTE: The tip can be calculated by simply subtracting the requested amount from the paid amount (12-10 = 2€ tip) or alternatively from the 2nd pay item (-2 * -1 = 2€ tip). diff --git a/HOWTO_08_pay_sign_issue_csharp/Program.cs b/HOWTO_08_pay_sign_issue_csharp/Program.cs index 074c24c..b84a436 100644 --- a/HOWTO_08_pay_sign_issue_csharp/Program.cs +++ b/HOWTO_08_pay_sign_issue_csharp/Program.cs @@ -54,14 +54,15 @@ static async Task Main(string[] args) decimal totalAmount = chargeItems.Sum(ci => ci.Amount); Logger.LogInfo($"Total amount to pay: {totalAmount} EUR"); + PayItemRequest payRequest = new() + { + Amount = totalAmount, + Description = "Card" + }; var payRunner = new ftPosAPIOperationRunner(); (PayResponse? pResp, string errorMsg) = await payRunner.Execute(async () => { - PayItemRequest payRequest = new() - { - Amount = totalAmount, - Description = "Card" - }; + return await ftPosAPI.Pay.PaymentAsync(payRequest, PaymentProtocol.use_auto, null, payRunner.OperationID); }); @@ -72,7 +73,7 @@ static async Task Main(string[] args) else { Logger.LogInfo("Payment succeeded."); - Utils.DumpToLogger(pResp); + Utils.DumpToLogger(pResp, payRequest); /////////////////////////////////////////////////////////////////////////////////////////////////////// /// diff --git a/README.MD b/README.MD index a4bbee9..fb5f2ca 100644 --- a/README.MD +++ b/README.MD @@ -80,7 +80,7 @@ Any payment amount will return a SUCCESS response, except for the following defi | 30000,10 | DECLINED | | 30000,20 | TIMEOUT (returned as an error message as no other option is available yet) | | 30000,40 | CANCELLED BY USER | -| 30000,50 | SUCCESS with added guest tip | +| 30000,50 | SUCCESS with added guest tip (see [HOWTO_01_Payment](HOWTO_01_Payment_csharp/README.MD) on how to handle such a tip) | | 30000,60 | SUCCESS after 1-minute delay | | 30000,70 | SUCCESS after 3-minute delay | | 30000,80 | SUCCESS after 6-minute delay | diff --git a/libPosSystemAPI.Test/IntegrationTestsPayment.cs b/libPosSystemAPI.Test/IntegrationTestsPayment.cs index e7ded9f..99c9c2e 100644 --- a/libPosSystemAPI.Test/IntegrationTestsPayment.cs +++ b/libPosSystemAPI.Test/IntegrationTestsPayment.cs @@ -39,7 +39,7 @@ public async Task TestPayment() Assert.NotNull(payResponse); Assert.True(payResponse.Protocol == Payment.DTO.PaymentProtocol.use_auto); Assert.NotNull(payResponse.ftPayItems); - // dummy payment provider should return exactly one pay item (as we did not execute a 30000,50 which would result in multiple pay items as there would be a tip) + // dummy payment provider should return exactly one pay item Assert.Single(payResponse.ftPayItems); PayItem pi = payResponse!.ftPayItems![0]; diff --git a/libPosSystemAPI/PosAPIUtils/Utils.cs b/libPosSystemAPI/PosAPIUtils/Utils.cs index d1ebaad..86509a8 100644 --- a/libPosSystemAPI/PosAPIUtils/Utils.cs +++ b/libPosSystemAPI/PosAPIUtils/Utils.cs @@ -182,7 +182,7 @@ public static (Guid ftCashboxID, string ftCashboxAccessToken)? GetCashboxCredent return (ftCashboxID, ftCashboxAccessToken); } - public static void DumpToLogger(PayResponse payResp) + public static void DumpToLogger(PayResponse payResp, PayItemRequest? payItemRequest = null) { Logger.LogInfo("Payment successful! Queue ID: " + payResp.ftQueueID); if (payResp.ftPayItems == null || payResp.ftPayItems.Length == 0) @@ -193,9 +193,15 @@ public static void DumpToLogger(PayResponse payResp) // pretty log the response (JSON) Logger.LogDebug("PayResponse: " + JsonSerializer.Serialize(payResp, new JsonSerializerOptions { WriteIndented = true })); - - foreach (PayItem ftPayItem in payResp.ftPayItems) + if (payItemRequest != null) + { + Logger.LogInfo("Requested Payment:"); + Logger.LogInfo($"- Amount: {payItemRequest.Amount}"); + } + Logger.LogInfo("Received Pay Response:"); + for (int i = 0; i < payResp.ftPayItems.Length; i++) { + PayItem ftPayItem = payResp.ftPayItems[i]; var payItemCaseData = ftPayItem.GetPayItemCaseData(); Dictionary? providerInfo = payItemCaseData?.Provider; @@ -204,19 +210,32 @@ public static void DumpToLogger(PayResponse payResp) { protocol = protocolValue.GetString() ?? "ERROR: invalid type"; } - Logger.LogInfo($"- {protocol}:"); - if (payItemCaseData?.Receipt == null) - { - Logger.LogInfo("\t WARNING: No receipt info received!"); - } - else + Logger.LogInfo($"- PayItem {i+1}"); + Logger.LogInfo($"\t\tDescription: {ftPayItem.Description}"); + if (protocol != "unknown") Logger.LogInfo($"\t\tprotocol: {protocol}"); + Logger.LogInfo($"\t\tAmount: {ftPayItem.Amount}"); + // only show calculate included tip and process receipt for the real payment item and not for the additional info entries + if (payItemCaseData != null) { - string[]? payReceipt = ftPayItem.GetPayItemCaseData()?.Receipt; - if (payReceipt != null) + if (payItemRequest != null) + { + decimal tipAmount = ftPayItem.Amount - payItemRequest.Amount; + Logger.LogInfo($"\t\t\tIncluded Tip Amount: {tipAmount}"); + } + Logger.LogInfo("\t\tReceipt:"); + if (payItemCaseData?.Receipt == null) + { + Logger.LogInfo("\t\t\t WARNING: No receipt info received!"); + } + else { - foreach (string line in payReceipt) + string[]? payReceipt = ftPayItem.GetPayItemCaseData()?.Receipt; + if (payReceipt != null) { - Logger.LogInfo($"\t{line}"); + foreach (string line in payReceipt) + { + Logger.LogInfo($"\t\t\t{line}"); + } } } }