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
- You can install the Analytics module from NuGet with
dotnet add package Confinity.Payment.Saferpay
In your
Startup.csyou can now register the module with the module builder by calling theAddModulemethod withSaferpayPaymentModule. See Modules documentation for more information.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!)