|
This describes the process of cancelling the remaining part of an order, ie cancelling the items that has not yet been included in a shipment. This operation can be done both from the Backoffice UI and by API calls.
Cancel an order from Backoffice UI
Go to the Order Details page, and from the menu. select the Cancel remaining products option to execute the process of order cancellation.

If the order has a pending payment, the process of cancellation will be stopped and the error message "Cannot finalize the order due to payment in pending" is shown. Otherwise, the system finds all items that are not part of any shipment and adds them to a automatically created shipment of type Cancellation.
Then, this shipment is set to Processing state, and a cancel or refund transaction is created from it:
- If the payment is already captured, a refund transacton is created.
- Otherwise, a cancel transaction is created.
Litium calls the payment app with the cancel or refund transaction. The payment app executes the cancel or refund, and notifies Litium of the result at which point the payment status will be updated in the system. In case of success, the cancellation shipment is set to Cancelled state, and the order is set to Completed state.
Cancel an order from Connect ERP API
It is a common scenario that it is the ERP system that initiates a cancellation of an order, eg because items have become out of stock during order processing. Since Connect ERP API 2.1, the Finalize action can be called to cancel the remaining part of an order:
/Litium/api/connect/erp/orders/{orderId}/action/finalize
The call to this endpoint will initiate the same process as the Backoffice UI, ie creating a Cancellation shipment and managing the cancellation or refund of the associated payment.
How to cancel the remaining part of an order in the .NET Code
ShipmentManager can be used to create a cancellation shipment and use StateTransitionService to set the shipment to Processing:
public class SampleController
{
private readonly OrderOverviewService _orderOverviewService;
private readonly StateTransitionsService _stateTransitionService;
private readonly PaymentStateService _paymentStateService;
private readonly ShipmentService _shipmentService;
private readonly ShipmentManager _shipmentManager;
private readonly ILogger<SalesOrderController> _logger;
public SampleController(
OrderOverviewService orderOverviewService,
StateTransitionsService stateTransitionService,
PaymentStateService paymentStateService,
ShipmentService shipmentService,
ShipmentManager shipmentManager,
ILogger<SalesOrderController> logger)
{
_orderOverviewService = orderOverviewService;
_stateTransitionService = stateTransitionService;
_paymentStateService = paymentStateService;
_shipmentService = shipmentService;
_shipmentManager = shipmentManager;
_logger = logger;
}
/// <summary>
/// Finalize the order.
/// </summary>
/// <param name="systemId">The sales order system identifier.</param>
[Route("{systemId}/action/finalizeOrder")]
[HttpPost]
[ProducesResponseType(202)]
public async Task<IHttpActionResult> FinalizeOrder(Guid systemId)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var orderOverview = _orderOverviewService.Get(systemId);
if (orderOverview is null || orderOverview.SalesOrder is null)
{
return NotFound();
}
else if (_paymentStateService.GetPaymentStatuses(orderOverview).Values.Any(x => x == PaymentState.Pending))
{
ModelState.AddModelError("general", "Cannot finalize the order due to payment in pending.");
return BadRequest(ModelState);
}
try
{
var shipment = await _shipmentManager.CreateCancelShipment(orderOverview.SalesOrder.SystemId);
_shipmentService.Create(shipment);
_stateTransitionService.SetState<Shipment>(shipment.SystemId, ShipmentState.Processing);
return Accepted();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error when trying to finalize the order.");
ModelState.AddModelError("general", ex.Message);
return BadRequest(ModelState);
}
}
}
Then, when the shipment state is changed to Processing, TransactionFactory can be used to create transaction, then execute the cancel or refund:
public async Task ShipmentChangedToProcessingState(Shipment shipment)
{
var orderOverview = _orderOverviewService.Get(shipment.OrderSystemId);
if (orderOverview is null || orderOverview.SalesOrder is null)
{
throw new ArgumentException($"The order ({shipment.OrderSystemId}) cannot be found.");
}
var paymentOverview = orderOverview.PaymentOverviews.FirstOrDefault();
if (paymentOverview is null || paymentOverview.Payment is null)
{
throw new ArgumentException("The payment cannot be found.");
}
if (orderOverview.IsFullyCaptured())
{
var refundTransaction = await _transactionFactory.CreateRefundTransactions(shipment);
_transactionService.Create(refundTransaction);
await _paymentTransactionsProcessor.EnqueueAsysnc(new() { TransactionSystemId = refundTransaction.SystemId });
}
else
{
var cancelTransaction = await _transactionFactory.CreateCancelTransactions(shipment);
_transactionService.Create(cancelTransaction);
await _paymentTransactionsProcessor.EnqueueAsysnc(new() { TransactionSystemId = cancelTransaction.SystemId });
}
}
|