Zewnętrzne Typy Płatności
Przegląd
Jeśli zaimplementujesz interfejs IExternalPaymentProcessor i zarejestrujesz go poprawnie, nowy system płatności będzie dostępny w instancji Syrve. Dla uproszczenia nazywamy go zewnętrznym typem płatności. Wtyczki używane dla zewnętrznych typów płatności muszą posiadać licencję.
Rejestracja
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. Znajdziesz go pod nazwą IExternalPaymentProcessor.PaymentSystemName w polu Typ bezgotówkowy, 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.
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łej instancji Syrve.
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 uczynisz zewnętrzny typ płatności fiskalnym, zostanie on sklasyfikowany jako Typ Kart Bankowych, jeśli pozostawisz go niefiskalnym, będzie należał do Typu Płatności Bezgotówkowej. W ramach jednego systemu płatności możesz dodać dowolną liczbę typów płatności.
Element Płatności odnosi się do zamówień w Syrve POS. Gdy użytkownik na ekranie płatności lub przedpłaty wybierze dowolną metodę płatności, do zamówienia zostanie dodany element płatności odpowiadający wybranej metodzie. Elementy 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 przy użyciu zewnętrznych typów płatności, należy zaimplementować interfejs 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 to unikalny klucz, który nowy zewnętrzny system płatności przyjmuje podczas rejestracji.
-
PaymentSystemName to nazwa wyświetlana w interfejsie użytkownika Syrve Instance.
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ę używając określonego typu płatności, kontrola 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ą metody IOperationService.GetPaymentTypes(); konkretny typ płatności można uzyskać za pomocą metody OperationService_TryGetPaymentTypeById(...)
-
transactionId – ID transakcji
-
pointOfSale – punkt sprzedaży, w którym przetwarzany jest ten element płatności
-
cashier – kasjer
-
printer – instancja IReceiptPrinter, która umożliwia drukowanie na drukarce paragonów Syrve POS
-
viewManager – instancja IViewManager używana do wyświetlania okien podczas przetwarzania płatności
-
context – instancja IPaymentDataContext używana do zapisywania danych w elemencie płatności
-
progressBar – instancja IProgressBar uż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 otrzymanym z systemu hotelowego.
Przejdź do Syrve 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)
{
// Pokaż dialog wprowadzania numeru i przesuwania karty w Syrve POS
var input = viewManager.ShowInputDialog("Wprowadź numer lub przesuń 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 karta została przesunięta, 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ś systemie hotelowym, 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 jest anulowana.
throw new PaymentActionFailedException("Płatność nie została przetworzona.");
// Przygotowanie paragonu do druku. 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 mojego systemu hotelowego"
: "Pokój mojego systemu hotelowego";
// Zapisanie danych do raportów.
context.SetInfoForReports(room ?? cardTrack, cardType);
// Zapisanie danych do użycia przy zwrocie.
context.SetRollbackData(cardInfoData);
}
Wyjątek PaymentActionFailedException służy do przerwania operacji płatności. Użytkownik Syrve POS zobaczy komunikat wyjątku. Ma to sens, jeśli podczas komunikacji z zewnętrzną usługą wystąpiły jakiekolwiek problemy, płatność nie może zostać przetworzona, a użytkownik musi zostać poinformowany o przyczynach.
Aby przerwać operację cicho, można użyć wyjątku PaymentActionCancelledException. Ma to sens, jeśli w trakcie płatności wyświetlane jest okno dialogowe, a użytkownik wybiera Anuluj.
Argumenty IReceiptPrinter, IViewManager, oraz IPaymentDataContext istnieją tylko podczas działania metody; po jej zakończeniu instancje są usuwane. Nie ma więc sensu zapisywać ich jako zmienne, ponieważ nie można ich używać poza metodą.
Cicha płatność
Czasami firmy potrzebują rozwiązań umożliwiających realizację płatności wtyczkowych bezpośrednio w samych wtyczkach, ale bez ekranu kasy Syrve POS. W tym celu wtyczka powinna zaimplementować metodę CanPaySilently 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 wtyczkowej. Ciche płatności mogą wymagać wywołania metody ProcessPrepay z flagą isProcessed ustawioną na false. SDK pokazuje przykład użycia klasy napisanej przez użytkownika z właściwością SilentPay:
[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.
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() i 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 zaksięgowaną płatność. Metoda EmergencyCancelPayment() jest wywoływana, gdy zaksięgowana 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 tym ostatnim przypadku nie jest wymagana żadna specyficzna logika, metoda ReturnPayment() może być wywołana z metody EmergencyCancelPayment().
Metody przyjmują te same parametry co metody płatności. transactionId jest taki sam, jak ten wysłany wcześniej do operacji - Pay().
Metody są uznawane za zakończone, jeśli w trakcie procesu 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 przesunięta lub numer wprowadzony.
[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 jest realizowany przez ID transakcji w jakimś systemie hotelowym, który zwróci true, jeśli zwrot płatności zakończy się sukcesem, oraz false, jeśli zwrot się nie powiedzie.
var success = hotelSystem.ProcessReturnPayment(transactionId);
if (!success)
throw new PaymentActionFailedException("Nie udało się dokonać zwrotu.");
// 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. Począwszy od wersji Syrve 6.2.2, można zwracać pozycje używając 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, należy 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 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żna wyświetlać użytkownikom okna dialogowe i drukować paragony, tak jak 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 zbierać jakiekolwiek dane w momencie dodawania zewnętrznego elementu płatności do zamówienia, a nie w chwili naciśnięcia przycisku Pay, możesz odpowiednio zmodyfikować metodę CollectData().
Metoda OnPaymentAdded() jest wywoływana zaraz po dodaniu elementu płatności do zamówienia. Ta metoda jest szczególna, 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ć wymagane na przykład do ustawienia kwoty elementu płatności lub nawet dodania dowolnej pozycji menu do zamówienia. Metoda OnPreliminaryPaymentEditing() jest wywoływana podczas edycji przedpłat. Ta metoda może również zmienić bieżące zamówienie, korzystając z argumentu IOperationService operationService. Metoda zwraca bool, którego znaczenie jest następujące: czy kwota pozycji przedpłaty może zostać 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 otwarcia lub zamknięcia zmiany kasy w Syrve POS. Na przykład systemy bankowe muszą przeprowadzić weryfikację przy zamykaniu zmiany kasy. W tym celu należy subskrybować INotificationService.SubscribeOnCafeSessionOpening oraz INotificationService.SubscribeOnCafeSessionClosing.
Gdy otwierasz lub zamykasz zmianę kasy, odpowiedni obserwator otrzymuje nowe zdarzenie. Przykład kodu, który w momencie otwierania lub zamykania zmiany wypisuje klucz systemu płatności oraz informację, czy zmiana została zamknięta czy otwarta:
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("Zamknięcie sesji kawiarni.");
var slip = new ReceiptSlip
{
Doc = new XElement(Tags.Doc,
new XElement(Tags.Center, PaymentSystemKey),
new XElement(Tags.Center, "Sesja kawiarni została 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 działania CafeSessionOpening() oraz CafeSessionClosing(), nie przerywają operacji otwierania lub 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.