Syrve POS API SDK

External Payment Types

Integration With External Payment Types

Overview

it the external payment type. Plugins used for external payment types must be licensed.

External payment system registration

A plugin registers an external payment type using IOperationService.RegisterPaymentSystem(...). paymentSystem is a required parameter to be passed to this method. It is a class instance that implements IExternalPaymentProcessor. Upon registration, a new payment system will be available in Syrve Office. You can find it under the name IExternalPaymentProcessor.PaymentSystemName in the «Non-cash» type field if you select the «External Payment Type» as the payment type in the new payment type window.

backPT

If you add a payment type within this payment system, this external payment type will be available on Syrve POS terminals.

frontPT

Explanation of terms:

IExternalPaymentProcessor Interface

To carry out the required business procedures of posting and refunding payments made using external payment types, the IExternalPaymentProcessor interface needs to be implemented

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);
}

Where:

Payment Processing Methods

When a user selects a payment type on the Syrve POS payment screen, specifies the amount, and presses the «Pay» button or when a user deposits a prepayment using a certain payment type, the control will use the Pay() method:

void Pay(decimal sum, Guid? orderId, Guid paymentTypeId, Guid transactionId, [NotNull] IPointOfSale pointOfSale, [NotNull] IUser cashier, IReceiptPrinter printer, IViewManager viewManager, IPaymentDataContext context, IProgressBar progressBar);

Where:

For details on the object signature, please refer to the documentation.

If you, for example, need to integrate with a hotel system:

[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)
{
    // Show number input and card swiping dialog in Syrve POS
    var input = viewManager.ShowInputDialog("Enter the number or swipe the card", InputDialogTypes.Card | InputDialogTypes.Number);
    string room = null;
    string cardTrack = null;
    
    // If number is entered, result is NumberInputDialogResult
    var roomNum = input as NumberInputDialogResult;
    if (roomNum != null)
        room = roomNum.Number.ToString();
    
    // If card is swiped, result is CardInputDialogResult
    var card = input as CardInputDialogResult;
    if (card != null)
        cardTrack = card.FullCardTrack;
    
    if (room == null && cardTrack == null)
        // Nothing entered, operation is aborted.
        throw new PaymentActionFailedException("No data entered.");
    
    // Get order using API by ID via IOperationService.
    var order = PluginContext.Operations.TryGetOrderById(orderId.Value);
    
    // Running random methods. For example, effecting payment in some hotelSystem which would return the guest name if the payment is accepted and null, if the payment is declined.
    var guestName = hotelSystem.ProcessPaymentOnGuest(cardTrack, room, order?.Number, transactionId, sum);
    if (guestName == null)
        // Payment not processed, operation is aborted.
        throw new PaymentActionFailedException("Payment not processed.");
    
    // Preparing receipt for printing. Receipt includes XElement
    var slip = new ReceiptSlip
    {
        Doc =  new XElement(Tags.Doc,
            new XElement(Tags.Pair, "Guest", guestName),
            new XElement(Tags.Pair, "Amount", sum))
    };
    
    // Printing.
    printer.Print(slip);
    var cardInfoData = new IsCardClass { IsCard = card != null };
    var cardType = cardInfoData.IsCard
        ? "My Hotel System Card"
        : "My Hotel System Room";
    // Saving the data to be given in reports.
    context.SetInfoForReports(room ?? cardTrack, cardType);
    // Saving the data to be used for refund.
    context.SetRollbackData(cardInfoData);
}

The PaymentActionFailedException payment exclusion serves to abort the payment operation. An Syrve POS user will be shown the exception message. This makes sense if any issues occurred during the communication with the external service, the payment cannot be processed, and a user must be informed of the reasons.

To abort the operation silently, you can use the PaymentActionCancelledExceptionexception. It makes sense if in the process of payment, a dialog box is displayed and a user selects «Cancel».

Arguments IReceiptPrinter, IViewManager and IPaymentDataContext exist only while the method is running; once the method is complete, the instances are removed. So there is no point to save them as variables for they cannot be used outside the method.

Silent Payment

Sometimes, businesses need solutions to effect plugin payments within the very plugins but without the Syrve POS cash register screen. For this, the plugin should implement the CanPaySilently method of the plugin payment processor. The method result is the answer to this question: «Whether or not the plugin can process silent payments?». To make this possible, an order should have a plugin payment item added. Silent payments may require the ProcessPrepay method with the isProcessed flag set to false to be called. The SDK shows a use case of the user-written class with the SilentPay property:

[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);
}

In turn, Syrve POS sends the specified serialized class to the payment context (IPaymentContext) and then this class is extracted and deserialized in the CanPaySilently method.

public bool CanPaySilently(decimal sum, Guid? orderId, Guid paymentTypeId, IPaymentDataContext context)
{
    var customData = context.GetCustomData<PaymentAdditionalData>();    
    return customData?.SilentPay ?? false;
}

Depending on the CanPaySilently response, Syrve POS would call Pay or PaySilently plugin processor method. Therefore, the plugin defines the way a new payment should be processed.

Refund Methods

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);

The EmergencyCancelPayment() and ReturnPayment() methods are called when a user makes a refund on Syrve POS.

The ReturnPayment() method takes over when the «Refund» or «Delete order» button is tapped on the closed order screen. Or if a user deletes a posted payment. The EmergencyCancelPayment() takes over when a posted payment is canceled in the open order. For example, if the fiscal payment is canceled due to the fiscal receipt printing error. If the last case does not require any specific logic, the ReturnPayment() method can be called from the EmergencyCancelPayment().

The methods take the same parameters as the payment methods. transactionId is the same as the one sent to the operation earlier performed Pay().

Methods are considered completed if no exceptions, like PaymentActionFailedException or PaymentActionCancelledException, occurred in the process. If such exceptions took place, the refund operation is aborted as is the case with the payment.

Hotel integration code sample. The refund method cancels the transaction and prints a receipt with the refund amount and saved details: card swiped or number entered.

[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)
{
    // Running random methods. For example, payment is refunded by the transaction ID in some hotelSystem which would return true if the payment is refunded successfully and false if the refund failed.
    var success = hotelSystem.ProcessReturnPayment(transactionId);
    if (!success)
        throw new PaymentActionFailedException("Failed to refund.");
    
    // Obtaining data saved in the payment item.
    var isCard = context.GetRollbackData<IsCardClass>();
    
    var slip = new ReceiptSlip
    {
        Doc =  new XElement(Tags.Doc, 
            new XElement(Tags.Pair, "Payment refund", sum),
            new XElement(Tags.Pair, "Was the card used", isCard.IsCard ? "YES" :    "NO" ))
    };
    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);
}

The ReturnPaymentWithoutOrder() method is called when the external payment refund takes place. You can refund items using external types even if orders are not prepaid. To be able to select the external type in the refund UI, you need to register the payment system with the optional canProcessPaymentReturnWithoutOrder = true parameter. That is

var disposable = PluginContext.Operations.RegisterPaymentSystem(paymentSystem, true);

Unlike other methods mentioned above, the ReturnPaymentWithoutOrder() method has neither order nor prepayment context. We assume that such details as the amount and payment method are enough to make a refund. In the process, you can show dialogs to users and print receipts as is the case with other methods mentioned above.

Data Collection Methods

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);

If you need to collect any data at the time an external payment item is added to the order rather than the moment when the «Pay» button is hit, you can change the CollectData() method accordingly.

The OnPaymentAdded() method is called out once the payment item is added to the order. This method is special for having IOperationService operationService as one of its arguments. Unlike PluginContext.Operations, this instance is privileged to change the current order. This may be required, for example, to set the payment item amount or even add any menu item to the order.

The OnPreliminaryPaymentEditing() is called out when editing prepayments. This method may also change the current order using the IOperationService operationService argument. The method returns bool, the meaning of which is the following: whether or not the prepayment item amount can be changed in the UI once the method is complete.

Closing & Opening Till Shifts in Syrve POS

Some external payment systems need to perform certain actions on their side at the time a till shift is opened or closed in Syrve POS. For example, banking systems need to carry out the verification at the till shift closing. For this, you need to subscribe to INotificationService.SubscribeOnCafeSessionOpening and INotificationService.SubscribeOnCafeSessionClosing.

When you open or close a till shift, a corresponding observer receives a new event. Code sample which, at the time of opening or closing a shift, prints the payment system key and whether the shift is closed or opened:

ctor
{
    // ...   
    PluginContext.Notifications.SubscribeOnCafeSessionClosing(CafeSessionClosing);
    PluginContext.Notifications.SubscribeOnCafeSessionOpening(CafeSessionOpening)                
}

private void CafeSessionOpening([NotNull] IReceiptPrinter printer, [NotNull] IProgressBar progressBar)
{
    PluginContext.Log.Info("Cafe session opening.");
    var message =
        "I cannot connect to my server and open a shift.";
    PluginContext.Operations.AddNotificationMessage(message, "SamplePaymentPlugin");
}

private void CafeSessionClosing([NotNull] IReceiptPrinter printer, [NotNull] IProgressBar progressBar)
{
    PluginContext.Log.Info("Cafe session closing.");
    var slip = new ReceiptSlip
    {
        Doc = new XElement(Tags.Doc,
            new XElement(Tags.Center, PaymentSystemKey),
            new XElement(Tags.Center, "Cafe session closed."))
    };
    printer.Print(slip);
}

If you need to give a user any warning, you can use notifications. Exceptions that occur while running CafeSessionOpening() and CafeSessionClosing()do not interrupt till shift opening or closing operations in Syrve POS. Moreover, if the processor encounters some exceptions, it is considered broken and is not called out until the plugin is restarted.