App integration
Here's everything you require for integrating with the OderoPOS Android system, enabling your application to seamlessly interact with our devices for card payments and printing via a set of clearly defined APIs. Our tools simplify the integration process, ensuring ease and speed while minimizing the need for future adjustments on your end. OderoPOS, integrated into the Android system, is continuously maintained and updated, forming our robust ecosystem.
Launcher
In order to be recognized by Token Platform and be visible in the Launcher, please also add the following lines to AndroidManifest.xml
file.
AndroidManifest.xml
<application>
<!-- Other fields -->
<!-- App Name should be started with LYL, thus Launcher can differentiate sale apps -->
<meta-data android:name="app_name" android:value="LYL_APP_NAME" />
<meta-data android:name="app_model_type" android:value="400TR" />
<meta-data android:name="app_version" android:value="1" />
<meta-data android:name="sale_activity_name" android:value="<internal_packages>.<activity_name>" />
</application>
Please ensure that sale_activity_name
is the Activity that will be shown to the customers as the first screen.
<activity android:name=".activity.MyActivity"
android:exported="true"></activity>
<!-- Example Usage -->
<meta-data android:name="sale_activity_name" android:value=".android.MyActivity" />
PaymentGateway
Allows payment requests in a uniform way across all POS family.
The API is agnostic of the underlying payment procedures, which may involve banking, card reading, etc.
The PGW will take care of all the printing during a payment request. The caller does not need to handle it via the printer app.
Setup
In order to access the PGW app, the following pgw-launcher and common library should be included in the app. To do this add the following maven url to settings.gradle
under dependencyResolutionManagement/repositories:
settings.gradle
maven {
url 'https://ro-artifactory.devtokeninc.com/artifactory/PublicLibraries/'
}
And also in app/build.gradle
the actual dependency should be added: implementation 'PaymentGateway:pgw-launcher:1.0.9'
build.gradle
dependencies {
implementation 'PaymentGateway:pgw-launcher:1.0.9'
}
Request payment
The payment request is done via an ActivityResultLauncher contract.
Obtaining the PGW contract
val paymentResultContract = PGWLauncher.getPGWContract(context)
Returns null if the PGW app is not installed on the device.
Payment request
import ro.odero.paymentgateway.common.data.CurrencyCode
import ro.odero.paymentgateway.common.data.PaymentOption
import ro.odero.paymentgateway.common.data.request.PaymentRequest
import ro.odero.paymentgateway.launcher.PGWLauncher
val pgwContract = PGWLauncher.getPGWContract(context)
if (pgwContract == null) {
}
val paymentLauncher = registerForActivityResult(pgwContract) {
paymentResponse : PaymentResponse ->
//payment response
}
paymentLauncher.launch(PaymentRequest(
amount = 12345,
paymentOptions = listOf(PaymentOption.Card),
currencyCode = CurrencyCode.RON
))
According to the selected payment options PGW
will handle the payment and printing. The amount will be internally translated as amount/100 to cover the currency decimal; in this case 12345 will internally converted to 123,45
Handling PaymentResponse
Check the PaymentResponse based on the payment options sent.
PaymentResponse handling
paymentResponse : PaymentResponse ->
if(paymentResponse is CardPaymentResponse)...
if(paymentResponse is CashPaymentResponse)...
Types of PaymentResponse
sealed class CardPaymentResponse : PaymentResponse {
data class Success(
val receiptNumber: String,
val bankActivityResultCode: Int,
val bankStatus: Int,
val customerSlip: String,
val merchantSlip: String,
) : CardPaymentResponse()
data class Fail(
val receiptNumber: String,
val bankActivityResultCode: Int,
val bankStatus: Int,
val message: String,
) : CardPaymentResponse()
}
sealed class CashPaymentResponse : PaymentResponse {
data class Success(
val receiptNumber: String,
) : CashPaymentResponse()
data class Fail(
val receiptNumber: String,
val message: String,
) : CashPaymentResponse()
}
Printer App
Allows printing in a uniform way across all POS family.
The API is agnostic of the underlying hardware but provides a set of well defined PrintCommands.
Setup
In order to access the Printer App, the following printer-launcher
and common
library should be included in the app. To do this add the following code into settings.gradle
under dependencyResolutionManagement/repositories:
settings.gradle
maven {
url 'https://ro-artifactory.devtokeninc.com/artifactory/PublicLibraries/'
}
Add in build.gradle
the lib dependencies: implementation 'PrinterApp:printer-launcher:1.0.3'
(the pgw-launcher should also add the common lib, if not please add the dependency implementation 'PrinterApp:common:1.0.3'
)
build.gradle
dependencies{
implementation 'PrinterApp:printer-launcher:1.0.3'
}
Printing
There are two distinct flows that have to be implemented
- Checking if printer is ready to print
- The check includes also if paper is present
When printer is ready print the receipts
val printerErrorCheck = registerForActivityResul(PrinterHealthResultContract()){
printerHealth: PrinterHealthResponse? ->
if(printerHealth.hasErrors.not()){
printReceipts()
}else
{
//treat the error
}
}
- Actual printing
Actual printing contract that is used to start the PrinterApp
val printerLauncher = registerForActivityResult(PrinterResultContract()) {
printerResponse: PrinterResponse? ->
//treat printer response
}
Receipts example
val customerReceipt = listOf(
PrintCommand.PrintSpace,
PrintCommand.PrintImage(image), PrintCommand.PrintSpace,
PrintCommand.PrintWithBold(true),
PrintCommand.PrintText(
"Test SRL",
TextAlignment.Left,
),
PrintCommand.PrintWithBold(false),
PrintCommand.PrintSpace,
PrintCommand.PrintText(
"Data:10.03.2023",
TextAlignment.Left,
),
PrintCommand.PrintText(
"Ora:10:00",
TextAlignment.Right,
),
PrintCommand.PrintText(
"TID:0 MID:0",
TextAlignment.Left,
),
PrintCommand.PrintSpace,
PrintCommand.PrintSpace,
PrintCommand.PrintText(
"AID:00000",
TextAlignment.Right,
),
PrintCommand.PrintText(
"0000000000",
TextAlignment.Left,
),
PrintCommand.PrintFontSize(FontSize.Small),
PrintCommand.PrintColumns(arrayOf("BATCH:000001", "BON:000001")),
PrintCommand.PrintColumns(arrayOf("RRN:00123456789", "RC: 00")),
PrintCommand.PrintColumns(arrayOf("AUTH:775040", "STAN:012345")),
PrintCommand.PrintLine, PrintCommand.PrintWithBold(true),
PrintCommand.PrintFontSize(FontSize.Large),
PrintCommand.PrintColumns(arrayOf("SUMA:", "50")),
PrintCommand.PrintWithBold(false), PrintCommand.PrintSpace,
PrintCommand.PrintText("APPROVED", TextAlignment.Left),
PrintCommand.PrintFontSize(FontSize.Medium),
PrintCommand.PrintText("EXEMPLAR CLIENT", TextAlignment.Left),
PrintCommand.PrintLine, PrintCommand.PrintSpace,
PrintCommand.PrintSpace,
)
val merchantReceipt = listOf(
PrintCommand.PrintSpace,
PrintCommand.PrintImage(image), PrintCommand.PrintSpace,
PrintCommand.PrintWithBold(true),
PrintCommand.PrintText(
"Test SRL",
TextAlignment.Left,
),
PrintCommand.PrintWithBold(false),
PrintCommand.PrintSpace,
PrintCommand.PrintText(
"Data:10.03.2023",
TextAlignment.Left,
),
PrintCommand.PrintText(
"Ora:10:00",
TextAlignment.Right,
),
PrintCommand.PrintText(
"TID:0 MID:0",
TextAlignment.Center,
),
PrintCommand.PrintSpace,
PrintCommand.PrintSpace,
PrintCommand.PrintText(
"AID:00000",
TextAlignment.Right,
),
PrintCommand.PrintText(
"0000000000", // cardno
TextAlignment.Left,
),
PrintCommand.PrintFontSize(FontSize.Small),
PrintCommand.PrintColumns(arrayOf("BATCH:000001", "BON:000001")),
PrintCommand.PrintColumns(arrayOf("RRN:00123456789", "RC: 00")),
PrintCommand.PrintColumns(arrayOf("AUTH:775040", "STAN:012345")),
PrintCommand.PrintLine, PrintCommand.PrintWithBold(true),
PrintCommand.PrintFontSize(FontSize.Large),
PrintCommand.PrintColumns(arrayOf("SUMA:", "50")),
PrintCommand.PrintWithBold(false), PrintCommand.PrintSpace,
PrintCommand.PrintText("APPROVED", TextAlignment.Left),
PrintCommand.PrintFontSize(FontSize.Medium),
PrintCommand.PrintText("EXEMPLAR VANZATOR", TextAlignment.Center),
PrintCommand.PrintLine, PrintCommand.PrintSpace,
PrintCommand.PrintSpace,
)
Printing
fun printReceipts(){
printerLauncher.launch(
PrinterRequest(
listOf(
PrintDocument(
clientReceipt,
"Chitanta client",
),
PrintDocument(merchantReceipt, "Chitanta vanzator"),
),
PrintRequestType.PrintWithDialog, // will show a dialog between each print
// document
// PrintRequestType.SimplePrint //will print each document back-to-back - useful
// for setlements, reports, etc
)),
}
PrinterResponse structure
data class PrinterResponse(
val status: Status,
val message: String,
val data: List<DocumentData>,
) {
enum class Status {
SUCCESS,
ERROR,
}
}
OPIT
Odero POS Integration Toolkit for Developers
Welcome to OPit, the Odero POS Integration Toolkit designed to streamline and enhance the communication between a Windows device (PC, laptop) and an Odero POS terminal. OPit enables developers to send commands such as Payment Requests, Settlements, Reports, and more, directly to the Odero POS terminal over a TCP/IP socket connection. By leveraging this powerful toolkit, developers can seamlessly integrate their Windows applications with Odero POS, unlocking new possibilities for efficient and secure payment processing.
At its core, OPit provides a reliable and secure channel for data exchange between the Windows device and the Odero POS terminal. This communication is facilitated through TCP/IP sockets, allowing for real-time, bidirectional communication over a local network. To ensure a seamless connection, both the Windows device and the Odero POS terminal must be connected to the same router or Wi-Fi network.
With OPit, developers gain full control over the payment flow, enabling them to initiate payment requests from their Windows applications and receive responses from the Odero POS terminal. This integration empowers developers to create custom workflows, tailor the user experience, and incorporate Odero POS functionality seamlessly into their existing applications. Whether you're building a point-of-sale system, a payment gateway, or any other payment-related application, OPit provides the tools you need to connect and communicate effortlessly with Odero POS.
By leveraging the power of OPit, developers can harness the extensive capabilities of Odero POS, such as processing transactions, generating reports, performing settlements, and more, directly from their Windows applications. This seamless integration ensures a cohesive experience for users and eliminates the need for manual data entry or redundant workflows. With OPit, you can empower your users with a unified and efficient payment processing solution, offering a streamlined experience that enhances productivity and accuracy.
In this developer documentation, we will provide you with comprehensive guidance on utilizing OPit to integrate your Windows applications with Odero POS. You'll find detailed instructions, sample code snippets, and best practices to help you make the most of this powerful toolkit. Whether you're a seasoned developer or just getting started, this documentation will serve as your roadmap to successfully integrating OPit and unlocking the full potential of Odero POS in your applications.
We're excited to have you embark on this journey with OPit, and we look forward to witnessing the innovative solutions you'll build with the power of seamless communication between Windows devices and Odero POS terminals. Let's dive in and get started!
Introduction
OPit Lan for Windows is a C++ library which allows for a Windows device (PC, laptop) to send commands (Payment Request, Settlement, Reports etc) to an Odero Pos terminal.
The communication between the two devices is performed through TCP/IP sockets.
Both devices have to be connected to the same router / wifi network.
Please follow the below instructions for setup:
- To use OPIT Lan you will need to use static ips for both devices, on the client tablet and the Odero POS terminal, and to have a dedicated router with - potentially - DHCP off (depends on the router)
Tutorial on how to set up static ip on Windows can be found here - Connect to wifi on the dedicated router on both devices.
- The Odero POS terminal must have the ip 192.168.1.1 this is the ip where the client device expects to find the host.
- Install app-lan-host-release.apk on the Odero POS terminal and OpitDesktopSampleApp on the Windows device.
- Start the OPIT Lan app on the Odero POS terminal.
- Start Sample app on windows.
- Send payment request.
Below you can find the OPit Lan for Windows diagram.
- OPit lan host on Odero POS terminal is started and the socket server is booted up.
- Client app (that implement OPit library) starts on windows device and connects to the socket server from the Odero POS terminal.
- Client app receives onConnected callback.
- Client app sends payment request via opened tcp/ip socket and is received on the Odero POS terminal.
- Client app terminates the socket connection.
- Client app receives onDisconnected callback.
- Odero POS terminal switchs from wifi to gsm data as required by the bank app in order to perform the transaction on secure connection. Also the host socket server is shut down.
- Odero POS terminal host awaits for response from the bank app.
- Client app sends connnection ping requests attempting to connect to the socket server on the host device. These attempts fail at this point because the socket server is shut down as described in previous step.
- Host app receives response from the bank app, network switch from gsm to wifi occurs and the socket server is started.
- Client app connection request is successful.
- Host app on Odero POS terminal sends the response from the bank app and is received on the client app (windows)
The client app side implementation is rather simple, only step 4 is required to be perfomed, all other steps on the client side are performed under the hood inside the library.
Setup
- Download the latest OPIT Lan for Windows release and unzip the archive.
- Copy archive contents inside the sample project folder.
- Open the sample app project solution with Microsoft Visual Studio (make sure C++ module is installed).
- In Microsoft Visual Studio go to Project -> Properties -> C/C++ -> Command Line -> Additional Options -> add text: /std:c++latest
- C/C++ -> General -> Additional Include Directories -> click on the dropdown -> Edit -> in the popup press the 3 dots (...) button from the right -> Choose headers folder from the opit-desktop project -> Ok
- Linker -> Input -> Additional Dependencies -> click on the dropdown -> Edit -> add text: OpitDesktop.lib -> Ok
- Linker -> General -> Additional Library Directories -> click on the dropdown -> Edit -> in the popup press the 3 dots (...) button from the right -> select the folder where the OpitDesktop.lib file is located -> Ok
- That's it! The solution should now build.
ECR integration
App Setup
Login in ECR app with integrator
user type.
A list with all the integrated apps on the ECR330 will be displayed.
If we want to mark an app as integrated with the ECR330 app we need to define in the manifest file of the target app the following metadata.
AndroidManifest.xml
<meta-data
android:name="app_name"
android:value="ECR_[app name]" />
After that, the app will be displayed on the main screen of the ECR330 app.
Fiscal commands
libVersion = Check latest version here
NOTE: For best compatibility libVersion
should match ECR version installed
Dependencies
//in settings.gradle
maven {
url 'https://ro-artifactory.devtokeninc.com/artifactory/PublicLibraries/'
}
//in app.gradle
implementation 'X330.integrationLibs:fiscal:$libVersion
Use EcrCommandExecutor
object for sending commands to the ECR and receiving the result.
The fiscal commands are sent to the ECR using the executeFiscalCommand
method. The result can be received
as a Flow
or if you want to receive as a EcrCommandResultListener
.
The user screen commands follow the same flow
, listener
pattern.
Type of fiscal commands
SetDateTime
SetCompanyData
GetCompanyData
SetVATRates
GetVATRates
Fiscalise
InitElectronicJournal
TechnicalReport
ZReport
XReport
InitJournal
Journal
JournalExport
XJournal
CustomerData
PeriodicReport
- ByDate
- ByZ
XMLReport
GetDailyTotals
GetStatus
GetSoftwareVersion
LoadProfile
ResetFiscalMemory
Sale
CashOperation
Fiscal commands
//Example of usage for sending a fiscal command and receiving the result as a [Flow]:
EcrCommandExecutor.executeFiscalCommand(
context = context,
command = FiscalCommand.SetDateTime(Calendar.getInstance().time),
).collect { result ->
result.runCatching {
Log.d("MainActivity", "Result.Success: $result")
Toast.makeText(context, result.toString(), Toast.LENGTH_SHORT)
.show()
}.onFailure {
Log.e("MainActivity", "Result.Failure: $result")
Toast.makeText(context, result.toString(), Toast.LENGTH_SHORT)
.show()
}
}
//Example of usage for sending a fiscal command and receiving the result as a [EcrCommandResultListener]:
EcrCommandExecutor.executeFiscalCommand(
context = context,
command = FiscalCommand.SetDateTime(Calendar.getInstance().time),
listener = object : EcrCommandResultListener {
override fun onEcrCommandResult(result: String) {
// Handle the successful result
}
override fun onEcrCommandError(error: String) {
// Handle the error result
}
},
)
//Example of usage for sending a user screen command and receiving the result as a [Flow]:
EcrCommandExecutor.executeUserScreenCommand(
context = context,
newText = SecureRandom().nextInt(100).toString(),
).collect { result ->
result.runCatching {
Log.d("MainActivity", "Result.Success: $result")
Toast.makeText(context, result.toString(), Toast.LENGTH_SHORT)
.show()
}.onFailure {
Log.e("MainActivity", "Result.Failure: $result")
Toast.makeText(context, result.toString(), Toast.LENGTH_SHORT)
.show()
}
}
//Example of usage for sending a user screen command and receiving the result as a [EcrCommandResultListener]:
EcrCommandExecutor.executeUserScreenCommand(
context = context,
newText = SecureRandom().nextInt(100).toString(),
listener = object : EcrCommandResultListener {
override fun onEcrCommandResult(result: String) {
// Handle the successful result
}
override fun onEcrCommandError(error: String) {
// Handle the error result
}
},
)
//Example of a sale fiscal command
FiscalCommand.Sale(
sellerInfo = SellerInfo(
cashierName = "John Doe",
posNumber = "123413",
),
products = listOf(
FiscalProduct(
productName = "Product 1",
unit = "kg",
quantity = "1",
pricePerUnit = "10,5",
vatId = "B",
),
FiscalProduct(
productName = "Product 2",
unit = "kg",
quantity = "1",
pricePerUnit = "15,5",
vatId = "B",
),
),
payments = listOf(
FiscalPayment(
paymentMethod = FiscalPayment.PaymentMethod.Card,
amount = "26",
),
),
cifNumber = "43511483",
)
Sending a card payment
Please check out PaymentGateway docs