Integracja z Zewnętrznymi Typami Płatności
Przegląd
to zewnętrzny typ płatności.
Wtyczki używane do zewnętrznych typów płatności muszą być licencjonowane.
Rejestracja zewnętrznego systemu płatności
Wtyczka rejestruje zewnętrzny typ płatności za pomocą IOperationService.RegisterPaymentSystem(...).
paymentSystem jest wymaganym parametrem, który należy przekazać do tej metody. Jest to instancja klasy implementującej IExternalPaymentProcessor.
Po rejestracji nowy system płatności będzie dostępny w Syrve Office.
Można go znaleźć pod nazwą IExternalPaymentProcessor.PaymentSystemName w polu typu «Bezgotówkowe», jeśli wybierzesz «Zewnętrzny Typ Płatności» jako typ płatności w oknie nowego typu płatności.

Jeśli dodasz typ płatności w ramach tego systemu płatności, ten zewnętrzny typ płatności będzie dostępny na terminalach Syrve POS.

Wyjaśnienie terminów:
- System Płatności – definiuje sposób księgowania i zwrotu płatności. System płatności jest używany jako pojedyncza instancja w całym Syrve Office.
- Typ Płatności – odnosi się do dowolnego systemu płatności. Posiada konfigurowalne właściwości, takie jak fiskalność, konta transferowe i inne. Jeśli zewnętrzny typ płatności jest fiskalny, zostanie sklasyfikowany jako typ «Karty Bankowe», jeśli pozostanie niefiskalny, będzie należał do typu «Płatność Bezgotówkowa». W ramach jednego systemu płatności można dodać dowolną liczbę typów płatności.
- Pozycja Płatności – odnosi się do zamówień w Syrve POS. Gdy użytkownik na ekranie płatności lub ekranie przedpłaty wybierze dowolną metodę płatności, do zamówienia zostanie dodana pozycja płatności odpowiadająca wybranej metodzie. Pozycje płatności można dodawać za pomocą API (Dodawanie Płatności).
Interfejs IExternalPaymentProcessor
Aby przeprowadzić wymagane procedury biznesowe księgowania i zwrotu płatności dokonanych za pomocą zewnętrznych typów płatności, należy zaimplementować interfejs IExternalPaymentProcessor
public interface IExternalPaymentProcessor
{
string PaymentSystemKey { get; }
string PaymentSystemName { get; }
void CollectData(Guid orderId, Guid paymentTypeId, [NotNull] IUser cashier, IReceiptPrinter printer, UI.IViewManager viewManager, IPaymentDataContext context, UI.IProgressBar progressBar);
void OnPaymentAdded([NotNull] IOrder order, [NotNull] IPaymentItem paymentItem, [NotNull] IUser cashier, [NotNull] IOperationService operationService, IReceiptPrinter printer, UI.IViewManager viewManager, IPaymentDataContext context, UI.IProgressBar progressBar);
bool OnPreliminaryPaymentEditing([NotNull] IOrder order, [NotNull] IPaymentItem paymentItem, [NotNull] IUser cashier, [NotNull] IOperationService operationService, IReceiptPrinter printer, UI.IViewManager viewManager, IPaymentDataContext context, UI.IProgressBar progressBar);
void Pay(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);
void EmergencyCancelPayment(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);
void ReturnPayment(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);
void ReturnPaymentWithoutOrder(decimal sum, Guid paymentTypeId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IProgressBar progressBar);
void PaySilently(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IPaymentDataContext context);
void EmergencyCancelPaymentSilently(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IPaymentDataContext context);
bool CanPaySilently(decimal sum, Guid? orderId, Guid paymentTypeId, IPaymentDataContext context);
}
Gdzie:
PaymentSystemKey– jest unikalnym kluczem, który nowy zewnętrzny system płatności otrzymuje podczas rejestracji.PaymentSystemName– jest nazwą wyświetlaną w interfejsie Syrve Office.
Metody Przetwarzania Płatności
Gdy użytkownik wybierze typ płatności na ekranie płatności Syrve POS, określi kwotę i naciśnie przycisk «Zapłać» lub gdy użytkownik wpłaci przedpłatę za pomocą określonego typu płatności, kontrolka użyje metody Pay():
void Pay(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);
Gdzie:
sum– kwota płatności;orderId– ID zamówienia Syrve POS;paymentTypeId– ID typu płatności. Listę wszystkich typów płatności można uzyskać za pomocą metodyIOperationService.GetPaymentTypes(); konkretny typ płatności można uzyskać za pomocą metodyOperationService_TryGetPaymentTypeById(...);transactionId– ID transakcji;pointOfSale–punkt sprzedaży, w którym przetwarzana jest ta pozycja płatności;cashier– kasjer;printer– instancjaIReceiptPrinter, umożliwiająca drukowanie na drukarce paragonów Syrve POS;viewManager– instancjaIViewManagerużywana do wyświetlania okien podczas przetwarzania płatności;context– instancjaIPaymentDataContextużywana do zapisywania danych w pozycji płatności;progressBar– instancjaIProgressBarużywana do zmiany tekstu na pasku postępu podczas przetwarzania płatności.
Szczegóły dotyczące sygnatury obiektu znajdują się w dokumentacji.
Jeśli na przykład potrzebujesz integracji z systemem hotelowym:
- Podczas przyjmowania płatności musisz poprosić użytkowników o podanie numeru pokoju lub okazanie klucza do pokoju.
- Następnie odwołać się do usługi hotelowej.
- Jeśli operacja zakończy się sukcesem, wydrukuj paragon z kwotą i nazwiskiem gościa otrzymanymi z systemu hotelowego.
- Przejdź do Syrve Office i zapisz wprowadzony numer lub zeskanowaną kartę, aby zobaczyć szczegóły w raportach OLAP.
- W przypadku konieczności zwrotu, musisz wiedzieć, czy użyto karty czy numeru.
- Jeśli operacja się nie powiedzie, anuluj płatność.
[Serializable]
internal class IsCardClass
{
public bool IsCard;
}
public void Pay(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, IPointOfSale pointOfSale, IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar)
{
// Wyświetl okno dialogowe do wprowadzenia numeru i zeskanowania karty w Syrve POS
var input = viewManager.ShowInputDialog("Wprowadź numer lub zeskanuj kartę", InputDialogTypes.Card | InputDialogTypes.Number);
string room = null;
string cardTrack = null;
// Jeśli wprowadzono numer, wynik to NumberInputDialogResult
var roomNum = input as NumberInputDialogResult;
if (roomNum != null)
room = roomNum.Number.ToString();
// Jeśli zeskanowano kartę, wynik to CardInputDialogResult
var card = input as CardInputDialogResult;
if (card != null)
cardTrack = card.FullCardTrack;
if (room == null && cardTrack == null)
// Nic nie wprowadzono, operacja jest anulowana.
throw new PaymentActionFailedException("Nie wprowadzono danych.");
// Pobierz zamówienie za pomocą API po ID przez IOperationService.
var order = PluginContext.Operations.TryGetOrderById(orderId.Value);
// Uruchamianie losowych metod. Na przykład, realizacja płatności w jakimś hotelSystem, który zwróci nazwisko gościa, jeśli płatność zostanie zaakceptowana, lub null, jeśli płatność zostanie odrzucona.
var guestName = hotelSystem.ProcessPaymentOnGuest(cardTrack, room, order?.Number, transactionId, sum);
if (guestName == null)
// Płatność nie została przetworzona, operacja zostaje przerwana.
throw new PaymentActionFailedException("Płatność nie została przetworzona.");
// Przygotowywanie paragonu do wydruku. Paragon zawiera XElement
var slip = new ReceiptSlip
{
Doc = new XElement(Tags.Doc,
new XElement(Tags.Pair, "Gość", guestName),
new XElement(Tags.Pair, "Kwota", sum))
};
// Drukowanie.
printer.Print(slip);
var cardInfoData = new IsCardClass { IsCard = card != null };
var cardType = cardInfoData.IsCard
? "Karta systemu My Hotel"
: "Pokój systemu My Hotel";
// Zapisywanie danych do raportów.
context.SetInfoForReports(room ?? cardTrack, cardType);
// Zapisywanie danych do zwrotu.
context.SetRollbackData(cardInfoData);
}
Wyjątek [`PaymentActionFailedException`](https://syrve.github.io/front.api.sdk/v6/html/T_Resto_Front_Api_Exceptions_PaymentActionFailedException.htm) służy do przerwania operacji płatności. Użytkownik Syrve POS zobaczy komunikat wyjątku `message`. Ma to sens, jeśli podczas komunikacji z zewnętrzną usługą wystąpiły problemy, płatność nie może zostać przetworzona i użytkownik musi zostać poinformowany o przyczynach.
Aby przerwać operację cicho, można użyć wyjątku [`PaymentActionCancelledException`](https://syrve.github.io/front.api.sdk/v6/html/T_Resto_Front_Api_Exceptions_PaymentActionCancelledException.htm). Ma to sens, jeśli w trakcie płatności wyświetlane jest okno dialogowe, a użytkownik wybiera *«Anuluj»*.
Argumenty [`IReceiptPrinter`](https://syrve.github.io/front.api.sdk/v6/html/T_Resto_Front_Api_IReceiptPrinter.htm), [`IViewManager`](https://syrve.github.io/front.api.sdk/v6/html/T_Resto_Front_Api_UI_IViewManager.htm) oraz [`IPaymentDataContext`](https://syrve.github.io/front.api.sdk/v6/html/T_Resto_Front_Api_IPaymentDataContext.htm) istnieją tylko podczas działania metody; po jej zakończeniu instancje są usuwane. Dlatego nie ma sensu zapisywać ich jako zmienne, ponieważ nie mogą być używane poza metodą.
### Cicha płatność
Czasami firmy potrzebują rozwiązań do realizacji płatności wtyczkowych wewnątrz samych wtyczek, ale bez ekranu kasy Syrve POS. W tym celu wtyczka powinna zaimplementować metodę [`CanPaySilently`](https://syrve.github.io/front.api.sdk/v6/html/M_Resto_Front_Api_IExternalPaymentProcessor_CanPaySilently.htm) procesora płatności wtyczki. Wynik metody odpowiada na pytanie: *«Czy wtyczka może przetwarzać ciche płatności?»*.
Aby to umożliwić, do zamówienia powinien zostać dodany element płatności wtyczki. Ciche płatności mogą wymagać wywołania metody [`ProcessPrepay`](https://syrve.github.io/front.api.sdk/v6/html/M_Resto_Front_Api_IOperationService_ProcessPrepay.htm) z flagą `isProcessed` ustawioną na `false`.
`SDK` pokazuje przykład użycia klasy napisanej przez użytkownika z właściwością *SilentPay*:
```cs
[Serializable]
public class PaymentAdditionalData
{
public bool SilentPay { get; set; }
}
private string Serialize<T>(T data) where T : class
{
using (var sw = new StringWriter())
using (var writer = XmlWriter.Create(sw))
{
new XmlSerializer(typeof(T)).Serialize(writer, data);
return sw.ToString();
}
}
private void AddAndProcessExternalPrepay()
{
var order = PluginContext.Operations.GetOrders().Last(o => o.Status == OrderStatus.New);
var paymentType = PluginContext.Operations.GetPaymentTypes().Single(i => i.Kind == PaymentTypeKind.External && i.Name == "SamplePaymentType");
var additionalData = new ExternalPaymentItemAdditionalData
{
CustomData = Serialize(new PaymentAdditionalData {SilentPay = true})
};
var credentials = PluginContext.Operations.AuthenticateByPin("777");
var paymentItem = PluginContext.Operations.AddExternalPaymentItem(order.ResultSum, false, additionalData, paymentType, order, credentials);
PluginContext.Operations.ProcessPrepay(credentials, order, paymentItem);
}
Z kolei Syrve POS wysyła określoną zserializowaną klasę do kontekstu płatności (IPaymentContext), a następnie ta klasa jest wyodrębniana i deserializowana w metodzie CanPaySilently.
public bool CanPaySilently(decimal sum, Guid? orderId, Guid paymentTypeId, IPaymentDataContext context)
{
var customData = context.GetCustomData<PaymentAdditionalData>();
return customData?.SilentPay ?? false;
}
W zależności od odpowiedzi CanPaySilently Syrve POS wywoła metodę Pay lub PaySilently procesora wtyczki. W związku z tym wtyczka definiuje sposób przetwarzania nowej płatności.
Metody zwrotu
void EmergencyCancelPayment(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);
void ReturnPayment(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);
void ReturnPaymentWithoutOrder(decimal sum, Guid paymentTypeId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IProgressBar progressBar);
void EmergencyCancelPaymentSilently(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IPaymentDataContext context);
Metody EmergencyCancelPayment() oraz ReturnPayment() są wywoływane, gdy użytkownik dokonuje zwrotu w Syrve POS.
Metoda ReturnPayment() jest wywoływana, gdy na ekranie zamkniętego zamówienia zostanie naciśnięty przycisk «Zwrot» lub «Usuń zamówienie». Lub jeśli użytkownik usuwa opublikowaną płatność. Metoda EmergencyCancelPayment() jest wywoływana, gdy opublikowana płatność jest anulowana w otwartym zamówieniu. Na przykład, jeśli płatność fiskalna jest anulowana z powodu błędu drukowania paragonu fiskalnego. Jeśli w ostatnim przypadku nie jest wymagana żadna specyficzna logika, metoda ReturnPayment() może być wywołana z EmergencyCancelPayment().
Metody przyjmują te same parametry co metody płatności. transactionId jest taki sam, jak ten przesłany do wcześniej wykonanej operacji Pay().
Metody są uznawane za zakończone, jeśli w trakcie działania nie wystąpiły wyjątki, takie jak PaymentActionFailedException lub PaymentActionCancelledException. Jeśli takie wyjątki wystąpiły, operacja zwrotu jest przerywana, podobnie jak w przypadku płatności.
Przykład integracji hotelowej. Metoda zwrotu anuluje transakcję i drukuje paragon z kwotą zwrotu oraz zapisanymi szczegółami: karta przeciągnięta lub numer wpisany.
[Serializable]
public class IsCardClass
{
public bool IsCard;
}
public void ReturnPayment(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar)
{
// Uruchamianie losowych metod. Na przykład, zwrot płatności według ID transakcji w jakimś hotelSystem, który zwróci true, jeśli zwrot płatności zakończył się sukcesem, oraz false, jeśli zwrot nie powiódł się.
var success = hotelSystem.ProcessReturnPayment(transactionId);
if (!success)
throw new PaymentActionFailedException("Nie udało się zwrócić płatności.");
// Pobieranie danych zapisanych w elemencie płatności.
var isCard = context.GetRollbackData<IsCardClass>();
var slip = new ReceiptSlip
{
Doc = new XElement(Tags.Doc,
new XElement(Tags.Pair, "Zwrot płatności", sum),
new XElement(Tags.Pair, "Czy użyto karty", isCard.IsCard ? "TAK" : "NIE" ))
};
printer.Print(slip);
}
public void EmergencyCancelPayment(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar)
{
ReturnPayment(sum, orderId, paymentTypeId, transactionId, pointOfSale, cashier, printer, viewManager, context, progressBar);
}
Metoda ReturnPaymentWithoutOrder() jest wywoływana, gdy następuje zwrot płatności zewnętrznej. Możesz zwracać przedmioty, korzystając z typów zewnętrznych, nawet jeśli zamówienia nie są opłacone z góry. Aby móc wybrać typ zewnętrzny w interfejsie zwrotu, musisz zarejestrować system płatności z opcjonalnym parametrem canProcessPaymentReturnWithoutOrder = true. To znaczy
var disposable = PluginContext.Operations.RegisterPaymentSystem(paymentSystem, true);
W przeciwieństwie do innych wymienionych powyżej metod, metoda ReturnPaymentWithoutOrder() nie ma kontekstu zamówienia ani przedpłaty. Zakładamy, że takie szczegóły jak kwota i metoda płatności są wystarczające do dokonania zwrotu. W trakcie procesu możesz wyświetlać użytkownikom okna dialogowe i drukować paragony, tak jak ma to miejsce w przypadku innych wymienionych metod.
Metody zbierania danych
void CollectData(Guid orderId, Guid paymentTypeId, [NotNull] IUser cashier, IReceiptPrinter printer, UI.IViewManager viewManager, IPaymentDataContext context, UI.IProgressBar progressBar);
void OnPaymentAdded([NotNull] IOrder order, [NotNull] IPaymentItem paymentItem, [NotNull] IUser cashier, [NotNull] IOperationService operationService, IReceiptPrinter printer, UI.IViewManager viewManager, IPaymentDataContext context, UI.IProgressBar progressBar);
bool OnPreliminaryPaymentEditing([NotNull] IOrder order, [NotNull] IPaymentItem paymentItem, [NotNull] IUser cashier, [NotNull] IOperationService operationService, IReceiptPrinter printer, UI.IViewManager viewManager, IPaymentDataContext context, UI.IProgressBar progressBar);
Jeśli potrzebujesz zebrać jakiekolwiek dane w momencie dodawania elementu płatności zewnętrznej do zamówienia, a nie w chwili naciśnięcia przycisku „Zapłać”, możesz odpowiednio zmienić metodę CollectData().
Metoda OnPaymentAdded() jest wywoływana zaraz po dodaniu elementu płatności do zamówienia. Ta metoda jest wyjątkowa, ponieważ jednym z jej argumentów jest IOperationService operationService. W przeciwieństwie do PluginContext.Operations, ta instancja ma uprawnienia do zmiany bieżącego zamówienia. Może to być potrzebne na przykład do ustawienia kwoty elementu płatności lub nawet dodania dowolnego elementu menu do zamówienia.
Metoda OnPreliminaryPaymentEditing() jest wywoływana podczas edycji przedpłat. Ta metoda również może zmienić bieżące zamówienie, korzystając z argumentu IOperationService operationService. Metoda zwraca bool, którego znaczenie jest następujące: czy kwota elementu przedpłaty może być zmieniona w interfejsie użytkownika po zakończeniu działania metody.
Zamykanie i otwieranie zmian kasy w Syrve POS
Niektóre zewnętrzne systemy płatności muszą wykonać określone działania po swojej stronie w momencie otwierania lub zamykania zmiany kasy w Syrve POS. Na przykład systemy bankowe muszą przeprowadzić weryfikację przy zamykaniu zmiany kasy. W tym celu musisz subskrybować INotificationService.SubscribeOnCafeSessionOpening oraz INotificationService.SubscribeOnCafeSessionClosing.
Gdy otwierasz lub zamykasz zmianę kasy, odpowiedni obserwator otrzymuje nowe zdarzenie. Przykładowy kod, który w momencie otwierania lub zamykania zmiany drukuje klucz systemu płatności oraz informację, czy zmiana jest zamykana czy otwierana:
ctor
{
// ...
PluginContext.Notifications.SubscribeOnCafeSessionClosing(CafeSessionClosing);
PluginContext.Notifications.SubscribeOnCafeSessionOpening(CafeSessionOpening)
}
private void CafeSessionOpening([NotNull] IReceiptPrinter printer, [NotNull] IProgressBar progressBar)
{
PluginContext.Log.Info("Otwarcie sesji kawiarni.");
var message =
"Nie mogę połączyć się z moim serwerem i otworzyć zmiany.";
PluginContext.Operations.AddNotificationMessage(message, "SamplePaymentPlugin");
}
private void CafeSessionClosing([NotNull] IReceiptPrinter printer, [NotNull] IProgressBar progressBar)
{
PluginContext.Log.Info("Zamykanie sesji kawiarni.");
var slip = new ReceiptSlip
{
Doc = new XElement(Tags.Doc,
new XElement(Tags.Center, PaymentSystemKey),
new XElement(Tags.Center, "Sesja kawiarni zamknięta."))
};
printer.Print(slip);
}
Jeśli chcesz wyświetlić użytkownikowi jakieś ostrzeżenie, możesz użyć powiadomień. Wyjątki, które wystąpią podczas wykonywania CafeSessionOpening() i CafeSessionClosing(), nie przerywają operacji otwierania ani zamykania zmiany kasy w Syrve POS. Co więcej, jeśli procesor napotka jakieś wyjątki, jest uznawany za uszkodzony i nie jest wywoływany aż do ponownego uruchomienia wtyczki.