Transaction Sync
Synchronize purchase and refund transactions from PlaySuper to your game via the SDK
Transaction Sync
This guide explains how to receive and process purchase and refund transactions in your game using the PlaySuper SDK's transaction sync feature.
The Problem
When players make purchases or receive refunds through PlaySuper:
- Purchases: Player spends coins to buy a reward (e.g., gift card, coupon)
- Refunds: Player receives coins back if an order fails or is cancelled
Your game needs to know about these transactions to:
- Update local currency displays
- Show purchase/refund notifications
- Trigger in-game rewards or achievements based on spending
- Maintain accurate transaction history for the player
Without transaction sync, your game has no visibility into what happened on the PlaySuper platform.
The Solution
The SDK Transaction Sync system provides:
- Realtime Notifications: When a player makes a purchase in the store, the SDK is notified instantly via a URL scheme bridge — your game receives the transaction while the store is still open
- Automatic Fetching: Transactions are also fetched on SDK initialization, after authentication, and when the store closes (as a safety net)
- Event-Based Notifications: Your game receives an
OnSdkTransactionsReceivedevent whenever new transactions are available - Local Persistence: Transactions are stored locally until your game processes them
- Checkpoint System: Server tracks which transactions have been processed to avoid duplicates
- Deduplication: Transactions received via realtime notification and later fetched on store close are automatically deduplicated — your game never sees the same transaction twice
- Store Visit Optimization: Transaction syncing is only enabled for players who have visited the store, reducing unnecessary API calls
Transaction Types
| Source | Type | Description |
|---|---|---|
PURCHASE_DEBIT | DEBIT | Player spent coins on a purchase |
REFUND_CREDIT | CREDIT | Player received coins back from a refund |
How It Works — Full Flow
1. App launch
PlaySuperUnitySDK.Initialize() checks for a previously authenticated session. If found, it fetches any transactions that happened while the app was closed and fires OnSdkTransactionsReceived with a List<SdkTransaction>.
2. Player opens the store
PlaySuperUnitySDK.OpenStore() opens the store and enables transaction syncing for this player.
3. Player makes a purchase
When a purchase completes inside the store, the SDK is notified immediately. It fetches the latest transactions and fires OnSdkTransactionsReceived with a List<SdkTransaction> — while the store is still open. The player can continue shopping, and your game receives each transaction as it happens.
4. Player closes the store
The SDK performs a final fetch to catch any transactions that may have been missed. Duplicates are automatically filtered — your callback only receives new transactions.
5. Next app session
Same as step 1 — any transactions that occurred between sessions are picked up on launch.
Backward compatible: Older SDK versions that don't support realtime notifications will still receive all transactions when the store closes.
Integration Guide
Important: Always call PlaySuperUnitySDK.Initialize() as early as
possible when your game opens — ideally in your main scene's Awake() or
Start() method. This ensures the SDK can fetch any transactions that
occurred while the app was closed and sets up the authentication session for
transaction syncing.
Step 1: Subscribe to Transaction Events
Subscribe to the OnSdkTransactionsReceived event to be notified when transactions are available:
using PlaySuperUnity;
using System.Collections.Generic;
void Awake()
{
// Initialize the SDK as early as possible on game open
PlaySuperUnitySDK.Initialize();
}
void Start()
{
// Subscribe — callback receives List<SdkTransaction> on every sync
PlaySuperUnitySDK.OnSdkTransactionsReceived += HandleSdkTransactions;
}
void OnDestroy()
{
// Always unsubscribe when the object is destroyed
PlaySuperUnitySDK.OnSdkTransactionsReceived -= HandleSdkTransactions;
}
void HandleSdkTransactions(List<SdkTransaction> transactions)
{
var processedIds = new List<string>();
foreach (var txn in transactions)
{
Debug.Log($"Transaction: {txn.source} - {txn.amount} {txn.coinName}");
if (txn.source == "PURCHASE_DEBIT")
{
// Player made a purchase - show notification, update UI, etc.
ShowPurchaseNotification(txn);
}
else if (txn.source == "REFUND_CREDIT")
{
// Player received a refund - update balance display, show notification
ShowRefundNotification(txn);
}
processedIds.Add(txn.id);
}
// After processing, commit only the transactions we processed
CommitTransactions(processedIds);
}
async void CommitTransactions(List<string> transactionIds)
{
if (transactionIds.Count > 0)
{
// Commit specific transactions by their IDs
var result = await PlaySuperUnitySDK.CommitSdkTransactionsByIds(transactionIds.ToArray());
if (result != null && result.Success)
{
Debug.Log($"Committed {result.CommittedCount} transactions");
}
else
{
Debug.LogWarning("Failed to commit transactions - will retry on next fetch");
}
}
}The OnSdkTransactionsReceived event fires automatically: - In realtime
when the player makes a purchase while the store is open (via URL scheme
bridge) - After player authentication - On SDK initialization if the player
was previously logged in - When the store WebView closes (safety net to catch
any missed transactions)
Step 2: Understanding the SdkTransaction Object
Each transaction contains the following fields:
| Field | Type | Description |
|---|---|---|
id | string | Unique transaction identifier |
amount | float | Transaction amount (positive for credits, negative for debits) |
type | string | CREDIT or DEBIT |
source | string | PURCHASE_DEBIT or REFUND_CREDIT |
coinId | string | ID of the coin involved |
coinName | string | Display name of the coin (e.g., "Gold Coins") |
description | string | Human-readable description (e.g., "Purchase: Amazon Gift Card") |
createdAt | string | ISO 8601 timestamp when the transaction occurred |
Step 3: Committing Transactions
After your game has processed the transactions, you must commit them to the server. This prevents you from receiving the same transactions again.
Per-Transaction Commit (Recommended)
Commit specific transactions by their IDs after processing each one. This approach is safer because:
- Only successfully processed transactions are marked as committed
- Failed or unprocessed transactions remain pending for retry
- You get detailed feedback on what succeeded or failed
async void ProcessAndCommitTransactions(List<SdkTransaction> transactions)
{
var processedIds = new List<string>();
foreach (var txn in transactions)
{
try
{
// Process the transaction in your game
ProcessTransaction(txn);
processedIds.Add(txn.id);
}
catch (Exception e)
{
Debug.LogError($"Failed to process transaction {txn.id}: {e.Message}");
// Don't add to processedIds - will retry on next fetch
}
}
if (processedIds.Count > 0)
{
// Commit only the transactions we successfully processed
var result = await PlaySuperUnitySDK.CommitSdkTransactionsByIds(processedIds.ToArray());
Debug.Log($"Committed: {result.CommittedCount}");
Debug.Log($"Already committed: {result.AlreadyCommittedCount}");
Debug.Log($"Not found: {result.NotFoundCount}");
Debug.Log($"Failed: {result.FailedCount}");
}
}The CommitByIdsResult provides detailed breakdown:
| Property | Description |
|---|---|
Committed | Transaction IDs successfully committed this request |
AlreadyCommitted | Transaction IDs that were already committed previously |
NotFound | Transaction IDs not found on server |
Failed | Transaction IDs that failed to commit |
CommittedCount, AlreadyCommittedCount, NotFoundCount, FailedCount | Count properties for each category |
Success | true if the request completed (even if some individual transactions failed) |
Legacy Checkpoint Commit
Alternatively, you can commit all pending transactions at once using the checkpoint-based approach:
// Commit all pending transactions at once
bool success = await PlaySuperUnitySDK.CommitAllPendingSdkTransactions();
// Or commit up to a specific transaction ID
bool success = await PlaySuperUnitySDK.CommitSdkTransactions(lastTransactionId);The checkpoint approach commits all transactions up to a given ID. If your game crashes after committing but before fully processing, those transactions are lost. Use per-transaction commit for safer handling.
Step 4: Manual Fetching (Optional)
While transactions are fetched automatically on login, you can also fetch them manually:
async void RefreshTransactions()
{
var transactions = await PlaySuperUnitySDK.FetchSdkTransactions();
if (transactions != null && transactions.Count > 0)
{
Debug.Log($"Fetched {transactions.Count} transactions");
// The OnSdkTransactionsReceived event will also fire
}
}Step 5: Checking for Pending Transactions
You can check if there are unprocessed transactions stored locally:
// Check if there are pending transactions
if (PlaySuperUnitySDK.HasPendingSdkTransactions())
{
// Get the pending transactions
List<SdkTransaction> pending = PlaySuperUnitySDK.GetPendingSdkTransactions();
Debug.Log($"There are {pending.Count} pending transactions");
}Step 6: Clearing State on Logout
When the player logs out, clear the transaction sync state:
void OnPlayerLogout()
{
// Clear SDK transaction sync state
PlaySuperUnitySDK.ClearSdkTransactionSyncState();
// ... other logout logic
}Complete Example
Here's a complete example of integrating transaction sync in your game:
using UnityEngine;
using PlaySuperUnity;
using System.Collections.Generic;
public class PlaySuperTransactionHandler : MonoBehaviour
{
[SerializeField] private TMPro.TextMeshProUGUI balanceText;
[SerializeField] private GameObject purchaseNotificationPrefab;
[SerializeField] private GameObject refundNotificationPrefab;
private List<string> processedTransactionIds = new List<string>();
void Awake()
{
// Initialize SDK as early as possible on game open
PlaySuperUnitySDK.Initialize();
}
void Start()
{
// Subscribe to transaction events
PlaySuperUnitySDK.OnSdkTransactionsReceived += OnTransactionsReceived;
}
void OnDestroy()
{
PlaySuperUnitySDK.OnSdkTransactionsReceived -= OnTransactionsReceived;
}
async void OnTransactionsReceived(List<SdkTransaction> transactions)
{
processedTransactionIds.Clear();
foreach (var txn in transactions)
{
try
{
ProcessTransaction(txn);
processedTransactionIds.Add(txn.id);
}
catch (System.Exception e)
{
Debug.LogError($"Failed to process transaction {txn.id}: {e.Message}");
// Don't add to processedTransactionIds - will retry on next fetch
}
}
// Commit only the transactions we successfully processed
await CommitProcessedTransactions();
}
void ProcessTransaction(SdkTransaction txn)
{
switch (txn.source)
{
case "PURCHASE_DEBIT":
// Show purchase notification
var purchaseNotif = Instantiate(purchaseNotificationPrefab);
purchaseNotif.GetComponent<NotificationUI>().Setup(
$"Purchase Complete!",
$"You spent {Mathf.Abs(txn.amount)} {txn.coinName}",
txn.description
);
break;
case "REFUND_CREDIT":
// Show refund notification
var refundNotif = Instantiate(refundNotificationPrefab);
refundNotif.GetComponent<NotificationUI>().Setup(
$"Refund Received!",
$"You received {txn.amount} {txn.coinName} back",
txn.description
);
break;
}
// Refresh balance display
RefreshBalance();
}
async void RefreshBalance()
{
var balances = await PlaySuperUnitySDK.Instance.GetBalance();
if (balances != null && balances.Count > 0)
{
balanceText.text = $"{balances[0].amount} {balances[0].name}";
}
}
async System.Threading.Tasks.Task CommitProcessedTransactions()
{
if (processedTransactionIds.Count == 0) return;
var result = await PlaySuperUnitySDK.CommitSdkTransactionsByIds(
processedTransactionIds.ToArray()
);
if (result != null)
{
Debug.Log($"Committed {result.CommittedCount} transactions");
if (result.FailedCount > 0)
{
Debug.LogWarning($"{result.FailedCount} transactions failed to commit");
}
}
else
{
Debug.LogWarning("Failed to commit transactions - will retry later");
}
}
}API Reference
Events
| Event | Parameters | Description |
|---|---|---|
OnSdkTransactionsReceived | List<SdkTransaction> | Fired when new transactions are available |
Methods
| Method | Returns | Description |
|---|---|---|
FetchSdkTransactions() | Task<List<SdkTransaction>> | Manually fetch transactions from server |
GetPendingSdkTransactions() | List<SdkTransaction> | Get locally stored pending transactions |
HasPendingSdkTransactions() | bool | Check if there are pending transactions |
HasVisitedStore() | bool | Check if player has opened the store |
CommitSdkTransactionsByIds(string[] ids) | Task<CommitByIdsResult> | Commit specific transactions by ID (recommended) |
CommitSdkTransactions(string id) | Task<bool> | Commit transactions up to a specific ID (legacy) |
CommitAllPendingSdkTransactions() | Task<bool> | Commit all pending transactions (legacy) |
ClearSdkTransactionSyncState() | void | Clear all local sync state (call on logout) |
Troubleshooting
Transactions not appearing
- Ensure the player is authenticated before fetching transactions
- Check that you're subscribing to
OnSdkTransactionsReceivedbefore the player logs in - Verify the API key is correct and matches your game
Receiving duplicate transactions
- Make sure you're calling
CommitSdkTransactionsorCommitAllPendingSdkTransactionsafter processing - Check that the commit call is succeeding (returns
true)
Transactions not persisting across sessions
- Ensure you're not calling
ClearSdkTransactionSyncState()during normal gameplay - The sync state is stored in PlayerPrefs - verify PlayerPrefs are not being cleared
If you encounter issues, enable debug logging by checking the Unity Console
for messages prefixed with [PlaySuper].