Tests and package name updated

This commit is contained in:
Savya Bikram Shah
2026-05-07 17:42:48 +05:45
parent 9f620084b2
commit 6a8a6e46f0
93 changed files with 4 additions and 4 deletions

View File

@@ -0,0 +1,15 @@
using UnityEngine;
namespace Darkmatter.Fonepay
{
/// <summary>
/// Transient ScriptableObject containing baked secrets for player builds.
/// Created by FonepayBuildSecretsInjector before build, deleted after build completes.
/// Never commit this asset — added to .gitignore by the settings window.
/// </summary>
public class FonepayBakedSecrets : ScriptableObject
{
public string password;
public string secretKey;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f6baad01d5e6740bfb8abc8faadfe594

View File

@@ -0,0 +1,94 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Darkmatter.Fonepay
{
/// <summary>
/// Public facade. Auto-loads config + secrets via <see cref="FonepayConfig.Load"/>.
/// Caller never touches credentials.
/// </summary>
public sealed class FonepayClient
{
private readonly FonepayApiClient _api;
public FonepayClient() : this(FonepayConfig.Load())
{
}
public FonepayClient(FonepayConfigSO config)
{
var signer = new HmacSha512Signer(config.GetSecretKey());
_api = new FonepayApiClient(config, signer);
}
/// <summary>
/// Request a new QR code. The returned URL is valid for 15 minutes. Call GetStatusAsync() to check if the QR code has been paid.
/// </summary>
/// <param name="req"></param>
/// <param name="ct"></param>
/// <returns></returns>
public Task<QrResult> PurchaseAsync(QrRequest req, CancellationToken ct = default)
=> _api.PostQRAsync(req, ct);
/// <summary>
/// Check if a QR code has been paid. Call after PostQRAsync() to check if the QR code has been paid. Returns "PAID" if successful, "UNPAID" if not yet paid, or an error message if the PRN is invalid or expired.
/// </summary>
/// <param name="prn"></param>
/// <param name="ct"></param>
/// <returns></returns>
public Task<QrResult> GetStatusAsync(string prn, CancellationToken ct = default)
=> _api.GetStatusAsync(prn, ct);
/// <summary>
/// Request a tax refund. Returns "REFUND_SUCCESS" if successful, or an error message if the PRN is invalid, expired, or not eligible for refund.
/// </summary>
/// <param name="req"></param>
/// <param name="ct"></param>
/// <returns></returns>
public Task<TaxRefundResponse> PostTaxRefundAsync(TaxRefundRequest req, CancellationToken ct = default)
=> _api.PostTaxRefundAsync(req, ct);
/// <summary>
/// Connects to the QR websocket and awaits the terminal payment frame, then auto-disconnects.
/// Pass <see cref="QrResult.thirdpartyQrWebSocketUrl"/> from <see cref="PurchaseAsync"/>.
/// </summary>
public async Task<QRPaymentStatus> AwaitPaymentAsync(
string websocketUrl,
Action<bool> onQrVerified = null,
CancellationToken ct = default)
{
if (string.IsNullOrEmpty(websocketUrl))
throw new ArgumentException("Websocket URL required", nameof(websocketUrl));
using var ws = new FonepayWebsocketClient();
var tcs = new TaskCompletionSource<QRPaymentStatus>(
TaskCreationOptions.RunContinuationsAsynchronously);
if (onQrVerified != null)
ws.OnQrVerified += onQrVerified;
ws.OnPaymentReceived += msg => tcs.TrySetResult(msg.Status);
ws.OnClosed += err =>
{
if (err != null)
tcs.TrySetException(err);
else
tcs.TrySetException(new InvalidOperationException(
"Fonepay websocket closed before payment frame received."));
};
using var ctReg = ct.Register(() => tcs.TrySetCanceled(ct));
try
{
await ws.ConnectAsync(websocketUrl, ct);
return await tcs.Task;
}
finally
{
await ws.DisconnectAsync();
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e1ab3a2c9f1aa4d5aa87b6305323a1fc

View File

@@ -0,0 +1,61 @@
using System;
using UnityEngine;
namespace Darkmatter.Fonepay
{
/// <summary>
/// Static accessor. Returns ready-to-use <see cref="FonepayConfigSO"/> with credentials injected.
/// Editor: secrets supplied via <see cref="EditorSecretsProvider"/> hook (set by FonepaySecretsStore on editor load).
/// Builds: secrets read from <see cref="FonepayBakedSecrets"/> resource (written by build preprocessor, removed after build).
/// </summary>
public static class FonepayConfig
{
private const string ResourcePath = "FonepayConfig";
private const string BakedSecretsResource = "FonepayBakedSecrets";
public static Func<(string password, string secretKey)> EditorSecretsProvider;
private static FonepayConfigSO _cached;
public static FonepayConfigSO Load()
{
if (_cached != null) return _cached;
var so = Resources.Load<FonepayConfigSO>(ResourcePath);
if (so == null)
throw new FonepayError(0,
"FonepayConfig asset missing. Open Tools > Fonepay > Settings to create it.",
"FonepayConfig.Load");
string password = null;
string secretKey = null;
if (Application.isEditor && EditorSecretsProvider != null)
{
var creds = EditorSecretsProvider();
password = creds.password;
secretKey = creds.secretKey;
}
else
{
var baked = Resources.Load<FonepayBakedSecrets>(BakedSecretsResource);
if (baked != null)
{
password = baked.password;
secretKey = baked.secretKey;
}
}
if (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(secretKey))
throw new FonepayError(0,
"Fonepay credentials missing. Open Tools > Fonepay > Settings.",
"FonepayConfig.Load");
so.SetCredentials(password, secretKey);
_cached = so;
return so;
}
public static void Invalidate() => _cached = null;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3617f631547ae4b10a0f8f634751c61f

View File

@@ -0,0 +1,59 @@
using System;
using UnityEngine;
namespace Darkmatter.Fonepay
{
// <summary>
// ScriptableObject. Holds non-secret config fields serialised to disk. Secret key and password are injected at runtime only — never serialised.
// </summary>
public class FonepayConfigSO : ScriptableObject
{
[SerializeField] private FonepayEnvironment environment;
[SerializeField] private string merchantCode;
[SerializeField] private string username;
private string _password;
private string _secretKey;
private bool _credentialsSet;
// ── public read access ────────────────────────────────────────────
public FonepayEnvironment Environment => environment;
public string MerchantCode => merchantCode;
public string Username => username;
// ── runtime credential injection (called by FonepayPlayModeInjector) ──
public void SetCredentials(string password, string secretKey)
{
_password = password;
_secretKey = secretKey;
_credentialsSet = true;
}
private const string FonepayLiveEndpoint = "https://merchantapi.fonepay.com/api/";
private const string FonepayDevEndpoint = "https://dev-merchantapi.fonepay.com/api/";
// ── used internally by FonepayApiClient and HmacSha512Signer ─────
internal string GetPassword() => GuardCredentials(_password);
internal string GetSecretKey() => GuardCredentials(_secretKey);
// ── URL resolution ────────────────────────────────────────────────
public string ResolveBaseUrl() => environment == FonepayEnvironment.Live
? FonepayLiveEndpoint
: FonepayDevEndpoint;
// ── guards ────────────────────────────────────────────────────────
private string GuardCredentials(string value)
{
if (!_credentialsSet)
throw new FonepayError(0,
"Credentials not set. Call SetCredentials() before using FonepayClient.",
"FonepayConfig.SetCredentials");
return value;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ba80c86a04c584a649c6d31e8f10adad

View File

@@ -0,0 +1,8 @@
namespace Darkmatter.Fonepay
{
public enum FonepayEnvironment
{
Dev,
Live
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c92e6dd7cf3a84c1e85fa2f19741794e