Using FoxPro to Send or Retrieve data, using WebConnect WWHTTP library (any HTTP client library will work - such as curl)
Freshbooks provides simple and inexpensive on-line timekeeping, billing, and support for the small and one-person consultant. Payments can be received online through Paypal, Google and major gateways, and invoices can be sent via email or snail-mail. Automated and recurring invoicing is supported.
Retrieving your Client List:
SET PROCEDURE TO classes\wwHTTP ADDITIVE
* The following is pulled almost verbatem from the API docs page
* EXCEPT <per_page> - which defaults to 25! Unless you have fewer than 25 items to be returned,
*
increase this or call recursively for additional pages! Max items per page = 100
TEXT TO strReq NOSHOW PRETEXT 15
<?xml version="1.0" encoding="utf-8"?>
<request method="client.list">
<!-- The page number to show (Optional) -->
<page>1</page>
<!-- Number of results per page, default 25 (Optional) -->
<per_page>100</per_page>
<!-- One of 'active', 'archived', 'deleted' -->
<folder>active</folder>
</request>
ENDTEXT
oHTTP = CREATEOBJECT("wwHttp")
oHTTP.cUserAgent = [CondoConduit Invoicing]
oHTTP.nHTTPPostMode = 4 && Ideal for posting XML
oHTTP.AddPostKey(strReq) && This is where we add the request above to post to FreshBooks
lcFBUserName = [xxxxxxxxxxxxxxxxxxxxxxxxxxx] && Use your own Freshbooks Authentication Token - it changes when you change your FB password
lcFBPassword = lcFBUserName && Some http libraries require a password be used - it can be anything
* Freshbooks API has a single entry point for all requests - https://yourcompanyid.freshbooks.com/api/2.1/xml-in
lcResult = oHTTP.HTTPGet("https://ideatellc.freshbooks.com/api/2.1/xml-in",lcFBUserName,lcFBPassword)
IF [status="fail"] $ lcResult && Check for Error Condition in returned XML
* Bad result
STRTOFILE(strReq,"FBRequest.xml",0)
STRTOFILE(lcResult,"FBResult.xml",0)
lcReturnValue = [Error: ] + STREXTRACT(lcResult,[<error>],[</error>])
ELSE
* CAUTION HERE: FoxPro's XMLtoCURSOR does not like the <response></response> Node, so strip it out of the response
lcResult = STRTRAN(lcResult,[<response xmlns="http://www.freshbooks.com/api/" status="ok">],[])
lcResult = STRTRAN(lcResult,[</response>],[])
XMLTOCURSOR(lcResult,'Clients')
* You now have the results in a cursor - do what you will from this point forward.
* I'm creating a JSON response that can be called using AJAX from a web page
loSerializer = CREATEOBJECT("wwJsonSerializer")
lcReturnValue = loSerializer.Serialize("cursor:TQuery")
loSerializer.Destroy()
ENDIF
oHTTP.Destroy()
Retrieving Invoices:
Each Client is assigned an integer ID, which must be used when performing client specific actions. For example to grab all invoices for a single Client, use the following (documented here) :
<?xml version="1.0" encoding="utf-8"?>
<request method="invoice.list">
<!-- Filter by client (Optional) -->
<client_id>3</client_id>
<!-- Filter by recurring id (Optional) -->
<recurring_id>10</recurring_id>
<!-- Filter by status (Optional) -->
<status>draft</status>
<!-- Return invoices dated after this arg (Optional) -->
<date_from>2007-01-01</date_from>
<!-- Return invoices dated before this arg (Optional) -->
<date_to>2007-04-01</date_to>
<!-- Return invoices modified after this arg (Optional) -->
<updated_from>2007-01-01 00:00:00</updated_from>
<!-- Return invoices modified before this arg (Optional) -->
<updated_to>2007-01-02 00:00:00</updated_to>
<!-- Page number to return, default is 1 (Optional) -->
<page>1</page>
<!-- Number of results per page, default is 25 (Optional) -->
<per_page>10</per_page>
<!-- One of 'active', 'archived', 'deleted' (Optional)-->
<folder>active</folder>
</request>
Returning all open Invoices, or all Invoices for a single Client:
************************************************************************
*** Function: FreshBilling
*** Assume:
*** Created: 01/11/2011
*** Revised:
*** Copyright: (c) 2011, Ideate, LLC
************************************************************************
FUNCTION FreshBilling()
#IF .F.
LOCAL Request as wwRequest, Response as wwResponse
#ENDIF
lcPropCode = ""
lcPropCode = ALLTRIM(Request.QueryString("PropCode"))
llGetClients = .F.
THIS.TableOpen('Props','CondoUsers')
loUserInfo=THIS.CheckLogin()
IF INDEXSEEK(lcPropCode,.T.,"Props","PropCode") AND Props.FBClientCode > 0
lcFBClientCode = ALLTRIM(STR(Props.FBClientCode))
strReq = [ <?xml version="1.0" encoding="utf-8"?>] + CRLF + ;
[<request method="invoice.list">] + CRLF + ;
[ <per_page>100</per_page>] + CRLF + ;
[ <client_id>] + lcFBClientCode + [</client_id>] + CRLF + ;
[</request>] + CRLF
ELSE
TEXT TO strReq NOSHOW PRETEXT 15
<?xml version="1.0" encoding="utf-8"?>
<request method="invoice.list">
<!-- Number of results per page, default is 25 (Optional) -->
<per_page>100</per_page>
<!-- Filter by status (Optional) -->
<status>unpaid</status>
</request>
ENDTEXT
ENDIF
*** Connect to the server
oHTTP = CREATEOBJECT("wwHttp")
oHTTP.cUserAgent = [CondoConduit Invoicing]
oHTTP.nHTTPPostMode = 4
oHTTP.AddPostKey(strReq)
lcFBUserName = [xxxxxxxxxxxxxxxxxxxxxxxxxxx]
lcFBPassword = lcFBUserName
lcResult = oHTTP.HTTPGet("https://ideatellc.freshbooks.com/api/2.1/xml-in",lcFBUserName,lcFBPassword)
* _Cliptext = lcResult
IF [status="fail"] $ lcResult
* Bad result
STRTOFILE(strReq,"FBRequest.xml",0)
STRTOFILE(lcResult,"FBResult.xml",0)
lcReturnValue = [Error: ] + STREXTRACT(lcResult,[<error>],[</error>])
ELSE
lcResult = STRTRAN(lcResult,[<response xmlns="http://www.freshbooks.com/api/" status="ok">],[])
lcResult = STRTRAN(lcResult,[</response>],[])
lcResult = STRTRAN(lcResult,[>0<],[>0.00<]) && force decimal values rather than FALSE
XMLTOCURSOR(lcResult,'Invoices')
SELECT Invoices.Number, Organization, First_Name, Last_Name, Status, ;
Amount, Amount_Outstanding, DTOC(Invoices.Date) AS InvDate, Props.PropCode, Invoices.URL as LinkURL ;
FROM Invoices ;
INNER JOIN Props ON Invoices.Client_ID = Props.FBClientCode ;
ORDER BY InvDate DESC ;
INTO CURSOR TQuery
loSerializer = CREATEOBJECT("wwJsonSerializer")
lcReturnValue = loSerializer.Serialize("cursor:TQuery")
loSerializer.Destroy()
ENDIF
oHTTP.Destroy()
USE IN SELECT('TQuery')
USE IN SELECT('Invoices')
Response.Write(lcReturnValue)
ENDFUNC && FreshBilling
Other online services used:
Outright for simple accounting, links to Freshbooks AND Bank and Credit Card accounts for easy-as-pie maintenance.
ProvideSupport for economical monitoring of web site activity from any platform (Win, Mac, Linux and Web) - includes proactive chatting
Paessler Server Monitoring - free for up to 100 nodes
Producteev - simple task manager that syncs with Google Tasks (has iPhone client) and the fabulous Astrid task manager for Android
UserVoice - Track and vote on suggestions from your users