Adyen C# HMAC Signature Calculation for Hosted Payment Page (HPP)

This is a C# example (Razor Pages in ASP.NET) showing how to do the HMAC signature calculation for a Adyen Hosted Payment Page…

@{
    Layout = "~/_ck_layout_adyen.cshtml";
}

@{
    Chilkat.Global glob = new Chilkat.Global();
    bool success = glob.UnlockBundle("Anything for 30-day trial");
    if (success != true) {
        @Html.Raw("<h2>Failed to unlock Chilkat</h2>");
        return;
    }

    <!-- The orderData contains the following HTML...  -->
    string orderData = @"<table class=""od"">
    <tr>
    <th>Description </th> <th>Quantity </th> <th>Amount </th>
    </tr>
    <tr>
    <td>1 Digital Camera (xyz)</td> <td class=""r"">1</td> <td class=""r"">100 GBP </td>
    </tr>
    <tr>
    <td class=""b"">Total</td> <td class=""r""></td> <td class=""b r"">100.00 GBP </td>
    </tr>
    </table>";

    Chilkat.Gzip gzip = new Chilkat.Gzip();
    string gzOrderData = gzip.CompressStringENC(orderData,"utf-8","base64");

    Chilkat.Xml xml = new Chilkat.Xml();
    xml.Tag = "keyValuePairs";

    xml.NewChild2("orderData", gzOrderData);

    // required, The payment deadline; the payment needs to occur within the specified time value.
    string sessionValidity = "2019-08-11T10:30:00Z";
    xml.NewChild2("sessionValidity", sessionValidity);

    // optional.  Normally we'll let Adyen automatically know the country based on the IP address.
    // By default, the payment methods offered to a shopper are filtered based on the country the shopper's IP address is mapped to. 
    // In this way, shoppers are not offered payment methods that are not available in the country they are carrying out the transaction from. 
    // This IP-to-country mapping is not 100% accurate, so if you have already established the country of the shopper, you can set it explicitly 
    // in the countryCode parameter.
    string countryCode = "GB";
    xml.NewChild2("countryCode", countryCode);
 
    // optional
    string shopperLocale = "en_GB";
    // If not specified, the locale preference is set to en_GB   by default.
    // When it is not necessary to include the country-specific part, use only the language code.
    // For example: it instead of it_IT to set the locale preferences to Italian.
    xml.NewChild2("shopperLocale", shopperLocale);

    // required, A reference to uniquely identify the payment. This reference is used in all communication with you about the payment status. 
    // We recommend using a unique value per payment; however, it is not a requirement. If you need to provide multiple references for a transaction, 
    // you can enter them in this field. Separate each reference value with a hyphen character ("-"). This field has a length restriction: 
    // you can enter max. 80 characters.
    string merchantReference = "paymentTest1234";
    xml.NewChild2("merchantReference", merchantReference);

    // required, The merchant account identifier you want to process the (transaction) request with.
    string merchantAccount = "ChilkatSoftwareIncCOM";
    xml.NewChild2("merchantAccount", merchantAccount);

    // required.  10000 for $100.00
    string paymentAmount = "10000";
    xml.NewChild2("paymentAmount", paymentAmount);

    // required, The three-character ISO currency code
    string currencyCode = "GBP";
    xml.NewChild2("currencyCode", currencyCode);

    // required.
    string skinCode =  "S5uVskfB";
    xml.NewChild2("skinCode", skinCode);

    // optional, A unique identifier for the shopper, for example, a customer ID.
    // We recommend providing this information, as it is used in velocity fraud checks. It is also the key in recurring payments.
    // This field is mandatory in recurring payments.  
    string shopperReference = "somebody@example.com";
    xml.NewChild2("shopperReference", shopperReference);

    // optional
    string shopperEmail = "somebody@example.com";
    xml.NewChild2("shopperEmail", shopperEmail);

    // optional, An integer value that adds up to the normal fraud score.
    // The value can be either a positive or negative integer.
    xml.NewChild2("offset", "0");

    // Apparently this is a required field.
    string shipBeforeDate = "2019-06-04";
    xml.NewChild2("shipBeforeDate", shipBeforeDate);

    xml.SortByTag(true);

    // Encode...
    //  "\" (backslash) as "\\"
    //  ":" (colon) as "\:"

    System.Text.StringBuilder sbTags = new System.Text.StringBuilder();
    System.Text.StringBuilder sbValues = new System.Text.StringBuilder();

    int n = xml.NumChildren;
    for (int i = 0; i < n; i++)
        {
        if (i > 0)
            {
            sbTags.Append(":");
            sbValues.Append(":");
            }
        xml.GetChild2(i);
        sbTags.Append(xml.Tag);
        sbValues.Append(xml.Content.Replace("\\", "\\\\").Replace(":", "\\:"));
        xml.GetParent2();
        }
    
    string signingStr = sbTags.ToString() + ":" + sbValues.ToString();

    Chilkat.Crypt2 crypt = new Chilkat.Crypt2();
    crypt.HashAlgorithm = "sha256";
    crypt.MacAlgorithm = "hmac";

    string hmacKey = "831D1D807DDD99596EB430076FD7F8E2D12D0A3F5024304C0C389A703618F749";
    crypt.SetMacKeyEncoded(hmacKey, "hex");

    crypt.EncodingMode = "base64";
    string merchantSig = crypt.HmacStringENC(signingStr);

    // You can test the HMAC Calculation by submitting a payment request to https://ca-test.adyen.com/ca/ca/skin/checkhmac.shtml

    string paymentRequestUrl = "https://test.adyen.com/hpp/select.shtml";

}



    <form method="post" action=@paymentRequestUrl id="adyenForm" name="adyenForm" target="_parent">
      <input type="hidden" name="orderData" value=@gzOrderData>
      <input type="hidden" name="merchantSig" value=@merchantSig />
      <input type="hidden" name="sessionValidity" value=@sessionValidity />
      <input type="hidden" name="shipBeforeDate" value=@shipBeforeDate />
      <input type="hidden" name="shopperLocale" value=@shopperLocale />
      <input type="hidden" name="merchantAccount" value=@merchantAccount />
      <input type="hidden" name="paymentAmount" value=@paymentAmount />
      <input type="hidden" name="currencyCode" value=@currencyCode />
      <input type="hidden" name="skinCode" value=@skinCode />
      <input type="hidden" name="countryCode" value=@countryCode />
      <input type="hidden" name="merchantReference" value=@merchantReference />
      <input type="hidden" name="shopperReference" value=@shopperReference />
      <input type="hidden" name="shopperEmail" value=@shopperEmail />
      <input type="hidden" name="offset" value="0" />
      <input type="submit" value="Send" />
      <input type="reset" />
    </form>