Capturing Payments Using Mastercard MIGS Service And ASP.NET Core

OVERVIEW

 
Good day everyone! Today we will explore Mastercard MIGS payment service and learn how to capture payments from your clients through the provided API using C# and ASP.NET Core.
 
It is worth mentioning that the payment component we are going to create can be used from any project (not just ASP.NET core projects.) Moreover, it is very easy to port our test code to MVC, WebForms or even console/desktop apps.
 
 

FULL SOURCE CODE

Full code and its testing web application are available in the following link. The code is fully documented and easy to follow.

The packge is also available on NuGet here.

INTRODUCTION
 
Mastercard provides a service called MIGS (Mastercard Internet Gateway Service) that merchants can use to capture and collect funds from their clients as well as to issue refunds and monitor reports. This service exposes its functionality through an API called VPC (Virtual Payment Client) that can be easily accessed using some sort of credentials that merchants receive from their acquiring bank.
 
In other words, the VPC (Virtual Payment Client) acts as an interface that provides a secure method of communication between your online store and the Mastercard payment server, which facilitates the processing of payments.
 
 
For this to work, as a merchant you must register for the e-payment service through your bank which will give you two sets of credentials that can be used to communicate with VPC, the testing and the production credentials.
 

SECURE CREDENTIALS

 
Each secure credential (testing/production) that you will receive form the bank consists of five pieces,
  • Merchant ID: which you can think of as the username.
  • Access Code: which you can think of as the password.
  • Secure Hash Secret: which can be used for integrity validation (which we will cover later.) This code must be kept in a safe place.
  • The AMA user: will be covered later in the transaction queries.
  • The AMA password: will be covered later in the transaction queries.

INTEGRATION MODELS

 
To collect and capture your funds from clients you need to use one of two models:
  • Server-Hosted Model
  • Merchant-Hosted Model
SERVER-HOSTED MODEL / 3-PARTY
 
Server-Hosted Model, also called bank-hosted and 3-party, is where you do not handle client card information directly. Instead, you send the user to a secure page hosted by Mastercard where user can enter their card information and you wait for the page to return to you with the response.
 
 
Pros and cons of this model,
  • Pros
    • You are totally free of securing user card information.
  • Cons
    • User must be present.
    • Cannot be used in offline or mail/telephone orders.
    • You must check for pending transactions if the page has not returned to you (e.g., user has closed the browser or an internet issue.)
MERCHANT-HOSTED MODEL / 2-PARTY
 
Merchant-Hosted Model, also called 2-party, is where you handle client card information directly. You ask the user for card information or retrieve it from a database and send all the required information to VPC using a web request, where the VPC replies instantly with relevant information.
 
 
Pros and cons of the 2-party model,
  • Pros
    • No waits. You get the response instantly.
    • User may not be present.
    • Can be used with mail/telephone orders.
  • Cons
    • You must handle the security of credit card page and data being stored in the database (for example.)
ENDPOINTS
 
To start utilizing a model, you need to use its relevant endpoint which is available in the following table,
 
 Model Endpoint
 Server-Hosted  vpcpay
 Merchant-Hosted  vpcdps
 

PAYMENT TRANSACTIONS

 
Now let us get to work. To issue a payment request to the server you should include the following parameters,
 
 Group  Parameter Description
 Common  vpc_Version  API version. Currently covered version is ‘1’.
 vpc_Command  The command name, which will be ‘pay’ for payment requests.
 Authentication  vpc_Merchant  Your merchant ID.
 vpc_AccessCode  Your access code.
 Order Details  vpc_MerchTxnRef  Your unique transaction reference.
 vpc_OrderInfo  Your order ID.  
 vpc_Amount  Amount expressed in the smallest currency unit expressed as integer. For example, if the transaction amount is $49.95 then the amount value should be 4995.  
 
TRANSACTION REFERENCE VS. ORDER INFO
 
Now you might ask what is the difference between transaction reference and order info? And what makes transaction reference strictly recommended to be unique?
 
The answer is that ever asked yourself what will happen if the user in a server-hosted transaction closes the Mastercard page after a successful payment without returning to your site? The answer is without the unique transaction reference you will lose access to user’s payment transaction.
 
Using unique transaction references allows you to identify each payment request and to link them to user/order. And if you did not get a response from the payment request, you can use the unique reference to query about payment status later using the QueryDR command (will be covered later.)
 
You can also pass the order ID in the vpc_OrderInfo field keeping in mind that each order may have multiple payment requests, however, each payment request can only be
linked to a single order.
 
SERVER-HOSTED PAYMENT TRANSACTIONS
 
If you are going to use the server-hosted integration model, you will have to point your call to the correct API endpoint, which is vpcpay for server-hosted model, and provide two more parameters besides the parameters mentioned above. One of those two, as you might guess, is the return URL (i.e., callback URL.)
 
 Parameter  Description
 vpc_ReturnURL
The URL that Mastercard gateway will navigate to when the transaction completes. In this URL you can define a handler to process the payment transaction results. You may define a Thank You page, a receipt page, or something like this. Keep in mind that not all transactions are successful.
This URL must be:
  • Absolute
  • SSL-compliant.
 vpc_Locale  The ISO two-letter language code for the Mastercard server pages. Examples are: en (English), ar (Arabic), and es (Spanish.)
 
Now the question is: How do we send those parameters to the VPC API? Will we include them in body? Will we encode them as JSON? The answer is no! We simple include them as query string. As we are in server-hosted model, we will prepare our URL that has those parameters in the query string and then we will just navigate to this URL and wait for it to navigate back to our return URL (vpc_ReturnURL.)
 
 
Worth mentioning that you can define your custom parameters as long as they do not start with ‘vpc_’.
 
MERCHANT-HOSTED PAYMENT TRANSACTIONS
 
On the other hand, the merchant-hosted transactions use the endpoint vpcdps. It uses extra 3 parameters that you can easily guess,
 
 Parameter  Description
 vpc_Card  Card number
 vpc_CardExp  Card expiry date expressed in yyMM format. For example, Jan 2021 is expressed as 2101.
 vpc_CardSecurityCode  The card security code.
 
Now let us put the pieces together.
 
 
RESPONSE PARAMETERS
 
If we are using the server-hosted model, we may receive the response as query parameters of our callback URL. On the other hand, if we are using the merchant-hosted model, we may receive the response as query string in the body of the web response. Those parameters include,
 
 Parameter  Description
 vpc_MerchTxnRef  Our unique transaction reference.
 vpc_OrderInfo  Our order number.
 vpc_TxnResponseCode  Transaction response code. A value of ‘0’ indicates a successful transaction.
 vpc_Message  Indicates any error messages the transaction may have encountered.
 vpc_ReceiptNo  Transaction unique identifier. Also called the Reference Retrieval Number (RRN).
 vpc_AuthorizeId  An identifying code issued by the bank to approve or deny the transaction.
 vpc_BatchNo  A date supplied by the bank to indicate when this transaction will be settled.
 vpc_CardType  Card type. For example, ‘MC’ for Mastercard cards.
 
REQUEST AND RESPONSE SAMPLES
 
Here is an example of a request URL,
 
https://migs.mastercard.com.au/vpcpay?vpc_AccessCode=77426638&vpc_Amount=10025&vpc_Command=pay&vpc_Locale=en&vpc_MerchTxnRef=TX-1&vpc_Merchant=TESTEGPTEST&vpc_OrderInfo=100&vpc_ReturnURL=https://localhost:44376/Home/PaymentCallback&vpc_Version=1
 
Here in this link, you can see that the endpoint is ‘vpcpay’ which indicates a server-hosted model (i.e., we will navigate the user to this link. We can also see that the amount is 10025 which means $100.25 (or whatever currency we are operating.) We can also see that we have specified our return URL as ‘https://localhost:44376/Home/PaymentCallback. Which the user will be automatically navigated to when he completes the transaction.
 
When the user completes the transaction, he will be navigated to,
 
https://localhost:44376/Home/PaymentCallback&vpc_Amount=121300&vpc_AuthorizeId=586587&vpc_BatchNo=20210129&vpc_CSCResultCode=M&vpc_Card=MC&vpc_Command=pay&vpc_MerchTxnRef=TX-1&vpc_Merchant=TESTEGPTEST&vpc_Message=Approved&vpc_OrderInfo=O-1&vpc_ReceiptNo=103006586587&vpc_TxnResponseCode=0&vpc_Version=1
 
No need for more explanation. The parameters are easy to understand and follow.
 
INTEGRITY CHECKING
 
Ever wondered, what if a user simulates the payment process and just called the above URL of your site with any fake values?! Will you still count it as a valid transaction?
 
How do you differentiate between the true and fake responses? The answer is the integrity checking.
 
The last third in the secure credentials that we did not cover yet is the secure hash secret. This code is used to prevent the cardholder from modifying a transaction request and response when passing it though cardholder’s browser. This is what we call integrity checking.
 
Integrity checking works by using the hash secret code as a key to hash all our parameters and to send the generated hash along with our call to the server which in turn validates the hash and proceeds if valid. On the contrary, the server hashes all response parameters using the same hash secret code and returns the response along with the generated hash to our application, which we can validate by regenerating the hash and comparing it to the returned value.
 
Three things should be noted in this process,
  1. VPC requires the hashed parameters to be ordered by ASCII ordinal values before hashing, e.g., the capital ‘E’ is before the small ‘a’.
  2. VPC requires the usage of either HMAC SHA-256 or MD5 hashing algorithm.
  3. The hash secret must be kept safe, it should not be exposed under any circumstances.
So, for this to work, we have two extra parameters that will be included in the request,
 
 Parameter  Description
 vpc_SecureHash  The generated hash.
 vpc_SecureHashType  The hashing mechanism, which is ‘SHA256’ or ‘MD5’.
 
Beware not to include your original secret hash.
 
Let us see this in action.
 
SHA-256 HASHING
 
For SHA-256 hashing you can use the following procedure for requests,
 
 
On the other hand, here is the response integrity checking mechanism for SHA-256.
 
 
MD5 HASHING
 
For MD5 request hashes use the following procedure,
 
 
For response checking, use the following procedure,
 
 

TRANSACTION QUERIES

 
Previously we have raised an issue about what will happen in server-hosted transactions if the user has closed the browser (for example) without returning to your site? The answer is in your unique merchant transaction number. You can use this number in the query API to query for transactions that has this number. That is why this number is strictly recommended to be unique.
 
The query command is based on the merchant-hosted model. And that means there will be no navigations, you will create the request and read its response instantly. It means also that it will use the vpcdps endpoint.
 
One critical thing to mention is that the transaction query command is part of the Advanced Merchant Administration (AMA) commands. Those advanced commands use an extra security credentials called the AMA username and password. Those credentials must be included as query parameters in the request.
 
For the transaction query request, we are going to use the following parameters,
 
 Group  Parameter  Description
 Common  vpc_Version  API version. Currently covered version is ‘1’.
 vpc_Command  The command name, which will be ‘queryDR’.
 Authentication  vpc_Merchant  Your merchant ID.
 vpc_AccessCode  Your access code.
 vpc_User  AMA username.
 vpc_Password  AMA password.
 Request Details  vpc_MerchTxnRef  Your unique transaction reference.
 
For the response, we are going to have the following parameters,
 
 Parameter  Description
 vpc_DRExists  Returns ‘Y’ if the requested transaction reference has been found.
 vpc_FoundMultipleDRs  Returns ‘Y’ if there’s multiple transactions with the requested merchant transaction reference.
 vpc_Amount  If one transaction found, we are going to have its amount here.
 vpc_BatchNo  If one transaction found, we are going to have its batch number here.
 vpc_TransactionNo  If one transaction found, we are going to have its unique transaction number here.
 

TEST ENVIRONMENT

 
CREDENTIALS
 
Here is a set of test credentials that can be used in the payment process. Normally you will receive your test credentials from your acquiring bank. You can also search the internet and get few test credentials like those.
 
 Item  Value
 Merchant ID  TESTEGPTEST
 Access Code  77426638
 Secure Hash  7E5C2F4D270600C61F5386167ECB8DA6
 
TEST CARDS
 
Here are a set of test cards that you can use,
 
 Item  Card #1  Card #2
 Type  Mastercard  Visa
 Number  5123456789012346  4987654321098769
 Expiry Date  05/21  05/21
 Security Code  100  100
 

CODE GLIMPSE

 
In this section we will go through the code very quickly.
 
Let us start with query parameters. To get our code generating parameters dynamically and to differentiate between parameter fields and other fields, we created an attribute that will be used to decorate only parameter fields.
  1. [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]  
  2. public class QueryParamAttribute : Attribute  
  3. {  
  4.   /// <summary>  
  5.   /// Target parameter name.  
  6.   /// </summary>  
  7.   public string Name { getset; }  
  8.   
  9.   /// <summary>  
  10.   /// Is parameter required. Indicates whether to include an empty string if parameter value is null/empty.  Default is True.  
  11.   /// </summary>  
  12.   public bool IsRequired { getset; }  
  13. }  
Next, we started defining our class hierarchy and parameter properties,
  1. public abstract class VpcCommand  
  2. {  
  3.   /// <summary>  
  4.   /// Command name.  
  5.   /// </summary>  
  6.   [QueryParam(Name = "vpc_Command", IsRequired = true)]  
  7.   public abstract string Command { get; }  
We used reflection to get the parameter query properties and their corresponding values,
  1. private static IEnumerable<MemberInfo> GetObjectQueryMembers(object targetObject)  
  2. {  
  3.   IEnumerable<MemberInfo> queryMembers;  
  4.   
  5.   // Loads for instance properties  
  6.   queryMembers = targetObject.GetType().GetProperties();  
  7.   // Loads for instance fields  
  8.   queryMembers = queryMembers.Concat(targetObject.GetType().GetFields());  
  9.   
  10.   // Checks QueryParamAttribute existence  
  11.   queryMembers = queryMembers.Where(a => a.GetCustomAttributes<QueryParamAttribute>().Any());  
  12.   return queryMembers;  
  13. }  
We used a simple code to generate and concatenate query strings,
  1. public static string CreateQueryString(IEnumerable<QueryParameter> parameters)  
  2. {  
  3.   string queryStr = string.Empty;  
  4.   foreach (var param in parameters)  
  5.   {  
  6.     queryStr += string.Format("{0}={1}&", param.Name, param.Value);  
  7.   }  
  8.   
  9.   queryStr = queryStr.TrimEnd('&');  
  10.   return queryStr;  
  11. }  
And here is our secure hash generating code,
  1. public virtual string SHA256Hash(string hashSecret, IEnumerable<QueryParameter> queryParams)  
  2. {  
  3.   queryParams = queryParams.OrderBy(a => a.Name, StringComparer.Ordinal);  
  4.   string queryStr = QueryManager.CreateQueryString(queryParams);  
  5.   return Sha256(queryStr, hashSecret);  
  6. }  
  7.   
  8.   
  9. public virtual string MD5Hash(string hashSecret, IEnumerable<QueryParameter> queryParams)  
  10. {  
  11.   queryParams = queryParams.OrderBy(a => a.Name, StringComparer.Ordinal);  
  12.   
  13.   string str = hashSecret + string.Join("", queryParams.Select(a => a.Value));  
  14.   
  15.   return MD5(str);  
  16. }  
And the code to execute a merchant-hosted command is fairly straightforward,
  1. public virtual string ExecuteCommandRaw(VpcCommand cmd)  
  2. {  
  3.   string reqUrl = ComputeCommand(cmd);  
  4.   
  5.   WebRequest req = HttpWebRequest.Create(reqUrl);  
  6.   
  7.   req.Method = "POST";  
  8.   
  9.   using (var stm = req.GetResponse().GetResponseStream())  
  10.   using (var stmReader = new StreamReader(stm))  
  11.   {  
  12.     return stmReader.ReadToEnd();  
  13.   }