Ref(DeviceInfo-PaymentResult-WebView):
- Refactor `PaymentResultEntity` to include a nested `PaymentResultDataEntity` and companion factory methods for success and failure. - Update `PaymentResultStatus` enum members from `OK`/`ERROR` to `SUCCESS`/`FAILURE`. - Modify `PspService` and its implementations (`P3`, `PS4`, `Stage`) to return a non-nullable `PaymentResultEntity`. - Enhance `PspWebView` with improved instance lifecycle management, `rememberUpdatedState` for callbacks, and new `onWebViewReady` and `onPageFinished` hooks. - Optimize `PspWebView` to prevent redundant `loadUrl` calls during recomposition. - Add `README.md` and `AGENT.md` to provide project architecture overviews and hardware-specific development guidelines. - Update `TisWebViewScreen` and `StageWebViewScreen` to handle the refactored result models and utilize new WebView callbacks.
This commit is contained in:
Generated
+14
@@ -12,10 +12,24 @@
|
|||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="tis_app">
|
<SelectionState runConfigName="tis_app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-05-12T16:21:58.593804400Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=TP030090489" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
<DialogSelection />
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="stage_app">
|
<SelectionState runConfigName="stage_app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-05-12T16:21:58.593804400Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=TP030090489" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
<DialogSelection />
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -12,5 +12,5 @@ interface PspService {
|
|||||||
fun decodePosResponse(
|
fun decodePosResponse(
|
||||||
id: String?,
|
id: String?,
|
||||||
activityResult: ActivityResult
|
activityResult: ActivityResult
|
||||||
): PaymentResultEntity?
|
): PaymentResultEntity
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,48 @@
|
|||||||
package com.example.core.model
|
package com.example.core.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class PaymentResultEntity(
|
data class PaymentResultEntity(
|
||||||
val id : String? = null,
|
val id: String? = null,
|
||||||
val status : PaymentResultStatus,
|
val status: PaymentResultStatus,
|
||||||
val terminalId : String,
|
val message: String?,
|
||||||
val stan : String,
|
@SerializedName("data")
|
||||||
val rrn : String,
|
val paymentResultDataEntity: PaymentResultDataEntity? = null,
|
||||||
val responseCode : String,
|
) {
|
||||||
val customerCardNO : String,
|
data class PaymentResultDataEntity(
|
||||||
val transactionDateTime : String,
|
val terminalId: String,
|
||||||
val description : 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
)
|
fun failure(
|
||||||
|
id: String? = null,
|
||||||
|
message: String? = "Failure",
|
||||||
|
data: PaymentResultDataEntity? = null
|
||||||
|
): PaymentResultEntity {
|
||||||
|
return PaymentResultEntity(
|
||||||
|
id = id,
|
||||||
|
status = PaymentResultStatus.FAILURE,
|
||||||
|
message = message,
|
||||||
|
paymentResultDataEntity = data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.example.core.model
|
package com.example.core.model
|
||||||
|
|
||||||
enum class PaymentResultStatus {
|
enum class PaymentResultStatus {
|
||||||
OK,
|
SUCCESS,
|
||||||
ERROR
|
FAILURE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ fun PspWebView(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var lastBackTime by remember { mutableLongStateOf(0L) }
|
var lastBackTime by remember { mutableLongStateOf(0L) }
|
||||||
var webView by remember { mutableStateOf<WebView?>(null) }
|
var webViewInstance by remember { mutableStateOf<WebView?>(null) }
|
||||||
|
|
||||||
|
|
||||||
// Use rememberUpdatedState to ensure the bridge always calls the latest callbacks
|
|
||||||
val currentOnPayClick by rememberUpdatedState(onPayClick)
|
val currentOnPayClick by rememberUpdatedState(onPayClick)
|
||||||
val currentOnPrintClick by rememberUpdatedState(onPrintClick)
|
val currentOnPrintClick by rememberUpdatedState(onPrintClick)
|
||||||
val currentOnDeviceInfo by rememberUpdatedState(onDeviceInfo)
|
val currentOnDeviceInfo by rememberUpdatedState(onDeviceInfo)
|
||||||
|
val currentOnWebViewReady by rememberUpdatedState(onWebViewReady)
|
||||||
|
val currentOnPageFinished by rememberUpdatedState(onPageFinished)
|
||||||
|
|
||||||
val bridge = remember {
|
val bridge = remember {
|
||||||
PspJavaScriptInterface(
|
PspJavaScriptInterface(
|
||||||
@@ -63,8 +63,8 @@ fun PspWebView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (canGoBack.value) {
|
if (webViewInstance?.canGoBack() == true) {
|
||||||
webView?.goBack()
|
webViewInstance?.goBack()
|
||||||
} else {
|
} else {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
if (now - lastBackTime < 2000) {
|
if (now - lastBackTime < 2000) {
|
||||||
@@ -72,38 +72,34 @@ fun PspWebView(
|
|||||||
} else {
|
} else {
|
||||||
lastBackTime = now
|
lastBackTime = now
|
||||||
scope.launch {
|
scope.launch {
|
||||||
SnackbarController.sendEvent(
|
SnackbarController.sendEvent(SnackbarEvent("برای خروج، دوباره کلیک کنید."))
|
||||||
SnackbarEvent(
|
|
||||||
message = "برای خروج، دوباره بر روی بازگشت کلیک کنید."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manage pooling and lifecycle
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
webView = WebViewManager.getWebView(context)
|
|
||||||
webView?.let(onWebViewReady)
|
|
||||||
onDispose {
|
onDispose {
|
||||||
webView?.let { WebViewManager.releaseWebView(it) }
|
webViewInstance?.let { WebViewManager.releaseWebView(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
factory = { ctx ->
|
factory = { ctx ->
|
||||||
(webView ?: WebViewManager.getWebView(ctx)).apply {
|
val wv = WebViewManager.getWebView(ctx).apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
)
|
)
|
||||||
overScrollMode = View.OVER_SCROLL_NEVER
|
overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
|
|
||||||
webViewClient = object : WebViewClient() {
|
webViewClient = object : WebViewClient() {
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
super.onPageFinished(view, url)
|
super.onPageFinished(view, url)
|
||||||
canGoBack.value = view?.canGoBack() ?: false
|
canGoBack.value = view?.canGoBack() ?: false
|
||||||
view?.let(onPageFinished)
|
view?.let(currentOnPageFinished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,16 +108,21 @@ fun PspWebView(
|
|||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
domStorageEnabled = true
|
domStorageEnabled = true
|
||||||
cacheMode = WebSettings.LOAD_NO_CACHE
|
cacheMode = WebSettings.LOAD_NO_CACHE
|
||||||
allowFileAccess = true // Needed for Service Worker
|
allowFileAccess = true
|
||||||
}
|
}
|
||||||
clearCache(true)
|
|
||||||
|
|
||||||
|
removeJavascriptInterface("NativeBridge")
|
||||||
addJavascriptInterface(bridge, "NativeBridge")
|
addJavascriptInterface(bridge, "NativeBridge")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webViewInstance = wv
|
||||||
|
currentOnWebViewReady(wv) // CRITICAL: Send the ACTUAL instance being shown
|
||||||
|
wv
|
||||||
},
|
},
|
||||||
update = {
|
update = {
|
||||||
it.loadUrl(url)
|
if (it.url != url) {
|
||||||
|
it.loadUrl(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,24 +159,33 @@ class IapPosService @Inject constructor(
|
|||||||
|
|
||||||
|
|
||||||
if (result.isNullOrEmpty()) {
|
if (result.isNullOrEmpty()) {
|
||||||
throw Exception("پرداخت کنسل شد")
|
return PaymentResultEntity.failure(
|
||||||
|
id = id,
|
||||||
|
message = "پرداخت لغو شد!"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
val type = object : TypeToken<Map<String, Any>>() {}.type
|
val type = object : TypeToken<Map<String, Any>>() {}.type
|
||||||
val data = gson.fromJson<Map<String, Any>>(result, type)
|
val data = gson.fromJson<Map<String, Any>>(result, type)
|
||||||
PaymentResultEntity(
|
PaymentResultEntity(
|
||||||
id = id,
|
id = id,
|
||||||
status = if (data["Status"] == "OK") PaymentResultStatus.OK else PaymentResultStatus.ERROR,
|
status = if (data["Status"] == "OK") PaymentResultStatus.SUCCESS else PaymentResultStatus.FAILURE,
|
||||||
terminalId = data["TerminalId"].toString(),
|
paymentResultDataEntity = PaymentResultEntity.PaymentResultDataEntity(
|
||||||
stan = data["STAN"].toString(),
|
terminalId = data["TerminalId"].toString(),
|
||||||
rrn = data["RRN"].toString(),
|
stan = data["STAN"].toString(),
|
||||||
responseCode = data["ResponseCode"].toString(),
|
rrn = data["RRN"].toString(),
|
||||||
customerCardNO = data["CustomerCardNO"].toString(),
|
responseCode = data["ResponseCode"].toString(),
|
||||||
transactionDateTime = data["TransactionDateTime"].toString(),
|
customerCardNO = data["CustomerCardNO"].toString(),
|
||||||
description = data["Description"].toString()
|
transactionDateTime = data["TransactionDateTime"].toString(),
|
||||||
|
),
|
||||||
|
message = data["Description"].toString()
|
||||||
|
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw Exception("POS response is not a JSON object.")
|
PaymentResultEntity.failure(
|
||||||
|
id = id,
|
||||||
|
message = e.message ?: "در هنگام پرداخت خطایی رخ داده است!"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,18 @@ class P3Service @Inject constructor(
|
|||||||
override fun decodePosResponse(
|
override fun decodePosResponse(
|
||||||
id: String?,
|
id: String?,
|
||||||
activityResult: ActivityResult
|
activityResult: ActivityResult
|
||||||
): PaymentResultEntity? {
|
): PaymentResultEntity {
|
||||||
return try {
|
return try {
|
||||||
iapPosService.decodeResponse(
|
iapPosService.decodeResponse(
|
||||||
id = id,
|
id = id,
|
||||||
activityResult = activityResult
|
activityResult = activityResult
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
e.printStackTrace()
|
||||||
|
PaymentResultEntity.failure(
|
||||||
|
id = id,
|
||||||
|
e.message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ class Ps4Service @Inject constructor() : PspService {
|
|||||||
override fun decodePosResponse(
|
override fun decodePosResponse(
|
||||||
id: String?,
|
id: String?,
|
||||||
activityResult: ActivityResult
|
activityResult: ActivityResult
|
||||||
): PaymentResultEntity? = null
|
): PaymentResultEntity = PaymentResultEntity.success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ fun StageWebViewScreen(
|
|||||||
activityResult = result
|
activityResult = result
|
||||||
)
|
)
|
||||||
val jsResult = viewModel.postPaymentResult(paymentResultEntity)
|
val jsResult = viewModel.postPaymentResult(paymentResultEntity)
|
||||||
val currentWebView = webView
|
webView?.let {
|
||||||
if (currentWebView == null) {
|
it.post {
|
||||||
Log.w(stageWebViewTag, "Skipping onPaymentResult JS callback: WebView is not ready yet.")
|
it.evaluateJavascript(jsResult, null)
|
||||||
} else {
|
|
||||||
currentWebView.post {
|
|
||||||
currentWebView.evaluateJavascript(jsResult, null)
|
|
||||||
}
|
}
|
||||||
|
} ?: run {
|
||||||
|
Log.w(stageWebViewTag, "Skipping onPaymentResult JS callback: WebView is not ready yet.")
|
||||||
|
|
||||||
}
|
}
|
||||||
isPaymentProcessing = false
|
isPaymentProcessing = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,17 +32,23 @@ class StageViewModel @Inject constructor(
|
|||||||
id: String?,
|
id: String?,
|
||||||
activityResult: ActivityResult
|
activityResult: ActivityResult
|
||||||
): PaymentResultEntity {
|
): PaymentResultEntity {
|
||||||
return PaymentResultEntity(
|
val mockPaymentResult = PaymentResultEntity.success(
|
||||||
id = id,
|
id = id,
|
||||||
status = com.example.core.model.PaymentResultStatus.ERROR,
|
message = "تراکنش با موفقیت انجام شد",
|
||||||
terminalId = "123456",
|
data = PaymentResultEntity.PaymentResultDataEntity(
|
||||||
stan = "123456",
|
terminalId = "123456",
|
||||||
rrn = "123456",
|
stan = "123456",
|
||||||
responseCode = "01",
|
rrn = "123456789012",
|
||||||
customerCardNO = "1234567890123456",
|
responseCode = "00",
|
||||||
transactionDateTime = "14030101120000",
|
customerCardNO = "603799******1234",
|
||||||
description = "تراکنش ناموفق"
|
transactionDateTime = "1403/05/20 12:30:45"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
return mockPaymentResult
|
||||||
|
// return pspService.decodePosResponse(
|
||||||
|
// id = id,
|
||||||
|
// activityResult = activityResult
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mock = listOf<PrintEntity>(
|
private val mock = listOf<PrintEntity>(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.tis_app.screen
|
package com.example.tis_app.screen
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -17,10 +18,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import com.example.core.getAndroidId
|
import com.example.core.getAndroidId
|
||||||
import com.example.core.getDeviceName
|
import com.example.core.getDeviceName
|
||||||
import com.example.design_system.PspWebView
|
import com.example.design_system.PspWebView
|
||||||
import com.example.design_system.util.WebViewManager
|
|
||||||
import com.example.tis_app.viewmodel.WebViewModel
|
import com.example.tis_app.viewmodel.WebViewModel
|
||||||
|
|
||||||
private const val tisWebUrl = "https://tis.shift-am.ir"
|
private const val tisWebUrl = "https://tis.shift-am.ir"
|
||||||
|
private const val tisWebViewTag = "TisWebViewScreen"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TisWebViewScreen(
|
fun TisWebViewScreen(
|
||||||
@@ -29,10 +30,11 @@ fun TisWebViewScreen(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var webView: WebView by remember { mutableStateOf(WebViewManager.getWebView(context)) }
|
var webView: WebView? by remember { mutableStateOf(null) }
|
||||||
var currentPaymentId by rememberSaveable { mutableStateOf<String?>(null) }
|
var currentPaymentId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
var isPaymentProcessing by rememberSaveable { mutableStateOf(false) }
|
var isPaymentProcessing by rememberSaveable { mutableStateOf(false) }
|
||||||
var isPrinting by rememberSaveable { mutableStateOf(false) }
|
var isPrinting by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var initialCallbackSent by rememberSaveable { mutableStateOf(false) }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val deviceInfo by remember {
|
val deviceInfo by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -50,17 +52,16 @@ fun TisWebViewScreen(
|
|||||||
val paymentResultEntity = viewModel.paymentResult(
|
val paymentResultEntity = viewModel.paymentResult(
|
||||||
id = currentPaymentId,
|
id = currentPaymentId,
|
||||||
activityResult = result
|
activityResult = result
|
||||||
|
|
||||||
)
|
)
|
||||||
|
val jsResult = viewModel.postPaymentResult(paymentResultEntity)
|
||||||
webView.post {
|
webView?.let {
|
||||||
webView.evaluateJavascript(
|
it.post {
|
||||||
viewModel.postPaymentResult(paymentResultEntity = paymentResultEntity),
|
it.evaluateJavascript(jsResult, null)
|
||||||
null
|
}
|
||||||
)
|
} ?: run {
|
||||||
|
Log.w(tisWebViewTag, "Skipping onPaymentResult JS callback: WebView is not ready yet.")
|
||||||
}
|
}
|
||||||
isPaymentProcessing = false
|
isPaymentProcessing = false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PspWebView(
|
PspWebView(
|
||||||
@@ -86,6 +87,15 @@ fun TisWebViewScreen(
|
|||||||
},
|
},
|
||||||
onDeviceInfo = {
|
onDeviceInfo = {
|
||||||
deviceInfo
|
deviceInfo
|
||||||
|
},
|
||||||
|
onWebViewReady = { readyWebView ->
|
||||||
|
webView = readyWebView
|
||||||
|
},
|
||||||
|
onPageFinished = { loadedWebView ->
|
||||||
|
if (!initialCallbackSent) {
|
||||||
|
loadedWebView.evaluateJavascript(viewModel.postPaymentResult(null), null)
|
||||||
|
initialCallbackSent = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ class WebViewModel @Inject constructor(
|
|||||||
fun paymentResult(
|
fun paymentResult(
|
||||||
id: String?,
|
id: String?,
|
||||||
activityResult: ActivityResult
|
activityResult: ActivityResult
|
||||||
): PaymentResultEntity? {
|
): PaymentResultEntity {
|
||||||
return pspService.decodePosResponse(
|
return pspService.decodePosResponse(
|
||||||
id = id,
|
id = id,
|
||||||
activityResult = activityResult
|
activityResult = activityResult
|
||||||
|
|||||||
Reference in New Issue
Block a user