![]() |
![]() |
![]() |
![]() |
||||
|
Other FPI Links:
FPI Client Guide |
Financial Posting Interface 1.1 - Server Guide© 2002 Quik Sense Software By Scott K. MaxwellThe Financial Posting Interface allows any application to post transactions to any FPI-compliant account manager. This document will tell you how to add FPI to your account manager.FPI was created by Quik Sense Software as a generic way to post transactions to account managers and to update those transactions if necessary. I created FPI primarily to interface my Quik Budget product to account managers. Through FPI, users can create and modify transactions in Quik Budget and the same transactions will be created and modified in the user's account manager of choice. This avoids the problem of having to enter the transaction into multiple applications. However, FPI is not for Quik Budget alone.Through FPI any application can post transactions with no knowledge of the internal workings of the current account manager. This document is aimed at account manager authors who are interested in making their applications FPI compliant. Features of FPIFPI plug-ins can:
Clients can:
Getting the FPI Server SDKAll of the code necessary for implementing FPI plug-ins is available in the FPI Server SDK at http://quiksense.com/FPI/FPIServerSDK.zip. You probably want to extract the SDK into your main project directory. You will need to add RegisterPlugIn.c to your project.source code. How the Plug-in WorksAn FPI plug-in is built as a PalmOS code resource. It is maintained as a separate project from your main application but it can share code files with your main app and CodeWarrior can be setup to automatically build the plug-in as a sub-project of your main app. It can access the resources of your main application but there is no easy way (that I know of) for the plug-in to access your main apps code. FPI plug-ins generally range between 12K and 16K in size. Although FPI is implemented in C++, you can add FPI support to your application even if your account manager is written completely in C. The only change you will need to make to your main source code is to call the RegisterAccountManager() function. That function is written in ANSI C. Alternatively, you could distribute FPI support as a separate PRC and make no changes to your main app but that could lead to versioning issues later so I don't really advise that approach. An account manager plug-in is built as a code resource of type 'FPAM', resource number zero. If you build the plug-in as a standalone PRC, it should be of type 'FPAM' with the same creator ID as your main app. If you build it into your main app, the RegisterAccountManager() function will create a small database of type 'FPAM' with the creator ID of your main app and will store the type/creator of your main application database in record 0. Call this in your app startup code so that your application will automatically register as the default account manager whenever it is run. FPI clients will open the first database of type 'FPAM'. If it is a resource database, the client will attempt to use that database as the plug-in. If not, it reads the first 8 bytes from record zero to discover the type/creator of the actual plug-in database. What is in the FPI Server SDKWhen you first extract the files from the FPI Server SDK, it may look a bit intimidating. Don't worry. It is mostly sample code. The SDK includes the following projects:
The actual code is split up into seven sub-directories as follows: FPI Plug-In Test - Code for the test app.
MyPlug-In - Code for the skeleton plug-in.
SamplePlug-In - Code for the complete sample plug-in and account manager.
AccountManagerBase - You will only need to deal with RegisterPlugIn.h/.c if you use the skeleton or sample plug-in.
ClientCode - Code that is used by both the plug-in and by FPI clients
PlugInBase - Base code for generic plug-ins. You can use this base code to create your own C++ plug-ins. Utility - Various utility functions to make life easier.
Plug-In TestWhen you extract the SDK, the test app is setup to build the sample account manager and plug-in. You may want to play with this a bit just to see how it works.
Implementation StrategyThis is what I believe to be the simplest way to implement a plug-in:
Adding a Sub-Project in CodeWarrior (Building the plug-in into your main app)To ensure that your main application and plug-in stay in sync, you will want to add the plug-in to your main project as a sub-project. Here is how:
Making the Plug-In Your OwnWhether you choose to start from the sample plug-in or the skeleton, there is one thing you have to do to make the plug-in your own. Change the creatorID to match your main app. Even though the plug-in will ultimately be built in to your main app, you will need to make this change so that you can debug the plug-in through the PRC. Registering Your Plug-inIf you build the plug-in into your main application database, you need to register it when your application is run. This will allow clients to find your plug-in. To register your plug-in, just call RegisterAccountManager() from your startup code. That will create a small glue database called FPAM-PlugIn that contains the type/creator for your app. The only problem with having the plug-in built into your application is that if the user runs your app from VFS, your plug-in will not be in RAM for clients to use. Fortunately, there is a solution. In the switch statement in your PilotMain, simply add these lines: case sysAppLaunchCmdCardLaunch: CopyAccountManagerPlugIn(); return 0; Notice the return 0. You are not supposed to actually run your application when you receive sysAppLaunchCmdCardLaunch. Your application will be launched again immediately with the normal launch code. Now when your app is launched from a card, this will copy the code resource for the plug-in to its own database so that it remains resident, even when the main application no longer is. It also temporarily sets a feature so that the RegisterAccountManager function will not overwrite the plug-in. If the user moves your app back to main RAM, RegisterAccountManager will automatically replace the plug-in with the small glue database, thus reclaiming the memory. Setup and CleanupHere is a basic description of the life cycle of an FPI plug-in:
Since FPIAccountManager is a sub-class of AccountManagerPlugIn, we have to start by calling the AccountManagerPlugIn constructor: AccountManagerPlugIn( UInt32 creator,
You should not do any work in the constructor. Just zero any pointers or DmOpenRef objects and call InitVTable(). The client will need to setup a few more things in the plug-in before you actually initialize it. You should open your databases and verify that everything is in working order in the Init method. If you encounter a problem and the plug-in cannot be fully initialized, set the m_valid member to false. In the destructor you will need to free any resources you have used and close your databases. Info from the ClientThe client will provide the plug-in with a bit of info about itself and its expectations. This info may be queried with the following methods: UInt32 ClientCreatorID(); None of these methods is available until the client calls the Init method so don't try to use them in your constructor.
Account MethodsSo that the client can get information about your accounts, you need to implement:
UInt16 AccountIndexOf(const char *name); const char* GetAccountName(UInt16 index); UInt16 AccountCount(); The client will need to get an index of the account in order to use the transaction number methods or to set the selection value for a list of accounts. AccountIndexOf should take a name and return a list index. If the account does not exist, it should return EOF (0xffff). GetAccountName should convert that index back into a name. The memory is managed by the plug-in. AccountCount returns the number of valid accounts. GetAccountList returns a list of the valid accounts.
If no valid accounts exist, return NULL. The client can pass this list
directly to LstSetChoices. Transaction Number/Type MethodsMany account managers support transaction numbers and types. FPI allows this to be set by passing a single string to the Post and Change methods. If that string is a number, it is assumed to be a check number. If a string, it is the transaction type. If empty or NULL, it is unspecified. So that the client can get a list of valid transaction types, you need to implement:
Int32 GetAccountNextCheckNumber(UInt16 acctIndex); void SetAccountLastCheckNumber(UInt16 acctIndex, Int32 last); UInt16 TransactionNumberStringCount(UInt16 acctIndex); const char** GetTransactionNumberList(UInt16 acctIndex); Each of these methods takes a parameter called acctIndex. This is the index returned by AccountIndexOf. GetAccountNextCheckNumber should return the next check number for the specified account. If you do not support this feature, just return zero. The client may also call SetAccountLastCheckNumber to update the value. If you do not support this feature, just do nothing. TransactionNumberStringCount returns the number of transaction types for the given account. GetTransactionNumberList returns a list of transaction
type strings for the given account. If no valid transaction types exist,
return NULL. The client can pass this list directly to LstSetChoices. Category MethodsFPI supports infinitely nested category structures. To accomodate this, each of the category methods takes a root string. The prototypes look like this:
bool HasCategories(const char *root=NULL); UInt16 CategoryCount(const char *root=NULL); const char** GetCategoryList(const char *root=NULL); If the client passes NULL to any of these methods, you should return data based on the root categories. If the client passes a root string, the return values should refer to sub-categories of that root. For instance, this call should return the number of sub-categories of "Dining":
If no valid categories or sub-categories exist, the GetCategoryList should return NULL. FPI 1.1 adds three more category methods: UInt16 CompleteCategoryCount(); const char** GetCompleteCategoryList(); const char* GetCategoryName(UInt16 index); These are used to handle complete, indented lists of categories. GetCompleteCategoryList() will typically return a list that looks something like this: Auto Fuel Wash Dining Breakfast Lunch Dinner Gifts Groceries GetCategoryName takes an index into this list and returns the fully qualified category name. In the above example, GetCategoryName(2) would return "Auto:Fuel". The account manager is responsible for managing the memory used for this string data. Typically, the account manager will have a single buffer it uses for building the category string and it reuses the buffer every time this method is called. WARNING: As with the Account and Transaction Number methods, the client is expected to do MemPtrFree on the block of data returned by GetCategoryList and GetCompleteCategoryList when the data is no longer needed. Be sure to use MemPtrNew to allocate the memory you need for this. Class MethodsClasses are similar to categories but they are not usually nested. Classes seem to be supported by about half of the account managers currently available for the Palm platform. If you support classes, you need to implement the following:
UInt16 ClassCount(); const char** GetClassList(); WARNING: As with the Account and Transaction Number methods, the client is expected to do MemPtrFree on the block of data returned by GetClassList when the data is no longer needed. Be sure to use MemPtrNew to allocate the memory you need for this. Payee MethodsFrom FPI's point of view, payees are simply lists of names. Some account managers have a separate list of payees for each category. If you support payees, you need to implement the following:
UInt16 PayeeCount(const char *category); const char** GetPayeeList(const char *category); WARNING: As with the Account and Transaction Number methods, the client is expected to do MemPtrFree on the block of data returned by GetPayeeList when the data is no longer needed. Be sure to use MemPtrNew to allocate the memory you need for this. Master CurrencySome clients will want to find out what your app's master currency is. Quik Budget uses this to make sure that it is using the same master currency as the account manager and warns the user if there is a mismatch. If you support currencies, you should implement:
bool GetMasterCurrency(char *symbol, char *code=0) The client is expected to provide 6-byte buffers for both symbol and code. Be sure you don't write more than 5 bytes plus a NULL. Financial Fixed-PointFPI uses Financial Fixed-Point (FFP) to specify all amounts. Include FFP.h in any .c/.cpp files that need to use FFP. Internally, FFP consists of an Int32 and a UInt16. The UInt16 specifies how many decimals of precision are needed. This will almost always be either zero or two. $20.00 is represented as an Int32 of 2000 and a UInt16 of 2. 2000 lira is represented as an Int32 of 2000 and a UInt16 of 0. This allows for a good range of values, very high precision and good performance. You can create FFP objects from either integers or doubles. To create an FFP object representing $98.47, you would do this: FFP amount(9847,2); // Constructor taking an Int32 Or: FFP amount(98.47,2); // Constructor taking a double You can query the value with 4 methods: Minimal PostClients use the Post method to send transactions to your app. Here is an example:
If the post is successful, it should return a unique ID that can be used by the client to retrieve the transaction again later. If the post fails, it should return 0. The Post method and the Change method are very similar. Post is used to create new transactions and Change is used to modify existing transactions. Although, you could do all of the work in the Post method, I would recommend that you not do that. Instead, do the bare minimum amount of work necessary to create the transaction here and then call the Change method. The Change method takes all of the same parameters as Post with the exception of the UIFlag at the end. You may want to assign default values to any of the parameters the client left blank before calling the Change method. Have a look at the Post method in the sample plug-in to see how to have the Post method call Change. The final parameter will usually be fpiShowUIIfIncomplete. This flag is a suggestion. You never need to show UI but you should never show UI if this flag is set to fpiShowUINever or if UIIsOkay returns false. GetTransaction and the FPITransactionHandle objectWhen a client wants to query or modify an existing transaction, it must first get a handle to the transaction record. The client does this by instantiating a glue object called AMTransaction. The AMTransaction object calls the FPIAccountManager::GetTransaction method and passes the unique ID that it previously received from the Post method. The GetTransaction method should use the UID to find the transaction record. If the record does not exist, GetTransaction returns NULL. Otherwise it creates an FPITransactionHandle object and returns a pointer to it. You can see examples of this in both the skeleton and the sample plug-in. The FPITransactionHandle object exists as long as the clients AMTransaction object does. The AMTransaction destructor deletes the FPITransactionHandle object. FPITransactionHandle is responsible for freeing all resources it needed to allocate. TIP: If you have pack and unpack functions, you may want to have the FPITransactionHandle object contain a copy of your unpacked structure. The constructor can then call the unpack function so that the transaction data is in an easy to use form. If the client calls the Change method, you can simply write the changes to the unpacked structure and set a dirty flag. Then in your destructor, you can repack the transaction if it is dirty and move it if the sort location has changed. ChangeThe Change method looks almost identical to the Post method. It works very much like DmSetRecordInfo or DmSetDatabaseInfo. The client will pass in valid pointers for everything that needs to be updated. Any NULL parameters are left as is. The first parameter is a link ID. If not 0, this is a unique ID for the account manager to store if you want to directly access the matching client record. If your plug-in does not know how to handle records from the client app, you should probably just ignore this. In the case of Quik Budget, the link ID is always the unique ID of the QB transaction that corresponds to this post. When QB posts transactions to an account manager, you always end up with the transaction existing in both the account manager and in QB. You can use the ClientHandles(BUDGETMANAGER_CLASS) call to see if the linkID is coming from Quik Budget. The second parameter is account. If the account does not exist, it is up to the account manager to decide what to do. You could create the account. If UI is allowed, you could ask the user if they want to create the account. Or you could just quit and return a unique ID of zero to indicate failure. The last option is probably the most common. The transferToAccount is specified if the transaction is a transfer. comment is often used for payee. The transaction number or type is used to indicate a check number, transaction number, or type of transaction such as 'ATM', 'Withdrawal', 'Deposit', etc. Category capability varies greatly among account managers. Some only have a single level of categories. Others allow for a second level or even unlimited levels of nested categories. In FPI, these are always represented as a single string with the different levels separated by a colon. For instance, "Dining:Lunch" would indicate the Lunch sub-category of Dining. It is entirely up to the account manager how it will handle categories. If the category has more levels than the account manager represents, you could just chop off the extra levels, create a new category containing the whole string or simply fail and return a 0. The last option is pretty unfriendly, though. Some account managers support transaction classes as well as categories. This is often used to specify 'Business' and 'Personal' or to specify the name of the person who spent the money. A negative amount indicates money spent, charged or withdrawn and a positive value indicates money deposited or credited. Expense amounts are always specified in Financial Fixed-Point as detailed above. The exchange rate is always a double representing the number to multiply the amount by to get the total in the native currency. If the native currency is US$, the transaction currency is British Pounds and the amount is 10 pounds, an exchange rate of 1.6 would indicate US$16.00. The client may specify both the symbol and the code of the currency as different account managers and different users prefer to use one over the other. The symbol is the common symbol used for that currency such as '$'. The code is the 3-letter code used to indicate the currency such as 'USD'. If you only use one or the other, you should probably check for a match against both strings. If the client specifies a category, class or currency that does not exist in your account manager, you should create it if possible. If the client is not Quik Budget, you should probably chain through to the Quik Budget plug-in. An example of how to do this is in both the skeleton and the sample plug-in. Return the unique ID of the transaction. If for some reason (i.e. account doesn't exist) you had to delete the transaction, return zero. GetOnce a client has a valid FPITransactionHandle, it can use the Get method to retrieve the details. Get allows the client to query all of the information that was set in the Post and Change methods and has almost exactly the same prototype. The main difference is that everything is a pointer. For instance, instead of: const char *commentGet expects: const char **comment This is very similar to the way DmRecordInfo and DmDatabaseInfo work. The client simply passes in valid pointers for the items it wishes to retrieve and NULL for those it doesn't need. Important: The string data is managed by the FPITransactionHandle object so be sure to free any strings you had to allocate when the destructor is called. DeleteDelete should remove the transaction and add the money back in to the account. AddSplitOnce the client has created a transaction, it may want to add splits to it. This is done with the AddSplit method. Here is an example of the client code:
The resulting transaction would be for $10 (6.92+3.08) and contain a split with two parts. Whenever you add a split, the original transaction becomes split 1 and the first AddSplit becomes split two. Notice that the AddSplit method takes a linkID and returns a unique ID. The reason for this is that each split can be accessed independently. For example, Quik Budget does not have true split support so the QB AddSplit method simply creates another transaction. An account manager with true split support could keep a separate QB link ID for each split. An account manager with true splits could return a different unique ID for each split by ORing the 24-bit transaction uid with an 8-bit split uid. Using ResourcesBefore you use resources, be sure to check the FPIAccountManager::UIIsOkay() method. If the value is true, you will need to ensure that your resource database is on top. PalmOS looks for resources first in the most recently opened resource database. When an FPI plug-in is loaded, the client reopens its own database to put its resources on top. To get your resources back on top, you will need to reopen your plug-in database. The safest way to do this is:
Financial App ClassesAlthough this document has focused on creating an FPI Account Manager plug-in, the ultimate goal is to create a variety of plug-in APIs that can be used to link many classes of financial application. You can see the current list of application classes in FinancialAppClasses.h. Your plug-in can query the FPIAccountManager::HandlesClass() method to see if the client handles a class of data that you know how to interact with. For instance, if you create a loan manager, you may want the loan manager to interact with your account manager. By checking FPIAccountManager::HandlesClass(LOANMANAGER_CLASS), your account manager could determine that a Post was coming from a loan manager and store the link ID so that it can write any changes back to the loan manager. Both the skeleton and the sample plug-in already include code to chain to Quik Budget. Please do this if at all possible. The Quik Budget plug-in also chains to the account manager if present. Quik Budget will, of course, only do this if !FPIAccountManager::HandlesClass(ACCOUNTMANAGER_CLASS). At the time of this writing, only this account manager interface has been written. This interface is also used by budget managers. If you are interested in defining interfaces for any other classes of financial application, we would be happy to assist in defining the API. Copyright © 2002, Quik Sense Software |
Home | Links
| Consulting | Privacy
Statement | Email us
Copyright © 1999-2006 Quik Sense Software. All rights reserved.