From 028a1a7f46c175741dd02c92bc27cf9864397972 Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Thu, 7 May 2026 17:49:53 +0545 Subject: [PATCH] Readme added --- README.md | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2685f2b --- /dev/null +++ b/README.md @@ -0,0 +1,231 @@ +# Fonepay Unity + +Fonepay payment SDK for Unity. Generate Fonepay QR codes, await payment confirmation over websocket, and process tax refunds — all from a single async-friendly client. + +This repo is a **Unity 6000.4.5f1** project that hosts the package source under `Packages/com.darkmattergameproduction.com.darkmatter.fonepay-unity/` and a sample scene under `Assets/`. + +--- + +## Features + +- `FonepayClient` façade — `PurchaseAsync`, `GetStatusAsync`, `AwaitPaymentAsync`, `PostTaxRefundAsync`. +- HMAC-SHA512 dataValidation on all signed payloads. +- Websocket payment listener with cancellation, timeout, and clean disconnect. +- QR rendered straight into a `Texture2D` ready to assign to `UnityEngine.UI.Image`. +- Editor-side credential management (Tools > Fonepay > Settings) — secrets baked at build time, never committed. +- NUnit edit-mode + play-mode tests. +- Importable sample under Package Manager > Samples. + +--- + +## Requirements + +| | | +|---|---| +| Unity | 6000.4.5f1+ | +| Test Framework | `com.unity.test-framework` 1.6.0+ | +| UniTask | for the sample only — runtime uses `System.Threading.Tasks` | + +--- + +## Install + +### As a local package (this repo) + +1. Clone: + ```bash + git clone "Fonepay Unity" + ``` +2. Open the project in Unity Hub (6000.4.5f1). + +### As a Git package in another project + +Add to your target project's `Packages/manifest.json`: +```json +"com.darkmattergameproduction.com.darkmatter.fonepay-unity": "https://github.com//.git?path=/Packages/com.darkmattergameproduction.com.darkmatter.fonepay-unity" +``` + +### Setup credentials + +1. **Tools > Fonepay > Settings** — create the `FonepayConfig` asset under `Assets/Resources/FonepayConfig.asset`. +2. Enter merchant code, username, password, and HMAC secret. The window stores secrets outside source control; a build preprocessor injects them as `FonepayBakedSecrets` and removes the resource after build. + +--- + +## Usage + +### Request a QR + +```csharp +using Darkmatter.Fonepay; + +var fonepay = new FonepayClient(); + +QrResult qr = await fonepay.PurchaseAsync(new QrRequest +{ + amount = 100f, + remarks1 = "order #1234", +}, ct); + +qrImage.sprite = Sprite.Create( + qr.qrCode, + new Rect(0, 0, qr.qrCode.width, qr.qrCode.height), + new Vector2(0.5f, 0.5f)); +``` + +### Await payment + +```csharp +QRPaymentStatus result = await fonepay.AwaitPaymentAsync( + qr.thirdpartyQrWebSocketUrl, + onQrVerified: scanned => Debug.Log($"QR scanned: {scanned}"), + ct: ct); + +switch (result.Outcome) +{ + case PaymentOutcome.Complete: break; // success + case PaymentOutcome.CancelledByUser: break; // dismissed in app + case PaymentOutcome.Failed: break; // server rejected +} +``` + +### Cancel / timeout + +```csharp +var cts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); +cts.CancelAfter(TimeSpan.FromMinutes(15)); // QR validity window + +try +{ + var payment = await fonepay.AwaitPaymentAsync(qr.thirdpartyQrWebSocketUrl, ct: cts.Token); +} +catch (OperationCanceledException) { /* user cancel or timeout */ } +catch (InvalidOperationException) { /* server closed websocket early */ } +catch (FonepayError e) { /* API error: e.ErrorCode, e.Docs */ } +``` + +`cts.Cancel()` from a button handler aborts the await and disconnects the socket. + +### Status poll + +```csharp +QrResult status = await fonepay.GetStatusAsync(prn, ct); +// status.message: "PAID" | "UNPAID" | error +``` + +### Tax refund + +```csharp +TaxRefundResponse refund = await fonepay.PostTaxRefundAsync(new TaxRefundRequest +{ + fonepayTraceId = "...", + merchantPRN = "...", + invoiceNumber = "INV-001", + invoiceDate = DateTime.UtcNow, + transactionAmount = 100f, +}, ct); +``` + +--- + +## API surface + +| Type | Purpose | +|---|---| +| `FonepayClient` | Public façade, auto-loads config + secrets. | +| `QrRequest` / `QrResult` | QR request/response DTOs. | +| `QRPaymentStatus` | Terminal payment frame, exposes `Outcome`. | +| `QRVerificationStatus` | Intermediate "QR scanned" frame. | +| `PaymentOutcome` | `Complete` / `CancelledByUser` / `Failed`. | +| `TaxRefundRequest` / `TaxRefundResponse` | Refund DTOs. | +| `FonepayError` | API-level exception with `ErrorCode` + `Docs`. | + +### `Outcome` rules + +| `success` | `paymentSuccess` | `Outcome` | +|---|---|---| +| true | true | Complete | +| true | false | CancelledByUser | +| false | * | Failed | + +### Termination of `AwaitPaymentAsync` + +| Condition | Result | +|---|---| +| Payment frame received | resolves with `QRPaymentStatus` | +| `CancellationToken` cancelled | throws `OperationCanceledException` | +| Server closes socket before payment | throws `InvalidOperationException` | +| Network/HTTP error during connect | throws `WebSocketException` | + +--- + +## Repo layout + +``` +Assets/ Sample scene + test MonoBehaviour +Packages/ + com.darkmattergameproduction.com.darkmatter.fonepay-unity/ + Runtime/ + Core/ FonepayClient, FonepayConfig, FonepayConfigSO + API/ FonepayApiClient (REST), FonepayWebsocketClient + Crypto/ HmacSha512Signer + Models/ DTOs (QrRequest, QrResult, …) + QR/ FonepayQRGenerator + Editor/ Settings window, build secrets injector + Samples~/ Importable example + Tests/ + Editor/ Edit-mode unit tests (NUnit) + Runtime/ Play-mode tests + Documentation/ Architecture notes + README.md Package-level docs +``` + +--- + +## Tests + +**Window > General > Test Runner** + +- **EditMode** — `WebsocketMessage.Status` parsing, `PaymentOutcome` truth table, `FonepayError`. +- **PlayMode** — runtime parse sanity, missing-config throw, empty-URL throw, pre-cancelled token throw. + +CLI (Unity 6 batchmode): +```bash +"" -batchmode -nographics -projectPath . -runTests \ + -testPlatform EditMode -testResults Logs/edit-tests.xml -quit +``` + +--- + +## Security + +- Credentials never live in this repo. `FonepayConfigSO` holds only public fields; password + HMAC secret are stored via `FonepaySecretsStore` (EditorPrefs) and baked into a temp `FonepayBakedSecrets` resource at build, then removed. +- All signed requests use HMAC-SHA512 — see `HmacSha512Signer`. +- `FonepayConfig.Invalidate()` clears the cached config (use after rotating credentials in Editor). + +--- + +## Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| `FonepayError: FonepayConfig asset missing` | No `Resources/FonepayConfig.asset` | Tools > Fonepay > Settings → Save | +| `FonepayError: Fonepay credentials missing` | Secrets not entered (Editor) or baked secrets stripped (build) | Re-open Settings window | +| `AwaitPaymentAsync` returns empty/default `QRPaymentStatus` | Old build pre-fix on `WebsocketMessage.Status` | Update to current; status now reparses each access | +| `AwaitPaymentAsync` hangs forever | Server closed socket without payment frame | Update to current; now throws `InvalidOperationException` | +| `transactionDate` always default | `DateTime` not supported by `JsonUtility` | Fixed — field is `string` now | + +--- + +## Contributing + +1. Branch from `main`. +2. Run **Test Runner** (EditMode + PlayMode) before opening a PR. +3. Update `CHANGELOG.md` in the package. +4. Keep `FonepayConfig.asset` and any baked secrets out of commits. + +--- + +## License + +See `Packages/com.darkmattergameproduction.com.darkmatter.fonepay-unity/Third Party Notices.md` for third-party attributions (QRCoder, UniTask). Project license: TBD.