5Appendix A
Returns only the first half of the generated Hash. This is to make brute force attack harder.
It’s easier to guess the MAC key for a single request, but harder to know if this is the correct key since many keys can generate the first half of the output.
5.1.1SHA1 Hash .Net
public static String GetMAC(String generatedString)
{
Encoding enc = Encoding.UTF8;
Byte[] rawMessage = enc.GetBytes(generatedString);
SHA1 sha1 = new SHA1CryptoServiceProvider();
Byte[] result = sha1.ComputeHash(rawMessage);
string mac = Convert.ToBase64String(result);
//split the string in 2 and return the first half
mac = mac.Substring(0, mac.Length / 2);
return mac;
}
5.1.2SHA1 Hash – JAVA
public class Sha1Hash {
/** Creates a new instance of Sha1Hash */
public Sha1Hash() {
}
/**
* @param inputData
* @return
*/
public String getHashedValue(String inputData) {
String sResp = null;
try {
byte byteHash[];
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(inputData.getBytes("utf-8"));
byteHash = sha1.digest();
sha1.reset();
// see code snippet below..
String hash = Base64Util.encode(byteHash);
return hash.substring(0, (hash.length()/2));
} catch (Exception e) {
System.err.println("getHashedValue failed: " + e.getMessage());
return null;
}
}
private String toHexString(byte[] array) {
StringBuilder sb = new StringBuilder ();
for (int i=0; i< array.length; i++) {
String hex = "0"+Integer.toHexString(array[i]);
String end = hex.substring(hex.length()-2);
sb.append(end.toUpperCase());
}
System.err.println("HASH:"+sb.toString());
return sb.toString();
}
}
/**
* Freeware from:
* Roedy Green
* Canadian Mind Products
* #208 - 525 Ninth Street
* New Westminster, BC Canada V3M 5T9
* tel: (604) 777-1804
* mailto:roedy@mindprod.com
*/
/**
* Encode arbitrary binary into printable ASCII using BASE64 encoding.
* very loosely based on the Base64 Reader by
* Dr. Mark Thornton
* Optrak Distribution Software Ltd.
* http://www.optrak.co.uk
* and Kevin Kelley's
*http://www.ruralnet.net/~kelley/java/Base64.java
*/
public class Base64Util
{
/**
* how we separate lines, e.g. \n, \r\n, \r etc.
**/
static String lineSeparator = System.getProperty( "line.separator");
/**
* max chars per line. A multiple of 4.
**/
private static int lineLength = 72;
/**
* determines how long the lines are that are generated by encode.
* Ignored by decode.
* @param length 0 means no newlines inserted.
*/
public static void setLineLength(int length)
{
lineLength = length;
}
/**
* letter of the alphabet used to encode binary values 0..63
**/
static final char[] valueToChar = new char[64];
/**
* binary value encoded by a given letter of the alphabet 0..63
**/
static final int[] charToValue = new int[256];
/**
* Marker value for chars we just ignore, e.g. \n \r high ascii
*/
static final int IGNORE = -1;
/**
* Marker for = trailing pad
*/
static final int PAD = -2;
static
{
// build translate valueToChar table only once.
// 0..25 -> 'A'..'Z'
for ( int i=0; i<=25; i++ )
valueToChar[i] = (char)('A'+i);
// 26..51 -> 'a'..'z'
for ( int i=0; i<=25; i++ )
valueToChar[i+26] = (char)('a'+i);
// 52..61 -> '0'..'9'
for ( int i=0; i<=9; i++ )
valueToChar[i+52] = (char)('0'+i);
valueToChar[62] = '+';
valueToChar[63] = '/';
// build translate charToValue table only once.
for ( int i=0; i<256; i++ )
{
charToValue[i] = IGNORE; // default is to ignore
}
for ( int i=0; i<64; i++ )
{
charToValue[valueToChar[i]] = i;
}
charToValue['='] = PAD;
}
/**
* Encode an arbitrary array of bytes as Base64 printable ASCII.
* It will be broken into lines of 72 chars each. The last line
* is not
* terminated with a line separator.
*/
public static String encode(byte[] b)
{
// Each group or partial group of 3 bytes becomes four chars
int outputLength = ((b.length + 2) / 3) * 4;
// account for embedded newlines
outputLength += (outputLength / lineLength) *
lineSeparator.length();
// must be local for recursion to work.
StringBuffer sb = new StringBuffer( outputLength );
// must be local for recursion to work.
int linePos = 0;
// first deal with even multiples of 3 bytes.
int len = (b.length / 3) * 3;
int leftover = b.length - len;
for ( int i=0; i
{
// Start a new line if next 4 chars won't fit on the current line
// We don't encapsulate so that linePos and sb will work recursively
{
linePos += 4;
if ( linePos > lineLength )
{
linePos = 0;
if ( lineLength != 0) {sb.append(lineSeparator);}
}
}
// get next three bytes in unsigned form lined up,
// in big-endian order
int combined = b[i+0] & 0xff;
combined <<= 8;
combined |= b[i+1] & 0xff;
combined <<= 8;
combined |= b[i+2] & 0xff;
// break those 24 bits into a 4 groups of 6 bits,
// working LSB to MSB.
int c3 = combined & 0x3f;
combined >>>= 6;
int c2 = combined & 0x3f;
combined >>>= 6;
int c1 = combined & 0x3f;
combined >>>= 6;
int c0 = combined & 0x3f;
// Translate into the equivalent alpha character
// emitting them in big-endian order.
sb.append( valueToChar[c0]);
sb.append( valueToChar[c1]);
sb.append( valueToChar[c2]);
sb.append( valueToChar[c3]);
}
// deal with leftover bytes
switch ( leftover )
{
case 0:
default:
// nothing to do
break;
case 1:
// One leftover byte generates xx==
// Start a new line if next 4 chars won't fit on the current line
// We don't encapsulate so that linePos and sb will work recursively
{
linePos += 4;
if ( linePos > lineLength )
{
linePos = 0;
if ( lineLength != 0) {sb.append(lineSeparator);}
}
}
// Handle this recursively with a faked complete triple.
// Throw away last two chars and replace with ==
sb.append(encode(new byte[] {b[len], 0, 0}
).substring(0,2));
sb.append("==");
break;
case 2:
// Two leftover bytes generates xxx=
// Start a new line if next 4 chars won't fit on the current line
// We don't encapsulate so that linePos and sb will work recursively
{
linePos += 4;
if ( linePos > lineLength )
{
linePos = 0;
if ( lineLength != 0) {sb.append(lineSeparator);}
}
}
// Handle this recursively with a faked complete triple.
// Throw away last char and replace with =
sb.append(encode(new byte[] {b[len], b[len+1], 0}
).substring(0,3));
sb.append("=");
break;
} // end switch;
if ( outputLength != sb.length() ) System.out.println("oops");
return sb.toString();
}// end encode
}
5.2Postback service examples 5.2.1Java JSP postback
<%@page import="org.w3c.dom.*,javax.xml.xpath.*,javax.xml.parsers.*,java.io.IOException,org.xml.sax.SAXException" %>
<%@page import="javax.servlet.ServletInputStream,javax.servlet.http.HttpServletRequest"%>
<%@page import="java.io.BufferedReader,java.io.InputStreamReader"%>
<%@page import="java.io.*" %>
<%@page import="org.xml.sax.InputSource" %>
<%
try
{
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder builder = domFactory.newDocumentBuilder();
//Load the XML document in the parser
Document doc = builder.parse(new InputSource(newStringReader(request.getParameter("PostbackRequestWrapper"))));
XPath xpath = XPathFactory.newInstance().newXPath();
// XPath Query for showing all nodes value
XPathExpression expr;
//Set the filter to find the elements
//Header parameters
expr = xpath.compile("//PostbackRequestWrapper/Header/AcquirerID/text()");
String AcquirerID = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Header/AgreementID/text()");
String AgreementID = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Header/MerchantID/text()");
String MerchantID = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Header/TransmissionTime/text()");
String TransmissionTime = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Header/ClientIP/text()");
String ClientIP = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Header/PostbackURL/text()");
String PostbackURL = (String) expr.evaluate(doc, XPathConstants.STRING);
//PostbackRequest parameters
expr = xpath.compile("//PostbackRequestWrapper/PostbackRequest/Operation/text()");
String Operation = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/PostbackRequest/Amount/text()");
String Amount = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/PostbackRequest/BatchID/text()");
String BatchID = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/PostbackRequest/OrderID/text()");
String OrderID = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/PostbackRequest/TransactionID/text()");
String TransactionID = (String) expr.evaluate(doc, XPathConstants.STRING);
//Status parameters
expr = xpath.compile("//PostbackRequestWrapper/Status/TransactionStatus/text()");
String TransactionStatus = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Status/TransactionStatusDesc/text()");
String TransactionStatusDesc = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Status/OperationStatus/text()");
String OperationStatus = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Status/OperationStatusDesc/text()");
String OperationStatusDesc = (String) expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("//PostbackRequestWrapper/Footer/MAC/text()");
String MAC = (String) expr.evaluate(doc, XPathConstants.STRING);
}
catch (Exception e) {
throw e;
}
%>
5.2.2ASP.net postback service
// Read the incoming HTTPS stream
StreamReader reader = new StreamReader(Request.InputStream, Encoding.Default);
if (!reader.EndOfStream)
{
string xmlString = reader.ReadToEnd();
xmlString = HttpUtility.UrlDecode(xmlString.Replace("PostbackRequestWrapper=", ""));
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
// Create a Hashtable to store values
Hashtable values = new Hashtable();
values.Add("AcquirerID", xmlDoc.GetElementsByTagName("AcquirerID").Item(0).FirstChild.InnerText);
values.Add("AgreementID", xmlDoc.GetElementsByTagName("AgreementID").Item(0).FirstChild.InnerText);
values.Add("MerchantID", xmlDoc.GetElementsByTagName("MerchantID").Item(0).FirstChild.InnerText);
values.Add("TransmissionTime",xmlDoc.GetElementsByTagName("TransmissionTime").Item(0).FirstChild.InnerText);
values.Add("ClientIP", xmlDoc.GetElementsByTagName("ClientIP").Item(0).FirstChild.InnerText);
values.Add("PostbackURL", xmlDoc.GetElementsByTagName("PostbackURL").Item(0).FirstChild.InnerText);
values.Add("PostbackRequest",xmlDoc.GetElementsByTagName("PostbackRequest").Item(0).FirstChild.InnerText);
values.Add("Operation", xmlDoc.GetElementsByTagName("Operation").Item(0).FirstChild.InnerText);
values.Add("Amount", xmlDoc.GetElementsByTagName("Amount").Item(0).FirstChild.InnerText);
values.Add("BatchID", xmlDoc.GetElementsByTagName("BatchID").Item(0).FirstChild.InnerText);
values.Add("OrderID", xmlDoc.GetElementsByTagName("OrderID").Item(0).FirstChild.InnerText);
values.Add("TransactionID", xmlDoc.GetElementsByTagName("TransactionID").Item(0).FirstChild.InnerText);
values.Add("TransactionStatus", xmlDoc.GetElementsByTagName("TransactionStatus").Item(0).FirstChild.InnerText);
values.Add("TransactionStatusDesc",xmlDoc.GetElementsByTagName("TransactionStatusDesc").Item(0).FirstChild.InnerText);
values.Add("OperationStatus", xmlDoc.GetElementsByTagName("OperationStatus").Item(0).FirstChild.InnerText);
values.Add("OperationStatusDesc", xmlDoc.GetElementsByTagName("OperationStatusDesc").Item(0).FirstChild.InnerText);
values.Add("MAC", xmlDoc.GetElementsByTagName("MAC").Item(0).FirstChild.InnerText);
}
6Appendix C: Simulator for API development
The API provides a simulator returning predefined transaction status codes depending on input parameters in the request. Developers using the API can use this simulator to force errors and test error handling in their application. The WSDL (service description) is identical to the actual API to be used in production. The simulator tries to emulate the behavior of the real service by maintaining a transaction history of the last 200 transactions and return an appropriate response code, but does not guarantee 100% compatibility.
6.1URL
URL for MAP PaymentService simulator:
https://mpaymenttest.payex.com/MAP/PaymentServiceTest?wsdl
6.2Valid request parameters
Parameter
|
Value
|
Remarks
|
AquirerID
|
WyWallet
|
|
AgreementID
|
One of 99990095, 99990096 or 99990097
|
|
ClientType
|
MSISDN
|
|
ClientID
|
004699999nnn
|
nnn is a number in the range 000…999.
Other values will return transaction status 6 (Account not found)
|
MAC
|
|
Shared secret = map123
| 6.3Response codes
The API will return a non-zero transaction status or timeout for the following combinations of Operation and ClientID:
Operation
|
ClientID
/MSISDN
|
Transaction status
|
Remarks
|
CheckOrder
|
0046999994nn
|
nn
|
nn = 01..20
|
CheckOrder
|
004699999499
|
Timeout
|
Takes 1 minute to complete
|
Purchase
|
0046999995nn
|
nn
|
nn = 01..20
ClientID is fetched from original Purchase transaction
|
Purchase
|
004699999599
|
Timeout
|
Takes 1 minute to complete
nn = 01..20
ClientID is fetched from original Purchase
|
Reversal
|
0046999996nn
|
nn
|
nn = 01..20
|
Reversal
|
004699999699
|
Timeout
|
Takes 1 minute to complete
nn = 01..20
|
Credit
|
0046999997nn
|
nn
|
nn = 01..20
|
Credit
|
004699999799
|
Timeout
|
Takes 1 minute to complete
|
6.4Frequently asked questions
Question
|
Answer
|
I have problems calculating the MAC Key for my request.
|
The most common problem is that there are missing parameters in the concatenated string.
Please see our source code examples for examples on how to generate the MAC. If you are using another programming language or still have problems you can try and use our Online MAC generator to get help and compare your calculations with a verified source.
https://shop.wywallet.se/mac-generator.aspx
|
Why do I get the error code 3 “Agreement not found”?
|
Most probably this is due to the fact that the payment request is sent to the wrong payment system / URL. Please see section for relevant systems and URL:s
If you still get the same error code after confirming that the correct system is used it could be a misconfiguration in our system. Please contact merchant support for further assistance.
|
I’ve submitted my PostbackURL in the payment request but I’ve waited and there is no HTTP POST to my URL.
|
Most merchant accounts are configured to be synchronous where you receive the payment status immediately and postbacks aren’t used. If you have a synchronous account you can ignore to set the PostbackURL parameter in your request.
If you have service with high performance requirements (e.g. TV & event votings) you should ask WyWallet merchant support (re)configure your account to asynchronous.
If you have an asynchronous account and you don’t receive and callbacks please check that you have entered a HTTPS url (we only allow postbacks via SSL port 443 due to security restrictions) and that your firewall accepts incoming traffic on this IP & port.
|
Is there a time limit for requesting a reversal on a transaction?
|
There are no technical time limit implemented and reversals are always accepted as long as the end user still has a WyWallet account.
|
Do you provide mobile short codes or SMS services?
|
No, WyWallet is a dedicated payment service and only handle the actual payment. Short codes and SMS services can either be ordered via each MNO/Operator or by using a SMS aggregator. If you don’t have
|
6.5Merchant support
For questions regarding the integration please contact our helpdesk merchant-support@wywallet.se
2017-01-05 WyWallet SMS Payment API v0.4 Page ()
Share with your friends: |