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 SolutionsAssuming 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 PaymentThe 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 ProIf 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:
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 TagVeriSign 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 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 PaymentsTo 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:
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 OrderIn 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:
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:
Creating the Checkout PageNow 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> </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. |