Tests and package name updated
This commit is contained in:
15
Runtime/Core/FonepayBakedSecrets.cs
Normal file
15
Runtime/Core/FonepayBakedSecrets.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
Runtime/Core/FonepayBakedSecrets.cs.meta
Normal file
2
Runtime/Core/FonepayBakedSecrets.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6baad01d5e6740bfb8abc8faadfe594
|
||||
94
Runtime/Core/FonepayClient.cs
Normal file
94
Runtime/Core/FonepayClient.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/Core/FonepayClient.cs.meta
Normal file
2
Runtime/Core/FonepayClient.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1ab3a2c9f1aa4d5aa87b6305323a1fc
|
||||
61
Runtime/Core/FonepayConfig.cs
Normal file
61
Runtime/Core/FonepayConfig.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
Runtime/Core/FonepayConfig.cs.meta
Normal file
2
Runtime/Core/FonepayConfig.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3617f631547ae4b10a0f8f634751c61f
|
||||
59
Runtime/Core/FonepayConfigSO.cs
Normal file
59
Runtime/Core/FonepayConfigSO.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/Core/FonepayConfigSO.cs.meta
Normal file
2
Runtime/Core/FonepayConfigSO.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba80c86a04c584a649c6d31e8f10adad
|
||||
8
Runtime/Core/FonepayEnvironment.cs
Normal file
8
Runtime/Core/FonepayEnvironment.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Darkmatter.Fonepay
|
||||
{
|
||||
public enum FonepayEnvironment
|
||||
{
|
||||
Dev,
|
||||
Live
|
||||
}
|
||||
}
|
||||
2
Runtime/Core/FonepayEnvironment.cs.meta
Normal file
2
Runtime/Core/FonepayEnvironment.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c92e6dd7cf3a84c1e85fa2f19741794e
|
||||
Reference in New Issue
Block a user