Implementation Examples
HTTP Client Libraries Used In The Examples
The example code in this guide uses strongly-typed HTTP client libraries to simplify calling the IPP APIs and handling JSON payloads. Any JSON manipulation will be done using the Gson library from google.
Using these libraries ensures that the examples are concise, consistent across languages, and easy to adapt into your own applications.
Java – Retrofit
The Java examples use Retrofit, a type-safe HTTP client for Java and Android. Retrofit lets you define your API as annotated Java interfaces. It then generates the client implementation at runtime, handling HTTP requests, responses, and JSON (de)serialization for you. This keeps the example code focused on request/response models and business logic, rather than low-level HTTP details.
C# – Refit
The C# examples use Refit, a REST library inspired by Retrofit. Like Retrofit, Refit allows you to declare your API as C# interfaces with attributes describing the HTTP methods, paths, and parameters. Refit then creates the client implementation dynamically, making the code easier to read, test, and maintain while abstracting away the underlying HTTP plumbing.
Example Interface Implemented In java and C#
Libraries use an interface that describes the endpoints and their required parameters.
// Retrofit Interface Example
public interface PosBuddyCloudApi {
@POST("payment/sync")
Call<JsonObject> payAppRequest(@HeaderMap Map<String, String> headers, @Body Map<String, Object> payAppRequest);
@POST("payment/async")
Call<JsonObject> asyncPayAppRequest(@HeaderMap Map<String, String> headers, @Body Map<String, Object> payAppRequest);
@POST("cardquery")
Call<JsonObject> cardQuery(@HeaderMap Map<String, String> headers, @Body Map<String, Object> cardQueryRequest);
@GET("transaction/{uuid}")
Call<JsonObject> transactionLookup(@HeaderMap Map<String, String> headers, @Path("uuid") String uuid);
@POST("transaction")
Call<JsonObject> transactionLookup(@HeaderMap Map<String, String> headers, @Body Map<String, Object> transactionLookupRequest);
@POST("terminal/{commandType}")
Call<JsonObject> genericCommand(@HeaderMap Map<String, String> headers, @Path("commandType") String commandType, @Body Map<String, Object> genericCommandRequest);
}// Refit Interface Example
public interface IPosBuddyCloudApi
{
[Post("/payment/sync")]
Task<JsonObject> PayAppRequest([HeaderCollection] IDictionary<string, string> headers, [Body] IDictionary<string, object> payAppRequest);
[Post("/payment/async")]
Task<JsonObject> AsyncPayAppRequest([HeaderCollection] IDictionary<string, string> headers, [Body] IDictionary<string, object> payAppRequest);
[Post("/cardquery")]
Task<JsonObject> CardQuery([HeaderCollection] IDictionary<string, string> headers, [Body] IDictionary<string, object> cardQueryRequest);
[Get("/transaction/{uuid}")]
Task<JsonObject> TransactionLookup([HeaderCollection] IDictionary<string, string> headers, string uuid);
[Post("/transaction")]
Task<JsonObject> TransactionLookup([HeaderCollection] IDictionary<string, string> headers, [Body] IDictionary<string, object> transactionLookupRequest);
[Post("/terminal/{commandType}")]
Task<JsonObject> GenericCommand([HeaderCollection] IDictionary<string, string> headers, string commandType, [Body] IDictionary<string, object> genericCommandRequest);
}In both cases of the examples two different enum classes are used to define the launch types for interacting with the payment app and generic command types for interacting with the POSBuddy device directly.
Launch Types
public enum LaunchType implements Serializable {
SALE,
REFUND,
CARDQUERY,
QR_CODE_PAYMENT,
TX_SEARCH
}public enum LaunchType
{
SALE,
REFUND,
CARDQUERY,
QR_CODE_PAYMENT,
TX_SEARCH
}Generic Command Types
public enum GenericCommandType implements Serializable {
PING,
SETTINGS,
ITEM_DISPLAY,
CLEAR_DISPLAY,
PRINT_HTML
}public enum GenericCommandType
{
PRINT_HTML,
SETTINGS,
ITEM_DISPLAY,
CLEAR_DISPLAY,
PING
}Create API Objects
To create the API object that will be used to interact with PosBuddy cloud, we use the builders from our retrofit/refit libraries.
private final PosBuddyCloudApi pbCloudApi = new Retrofit.Builder()
.baseUrl("https://paymentuat.test.thumbzup.com/posbuddy-cloud/v/1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PosBuddyCloudApi.class);private readonly IPosBuddyCloudApi _posBuddyCloudApi = RestService.For<IPosBuddyCloudApi>("https://paymentuat.test.thumbzup.com/posbuddy-cloud/v/1");In the case of the java implementation we add an additional converter factory from the Gson library from google to automatically serialize the object we pass to retrofit. In the C# instance this is not necessary the automatic serialization of objects is the default behaviour.
Objects Interaction
How to use the created API objects to interact with PosBuddy Cloud.
In most of these snippets you will see a function call to generate necessary the HTTP headers for the API call. That function is implemented as follows:
private Map<String, String> getRequestHeaders(String deviceSerial) {
try {
String timestamp = Long.toString(System.currentTimeMillis());
String randomVal = UUID.randomUUID().toString();
byte[] signature = getHmacSha256(secretKey.getBytes(), userAgent.getBytes());
signature = getHmacSha256(signature, timestamp.getBytes());
signature = getHmacSha256(signature, randomVal.getBytes());
return Map.of(
"X-pos-id", posId,
"X-tu-serial", deviceSerial,
"X-tu-date", timestamp,
"User-Agent", "CloudPos",
"x-tu-authorization", String.format("protocol:TU1,accesskey:%s,signedheaders:User-Agent;X-tu-date;X-tu-random,signature:%s", accessKey, HexFormat.of().formatHex(signature)),
"X-tu-random", randomVal
);
} catch (GeneralSecurityException e) {
return Map.of();
}
}
private byte[] getHmacSha256(byte[] key, byte[] data) throws GeneralSecurityException {
String algorithm = "HmacSHA256";
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Mac mac = Mac.getInstance(algorithm);
mac.init(secretKey);
return mac.doFinal(data);
}private Dictionary<string, string> GetRequestHeaders(string deviceSerial)
{
var timestamp = Math.Floor((DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString(CultureInfo.CurrentCulture);
var randomVal = Guid.NewGuid().ToString();
var signature = HMACSHA256.HashData(Encoding.ASCII.GetBytes(secretKey), Encoding.ASCII.GetBytes(userAgent));
signature = HMACSHA256.HashData(signature, Encoding.ASCII.GetBytes(timestamp));
signature = HMACSHA256.HashData(signature, Encoding.ASCII.GetBytes(randomVal));
return new Dictionary<string, string>
{
{"X-pos-id", posId},
{"X-tu-serial", deviceSerial},
{"X-tu-date", timestamp},
{"User-Agent", "CloudPos"},
{"x-tu-authorization", $"protocol:TU1,accesskey:{accessKey},signedheaders:User-Agent;X-tu-date;X-tu-random,signature:{Convert.ToHexString(signature).ToLower()}"},
{"X-tu-random", randomVal},
};
}Pay App Request
public JsonObject doPayAppRequest(String targetSerialNumber, LaunchType launchType, long amount, @Nullable Map<String, Object> extraParameters) throws IOException {
Map<String, String> headers = getRequestHeaders(targetSerialNumber);
Map<String, Object> request = new HashMap<>(Map.of(
"merchantID", mid,
"serialNumber", targetSerialNumber,
"posId", posId,
"launchType", launchType.name(),
"transactionAmount", amount
));
if (extraParameters != null && !extraParameters.isEmpty()) {
request.put("extraParameters", extraParameters);
}
return pbCloudApi.payAppRequest(headers, request).execute().body();
}public Task<JsonObject> DoPayAppRequest(string targetSerialNumber, LaunchType launchType, long amount, IDictionary<string, object>? extraParameters = null)
{
var headers = GetRequestHeaders(targetSerialNumber);
var request = new Dictionary<string, object>
{
{ "merchantID", mid },
{ "serialNumber", targetSerialNumber },
{ "posId", posId },
{ "launchType", launchType.ToString() },
{ "transactionAmount", amount }
};
if (extraParameters is { Count: > 0 })
{
request.Add("extraParameters", extraParameters);
}
return _posBuddyCloudApi.PayAppRequest(headers, request);
}Pay App Request Example
With the pay app request, there are currently three of the launch types that are used here (sale, refund and QR payment). The pay app requests all require the same base parameters to function so they are included in the function body. The only aspect that that parameters for these launch types differ is in the optional extraParameters field.
The examples use the synchronous endpoint. To use the asynchronous enpoint simply replace the PayAppRequest call with the AsyncPayAppRequest call as we defined in our API interface.
With these optional parameters a dictionary/map with the following example content can be used for each launch type:
// Example content for a matched refund (for unmatched refund pass null in the "extraParamers" field)
{
"originalTransactionUuid": "862475fe-f46c-4a2b-aec8-7f21e98724e4"
}
// Example content for a QR payment
{
"transactionUuid" : "bdf9d0af-17b3-48ca-8a0b-37dc52bf49bc",
"externalRRN": "HcMQSFcY",
"externalTerminalId": "98100010",
"externalInvoiceGUID": "2fdca02f-3cbe-4e8c-82ad-86a1a16b72e8",
"externalTransactionDateTime" : "2025-09-17T09:16:59.6387832+00:00"
}Transaction Search
GET
public JsonObject doTransactionSearch(String targetSerialNumber, String transactionUuid) throws IOException {
Map<String, String> headers = getRequestHeaders(targetSerialNumber);
return pbCloudApi.transactionLookup(headers, transactionUuid).execute().body();
}public Task<JsonObject> DoTransactionSearch(string targetSerialNumber, string transactionUuid)
{
var headers = GetRequestHeaders(targetSerialNumber);
return _posBuddyCloudApi.TransactionLookup(headers, transactionUuid);
}POST
public JsonObject doPayAppTxSearch(String targetSerialNumber, String transactionUuid) throws IOException {
Map<String, String> headers = getRequestHeaders(targetSerialNumber);
Map<String, Object> extraParameters = Map.of(
"transactionUuid", transactionUuid
);
Map<String, Object> request = Map.of(
"merchantID", mid,
"serialNumber", targetSerialNumber,
"posId", posId,
"launchType", LaunchType.TX_SEARCH.name(),
"extraParameters", extraParameters
);
return pbCloudApi.transactionLookup(headers, request).execute().body();
}public Task<JsonObject> DoPayAppTxSearch(string targetSerialNumber, string transactionUuid = "")
{
var headers = GetRequestHeaders(targetSerialNumber);
var extraParameters = new Dictionary<string, object>
{
{ "transactionUuid", transactionUuid }
};
var request = new Dictionary<string, object>
{
{ "merchantID", mid },
{ "serialNumber", targetSerialNumber },
{ "posId", posId },
{ "launchType", nameof(LaunchType.TX_SEARCH) },
{ "extraParameters", extraParameters }
};
return _posBuddyCloudApi.TransactionLookup(headers, request);
}Card Query
public JsonObject doCardQuery(String targetSerialNumber) throws IOException {
Map<String, String> headers = getRequestHeaders(targetSerialNumber);
Map<String, Object> request = Map.of(
"merchantID", mid,
"serialNumber", targetSerialNumber,
"posId", posId,
"launchType", LaunchType.CARDQUERY.name()
);
return pbCloudApi.cardQuery(headers, request).execute().body();
}public Task<JsonObject> DoCardQuery(string targetSerialNumber)
{
var headers = GetRequestHeaders(targetSerialNumber);
var request = new Dictionary<string, object>
{
{ "merchantID", mid },
{ "serialNumber", targetSerialNumber },
{ "posId", posId },
{ "launchType", nameof(LaunchType.CARDQUERY) }
};
return _posBuddyCloudApi.CardQuery(headers, request);
}Commands
public JsonObject doGenericCommand(String targetSerialNumber, GenericCommandType genericCommandType, @Nullable Map<String, Object> commandParameters) throws IOException {
Map<String, String> headers = getRequestHeaders(targetSerialNumber);
Map<String, Object> request = new HashMap<>(Map.of(
"merchantID", mid,
"serialNumber", targetSerialNumber,
"posId", posId
));
if (commandParameters != null && !commandParameters.isEmpty()) {
request.putAll(commandParameters);
}
return pbCloudApi.genericCommand(headers, genericCommandType.name(), request).execute().body();
}public Task<JsonObject> DoGenericCommand(string targetSerialNumber, GenericCommandType genericCommandType, IDictionary<string, object>? commandParameters = null)
{
var headers = GetRequestHeaders(targetSerialNumber);
var request = new Dictionary<string, object>
{
{ "merchantID", mid },
{ "serialNumber", targetSerialNumber },
{ "posId", posId }
};
if (commandParameters is not { Count: > 0 })
{
return _posBuddyCloudApi.GenericCommand(headers, genericCommandType.ToString(), request);
}
foreach (var pair in commandParameters)
{
request.Add(pair.Key, pair.Value);
}
return _posBuddyCloudApi.GenericCommand(headers, genericCommandType.ToString(), request);
}Updated about 15 hours ago
