FTN19.4: FutoIn Interface - Transaction Engine - Transactions Version: 1.0DV Date: 2017-12-24 Copyright: 2017 FutoIn Project (http://futoin.org) Authors: Andrey Galkin
This is sub-specification of main FTN19: Transaction Engine.
The following list is recommendation. Particular implementation may omit some or add additional types. However, if type is already listed in the spec then it must be used.
As many transaction processing types involve expenses for operator they may be accompanied by Fee transaction which should be atomically processed with relates transaction.
Fees due to operation initiatied by account holder action should not allow account balance to become negative. Other type of fees may result in negative account balance, if the cause is unavoidable.
rel_account
and ext_id
.orig_ts
and similar fields must always point to original initiation time in external system.orig_ts
is approximate value with 24h precision - it's minor mismatch must not cause failureIn all cases, if not noted otherwise, Withdrawals, Refunds, Wins, ClearBonus go in opposite direction of Deposits, Purchases, Bets and Bonus respectively.
Pre-authorizations must be reserved only on Regular accounts.
Third-party service may be integrated through External(Service) accounts:
The difference to Operator case, is that Operator has own system and Regular accounts are managed externally there.
Aggregator acts as a proxy. For Services, it shows as Operator, but for Operator it shows as Service. This allows chaining multiple Aggregators - typical case in modern online gaming.
Even if aggregator supports Payments as part of business, it should be provided as separate Payments service on architecture level.
There are cases when user wallet is managed outside of the system while transaction engine has control only over Transit account. All operations on Transit account must require online communication with external peer systems.
Similar to that synchronous risk assessment can be done in scope of single transaction processing.
Transaction engine should automatically workaround interruptions and repeated calls for error recovery.
There should be unified interface for transaction operation with only difference - time required for operation.
Some operations like withdrawals, purchases or manual risk analysis require relatively long interruption for human confirmation/rejection. Interface for such operations assumes such interruption by splitting processing into different API calls.
The interfaces defined here as well as in other parts of FTN19 are designed for INTERNAL USE.
Specific instances of transaction engine may support only subset of all possible transaction processing features. Therefore, each part is split into own interface/module.
General notes for cancellation: even if transaction is not known, it must be marked as canceled. So, if original transaction request comes after cancel request, it should get "AlreadyCanceled" status.
A special system "External" account ID is assumed in "rel_account" field. It is used as source of/sink for transaction credits. Primary reason is to manage limits per external system. It may also aid integrity checks.
Processing of deposit transactions. Actual external processing & integration is out of scope. The interface is only responsible for "recording" fact of transaction.
Fee is deducted from deposit amount.
{
"iface" : "futoin.xfer.deposit",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"funcs" : {
"preDepositCheck" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount"
},
"result" : "boolean",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject"
],
"desc" : "Check if system allows deposit"
},
"onDeposit" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "XferID",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
Similar to deposits, this interface is only reponsible for in-system processing of withdrawal transactions. External processing is out of scope.
Fee is processed as extra on top of transaction amount.
{
"iface" : "futoin.xfer.withdraw",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"funcs" : {
"startWithdrawal" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"extra_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : {
"xfer_id" : "XferID",
"wait_user" : "boolean"
},
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"NotEnoughFunds",
"AlreadyCanceled"
]
},
"confirmWithdrawal" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"extra_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"rejectWithdrawal" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"extra_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCompleted",
"OriginalTooOld",
"OriginalMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
This module is focused on processing of gaming transactions. It should be supported only for e-gaming and similar projects with real-time transactions.
Unlike other interfaces, there is no Account ID involved directly as it assumed that there is is unique pair of AccountHolderID + CurrencyCode for Regular Account type. However, it's possible to have more than one Bonus account types for proper bonus amount processing.
If there are associated bonus accounts of specified currency then their balance must be used in creation order (date), but only after main account is depleted.
For simplicity reasons, if main account is transit then "NotEnoughFunds" error is seen as "depleted" account even though some funds remain available for betting. Such situation may happen only if Bonus and Wallet systems are located on different nodes.
If more than one account is used for placing of bets then win amount must be distributed proportionally excluding non-Bonus accounts for fraud mitigation reasons.
Creation, cancellation and release of bonus accounts is out of scope.
It must not happen that "cancelBet" is called after any related "win". "round_id" is used to tie bets to wins for proper bonus win calculations and general security.
The "gameBalance" call may return different amounts based external info details (out of scope).
The interface is still internal and must not be exposed.
{
"iface" : "futoin.xfer.gaming",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"types" : {
"RelatedBetPH" : {
"type" : "enum",
"items" : [
"%FreeSpin%",
"%Prize%",
"%Award%"
]
},
"RelatedBet" : [ "XferExtID", "RelatedBetPH" ]
},
"funcs" : {
"bet" : {
"params" : {
"user" : "AccountHolderExternalID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"round_id" : "XferExtID",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : {
"xfer_id" : "XferID",
"balance" : "Balance",
"bonus_part" : "Amount"
},
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"OutOfBalance",
"LimitReject",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"cancelBet" : {
"params" : {
"user" : "AccountHolderExternalID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"round_id" : "XferExtID",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"reason" : "Reason"
},
"result" : {
"balance" : "Balance"
},
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"OriginalTooOld",
"OriginalMismatch"
]
},
"win" : {
"params" : {
"user" : "AccountHolderExternalID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"round_id" : "XferExtID",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : {
"xfer_id" : "XferID",
"balance" : "Balance"
},
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
},
"gameBalance" : {
"params" : {
"user" : "AccountHolderExternalID",
"currency" : "CurrencyCode",
"ext_info" : {
"type": "XferExtInfo",
"default": null
}
},
"result" : {
"balance" : "Balance"
},
"throws" : [
"UnknownHolderID",
"UnknownCurrency",
"CurrencyMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
This interface is responsible for processing transactions in scope of goods and service purchase.
Refunds is assumed to be partial. Otherwise, cancelPurchase must be used.
Refund is a separate transaction type. Therefore, it does not return xfer fee.
{
"iface" : "futoin.xfer.retail",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"funcs" : {
"purchase" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"fee" : {
"type" : "Fee",
"default" : null
},
"rel_preauth" : {
"type" : "XferID",
"default" : null
}
},
"result" : {
"xfer_id" : "XferID",
"wait_user" : "boolean"
},
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch",
"UnavailablePreAuth"
]
},
"cancelPurchase" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"fee" : {
"type" : "Fee",
"default" : null
},
"reason" : "Reason"
},
"result" : "boolean",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch",
"AlreadyRefunded"
]
},
"confirmPurchase" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"rejectPurchase" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCompleted",
"OriginalTooOld",
"OriginalMismatch"
]
},
"refund" : {
"params" : {
"purchase_id" : "XferID",
"purchase_ts" : "XferTimestamp",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : "boolean",
"throws" : [
"CurrencyMismatch",
"NotEnoughFunds",
"AmountTooLarge",
"PurchaseNotFound",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"preAuth" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : {
"xfer_id" : "XferID",
"wait_user" : "boolean"
},
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"clearPreAuth" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : "boolean",
"throws" : [
"OriginalTooOld",
"OriginalMismatch"
]
},
"confirmPreAuth" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp"
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"rejectPreAuth" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp"
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCompleted",
"OriginalTooOld",
"OriginalMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
Manage claiming, canceling and releasing bonus. To avoid confusion with regular transaction cancel, cancel operation is called "clear".
ext_id
on claim is used as external account ID and as transaction ID.
{
"iface" : "futoin.xfer.bonus",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"funcs" : {
"claimBonus" : {
"params" : {
"user" : "AccountHolderExternalID",
"rel_account" : "AccountID",
"alias" : "AccountAlias",
"currency" : "CurrencyCode",
"amount" : "Amount",
"bonus_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : "boolean",
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
},
"clearBonus" : {
"params" : {
"user" : "AccountHolderExternalID",
"rel_account" : "AccountID",
"bonus_id" : "XferExtID"
},
"result" : "boolean",
"throws" : [
"UnknownHolderID",
"UnknownAccountID",
"AlreadyReleased"
]
},
"releaseBonus" : {
"params" : {
"user" : "AccountHolderExternalID",
"rel_account" : "AccountID",
"bonus_id" : "XferExtID"
},
"result" : "boolean",
"throws" : [
"UnknownHolderID",
"UnknownAccountID",
"AlreadyCanceled"
]
}
},
"requires" : [ "SecureChannel" ]
}
Direct payments interface for incoming and outgoing payment processing.
{
"iface" : "futoin.xfer.direct",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"funcs" : {
"startOutbound" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"extra_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : {
"xfer_id" : "XferID",
"wait_user" : "boolean"
},
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch",
"NotEnoughFunds",
"AlreadyCanceled"
]
},
"confirmOutbound" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"extra_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCanceled",
"OriginalTooOld",
"OriginalMismatch"
]
},
"rejectOutbound" : {
"params" : {
"xfer_id" : "XferID",
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"extra_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "boolean",
"throws" : [
"UnknownXferID",
"AlreadyCompleted",
"OriginalTooOld",
"OriginalMismatch"
]
},
"onInbound" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"xfer_fee" : {
"type" : "Fee",
"default" : null
}
},
"result" : "XferID",
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
This interface is assumed to be used for operator activities and automatic scheduled tasks.
Settlement is used to adjust balance to reflect external operations like payment or some valuables transfer done outside.
All transactions are processed as force and may result in negative balance.
{
"iface" : "futoin.xfer.generic",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"types" : {
"CancelableXferType" : {
"type" : "enum",
"items" : [
"Deposit",
"Withdrawal",
"Purchase",
"Refund",
"PreAuth",
"Win",
"Fee",
"Settle",
"Generic"
]
}
},
"funcs" : {
"fee" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"reason" : "Reason",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"force" : "boolean"
},
"result" : "XferID",
"throws" : [
"UnknownHolderID",
"CurrencyMismatch",
"InvalidAmount",
"NotEnoughFunds",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
},
"settle" : {
"params" : {
"account" : "AccountID",
"rel_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"reason" : "Reason",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : "XferID",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"NotEnoughFunds",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
},
"cancel" : {
"params" : {
"xfer_id" : "XferID",
"type" : "CancelableXferType",
"src_account" : "AccountID",
"dst_account" : "AccountID",
"currency" : "CurrencyCode",
"amount" : "Amount",
"orig_ts" : "XferTimestamp",
"xfer_fee" : {
"type" : "Fee",
"default" : null
},
"extra_fee" : {
"type" : "Fee",
"default" : null
},
"reason" : "Reason"
},
"result" : "boolean",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"NotEnoughFunds",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
It is assumed that all external systems operate on the same transaction interfaces as already defined above.
External IDs in "current" systems refer to internal IDs in "other" system and vice-versa.
This interface is left as placeholder for system-to-system interface not defined in other specs.
{
"iface" : "futoin.xfer.peer",
"version" : "{ver}",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0",
"futoin.xfer.types:{ver}"
],
"funcs" : {
"pair" : {
"params" : {
"ext_id" : "AccountExternalID",
"currency" : "CurrencyCode",
"alias" : "AccountAlias"
},
"result" : "AccountID",
"throws" : [
"CurrencyMismatch"
]
},
"rawXfer" : {
"params" : {
"to_external" : "boolean",
"xfer_type" : "XferType",
"orig_currency" : "CurrencyCode",
"orig_amount" : "Amount",
"src_account" : "AccountID",
"src_currency" : "CurrencyCode",
"src_amount" : "Amount",
"dst_account" : "AccountID",
"dst_currency" : "CurrencyCode",
"dst_amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp"
},
"result" : "XferID",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch",
"NotEnoughFunds",
"AlreadyCanceled"
]
},
"cancelXfer" : {
"params" : {
"to_external" : "boolean",
"xfer_type" : "XferType",
"orig_currency" : "CurrencyCode",
"orig_amount" : "Amount",
"src_account" : "AccountID",
"src_currency" : "CurrencyCode",
"src_amount" : "Amount",
"dst_account" : "AccountID",
"dst_currency" : "CurrencyCode",
"dst_amount" : "Amount",
"ext_id" : "XferExtID",
"ext_info" : "XferExtInfo",
"orig_ts" : "XferTimestamp",
"reason" : "Reason"
},
"result" : "boolean",
"throws" : [
"UnknownAccountID",
"CurrencyMismatch",
"InvalidAmount",
"LimitReject",
"OriginalTooOld",
"OriginalMismatch"
]
}
},
"requires" : [ "SecureChannel" ]
}
=END OF SPEC=