diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 9ee426c..de575c8 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -12,10 +12,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AGENT.md b/AGENT.md
new file mode 100644
index 0000000..71d2eda
--- /dev/null
+++ b/AGENT.md
@@ -0,0 +1,56 @@
+# Agent Instructions: PSP Project
+
+This document provides essential context and guidelines for AI agents working on the PSP (Payment Service Provider) project.
+
+## Project Purpose
+A multi-module Android application for specialized POS (Point of Sale) hardware, providing a bridge between a Web-based POS interface and native hardware features (Thermal Printing & Payment Terminal).
+
+## Architecture Overview
+The project follows **Dependency Inversion** principles. Features are defined as interfaces in `:core` and implemented in hardware-specific modules like `:p3` or `:ps4`.
+
+### Key Modules
+- **`:core`**: Shared domain models (`PrintEntity`, `PaymentResultEntity`) and interfaces (`PspService`, `Printer`).
+- **`:design_system`**: Contains `PspWebView` and the `PspJavaScriptInterface` (Web-Native Bridge).
+- **`:p3` / `:ps4`**: Hardware SDK integrations.
+- **`:app` / `:tis_app` / `:stage_app`**: Application entry points and Hilt dependency configuration.
+
+## Critical Classes & Interfaces
+
+### 1. Web-Native Bridge
+- **`PspWebView.kt`**: The core component. Uses a `WebViewManager` for pooling and is highly optimized for weak hardware.
+- **`PspJavaScriptInterface.kt`**: Defines the methods exposed to JavaScript (`pay`, `print`, `reload`, `uuid`).
+ - **Important**: Methods must be annotated with `@JavascriptInterface` and `@Keep`.
+ - **Note**: Numbers from JS arrive as `Double`.
+
+### 2. Printer Implementation (`:p3`)
+- **`P3Printer.kt`**: Implements the `Printer` interface.
+- **`POIPrinterManager`**: The vendor SDK class.
+- **Font Handling**: Use `getFontPath(resourceName: String)` to copy fonts from `res/font` to internal storage before passing the path to the SDK.
+- **Logo Rendering**: Use `drawableToBitmap()` to handle VectorDrawables correctly.
+
+### 3. Payment Service (`:p3`)
+- **`Ps3Service.kt`**: Implementation of `PspService`.
+- **`IapPosService.kt`**: Handles communication with the underlying POS payment application.
+
+## Common Agent Tasks
+
+### Adding a new Bridge Method
+1. Add the method to `PspJavaScriptInterface.kt` with `@JavascriptInterface` and `@Keep`.
+2. Update the constructor of `PspJavaScriptInterface` to take a lambda.
+3. Update `PspWebView.kt` to pass the lambda from the UI layer.
+
+### Optimizing for Weak Hardware
+When performance issues arise in the WebView:
+1. Check `WebViewManager.kt` settings.
+2. Ensure `setLayerType(View.LAYER_TYPE_HARDWARE, null)` is set.
+3. Keep `setOffscreenPreRaster(false)` for very weak hardware to save RAM.
+4. Ensure `loadUrl` is NOT called in the `AndroidView.update` block (use `LaunchedEffect` instead).
+
+## Development Rules
+- **RTL Support**: The printer uses Persian/Arabic text. Always ensure numbers are converted to Persian digits using `toPersianDigits()` where appropriate.
+- **Dependency Injection**: Use Hilt for all service injections. Do not instantiate implementation classes (`P3Printer`, `Ps3Service`) directly in the app modules.
+- **WebView Lifecycle**: Always release WebViews back to the `WebViewManager` in `onDispose`.
+
+## Mocking in `:stage_app`
+- Use `StageViewModel` to return success/failure mocks for payment results without requiring physical hardware.
+- Use the `PaymentResultEntity.success()` or `PaymentResultEntity.failure()` factory methods.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d8c57fe
--- /dev/null
+++ b/README.md
@@ -0,0 +1,49 @@
+# PSP (Payment Service Provider) Project
+
+This project is a multi-module Android application designed to provide payment and printing services on specialized POS hardware. It uses a modular architecture to separate core logic, hardware SDKs, and different application variants.
+
+## Module Structure
+
+### Core Modules
+* **`:core`**: The backbone of the project. Contains shared domain models (`PaymentResultEntity`, `PrintEntity`), interfaces for services (`PspService`, `Printer`), and common utilities. It has no dependencies on other modules.
+* **`:design_system`**: Contains shared UI components and utilities. The most critical component is `PspWebView`, a highly optimized WebView wrapper used across all app variants for rendering the web-based POS interface. It depends on `:core`.
+
+### Hardware Implementation Modules
+These modules contain the specific SDK integrations for different POS hardware.
+* **`:p3`**: Implementation of `PspService` and `Printer` for P3 hardware. Includes SDK-specific logic for handling payments and thermal printing.
+* **`:ps4`**: Implementation for PS4 hardware variants.
+
+### Application Variants
+* **`:app`**: The primary production application.
+* **`:tis_app`**: A specialized version of the application targeting the TIS environment (`https://tis.shift-am.ir`).
+* **`:stage_app`**: A development and staging variant used for testing. It often uses mock implementations (like `StageViewModel`) to simulate POS behavior without requiring physical hardware.
+
+## Architecture & Relationships
+
+```mermaid
+graph TD
+ subgraph Apps
+ tis_app --> core
+ tis_app --> design_system
+ stage_app --> core
+ stage_app --> design_system
+ app --> core
+ app --> design_system
+ end
+
+ subgraph Hardware SDKs
+ p3 --> core
+ ps4 --> core
+ end
+
+ design_system --> core
+```
+
+1. **Dependency Inversion**: Apps depend on interfaces defined in `:core`. The actual hardware implementation (`:p3` or `:ps4`) is injected via Hilt, allowing the same app logic to run on different devices.
+2. **Web-Native Bridge**: The `design_system` module provides `PspJavaScriptInterface`, which allows the Web POS (running inside `PspWebView`) to communicate with native hardware features like the printer and payment terminal.
+3. **Hardware Performance**: The WebView is optimized for weak POS hardware with features like pooled instances, hardware acceleration, and lean rendering profiles.
+
+## Key Features
+* **Thermal Printing**: Optimized for RTL (Persian) text and high-performance bitmap rendering for logos (including VectorDrawable support).
+* **Payment Integration**: Standardized bridge for processing payments and returning structured results to the web-front-end.
+* **Weak Hardware Optimization**: Custom WebView configurations to handle smooth animations (drawers/sheets) on low-spec devices.
diff --git a/core/src/main/java/com/example/core/domain/PspService.kt b/core/src/main/java/com/example/core/domain/PspService.kt
index ffd5f64..d0edf7b 100644
--- a/core/src/main/java/com/example/core/domain/PspService.kt
+++ b/core/src/main/java/com/example/core/domain/PspService.kt
@@ -12,5 +12,5 @@ interface PspService {
fun decodePosResponse(
id: String?,
activityResult: ActivityResult
- ): PaymentResultEntity?
+ ): PaymentResultEntity
}
\ No newline at end of file
diff --git a/core/src/main/java/com/example/core/model/PaymentResultEntity.kt b/core/src/main/java/com/example/core/model/PaymentResultEntity.kt
index 0738182..41601a2 100644
--- a/core/src/main/java/com/example/core/model/PaymentResultEntity.kt
+++ b/core/src/main/java/com/example/core/model/PaymentResultEntity.kt
@@ -1,15 +1,48 @@
package com.example.core.model
+import com.google.gson.annotations.SerializedName
+
data class PaymentResultEntity(
- val id : String? = null,
- val status : PaymentResultStatus,
- val terminalId : String,
- val stan : String,
- val rrn : String,
- val responseCode : String,
- val customerCardNO : String,
- val transactionDateTime : String,
- val description : String?,
+ val id: String? = null,
+ val status: PaymentResultStatus,
+ val message: String?,
+ @SerializedName("data")
+ val paymentResultDataEntity: PaymentResultDataEntity? = null,
+) {
+ data class PaymentResultDataEntity(
+ val terminalId: String,
+ val stan: String,
+ val rrn: String,
+ val responseCode: String,
+ val customerCardNO: String,
+ val transactionDateTime: String,
+ )
+ companion object {
+ fun success(
+ id: String? = null,
+ message: String? = "Success",
+ data: PaymentResultDataEntity? = null
+ ): PaymentResultEntity {
+ return PaymentResultEntity(
+ id = id,
+ status = PaymentResultStatus.SUCCESS,
+ message = message,
+ paymentResultDataEntity = data
+ )
+ }
-)
\ No newline at end of file
+ fun failure(
+ id: String? = null,
+ message: String? = "Failure",
+ data: PaymentResultDataEntity? = null
+ ): PaymentResultEntity {
+ return PaymentResultEntity(
+ id = id,
+ status = PaymentResultStatus.FAILURE,
+ message = message,
+ paymentResultDataEntity = data
+ )
+ }
+ }
+}
diff --git a/core/src/main/java/com/example/core/model/PaymentResultStatus.kt b/core/src/main/java/com/example/core/model/PaymentResultStatus.kt
index d5826c0..cea2a18 100644
--- a/core/src/main/java/com/example/core/model/PaymentResultStatus.kt
+++ b/core/src/main/java/com/example/core/model/PaymentResultStatus.kt
@@ -1,6 +1,6 @@
package com.example.core.model
enum class PaymentResultStatus {
- OK,
- ERROR
+ SUCCESS,
+ FAILURE
}
diff --git a/design_system/src/main/java/com/example/design_system/PspWebView.kt b/design_system/src/main/java/com/example/design_system/PspWebView.kt
index 21db73e..2718240 100644
--- a/design_system/src/main/java/com/example/design_system/PspWebView.kt
+++ b/design_system/src/main/java/com/example/design_system/PspWebView.kt
@@ -46,13 +46,13 @@ fun PspWebView(
val scope = rememberCoroutineScope()
val context = LocalContext.current
var lastBackTime by remember { mutableLongStateOf(0L) }
- var webView by remember { mutableStateOf(null) }
+ var webViewInstance by remember { mutableStateOf(null) }
-
- // Use rememberUpdatedState to ensure the bridge always calls the latest callbacks
val currentOnPayClick by rememberUpdatedState(onPayClick)
val currentOnPrintClick by rememberUpdatedState(onPrintClick)
val currentOnDeviceInfo by rememberUpdatedState(onDeviceInfo)
+ val currentOnWebViewReady by rememberUpdatedState(onWebViewReady)
+ val currentOnPageFinished by rememberUpdatedState(onPageFinished)
val bridge = remember {
PspJavaScriptInterface(
@@ -63,8 +63,8 @@ fun PspWebView(
}
BackHandler {
- if (canGoBack.value) {
- webView?.goBack()
+ if (webViewInstance?.canGoBack() == true) {
+ webViewInstance?.goBack()
} else {
val now = System.currentTimeMillis()
if (now - lastBackTime < 2000) {
@@ -72,38 +72,34 @@ fun PspWebView(
} else {
lastBackTime = now
scope.launch {
- SnackbarController.sendEvent(
- SnackbarEvent(
- message = "برای خروج، دوباره بر روی بازگشت کلیک کنید."
- )
- )
+ SnackbarController.sendEvent(SnackbarEvent("برای خروج، دوباره کلیک کنید."))
}
}
}
}
+ // Manage pooling and lifecycle
DisposableEffect(Unit) {
- webView = WebViewManager.getWebView(context)
- webView?.let(onWebViewReady)
onDispose {
- webView?.let { WebViewManager.releaseWebView(it) }
+ webViewInstance?.let { WebViewManager.releaseWebView(it) }
}
}
AndroidView(
modifier = modifier,
factory = { ctx ->
- (webView ?: WebViewManager.getWebView(ctx)).apply {
+ val wv = WebViewManager.getWebView(ctx).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
overScrollMode = View.OVER_SCROLL_NEVER
+
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
canGoBack.value = view?.canGoBack() ?: false
- view?.let(onPageFinished)
+ view?.let(currentOnPageFinished)
}
}
@@ -112,16 +108,21 @@ fun PspWebView(
javaScriptEnabled = true
domStorageEnabled = true
cacheMode = WebSettings.LOAD_NO_CACHE
- allowFileAccess = true // Needed for Service Worker
+ allowFileAccess = true
}
- clearCache(true)
+ removeJavascriptInterface("NativeBridge")
addJavascriptInterface(bridge, "NativeBridge")
}
+
+ webViewInstance = wv
+ currentOnWebViewReady(wv) // CRITICAL: Send the ACTUAL instance being shown
+ wv
},
update = {
- it.loadUrl(url)
+ if (it.url != url) {
+ it.loadUrl(url)
+ }
}
)
-
}
diff --git a/p3/src/main/java/com/example/p3/IapPosService.kt b/p3/src/main/java/com/example/p3/IapPosService.kt
index 215941b..583fd32 100644
--- a/p3/src/main/java/com/example/p3/IapPosService.kt
+++ b/p3/src/main/java/com/example/p3/IapPosService.kt
@@ -159,24 +159,33 @@ class IapPosService @Inject constructor(
if (result.isNullOrEmpty()) {
- throw Exception("پرداخت کنسل شد")
+ return PaymentResultEntity.failure(
+ id = id,
+ message = "پرداخت لغو شد!"
+ )
}
return try {
val type = object : TypeToken