Payment Processing


Now that Orange Whip Studios' storefront and shopping-cart mechanisms are in place, it's time to tackle the checkout process. While by no means difficult, this part generally takes the most time to get into place because you must make some decisions about how to accept and process the actual payments from your users.

Depending on the nature of your application, you might not need real-time payment processing. For instance, if your company bills its customers at the end of each month, you probably just need to perform some type of query to determine the status of the user's account, rather than worrying about collecting a credit-card number and charging the card when the user checks out.

However, most online shopping applications call for getting a credit-card number from a user at checkout time and charging the user's credit-card account in real time. That is the focus of this section.

Payment-Processing Solutions

Assuming you'll be collecting credit-card information from your users, you must first decide how you will process the credit-card charges that come through your application. ColdFusion doesn't ship with any specific functionality for processing credit-card transactions. However, a number of third-party packages enable you to accept payments via credit cards and checks.

NOTE

Because it is quite popular, the examples in this chapter use the Payflow Pro payment-processing service from VeriSign (www.verisign.com). But VeriSign's service is just one of several solutions available. You are encouraged to investigate other payment-processing software to find the service or package that makes the most sense for your project.


Processing a Payment

The exact ColdFusion code you use to process payments will vary according to the payment-processing package you decide to use. This section uses VeriSign's Payflow Pro as an example. Please understand that other options are available and that Payflow Pro shouldn't necessarily be considered as superior or better suited than other solutions just because I discuss it here.

Getting Started with Payflow Pro

If you want to try the payment-processing code examples that follow in this section, you must download and install the Java version of the Payflow Pro software.

NOTE

At the time of this writing, the items discussed here were available for free download from VeriSign's Web site. It was necessary to sign up for a free test vendor account first to download and use the software.


To get started, do the following:

1.

Go to VeriSign's Web site and register for a free test vendor account.

2.

Download and install the Pure Java version of the Payflow Pro software. At the time of this writing, that meant unzipping the downloaded software to an appropriate location on your computer's hard drive. The guts of the software is the Java archive file named Verisign.jar. For purposes of this discussion, we will assume that you are using Windows and have unzipped the downloaded files so that the Verisign.jar file is at this location: c:\versign\payflowpro\java

3.

On the Java and JVM page of the ColdFusion Administrator, add the full path of the Verisign.jar file to the Class Path field (for instance, c:\versign\payflowpro\java\Verisign.jar). If the Class Path field already contains a value, add the path at the end, separated with a comma. Make sure to restart the ColdFusion service to make the changes take effect.

NOTE

The Payflow Pro software on your server needs to communicate with VeriSign's network over the Internet, so your ColdFusion server must be capable of accessing the Internet before you can start testing. Depending on your situation, you might need to configure Payflow Pro so it can get past your firewall or proxy software, or take other special steps. See the Payflow Pro documentation for details. Please understand that successful installation of VeriSign's software is not the primary focus of this chapter.


The <cf_VerisignPayflowPro> Custom Tag

VeriSign provides a Java CFX tag called <CFX_PAYFLOWPRO> for ColdFusion developers. Unfortunately, at the time of this writing, the <CFX_PAYFLOWPRO> tag did not work correctly with ColdFusion. This situation may be remedied by the time you read this book, in which case you can just use the official <CFX_PAYFLOWPRO> tag provided by VeriSign. See VeriSign's site for details.

To take the place of VeriSign's own <CFX_PAYFLOWPRO>, I have provided a CFML Custom Tag called <cf_VerisignPayflowPro>. This Custom Tag is a wrapper around the Pure Java API that VeriSign provides. While this API was designed with Java coders in mind, you can use it quite easily with CFML. Table 28.3 shows the attributes supported by the Custom Tag. Even if you don't end up needing this custom tag, it makes for an interesting example of what you can do with ColdFusion and APIs designed for Java.

Table 28.3. AttributesSupported by <cf_VerisignPayflowPro>

ATTRIBUTE

DESCRIPTION

certPath

The full path to the location of your VeriSign vendor certificates. For testing, you can just use the test certificate included in the Java API download from VeriSign. If you are using Windows, the correct value is c:\versign\payflowpro\java\certs or something similar.

serverName

The Payflow server to connect to for payment processing. For testing, you should use test-payflow.verisign.com. When you are ready to process real payments, you would change this to payflow.verisign. com. If needed, you can also provide ServerPort and ServerTimeout attributes.

payflowVendor

Your Payflow vendor user name. This is the same user name you use to log in to the Payflow Manager on VeriSign's Web site. This value is case-sensitive, so make sure to get it exactly right.

payflowPassword

Your Payflow vendor password, also case-sensitive.

returnVariable

A variable name you want the results of the transaction placed into. After the tag runs, a structure will exist with whatever name you specify here. The structure will always contain RESULT, RESPCODE, and PNREF values, plus whatever other values are appropriate for the transaction you are attempting. See the Payflow documentation for details about the meaning of these values.

acct

The credit-card number (or other account number) that you want to charge.

expDate

The four-digit expiration date, in the form MMYY.

amt

The amount of the sale.

tender

Optional. The type of account to charge. The default is C for credit card. See the Payflow documentation for other values.

trxType

Optional. The type of transaction. The default is S for sale. You can also provide C (Credit), A (Authorization), D (Delayed Capture), V (Void), F (Voice Authorization), or I (Inquiry). See the Payflow documentation for details.

origid

If the TRXTYPE is D (Delayed Capture), V (Void), F (Voice Authorization), or I (Inquiry), then you must also provide the original transaction number here. Most likely, you would supply the PNREF value returned by some previous transaction (see ReturnVariable, above).

comment1 and comment2

Optional. Use these attributes to record any comments along with the transaction.

avsstreet and avszip

Optional. Use these attributes to use AVS address verification, most likely when using with trXTYPE="A". See the Payflow documentation for details.

parmList

Optional. If for some reason you need to provide additional information, you can create your own Payflow parameter list and provide it here, in which case the ACCT, EXPDATE, AMT, TENDER, trXTYPE, COMMENT1, COMMENT2, AVSSTREET, and AVSZIP attributes will be ignored. See the Payflow documentation for details about parameter lists.


Table 28.3 shows the attributes supported by <cf_VerisignPayflowPro>.

NOTE

I don't have the space here to discuss the code used to build the <cf_VerisignPayflowPro> tag in detail. In general, connecting to native Java objects is conceptually beyond the scope of this book. That is why I've provided this Custom Tag, so that you can have a complete working example without needing to understand how the Java API is used (aren't custom tags great?). That said, I invite you to examine the code for <cf_VerisignPayflowPro>, which is included on the CD-ROM. You may find it a useful guide for creating your own CFML wrappers around other Java APIs. For more information about using native Java objects in ColdFusion templates, see the ColdFusion documentation or this book's companion volume, Advanced Macromedia ColdFusion MX 7 Application Development.


Writing a Custom Tag Wrapper to Accept Payments

To make the <cf_VerisignPayflowPro> tag easier to use in your own ColdFusion templates, and to make it easier to switch to some other payment-processing solution in the future, you might consider hiding all payment-package-specific code within a more abstract, general-purpose Custom Tag that encapsulates the notion of processing a payment.

Listing 28.10 creates a CFML Custom Tag called <cf_ProcessPayment>. It accepts a Processor attribute to indicate which payment-processing software should process the payment. As you will see, this example supports Processor="PayflowPro" and Processor="JustTesting". You would need to expand the tag by adding the package-specific code necessary for any additional software.

NOTE

Actually, the tag also includes code for Processor="CyberCash". CyberCash was a payment processing solution that was similar to Payflow Pro; it is no longer in operation. The last edition of this book used CyberCash as the main example. I am including the CyberCash-specific code in Listing 28.10 so that you can see how different payment processing solutions could be handled within a single custom tag. The CyberCash setting won't actually work, though, because its servers no longer respond to requests.


The idea here is similar to the idea behind the <cf_ShoppingCart> tag created earlier in this chapter: Keep all the mechanics in the Custom Tag template, so each individual page can use simpler, more goal-oriented syntax. In addition to the Processor attribute, this sample version of the <cf_ProcessPayment> tag accepts the following attributes:

  • orderID. This is passed along to the credit-card company as a reference number.

  • orderAmount, creditCard, creditExpM, creditExpY, and creditName0 These describe the actual payment to be processed. orderAmount is the total of the order. creditCard is the credit card number. creditExpM and creditExpY are the month and year when the credit card expires. creditName is the name on the credit card.

  • returnVariable. This indicates a variable name the Custom Tag should use to report the status of the attempted payment transaction. The returned value is a ColdFusion structure that contains a number of status values.

Listing 28.10. ProcessPayment.cfmCreating the <cf_ProcessPayment> Custom Tag
 <!---  Filename: ProcessPayment.cfm  Created by: Nate Weiss (NMW)  Please Note Creates the <CF_ProcessPayment> Custom Tag  Purpose: Handles credit card and other transactions ---> <!--- Tag Parameters ---> <cfparam name="ATTRIBUTES.processor" type="string"> <cfparam name="ATTRIBUTES.orderID" type="numeric"> <cfparam name="ATTRIBUTES.orderAmount" type="numeric"> <cfparam name="ATTRIBUTES.creditCard" type="string"> <cfparam name="ATTRIBUTES.creditExpM" type="string"> <cfparam name="ATTRIBUTES.creditExpY" type="string"> <cfparam name="ATTRIBUTES.returnVariable" type="variableName"> <cfparam name="ATTRIBUTES.creditName" type="string"> <!--- Depending on the PROCESSOR attribute ---> <cfswitch expression="#ATTRIBUTES.processor#">   <!--- If PROCESSOR="PayflowPro" --->   <cfcase value="PayflowPro">     <!--- Force expiration into MM and YY format --->     <cfset expM = numberFormat(ATTRIBUTES.creditExpM, "00")>     <cfset expY = numberFormat(right(ATTRIBUTES.creditExpY, 2), "00")>     <!--- Attempt transaction with Payflow Pro --->     <cf_VerisignPayflowPro     certPath="c:\verisign\payflowpro\java\certs"     serverName="test-payflow.verisign.com"     payflowPartner="VeriSign"     payflowVendor="YOUR_INFO_HERE"     payflowPassword="YOUR_INFO_HERE"     acct="#ATTRIBUTES.creditCard#"     expDate="#expM##expY#"     amt="#numberFormat(ATTRIBUTES.orderAmount, '9.00')#"     comment1="Orange Whip OrderID: #ATTRIBUTES.orderID#"     comment2="Customer Name on Card: #ATTRIBUTES.creditName#"     returnVariable="PayflowResult">     <!--- Values to return to calling template --->     <cfset s = structNew()>     <!--- Always return IsSuccessful (Boolean) --->     <cfset s.isSuccessful = PayflowResult.RESULT eq 0>     <!--- Always return status of transaction --->     <cfset s.status = PayflowResult.RESPMSG>     <!--- If Successful, return the Auth Code --->     <cfif s.isSuccessful>       <cfset s.authCode = PayflowResult.AUTHCODE>       <cfset s.orderID = ATTRIBUTES.orderID>       <cfset s.orderAmount = ATTRIBUTES.orderAmount>       <!--- If not successful, return the error --->     <cfelse>       <cfset s.errorCode = PayflowResult.RESULT>       <cfset s.errorMessage = PayflowResult.RESPMSG>     </cfif>     <!--- Return values to calling template --->     <cfset "Caller.#ATTRIBUTES.returnVariable#" = s>   </cfcase>   <!--- If PROCESSOR="JustTesting" --->   <!--- This puts the tag into a "testing" mode --->   <!--- Where any transaction will always succeed --->   <cfcase value="JustTesting">     <!--- Values to return to calling template --->     <cfset s = structNew()>     <!--- Always return IsSuccessful (Boolean) --->     <cfset s.isSuccessful = True>     <!--- Always return status of transaction --->     <cfset s.status = "success">     <!--- Return other data, as if transaction succeeded --->     <cfset s.authCode = "DummyAuthCode">     <cfset s.orderID = ATTRIBUTES.orderID>     <cfset s.orderAmount = ATTRIBUTES.orderAmount>     <!--- Return values to calling template --->     <cfset "Caller.#ATTRIBUTES.returnVariable#" = s>   </cfcase>   <!--- If PROCESSOR="CyberCash" --->   <!--- *** This is now defunct but remains as an example --->   <cfcase value="CyberCash">     <!--- Force expiration into MM and YY format --->     <cfset expM = numberFormat(ATTRIBUTES.creditExpM, "00")>     <cfset expY = numberFormat(right(ATTRIBUTES.creditExpY, 2), "00")>     <!--- Attempt to process the transaction --->     <CFX_CYBERCASH     VERSION="3.2"     CONFIGFILE="C:\mck-3.3.1-NT\test-mck\conf\merchant_conf"     MO_ORDER_     MO_VERSION="3.3.1"     MO_PRICE="USD #numberFormat(ATTRIBUTES.orderAmount, '9.00')#"     CPI_CARD_NUMBER="#ATTRIBUTES.creditCard#"     CPI_CARD_EXP="#expM#/#expY#"     CPI_CARD_NAME="#ATTRIBUTES.creditName#"     OUTPUTPOPQUERY="Charge">     <!--- Values to return to calling template --->     <cfset s = structNew()>     <!--- Always return IsSuccessful (Boolean) --->     <cfset s.isSuccessful = charge.STATUS eq "success">     <!--- Always return status of transaction --->     <cfset s.status = Charge.STATUS>     <!--- If Successful, return the Auth Code --->     <cfif s.isSuccessful>       <cfset s.authCode = Charge.AUTH_CODE>       <cfset s.orderID = ATTRIBUTES.orderID>       <cfset s.orderAmount = ATTRIBUTES.orderAmount>     <!--- If not successful, return the error --->     <cfelse>       <cfset s.errorCode = Charge.ERROR_CODE>       <cfset s.errorMessage = Charge.ERROR_MESSAGE>     </cfif>     <!--- Return values to calling template --->     <cfset "Caller.#ATTRIBUTES.returnVariable#" = s>   </cfcase>   <!--- If the PROCESSOR attribute is unknown --->   <cfdefaultcase>     <cfthrow message="Unknown PROCESSOR attribute.">   </cfdefaultcase> </cfswitch> 

NOTE

The CERTPATH attribute for the <cf_VerisignPayflowPro> tag needs to point to your own certs folder, wherever it is located (see the previous section, "Getting Started with Payflow Pro").


NOTE

You need to provide your own account information for the PayflowVendor and PayflowPassword attributes before you can expect this code to work. You may also need to alter the PayflowPartner attribute if someone other than VeriSign is providing your payment services.


At the top of Listing 28.10, eight <cfparam> tags establish the tag's various attributes (all of the attributes are required). Then a <cfswitch> tag executes various payment-processing code, based on the Processor attribute passed to the Custom Tag. The syntax specific to <cf_VerisignPayflowPro> is the only code fleshed out in this template; you would add syntax for other packages in separate <cfcase> blocks.

The actual code in the <cfcase> block is quite simple. Most of the Custom Tag's attributes are fed directly to the <cf_VerisignPayflowPro> tag. You need to tweak some of the values a bit to conform to what the custom tag expects. For instance, the Custom Tag expects the expiration month and year to be provided as simple numeric values, but the Payflow tag wants them provided as a single string in MM/YY format.

After the <cf_VerisignPayflowPro> tag executes, a new structure named s is created using structNew(). Next, a Boolean value called isSuccessful is added to the structure. Its value will be TRue if the RESULT code returned by the Payflow servers is 0 (which indicates success); otherwise, it will be False. The calling template can look at isSuccessful to tell whether the payment was completed successfully. Additionally, if the order was successful, AuthCode, OrderID, and OrderAmount values are added to the structure. If it was not successful, values called ErrorCode and ErrorMessage are added to the structure, so the calling template can understand exactly what went wrong. Then the whole structure is passed back to the calling template using the quoted <cfset> return variable syntax (explained in Chapter 23).

Thus, the generic <cf_ProcessPayment> tag is able to support Payflow Pro.

NOTE

A similar <cfcase> block is also provided, which will execute if Processor="JustTesting" is passed to the tag. This provides an easy way to test the checkout functionality even if you don't want to bother registering for Payflow Pro. Just change the Processor attribute to JustTesting in Listing 28.11.


NOTE

If you adapt this Custom Tag to handle other payment processors, try to return the same value names (IsSuccessful, AuthCode, and so on) to a structure. In this way, you will build a common API to deal with payment processing in an application-agnostic way.


Processing a Complete Order

In addition to explaining how to build commerce applications, this chapter emphasizes the benefits of hiding the mechanics of complex operations within goal-oriented Custom Tag wrappers that can accomplish whole tasks on their own. Actual page templates that use these Custom Tags look very clean, since they deal only with the larger concepts at hand rather than including a lot of low-level code. That's the difference, for instance, between the two versions of the StoreCart.cfm template (Listings 28.4 and 28.6).

In keeping with that notion, Listing 28.11 creates another Custom Tag, called <cf_PlaceOrder>. This tag is in charge of handling all aspects of accepting a new order from a customer, including:

  • Inserting a new record into the MerchandiseOrders table

  • Inserting one or more detail records into the MerchandiseOrdersItems table (one detail record for each item ordered)

  • Attempting to charge the user's credit card, using the <cf_ProcessPayment> Custom Tag created in the previous section (refer to Listing 28.10)

  • For a successful charge, sending an order-confirmation message to the user via email, using the <cf_SendOrderConfirmation> Custom Tag created in Chapter 27, "Interacting with Email"

  • For an unsuccessful charge (because of an incorrect credit-card number, expiration date, or the like), ensuring that the just-inserted records from MerchandiseOrders and MerchandiseOrdersItems are not actually permanently committed to the database

Listing 28.11. PlaceOrder.cfmCreating the <cf_PlaceOrder> Custom Tag
 <!---  Filename: PlaceOrder.cfm (creates <CF_PlaceOrder> Custom Tag)  Created by: Nate Weiss (NMW)  Please Note Depends on <CF_ProcessPayment> and <CF_SendOrderConfirmation>  Purpose: Handles all operations related to placing a customer's order ---> <!--- Tag Parameters ---> <cfparam name="ATTRIBUTES.processor" type="string" default="PayflowPro"> <cfparam name="ATTRIBUTES.merchList" type="string"> <cfparam name="ATTRIBUTES.quantList" type="string"> <cfparam name="ATTRIBUTES.contactID" type="numeric"> <cfparam name="ATTRIBUTES.creditCard" type="string"> <cfparam name="ATTRIBUTES.creditExpM" type="string"> <cfparam name="ATTRIBUTES.creditExpY" type="string"> <cfparam name="ATTRIBUTES.creditName" type="string"> <cfparam name="ATTRIBUTES.shipAddress" type="string"> <cfparam name="ATTRIBUTES.shipCity" type="string"> <cfparam name="ATTRIBUTES.shipCity" type="string"> <cfparam name="ATTRIBUTES.shipState" type="string"> <cfparam name="ATTRIBUTES.shipZIP" type="string"> <cfparam name="ATTRIBUTES.shipCountry" type="string"> <cfparam name="ATTRIBUTES.htmlMail" type="boolean"> <cfparam name="ATTRIBUTES.returnVariable" type="variableName"> <!--- Begin "order" database transaction here ---> <!--- Can be rolled back or committed later ---> <cftransaction action="begin">   <!--- Insert new record into Orders table --->   <cfquery datasource="#APPLICATION.dataSource#">   INSERT INTO MerchandiseOrders (   ContactID,   OrderDate,   ShipAddress, ShipCity,   ShipState, ShipZip,   ShipCountry)   VALUES (   #ATTRIBUTES.contactID#,   <cfqueryparam cfsqltype="CF_SQL_TIMESTAMP"   VALUE="#dateFormat(now())# #timeFormat(now())#">,   '#ATTRIBUTES.shipAddress#', '#ATTRIBUTES.shipCity#',   '#ATTRIBUTES.shipState#', '#ATTRIBUTES.shipZip#',   '#ATTRIBUTES.shipCountry#'   )   </cfquery>   <!--- Get just-inserted OrderID from database --->   <cfquery datasource="#APPLICATION.dataSource#" name="getNew">   SELECT MAX(OrderID) AS NewID   FROM MerchandiseOrders   </cfquery>   <!--- For each item in user's shopping cart --->   <cfloop from="1" to="#listLen(ATTRIBUTES.merchList)#" index="i">     <cfset thisMerchID = listGetAt(ATTRIBUTES.merchList, i)>     <cfset thisQuant = listGetAt(ATTRIBUTES.quantList, i)>     <!--- Add the item to "OrdersItems" table --->     <cfquery datasource="#APPLICATION.dataSource#">     INSERT INTO MerchandiseOrdersItems     (OrderID, ItemID, OrderQty, ItemPrice)     SELECT     #getNew.NewID#, MerchID, #thisQuant#, MerchPrice     FROM Merchandise     WHERE MerchID = #thisMerchID#     </cfquery>   </cfloop>   <!--- Get the total of all items in user's cart --->   <cfquery datasource="#APPLICATION.dataSource#" name="getTotal">   SELECT SUM(ItemPrice * OrderQty) AS OrderTotal   FROM MerchandiseOrdersItems   WHERE OrderID = #getNew.NewID#   </cfquery>   <!--- Attempt to process the transaction --->   <cf_ProcessPayment   processor="#ATTRIBUTES.processor#"   order   orderAmount="#getTotal.OrderTotal#"   creditCard="#ATTRIBUTES.creditCard#"   creditExpM="#ATTRIBUTES.creditExpM#"   creditExpY="#ATTRIBUTES.creditExpY#"   creditName="#ATTRIBUTES.creditName#"   returnVariable="chargeInfo">   <!--- If the order was processed successfully --->   <cfif chargeInfo.IsSuccessful>     <!--- Commit the transaction to database --->     <cftransaction action="Commit"/>   <cfelse>     <!--- Rollback the Order from the Database --->     <cftransaction action="RollBack"/>   </cfif> </cftransaction> <!--- If the order was processed successfully ---> <cfif ChargeInfo.isSuccessful>   <!--- Send Confirmation E-Mail, via Custom Tag --->   <cf_SendOrderConfirmation   order   useHTML="#ATTRIBUTES.htmlMail#"> </cfif> <!--- Return status values to callling template ---> <cfset "Caller.#ATTRIBUTES.returnVariable#" = chargeInfo> 

At the top of the template is a rather large number of <cfparam> tags that define the various attributes for the <cf_PlaceOrder> Custom Tag. The Processor, ReturnVariable, and four Credit attributes are passed directly to <cf_ProcessPayment>. The MerchList and QuantList attributes specify which item is actually being ordered, in the same comma-separated format that the CLIENT variables use in Listing 28.4. The contactID and the six ship attributes are needed for the MerchandiseOrders table. The htmlMail attribute sends an email confirmation to the user if the payment is successful.

After the tag attributes have been defined, a large <cftransaction> block starts. The <cftransaction> tag is ColdFusion's representation of a database transaction. You saw it in action in Chapter 14, "Using Forms to Add or Change Data," and you will learn about it more formally in Chapter 30, "More On SQL and Queries," and Chapter 32, "Error Handling." The <cftransaction> tag tells the database to consider all queries and other database operations within the block as a single transaction, which other operations cannot interrupt.

The use of <cftransaction> in this template accomplishes two things. First, it makes sure that no other records are inserted between the first INSERT query and the getNew query that comes right after it. This in turn ensures that the ID number retrieved by the getNew query is indeed the correct one, rather than a record some other process inserted. Second, the <cftransaction> tag allows any database changes (inserts, deletes, or updates) to be rolled back if some kind of problem occurs. A rollback is basically the database equivalent of the Undo function in a word processorit undoes all changes, leaving the database in the same state as at the start of the transaction. Here, the transaction is rolled back if the credit-card transaction fails (perhaps because of an incorrect credit-card number), which means that all traces of the new order will be removed from the database.

After the opening <cftransaction> tag, the following actions are taken:

1.

A new order record is inserted into the MerchandiseOrders table.

2.

The getNew query obtains the OrderID number for the just-inserted order record.

3.

A simple <cfloop> tag inserts one record into the MerchandiseOrdersItems table for each item supplied to the MerchList attribute. Each record includes the new OrderID, the appropriate quantity from the QuantList attribute, and the current price for the item (as listed in the Merchandise table). (The INSERT/SELECT syntax used here is explained in Chapter 30.)

4.

The getTotal query obtains the total price of the items purchased by adding up the price of each item times its quantity.

5.

The <cf_ProcessPayment> tag (refer to Listing 28.10) attempts to process the credit-card transaction. The structure of payment-status information is returned as a structure named ChargeInfo.

6.

If the charge was successful, the database transaction is committed by using the <cftransaction> tag with action="Commit". This permanently saves the inserted records in the database and ends the transaction.

7.

If the charge was not successful, the database transaction is rolled back with a <cftransaction> tag of action="RollBack". This permanently removes the inserted records from the database and ends the transaction.

8.

If the charge was successful, a confirmation email message is sent to the user, using the <cf_SendOrderConfirmation> Custom Tag from Chapter 27. Because this tag performs database interactions of its own, it should sit outside the <cftransaction> block.

9.

Finally, the chargeInfo structure returned by <cf_PlaceOrder> is passed back to the calling template so it can understand whether the order was placed successfully.

Creating the Checkout Page

Now that you've created the <cf_PlaceOrder> Custom Tag, actually creating the Checkout page for Orange Whip Studios' visitors is simple. Listing 28.12 provides the code for the StoreCheckout.cfm page, which users can access via the Checkout link at the top of each page in the online store or by clicking the Checkout button on the StoreCart.cfm page (refer to Figure 28.2).

Listing 28.12. StoreCheckout.cfmAllowing the User to Complete the Online Transaction
 <!---  Filename: StoreCheckout.cfm (save with Chapter 28's listings)  Created by: Nate Weiss (NMW)  Purpose: Provides final Checkout/Payment page  Please Note Depends on <CF_PlaceOrder> and StoreCheckoutForm.cfm ---> <!--- Show header images, etc., for Online Store ---> <cfinclude template="StoreHeader.cfm"> <!--- Get current cart contents, as a query object ---> <cfset getCart = SESSION.myShoppingCart.List()> <!--- Stop here if user's cart is empty ---> <cfif getCart.recordCount eq 0>  There is nothing in your cart.  <cfabort> </cfif> <!--- If user is not logged in, force them to now ---> <cfif not isDefined("SESSION.auth.isLoggedIn")>  <cfinclude template="LoginForm.cfm">  <cfabort> </cfif> <!--- If user is attempting to place order ---> <cfif isDefined("FORM.isPlacingOrder")>   <cftry>   <!--- Attempt to process the transaction --->   <!--- Change to PayflowPro to use VeriSign --->   <cf_PlaceOrder   Processor="JustTesting"   contact   merchList="#valueList(getCart.MerchID)#"   quantList="#valueList(getCart.Quantity)#"   creditCard="#FORM.creditCard#"   creditExpM="#FORM.creditExpM#"   creditExpY="#FORM.creditExpY#"   creditName="#FORM.creditName#"   shipAddress="#FORM.shipAddress#"   shipState="#FORM.shipState#"   shipCity="#FORM.shipCity#"   shipZIP="#FORM.shipZIP#"   shipCountry="#FORM.shipCountry#"   htmlMail="#FORM.htmlMail#"   returnVariable="orderInfo">   <!--- If any exceptions in the "ows.MerchOrder" family are thrown... --->   <cfcatch type="ows.MerchOrder">   <p>Unfortunately, we are not able to process your order at the moment.<br>   Please try again later. We apologize for the inconvenience.<br>   <cfabort>   </cfcatch>   </cftry>   <!--- If the order was processed successfully --->   <cfif orderInfo.isSuccessful>     <!--- Empty user's shopping cart --->     <cfset SESSION.myShoppingCart.Empty()>     <!--- Display Success Message --->     <cfoutput>     <h2>Thanks For Your Order</h2>     <p><b>Your Order Has Been Placed.</b><br>     Your order number is: #orderInfo.orderID#<br>     Your credit card has been charged:     #lsCurrencyFormat(orderInfo.OrderAmount)#<br>     <p>A confirmation is being Emailed to you.<br>     </cfoutput>     <!--- Stop here. --->     <cfabort>   <cfelse>     <!--- Display "Error" message --->     <font color="red">     <strong>Your credit card could not be processed.</strong><br>     Please verify the credit card number, expiration date, and     name on the card.<br>     </font>     <!--- Show debug info if viewing page on server --->     <cftrace inline="True" var="OrderInfo">   </cfif> </cfif> <!--- Show Checkout Form (Ship Address/Credit Card) ---> <cfinclude template="StoreCheckoutForm.cfm"> 

First, the standard page header for the online store is displayed with the <cfinclude> tag at the top of Listing 28.12. Next, the List method of the ShoppingCart CFC gets the current contents of the user's cart. If getCart.RecordCount is 0, the user's cart must be empty, so the template displays a short "Your cart is empty" message and stops further processing. Next, the template ensures that the user has logged in, using the same <cfinclude> file developed in Chapter 21.

Of course, if you want to use the client variablebased <cf_ShoppingCart> Custom Tag instead of the session variablebased ShoppingCart CFC, you can easily do so by changing the first <cfset> line (near the top of Listing 28.12) to this:

 <!--- Get current cart contents, as a query object ---> <cf_ShoppingCart  action="List"  returnVariable="GetCart"> 

At the bottom of the template, a <cfinclude> tag includes the form shown in Figure 28.4, which asks the user for shipping and credit-card information. The form is self-submitting, so when the user clicks the Place Order Now button, the code in Listing 28.12 is executed again. This is when the large <cfif> block kicks in.

Figure 28.4. Users provide credit-card information on the Checkout page.


Within the <cfif> block, the <cf_PlaceOrder> tag attempts to complete the user's order. If all goes well, the order will be committed to the database, the confirmation email message will be sent, the orderInfo.isSuccessful value will be true, and the new order's ID number will be returned as orderInfo.orderID.

If the order is actually successful, the user's cart is emptied using a final call to <cf_ShoppingCart>, and a "Thanks for your order" message appears. If not, an error message appears and the checkout form (see Listing 28.13) is displayed again. Also, the <cftrace> tag displays additional diagnostic information if the debugging options are on in the ColdFusion Administrator.

Listing 28.13 is the StoreCheckoutForm.cfm template included via the <cfinclude> tag at the bottom of Listing 28.12. This template uses <cfform> and <cfinput> to display a Web-based form with some simple data validation (such as the validate="creditcard" attribute for the creditCard field). As a convenience to the user, it prefills the shipping-address fields based on the address information currently in the Contacts table. The resulting form was shown in Figure 28.4.

Listing 28.13. StoreCheckoutForm.cfmCollecting Shipping and Card Information from the User
 <!---  Filename: StoreCheckoutForm.cfm  Created by: Nate Weiss (NMW)  Please Note Included by StoreCheckout.cfm  Purpose: Displays a simple checkout form ---> <!--- Get the user's contact info from database ---> <cfquery name="getContact" datasource="#APPLICATION.DataSource#">  SELECT  FirstName, LastName, Address,  City, State, Zip, Country, Email  FROM Contacts  WHERE ContactID = #SESSION.auth.contactID# </cfquery> <!--- Used to pre-fill user's choice of HTML or Plain email ---> <cfparam name="FORM.htmlMail" type="string" default="Yes"> <cfoutput> <cfform action="#CGI.script_name#" method="post" preservedata="Yes"> <cfinput type="hidden" name="isPlacingOrder" value="Yes"> <table border="0" cellspacing="4"> <tr>   <th colspan="2" bgcolor="silver">Shipping Information</th> </tr> <tr>   <th align="right">Ship To:</th>   <td>   #getContact.FirstName# #getContact.LastName#   </td> </tr> <tr>   <th align="right">Address:</th>   <td>   <cfinput name="shipAddress" size="30"            required="yes" value="#getContact.Address#"            message="Please don't leave the Address blank!">   </td> </tr> <tr>   <th align="right">City:</th>   <td>   <cfinput name="shipCity" size="30" required="yes" value="#getContact.City#"    message="Please don't leave the City blank!">   </td> </tr> <tr>   <th align="right">State:</th>   <td>   <cfinput name="shipState" size="30" required="yes" value="#getContact.State#"    message="Please don't leave the State blank!">   </td> </tr> <tr>   <th align="right">Postal Code:</th>   <td>   <cfinput name="shipZIP" size="10" required="yes" value="#getContact.ZIP#"    message="Please don't leave the ZIP blank!">   </td> </tr> <tr>   <th align="right">Country:</th>   <td>   <cfinput name="shipCountry" size="10" required="Yes"            value="#getContact.Country#"            message="Please don't leave the Country blank!">   </td> </tr> <tr>   <th align="right">Credit Card Number:</th>   <td>   <cfinput name="creditCard" size="30" required="yes" validate="creditcard"    message="You must provide a credit card number.">   </td> </tr> <tr>   <th align="right">Credit Card Expires:</th>   <td>   <cfselect name="creditExpM">   <cfloop from="1" to="12" index="i">     <option value="#i#">#numberFormat(i, "00")#   </cfloop>   </cfselect>   <cfselect name="creditExpY">   <cfloop from="#year(now())#" to="#val(year(now())+10)#" index="i">     <option value="#i#">#i#   </cfloop>   </cfselect>   </td> </tr> <tr>   <th align="right">Name On Card:</th>   <td>   <cfinput name="creditName" size="30" required="Yes"    value="#getContact.FirstName# #getContact.LastName#"    message="You must provide the Name on the Credit Card.">   </td> </tr> <tr valign="baseline">   <th align="right">Confirmation:</th>   <td>   We will send a confirmation message to you at #getContact.EMail#<br>   <cfinput type="radio" name="htmlMail" value="Yes"            checked="#form.htmlMail#">   HTML (I sometimes see pictures in Email messages)<br>   <cfinput type="radio" name="htmlMail" value="No"            checked="#not form.htmlmail#">   Non-HTML (I never see any pictures in my messages)<br>   </td> </tr> <tr>   <td>&nbsp;</td>   <td>   <cfinput type="submit" name="submit" value="Place Order Now">   </td> </tr> </table> </cfform> </cfoutput> 

The online store for Orange Whip Studios is now complete. Users can add items to their shopping carts, adjust quantities, remove items, and check out. And all the code has been abstracted in a reasonable and maintainable fashion, thanks to ColdFusion's wonderful Custom Tag feature.



Macromedia Coldfusion MX 7 Web Application Construction Kit
Macromedia Coldfusion MX 7 Web Application Construction Kit
ISBN: 321223675
EAN: N/A
Year: 2006
Pages: 282

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net