Confinity Documentation
  • Latest Version
  • Latest Version
  • Getting Started

    • Introduction
    • Core Concepts
    • Create an Application
    • Glossary
  • Essentials

    • Authentication & SSO
    • Breaking Changes
    • Roslyn Source Analyzers
    • Changelog
    • ConfinityContent
    • ConfinitySelectable
    • Confinity Schedules
    • Data Seeding
    • Development guidelines [WIP]
    • Entity App
    • Entity Form
    • Entity Permissions
    • Frontend Configuration
    • Images
    • Known Issues
    • Localization
    • Migrations
    • Modules [WIP]
    • On-Site Editing
    • Settings
    • Cascade Delete
    • Replication
    • Infrastructure
  • Modules

    • Analytics Module
    • Assets Module
    • Blog Module
    • Cookie Consent Module
    • Forms Module
    • Friendly Captcha (Forms Module )
    • GeoIP Module
    • Htmx
    • Mail Module
    • Mailing Module
    • MediaPlayer Module
    • GoogleMyBusiness Module
    • OpenTelemetry Module
    • Pages Module [WIP]
    • Pattern Library Module
    • SIX Saferpay (worldline) Module
    • Products Module
    • Search Module
    • Wizard Module
  • Guides

    • Create a Custom Entity App Form Element
    • Date and Time
    • Entity Change Listener
    • File Upload / Temp File
    • HTTP security headers
    • conventions [WIP]
    • How to use Confinity with nginx
    • Notifications
    • Nullability
    • Rename Entity
    • Schedules
    • Useful snippets
    • Content Localization
  • Design Guidelines

    • Introduction
    • Page Components
    • Forms Module

SIX Saferpay (worldline) Module

The Confinity Payment Saferpay module allows to integrate Saferpay to handle your payments. This module provides an API and a configuration UI. The payment must be done by the host application.

Installation

Add the Module to your Project

  1. You can install the Analytics module from NuGet with
dotnet add package Confinity.Payment.Saferpay
  1. In your Startup.cs you can now register the module with the module builder by calling the AddModule method with SaferpayPaymentModule. See Modules documentation for more information.

  2. Create a migration for the new entities (dotnet ef migrations add AddConfinitySaferpay ...)

Usage

The service ISaferpayService contains all the methods required to use saferpay "Payment Page".

Payment Page

This describes a payment-flow where the user leaves the page and returns after the payment. Details can be found in the API Reference.

Requirements:

  • You need to setup a JSON API basic authentication test backoffice, production backoffice

A Terminal (TerminalId) test backoffice, production backoffice https://saferpay.github.io/jsonapi/index.html#authentication

Error handling

All methods of SaferpayService can throw exceptions when there is a network issue or the request could not be processed. For payment errors SaferpayException is thrown. Network / IO exceptions will bubble up.

Example Implementation

@using System.Text.Json
@using Confinity.Payment.Saferpay
@using Microsoft.AspNetCore.Http.Extensions
@inject ISaferpayService _saferpayService;

@{
    var jsonSerializerOptions = new JsonSerializerOptions() { WriteIndented = true };

    var orderId = DateTimeOffset.UtcNow.Ticks.ToString();
    var description = "Coffee ☕";
    decimal amount = 100;

    if (Context.Request.HasFormContentType)
    {
        if (Context.Request.Form.ContainsKey("orderId"))
            orderId = Context.Request.Form["orderId"].ToString();

        if (Context.Request.Form.ContainsKey("description"))
            description = Context.Request.Form["description"]!;

        if (Context.Request.Form.ContainsKey("amount"))
            amount = decimal.Parse(Context.Request.Form["amount"]!);
    }
}

@if (Context.Request.HasFormContentType)
{
    <pre>Form
    @JsonSerializer.Serialize(Context.Request.Form, jsonSerializerOptions)
</pre>

    if (Context.Request.Form.TryGetValue("action", out var action))
    {
        if (action == "new")
        {
            var returnUri = new Uri(Context.Request.GetDisplayUrl());
            var create = await _saferpayService.CreatePayment(orderId, returnUri, amount, CurrencyCodes.CHF, description);
            <pre>Create: @JsonSerializer.Serialize(create, jsonSerializerOptions))</pre>
            <p> Redirect to Saferpay: <a href="@create.SaferpayResult.RedirectUrl">@create.SaferpayResult.RedirectUrl</a></p>
        }
        else if (action == "capture")
        {
            var capture = await _saferpayService.FinalizePayment(orderId, TransactionState.CapturedPayment);
            <p>payment was successful</p>
            <pre>Capture: @JsonSerializer.Serialize(capture, jsonSerializerOptions))</pre>
        }
        else if (action == "cancel")
        {
            var cancel = await _saferpayService.FinalizePayment(orderId, TransactionState.CanceledPayment);
            <p>payment was canceled</p>
            <pre>Cancel: @JsonSerializer.Serialize(cancel, jsonSerializerOptions))</pre>
        }
    }
}
else if (_saferpayService.ShouldAssert(Context.Request.GetDisplayUrl()))
{
    <p>Asserting...</p>
    try
    {
        var assert = await _saferpayService.AssertPayment(Context.Request.GetDisplayUrl());
    
        @if (assert.Transaction.TransactionState == TransactionState.CapturedPayment)
        {
            <p>payment was successful</p>
        }
        else if (assert.Transaction.TransactionState == TransactionState.CanceledPayment)
        {
            <p>payment was canceled</p>
        }
        else if (assert.Transaction.TransactionState == TransactionState.AuthorizedPayment)
        {
            // make liability-check
    
            <form method="post">
                <label>
                    OrderId
                    <input readonly="readonly" name="orderId" id="orderId" value="@assert.Transaction.ReferenceId"/>
                </label>
    
                <label>
                    Order description
                    <input readonly="readonly" name="description" id="description" value="@assert.Transaction.Description"/>
                </label>
    
                <button name="action" value="capture">Capture payment</button>
                <button name="action" value="cancel">Cancel payment</button>
            </form>
        }
    
        <pre>Assert: @JsonSerializer.Serialize(assert, jsonSerializerOptions)</pre>
    }
    catch (Exception e)
    {
        if (e is SaferpayException se)
        {
            @if (se.SaferpayError.ErrorName == SaferpayErrorName.TransactionAborted)
            {
                <p>@SaferpayErrorName.TransactionAborted.Label</p>
            }
            var payment = await _saferpayService.GetPaymentByReturnUrl(Context.Request.GetDisplayUrl());
            <form method="post">
                <input hidden name="amount" id="amount" value="@payment?.Amount"/>
                <input hidden name="orderId" id="orderId" value="@payment?.ReferenceId"/>
                <input hidden name="description" id="description" value="@payment?.Description"/>
    
                <button name="action" value="new">Restart payment</button>
            </form>
        }
        else
        {
            throw;
        }
    }
}
else
{
    <form method="post">
        <label>
            Amount
            <input name="amount" id="amount" value="@amount"/>
        </label>

        <label>
            OrderId

            <input name="orderId" id="orderId" value="@orderId"/>
        </label>

        <label>
            Order description
            <input name="description" id="description" value="@description"/>
        </label>

        <button name="action" value="new">Make payment</button>
    </form>
}

Payment Listener

If your application needs to be notified about payment state changes, you can implement the ISaferpayPaymentListener as in the following example.

using Confinity.Payment.Saferpay;

namespace DocsDemos.Modules.Saferpay.ExampleStore;

public class ExamplePaymentNotifyListener : ISaferpayPaymentNotifyListener
{
    private readonly ILogger<ExamplePaymentNotifyListener> _logger;
    private readonly ISaferpayService _saferpayService;

    public ExamplePaymentNotifyListener(ILogger<ExamplePaymentNotifyListener> logger,
        ISaferpayService saferpayService)
    {
        _logger = logger;
        _saferpayService = saferpayService;
    }

    public async Task NotifyAsync(SaferpayPaymentNotifyContext paymentNotifyContext)
    {
        try
        {
            var assert = await _saferpayService.AssertPayment(paymentNotifyContext.Payment);

            _logger.LogInformation("New state for payment {PaymentId}: {State}", paymentNotifyContext.Payment.Id,
                paymentNotifyContext.Payment.TransactionState.Id);

            // payment has been authorized and must be captured or canceled
            if (assert.Transaction.TransactionState == TransactionState.AuthorizedPayment)
            {
                // finalize and capture payment
                var result = await _saferpayService.FinalizePayment(paymentNotifyContext.Payment.ReferenceId,
                    TransactionState.CapturedPayment);

                // or: finalize and cancel payment
                // var result = await _saferpayService.FinalizePayment(paymentNotifyContext.Payment.ReferenceId,
                //                     TransactionState.CanceledPayment);

                // your business logic, when payment has been captured or canceled
                if (result.Transaction.TransactionState == TransactionState.CapturedPayment)
                {
                    // myOrderService.MarkPaymentAsPayed(orderId: result.Transaction.ReferenceId);
                    _logger.LogInformation("Payment has been captured");
                }
                else if (result.Transaction.TransactionState == TransactionState.CanceledPayment)
                {
                    // myOrderService.MarkPaymentAsFailed(orderId: result.Transaction.ReferenceId);
                    _logger.LogInformation("Payment has been canceled");
                }
                else
                {
                    throw new ArgumentException("State not known");
                }
            }
            else
            {
                // handle all other states 
                _logger.LogInformation("Payment has unhandled state {State}", assert.Transaction.TransactionState);

                // mark your order as canceled/not payed.
                // myOrderService.MarkPaymentAsFailed(orderId: result.Transaction.ReferenceId);

                // await _saferpayService.FinalizePayment(paymentNotifyContext.Payment.ReferenceId,
                //     TransactionState.CanceledPayment);
            }
        }
        catch (SaferpayException e)
        {
            // you can optionally handle all kinds of saferpay errors here
            // e.g. handle user abortion
            if (e.SaferpayError.ErrorName == SaferpayErrorName.TransactionAborted)
            {
                _logger.LogInformation(e, "User has aborted payment transaction");
            }
            else
            {
                _logger.LogWarning(e, "Payment failed");
            }
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Unable to process notify");
            throw;
        }
    }
}

Don't forget to register your listener.


context.Configure.RegisterSaferpayPaymentListener<ExamplePaymentNotifyListener>();

Limitations

  • Currently paydirekt and WL Crypto Payments are not supported payment methods.
  • only Currencies with tow digits after the comma will correctly be stored in the database. [//]: # (Status Pending is not implemented!)
Prev
Pattern Library Module
Next
Products Module