Month: November 2015

Stripe Card Functions

Update 3/20/17: For new accounts, Stripe is requiring TLS 1.2. The CURL library I’m using to talk with Stripe appears to support only TLS 1.1. Until this is figured out, StripeX will NOT work with new Stripe accounts. It continues to work with OLD Stripe accounts. Sorry for any trouble. Hopefully I can track down a solution.

———-

I’ve released a new version of StripeX on VFPXThis version adds several Card functions that allow you to save a credit card to a customer. This is handy for setting up subscription (which I’ll be adding next to StripeX) or if you need to routinely charge a card for a customer. The new methods are:

CardCreate() – Adds a credit card to a customer
CardDelete() – Deletes a credit card from a customer
CardLIst() – Builds a cursor of all the credit cards on the customer
CardRetrieve() – Retrieves information for a particular card

ObjectExtract() – Is a new utility method that retrieves a value from Stripe objects. You can use that, for example, to retrieve the information from the CardRetrieve() method

Here’s some sample code using those functions. It creates a new card for a customer, gets a list of existing cards, retrieves the first card found, prints some info for that card, and then deletes the card that was added card.

local o
o = NEWOBJECT("StripeX")
o.cAPIKey = "YourAPIKeyHere" && test secret
IF !o.Authenticate() && Make sure Stripe working
?"Auth", "Failed", o.cErrorMessage
return
endif
* Get a customer
LOCAL lCustomer
lCustomer = "YourCustomerIDHere"
?"Load Customer ID: " + o.Customer(lCustomer)
* Add a card
LOCAL lCardAddedID
lCardAddedID = o.CardCreate(lCustomer, "4242424242424242", "0917", "123") && Test card
?"Card Create ID: " + lCardAddedID
* Get a list of cards for the customer
LOCAL lNumberCards
lNumberCards = o.CardList(lCustomer)
?"Cards found for customer: " + TRANSFORM(lNumberCards)
if lNumberCards > 0
LOCAL lCount
lCOunt = 0
SELECT CurStripeCard
SCAN
lCount = lCount + 1
?" ID for Card #" + TRANSFORM(lCount) + ": " + CurStripeCard.ID
ENDSCAN
ENDIF
* Retrieve a card, show some info
SELECT CurStripeCard
GO top
?"Retrieve info card #1"
o.CardRetrieve(lCustomer, CurStripeCard.ID)
?" id = ", o.ObjectExtract("id", o.cData)
?" address_City = ", o.objectExtract("address_city", o.cData)
?" last4 = ", o.objectExtract("last4", o.cData)
* Delete the card we added above.
?"Delete card: ", o.CardDelete(lCustomer, lCardAddedID)

view raw
StripeXCardDemo.prg
hosted with ❤ by GitHub

 

DebugX

For a while, I’ve had a simple little debug class that let’s me output messages to the debug window or to a text file. For example in my application that sends an email, if the sending fails for some reason, I create a little text file the user can send me:

if !oMail.Send()

local oDebug

oDebug = newobject(“DebugX”)

oDebug.Message(“Email Failed”)

oDebug.Message(“Email address was: ” + oMail.cSendTo)

oDebug.Message(“Error message was: ” + oMail.cErrorMsg)

endif

This class has a couple little interesting features:

  • As noted, the message function can send to the debug window or to a text file. Just set the .cOutputType property.
  • Every .Message() gets a date + time stamp so I know when it happens.
  • I know this never happens to you, but I sometimes forget to remove the debug code before sending to production. This class has a lIsDevelopement property. If it’s not developement, then the .Message() doesn’t do anything.

Tamar‘s talk about speeding up Foxpro code at Southwest Fox prompted me to add a couple new functions:

  • Added a .Start() and .Stop() to start/stop a timer. Any .Message() that is sent while the timer is going will include the elapsed seconds. This works correctly over Midnight as well.
  • Added a .Loop() function that will loop a .nLoop number of times. This is handy if one pass through your code is not enough to accurately time how long it takes. If you accidentally leave this code in for production, nLoop always resets to 1.

So you can do things like:

local oDebug

oDebug = newobject(“DebugX”)

oDebug.Message(“Debug Starting”)

oDebug.Start(“Start timing some code, Loop 4 times”)

oDebug.nLoop = 4

do while oDebug.Loop()

oDebug.Message(“Pass # ” + tran(oDebug.nLoop))

<some Foxpro code I want to time >

enddo

oDebug.Stop(“End of debugging test”)

Here’s the code:

* DebugX: Class for creating messages during debugging.
* Created 11/3/15 by Todd Landrum, todd@paladinpgm.com
* With thanks to:
* Tamar Granor – Who's speed talk at Southwest Fox 2015 inspired the Start, Stop and Loop functions.
* You can use this method to easily add messages to your code for debugging purposes.
* Messagbes can be sent to the debug window or a text file.
* It can be used in a number of ways.
* To just give messages in your code:
* DebugX.Message("This is some message")
* It has a timer built-in so you can get the time it took some code to run:
* DebugX.Start("My timer is starting")
* <some code>
* DebugX.End("The code has finished")
* It has a looping function built-in as well.
* DebugX.nLoop = 10
* do while DebugX.nLoop
* <some code>
* enddo
* IMPORTANT: There is a lIsDevelopement property. If this is .f., then no messages
* will be output and for a loop, it will always do it just once regardless of nLoop.
* This way if you leave this in your code by mistake, it won't hurt anything in production.
* The INIT method sets this property for me – you should override it!
DEFINE CLASS DebugX AS Custom
cOutputType = "Debug" && 'Debug' if you want messages to the debug output, else 'Text' for text file
cOutputTextFile = "Debug.txt" && Default name, change if you want.
lOutputEraseTextFile = .t. && First message should create a new text file, erasing any existing one.
lShowSeconds = .f. && Seconds are shown for each message, set to .t. if the .Start() method is called.
lIsDevelopment = .t. && Output will not happen if this is .f.
&& The init function sets this, you should override it.
tStartDateTime = {} && For the Start/Stop timer functions.
tEndDateTime = {}
nStartSeconds = 0
nEndSeconds = 0
nTotalSeconds = 0
nSecondsInDay = 86400
nLoop = 1 && Number of loops to perform
**************************
PROCEDURE Init()
* This is the code for me that determines if I'm
* on my development machine. You'd change this to
* something you want.
this.lIsDevelopment = FILE("w:\paladin.txt")
ENDPROC
**************************
FUNCTION Destroy()
ENDFUNC
**************************
FUNCTION Start()
PARAMETERS tMsg
IF EMPTY(tMsg)
tMsg = "Start"
ENDIF
this.tStartDateTime = DATETIME()
this.nStartSeconds = SECONDS()
this.lShowSeconds = .t.
this.Message(tMsg)
ENDFUNC
**************************
FUNCTION Stop()
PARAMETERS tMsg
IF EMPTY(tMsg)
tMsg = "Stop"
ENDIF
this.tEndDateTime = DATETIME()
this.nEndSeconds = SECONDS()
this.nTotalSeconds = this.CalcSeconds()
this.Message(tMsg)
this.lShowSeconds = .f.
ENDFUNC
**************************
FUNCTION CalcSeconds()
PARAMETERS nStartSeconds, nEndSeconds
IF EMPTY(nStartSeconds)
nStartSeconds = this.nStartSeconds
ENDIF
IF EMPTY(nEndSeconds)
nEndSeconds = this.nEndSeconds
ENDIF
* Adjust total seconds if we ran over midnight
LOCAL lDays, lSeconds
lDays = TTOD(DATETIME()) – TTOD(this.tStartDateTime)
lSeconds = nEndSeconds – nStartSeconds
IF lDays > 0
lSeconds = lSeconds + (lDays * this.nSecondsInADay)
ENDIF
RETURN lSeconds
ENDFUNC
**************************
FUNCTION Loop
IF !this.lIsDevelopment
this.nLoop = 1
ENDIF
LOCAL lReturn
lReturn = (this.nLoop > 0)
this.nLoop = this.nLoop – 1
RETURN lReturn
ENDFUNC
**************************
FUNCTION Message
PARAMETERS tMessage
* tMessage = Message to output
* tInit = Determines if a new text file should be created or append.
IF !this.lIsDevelopment
RETURN
ENDIF
LOCAL lMsg
lMsg = tMessage + " : " + TTOC(DATETIME())
IF this.lShowSeconds
lMsg = lMsg + ", Seconds Elapsed: " + tran(this.CalcSeconds(this.nStartSeconds, SECONDS()))
endif
IF this.cOutputType = "Debug"
DEBUGOUT lMsg
ELSE
IF this.lOutputEraseTextFile && First time, we want to create blank output file.
DELETE FILE (this.cOutputTextFile)
this.lOutputEraseTextFile = .f. && Don't erase output file anymore
ENDIF
* Get any existing output, append this onto it.
LOCAL lStr
IF FILE(this.cOutputTextFile)
lStr = FILETOSTR(this.cOutputTextFile)
lStr = lStr + CHR(13) + CHR(10)
else
lStr = ""
endif
STRTOFILE(lStr + lMsg, this.cOutputTextFile)
ENDIF
ENDFUNC
ENDDEFINE

view raw
DebugX
hosted with ❤ by GitHub

dbSchema

One of the most interesting things I saw at Southwest Fox was Tuvia’s presentation of dbSchema (see his white paper for all the great things about dbSchema). I rushed home, got it installed, and … ran into some issues. It’s still a great tool that I’ll be using, just not quite as great as I hoped. Here are the issues I’ve run into:

Comparing Schemas

In theory: With dbSchema, I’ve got one schema for my development database and then at the client I’ve got a schema for the production database. When I’m ready to go live, I just compare the two schemas and it’ll show me the differences and update my production database.

In reality: dbSchema is reporting a lot of differences that don’t appear to be differences:

dbSchemaDiff

This might be because my development server is SQL 2012 while the client is SQL 2005. Maybe if I went ahead and made those changes, the differences would go away. I haven’t been brave enough though to make a couple thousand changes to the production database.

I had hoped this would be automatic, instead I have to hunt through this list to find the actual changes. Still better than having to remember the changes or document them as I develop.

Layouts

dbSchema has a great tool called Layouts that you can use to document your database. It can show what tables are linked to what, what the keys are, add comments, etc. My client in this case is pretty tech savvy and I was excited to do the Layouts in development and then synch them up to the production schema.

Alas, the compare schema function does not synch the layouts. I can’t find a way to export/import a layout from one schema to another.

You could get around this by having just one schema – just replace the production one, but that then erases any work the client did.

HTML Documentation

dbSchema has a cool tool that exports all of your layouts to HTML which is nice. Each layout goes to a separate HTML page. There is not an index page or a way to navigate from one page to another page though. Minor annoyance.