Paying with a gift card is the most common scenario of paying an order with multiple payment methods. This white paper concentrates on gift cards specifically, the methods described here can be applied to combine any number of payment methods.
This is a description of when the gift card payment is lower than the order grand total. When the gift card available amount is larger or equal to the order grand total, it is the ordinary case of single payment scenario.
The buyer should be able to pay part of my order with a gift card and remaining with another payment method such as credit card.
The merchant should be able to fulfill the order in several shipments, if all items ordered cannot be sent in a single shipment. When this is done, the value of the shipment should be taken from the buyer (if buyer’s payment is only reserved at the time of placing the order); that is capture the relevant amount.
The buyer should also be able to return all or part of the items bought.
The payment provider, should be able to send a receipt for the payments a buyer has made, which includes which items are bought in that payment.
High-level requirements
- Pay with multiple payment methods, such as gift cards.
- Partial order returns, with partial refunds.
Use Cases
This section explains uses cases, where detailed calculation is shown with how the information in related entities are represented. For simplicity and easy understanding, only the relevant columns of the entities are shown.
Instead of Guids, “text” reference names are used on purpose to make it easy to understand. Some additional columns are added where necessary, for understanding and illustration purposes.
Example order:
For the following use cases, following order is used.

Use case: Buy with a gift card
The buyer is paying part of the order by using a gift card with a value of 100 SEK, and the remainder with a credit card. Following shows how it will look after the buyer has confirmed and paid for the order.

Normally, the gift cards do not have a “Reserve” function, and applies “Captured” as state directly.
In above diagram, the status of the gift card payment is “Paid” when the order is confirmed, shown as authorized and captured above, and the credit card payment is “Reserved”, shown as Authorized above.
For the gift card provider, following are payment info rows, transaction rows.

Observe that the whole set of order rows in the order are used, together with a row showing that a different payment method is used to pay for 1100 SEK. The total amount deducted from the gift card is therefore 100 SEK.
On the other hand, payment info rows for credit card is as follows.
The fact that 100 SEK is paid by a gift card is presented in the rows which is sent to the credit card provider. This row has the type “ExternalReference”.
This way, if the buyer requests transaction details from either the gift card provider or the credit card provider, the full picture of how the payment is carried out is received. The receipts sent out from both the gift card provider and the credit card provider will be consistent with each other and will also be consistent with the items bought by the buyer.
Implementation
Implementation of payment capture is different based on how the payment is captured.
For solutions like Klarna checkout, the Klarna iframe requires the amount that will subsequently be authorized. This means that the custom gift card payment capture must be done after the iframed capture, since the iframed capture happens outside Litium and Litium is not aware of when the buyer is clicking the “confirm” button which is inside the iframe.
For solutions like Adyen HPP, the redirect to the payment provider happens after the user has confirmed, therefore the gift card payment can be captured first.
Authorize and Capture with Redirect model payments
Do the order total calculation the usual way.
In the default implementation, it results in a single paymentInfo. The paymentInfo should be based on the card payment provider.

The resulting order has only one PaymentInfo. The PaymentInfo should be split in two, according to the way it is described in the use cases above.
Since we are not overriding the OrderTotalCalculation, nor the creation of PaymentInfoRows, create a copy of the PaymentInfo and do the reverse adjustment Row, to adjust the PaymentInfo total.
Get the payment for the GiftCard first. If it is successful, you can safely call the CardPayment for the remaining amount.
Use the AdditionalPaymentInfo to record the cross referencing required to point out the two related payments. This way it is clearer if there are other paymentInfos coming into the same payment. Other PaymentInfos will come, if you are doing “Partial shipments” if the shipment needs to be partially captured.
If the Gift card payment is not successful, you may decide to inform the buyer.
When the error is reported, make sure that the two PaymentInfos are removed, and let the code create a new PaymentInfo. This way the conflicts will be removed.
Authorize and Capture with iframed payment checkouts
In iframed checkouts like Klarna checkout, you need to do the steps before the order placement. After the order totals are calculated, split the PaymentInfo according to above and send the respective payment info to the iframed checkout provider.
Handling Returns and Cancellations
When processing returns, you need to calculate the refund amounts and decide which payment is used to process the refund. It can be that both payments may need to be refunded.
Following shows an example of processing a partial refund. If we use the example above in Use Cases, one X and one Y is returned.

Since the gift card only has 100 SEK in it, we can choose to refund the total amount using a credit card. However, assume that we decide to refund 100 SEK from the gift card and the remaining 100 SEK from the credit card, then the transactions would look as follows.

It results in two PaymentInfos into the SalesReturnOrder.
Allow the normal calculation to proceed, and then adjust the paymentInfoRows and amounts the same way as Authorize call.
Code sample
These modifications were made on Litium Accelerator 7.4.2.
CheckoutServiceImpl.cs, method PlaceOrder, after order was placed (line 210), adding two highlighted lines below:
public override ExecutePaymentResult PlaceOrder(CheckoutViewModel model, string responseUrl, string cancelUrl, out string redirectUrl)
{
var shouldRedirect = false;
var redirectArgs = string.Empty;
var checkoutFlowInfo = _requestModelAccessor.RequestModel.Cart.CheckoutFlowInfo;
checkoutFlowInfo.RequireConsumerConfirm = false;
checkoutFlowInfo.ExecuteScript = (args, redirect) => { redirectArgs = args; shouldRedirect = redirect; };
checkoutFlowInfo.ResponseUrl = responseUrl;
checkoutFlowInfo.CancelUrl = cancelUrl;
var currentLanguage = _languageService.Get(_requestModelAccessor.RequestModel.ChannelModel.Channel.WebsiteLanguageSystemId.Value);
checkoutFlowInfo.SetValue(CheckoutConstants.ClientLanguage, currentLanguage.CultureInfo.Name);
checkoutFlowInfo.SetValue(CheckoutConstants.ClientTwoLetterISOLanguageName, currentLanguage.CultureInfo.TwoLetterISOLanguageName);
if (model != null)
{
SetOrderDetails(model);
}
_cartService.PlaceOrder(out PaymentInfo[] paymentInfos);
if (paymentInfos != null && paymentInfos.Length > 0)
{
try
{
var giftCardValue = 100m;
PrepareGiftCardPayment(paymentInfos[0].OrderID, giftCardValue);
var order = _moduleECommerce.Orders.GetOrder(paymentInfos[0].OrderID, _moduleECommerce.AdminToken);
ExecutePaymentResult result = null;
foreach (var paymentInfo in order.PaymentInfo)
{
result = paymentInfo.ExecutePayment(checkoutFlowInfo, SecurityToken.CurrentSecurityToken);
}
Method PrepareGiftCardPayment :
/// <summary>
/// Add giftcard paymentInfo and update original paymentInfo to have PaidBy-GiftCard reference.
/// </summary>
/// <param name="orderId">Order identifier.</param>
/// <param name="giftCardValue">Gift card amount.</param>
protected virtual void PrepareGiftCardPayment(Guid orderId, decimal giftCardValue)
{
var order = ModuleECommerce.Instance.Orders[orderId, _moduleECommerce.AdminToken];
if (order == null)
{
throw new DoesNotExistException($"Order {orderId} does not exists.");
}
if (order.PaymentInfo.Count() != 1)
{
return;
}
//TODO If giftcard value larger than the order grand total, we should use gifcard for the whole payment info. This is not covered in this example.
if (giftCardValue > order.GrandTotal)
{
return;
}
//TODO Should have a paymentInfoId for giftcard payment, currently we use Litium internal DirectPay as Gift card provider.
var directPaymentInfo = _moduleECommerce.PaymentMethods.Get("DirectPayment", "DirectPay", _moduleECommerce.AdminToken);
var index = order.PaymentInfo[0].Rows.Max(x => x.Index);
var orderCarrier = order.GetAsCarrier(true, true, true, true, true, true);
var originalPaymentInfo = orderCarrier.PaymentInfo[0];
// Creating giftcard payment by cloning the original.
var giftCardPaymentInfo = originalPaymentInfo.Clone(true, true);
giftCardPaymentInfo.ID = Guid.NewGuid();
giftCardPaymentInfo.ReferenceID += "_GiftCard";
giftCardPaymentInfo.BillingAddress.ID = Guid.NewGuid();
giftCardPaymentInfo.PaymentMethod = directPaymentInfo.Name;
giftCardPaymentInfo.PaymentProvider = directPaymentInfo.PaymentProviderName;
giftCardPaymentInfo.BillingAddress.CarrierState.IsMarkedForCreating = true;
foreach (var row in giftCardPaymentInfo.Rows)
{
row.PaymentInfoRowID = Guid.NewGuid();
row.PaymentInfoID = giftCardPaymentInfo.ID;
row.CarrierState.IsMarkedForCreating = true;
}
//adding payment adjustment for original payment info.
originalPaymentInfo.Rows.Add(new PaymentInfoRowCarrier()
{
PaymentInfoRowID = Guid.NewGuid(),
PaymentInfoID = originalPaymentInfo.ID,
Index = index,
ReferenceID = giftCardPaymentInfo.ID.ToString(),
ReferenceType = PaymentInfoRowType.ExternalReference,
Quantity = 1,
Description = "PaidBy-GiftCard",
//The payment adjustment should have negative value.
TotalPriceWithoutRounding = giftCardValue * -1,
TotalPrice = giftCardValue * -1,
TotalVatAmountWithoutRounding = 0,
TotalVatAmount = 0,
VatPercentage = 0
});
//Adjust original payment total.
originalPaymentInfo.TotalAmountWithVAT -= giftCardValue;
originalPaymentInfo.CarrierState.IsMarkedForUpdating = true;
//adding payment adjustment for gift card.
giftCardPaymentInfo.Rows.Add(new PaymentInfoRowCarrier()
{
PaymentInfoRowID = Guid.NewGuid(),
PaymentInfoID = giftCardPaymentInfo.ID,
Index = index,
ReferenceID = originalPaymentInfo.ID.ToString(),
ReferenceType = PaymentInfoRowType.ExternalReference,
Quantity = 1,
Description = "PaidBy-" + originalPaymentInfo.PaymentProvider,
//The payment adjustment should have negative value.
TotalPriceWithoutRounding = (originalPaymentInfo.TotalAmountWithVAT - giftCardValue) * -1,
TotalPrice = (originalPaymentInfo.TotalAmountWithVAT - giftCardValue) * -1,
TotalVatAmountWithoutRounding = 0,
TotalVatAmount = 0,
VatPercentage = 0,
});
giftCardPaymentInfo.CarrierState.IsMarkedForCreating = true;
//Adjust giftcard payment total.
giftCardPaymentInfo.TotalAmountWithVAT = giftCardValue;
orderCarrier.PaymentInfo.Add(giftCardPaymentInfo);
orderCarrier.CarrierState.IsMarkedForUpdating = true;
order.SetValuesFromCarrier(orderCarrier, _moduleECommerce.AdminToken);
}
This is the result when creating an order using front store and pay with Klarna HP:


Giftcard value set to 100 SEK :

Payment in the back office:


