Contact me!
Eric Håård
+46 (0) 73 375 86 06

Checkout

When it comes to the CustomerService’s role in the checkout it boils down to mapping customer data from the checkout form to a Customer object and to decide if customers should be created in Storm Commerce or not.

If no customer is to be created, all there is to it is to populate a Customer object with data from the checkout form. Data can be name, email, address, phone and an additional company. That Customer object is then used in UpdateBuyer. UpdateBuyer will set Payer and ShipTo as well if they are not already defined. To set different Payer and ShipTo just call UpdatePayer and UpdateShipTo after UpdateBuyer.

When passing a Customer object to any of these Update-methods just pass one DeliveryAddress, even if the customer has several. The first will be picked if multiple address are passed in. Also note that the InvoiceAddress will end up on Payer and the DeliveryAddress will end up on ShipTo. No other adresses will be returned.

If a Customer is to be created it’s crucial to check if the customer already exists before creating the customer. This can be done with GetCustomerByEmail. If the customer does not already exists the process is the same as for when not creating customers as per above. Just add a call to CreateCustomer. If the customer exist then the mapped customer object from the checkout form must be updated with Customer.Id and Account from the existing instance. The same applies if a Company is to be used.
When sending in a Company, addresses are used from the Company if they exist.

Sample Code SendOrderButton_Click :

public void SendOrder() {
    ValidateCheckout();
    var customer = GetOrCreateCustomer();

    try
    {                    
        client.ShoppingProxy.UpdateBuyer(
             StormContext.BasketId.Value, customer, 1, 
             StormContext.PriceListIdSeed, StormContext.CultureCode, 
             StormContext.CurrencyId.ToString());
        Purchase();
    }
    catch (System.ServiceModel.FaultException<Expose.ErrorMessage_v2.ErrorMessage> ex)
    {
        HandlePurchaseException(ex);
    }
}
private Customer GetOrCreateCustomer()
{
    var customer = MapCustomer(); // Map data from Form
    if (CurrentCustomer == null || txtEmail.Text != CurrentCustomer.Email)
    {
        client.UseCache = false;
        var existing = client.CustomerProxy.GetCustomerByEmail(
           customer.Email, StormContext.CultureCode);
        client.UseCache = true;
        if (existing == null)
        {
            customer = client.CustomerProxy.CreateCustomer(customer,      
               StormContext.AccountId.GetValueOrDefault(1).ToString(), 
               StormContext.CultureCode);
        }
        else
        {
            // Copy Id, Account, Company and Addresses.
            customer = CopyCustomer(existing, customer); 
            customer = CreateCompany(customer);
        }
    }
    else 
    {
        // Copy Id, Account Company and Addresses.
        customer = CopyCustomer(CurrentCustomer, customer); 
        customer = CreateCompany(customer);
    }

    return customer;
}

The actual purchase in done with PurchaseEx which needs to be prepared with a NameValueCollection of parameters to the payment service:

public void Purchase() {
    var nvc = new NameValues 
    {
        new NameValue { 
         Name = "CancelUrl", 
         Value = "Url to page when user cancels the payment. Usually back to the checkout." },
        new NameValue { 
         Name = "ReturnUrl", 
         Value = "Url to the return url of asynchronous payments. Usually a Callback page." }
    };

    // For ResursBank also add
    new NameValue { 
     Name = "returnsignurl", 
     Value = "Url to the return url of paymnets. Usually a Callback page" }, 
    new NameValue { 
     Name = "returnfailsignurl", 
     Value = "Url to the fail url of payments. Usually a Callback page." },
    new NameValue { // Needed for business cards
     Name = "cardno",
     Value = "xxxxxx"
    }

    // For Klarna Checkout:
    var nvc = new NameValues 
    {
        new NameValue { 
         Name = "termsurl", 
         Value = "Url to page for terms and conditions." },
        new NameValue { 
         Name = "checkouturl", 
         Value = "Url to the checkout page." },
        new NameValue { 
         Name = "confirmationurl", 
         Value = "Url to the confirmation page." }
    };
}

Standard payment

var presponse = client.ShoppingProxy.PurchaseEx(StormContext.BasketId.Value, Request.UserHostAddress, Request.UserAgent, StormContext.PriceListIdSeed, StormContext.CultureCode, nvc);
var responseHandler = IoC.Container.Resolve<IRedirectCustomer>();
responseHandler.OnOrderConfirmedNotification += (x, y) => SendQuotationConfirmation();
responseHandler.RedirectCustomer(client, CurrentBasket, Context, presponse, "Url to the confirmed page.");

The ResponseHandler configured is responsible for handling the user redirect when placing the order. Synchronous payments (Invoice and such) will redirect the user directly to the confirmation page while asynchronous payments (Bank, card) will be redirected to the proper payment page at the Payment Service Provider (PSP). The SendQuotationConfirmation-EventHandler will be triggered for synchronous payments when payment returns with status = “OK”. This same handler should be used when returning from asynchronous payments as well, See Callback below.

Use RedirectCustomer to implement IRedirectCustomer.

Klarna checkout Form Payment

Form Payment is currently only used by Klarna Checkout payments. This is how you set up a Klarna Checkout Payment form

StormContext.SessionItems["snippet"] = null; 
CurrentCheckout = client.GetCheckout(StormContext.BasketId, StormContext.PricelistIdSeed, null, StormContext.CultureCode, StormContext.CurrencyId);

var model = new CheckoutModel {
     Basket = CurrentCheckout.Basket,
     DiscountCode = CurrentCheckout.DiscountCode,
     Script = GetCheckoutControl(nvc)
}

return View(model);

private string GetCheckoutControl(NameValue nvc)
{
   try 
   { 
          if (CurrentCheckout.Basket.Items.Count > 0) 
          {
               if (!string.IsNullOrWhiteSpace(StormContext.SessionItems["checkout"]))
               {
                     nvc.Add(new NameValue { Name = "checkoutId", Value = StormContext.SessionItems["checkout"] });
               }

               var paymentResponse = client.ShoppingProxy.GetPaymentForm(
                      StormContext.BasketId.Value,
                      Request.UserHostAddress,
                      Request.UserAgent,
                      StormContext.PriceListIdSeed,
                      StormContext.CultureCode,
                      nvc);

               StormContext.SessionItems["checkout"] = paymentResponse.RedirectUrl;

               return paymentResponse.PaymentReference;
           }
      }
      catch (Exception exception)
      { 
            StormContext.SessionItems["checkout"] = null;
            Response.Redirect(Request.RawUrl);
      }
 }

Gift card

To enable the giftcard functionality a giftcard needs to be reserved before the PurchaseEx method is called. A reservation can be canceled using the rollback method. All reserved giftcards will be redeemed after the amount left has been payed for when the purchase method is called.

PaymentGiftCardReserve

UpdatePaymentMethod needs to be called before this method.

Input:  cardNo = card number
        cvc = code
        basketid = id to identify current basket

Output: PaymentResponse object

Save the PaymentCode object for later usage.

PaymentGiftCardCheck (optional)

Input:   cardNo = card number
         cvc = code
         paymentMethodId = id of the payment method found in the checkout object

Output:  PaymentResponse object

PaymentGiftCardRollback

Input:   paymentParameters = Namevalue objects
                             Name="paymentcode", Value = paymentcode from reservation
                             Name="PaymentService", Value = "Retain24" or "Goyada"

Output:  PaymentResponse object

PurchseEx (optional)

Extra input paremeters: paymentParameters = Namevalue objects
                                            Name="cardno", Value = card number
                                            Name="cvc", Value = code
Output:   PaymentResponse object

Callback page

The Callback Page is a page that should handle the user when returning from the PSP after making a payment. the Url to this page is supplied in the NameValues-parameters to PurchaeEx above as “ReturnUrl” or “confirmationurl”.

Standard payment callback

Basically it just hooks up on events for Success, Fail or Default:

private void InitPaymentHandler()
{
    var handler = IoC.Container.Resolve<ICallbackHandler>();

    handler.OnOrderCallbackNotification += (x, y) =>
    {
         SendQuotationConfirmation();
    };

    handler.DefaultRedirect += (x, y) => {
         new StormUtils.Link("<Url to some default page>").Redirect();
    } 

    handler.SuccessRedirect += (x, y) => {
        new StormUtils.Link("<Url to the confirmed page>").Redirect();
    }

    handler.FailRedirect += (x, y) => {
        new StormUtils.Link("<Url to page when user cancels the payment. Usually back to the checkout>")
            .Set("paymentstatus", ((ICallbackHandler)x).StatusMessage).Redirect();
    }

    handler.ProcessRequest(HttpContext.Current); 
}

The Callback handler configured is responsible for handling the user redirected from a PSP after making the payment. The OnOrderCallbackNotification-EventHandler will be triggered when payment returns with status = “OK”. This is where the SendQuotationConfirmation() should be called to send a mail.

StormContext.BasketId is available in all events above, except for SuccessRedirect. After SuccessRedirect StormContext.BasketId is cleared and placed in StormContext.ConfirmedBasketId.

Sample Configuration under unity:

<namespace name="Enferno.Web.StormUtils" />
<assembly name="Enferno.Web.StormUtils" />
<container>
	<register type="IRedirectCustomer" mapTo="RedirectCustomer">
          <lifetime type="transient" ><constructor /></register>
	<register type="ICallbackHandler" mapTo="CallbackHandler">
            <lifetime type="transient" /><constructor /></register>
</container>

Use CallbackHandler to implement ICallbackHandler handlers.

Form payment callback

if (StormContext.SessionItems["checkout"] != null)
 {
 // Clear checkout session when confirmed so a new purchase can be made in the same session without reusing the session.
 StormContext.SessionItems["confirmed_checkout"] = StormContext.SessionItems["checkout"];
 StormContext.SessionItems["checkout"] = null;
 }
 if (StormContext.BasketId.HasValue)
 {
 // Clear BasketId from StormContext so a new basket will be created when customer puts a new item in basket.
 StormContext.ConfirmedBasketId = StormContext.BasketId;
 StormContext.BasketId = null;
 }
 // Reuse confirmed snippet if page is reloaded.
 var snippet = string.IsNullOrEmpty(StormContext.SessionItems["confirmed_snippet"]) ? GetConfirmedSnippet() : StormContext.SessionItems["confirmed_snippet"];
 
 var model = new ConfirmedModel {
 Snippet = snippet
 }
 return View(model);

private string Purchase()
 {
 string snippet = null;
 try
 {
 using (var client = new AccessClient())
 {
 var redirectParameters = new Expose.NameValues
 {
 new Expose.NameValue
 {
 Name = "checkoutId",
 Value = StormContext.SessionItems["confirmed_checkout"]
 },
 new Expose.NameValue
 {
 Name = "PaymentService",  // If other psp:s are active,
 Value = "KlarnaCheckout"  // set this for Klarna checkout v2 (KlarnaCheckoutV3 for Klarna chckout v3).
 }
 };
var response = client.ShoppingProxy.PaymentFormCallback(
BasketId,
StormContext.PriceListIdSeed,
StormContext.CultureCode,
redirectParameters); // For KCO v2 and RCO

var response = client.ShoppingProxy.PaymentCallback( 
BasketId, 
StormContext.PriceListIdSeed, 
StormContext.CultureCode, 
redirectParameters); // For KCO v3, will create order directtly (push notice to Strom will be sent 2 minutes later for v3)
snippet = response.PaymentReference;
 StormContext.SessionItems["confirmed_snippet"] = snippet;
 }
 }
 catch (Exception exception)
 {
 Log.LogEntry.Categories(CategoryFlags.Debug | CategoryFlags.Alert)
 .Message("Error on ConfirmationPage when creating the Klarna Checkout order.")
 .Exceptions(exception)
 .WriteError();
 }
 return snippet;
}

Important:
Note that StormContext.BasketId is moved to StormContext.ConfirmedBasketId so the customer will receive a new basket. If this is not done then the customer will continue to use his old basket. The same is done in the checkout code so customer get a new session.