In the summer of 2001, the State University of New York at Buffalo (UB) hired a new Provost. She surveyed various school services and came up with a short list of must-do projects. Given the level of competition in higher education, it's no surprise that she felt that we must be able to accept payments over the internet. In general, we try to make it as easy as possible for students and their parents to manage the business side of their education. Electronic payment is a key, new component of that.
How to Start?
Determining how to accept an electronic payment requires skills from a wide variety of groups on campus. So we first put together a committee with representatives from Accounting, Student Accounts, and some offices that wanted to accept electronic payments. My department, Administrative Computing Services, also sat on the committee since we would have to implement the actual technology. Our role was to work with the people who handled the manual payment system and figure out how to automate that system through the web.
We had no experience taking money over the internet, so we looked at our options:
Purchase a fully outsourced solution.
We evaluated many all-in-one systems, but most didn't do exactly what we wanted. The biggest problem was integration with our existing system, which had grown over time and had many legacy components. Many vendors had systems that worked only with their complete accounting systems.
Partially outsourced solution.
With this approach, we would do some work on our end and offload some to a vendor. This ended up being our initial approach since it allowed us to write the custom parts to interface with our systems, but still outsource the financial parts to reduce our initial risk.
Mostly custom solution with payment processing outsourced.
With this configuration, we'd host all of the web components in-house. We would use an external vendor for the payment-processing components, including validating credit cards, encumbering the funds from cards, and processing the actual payments through the payment networks and banks.
Complete in-house system.
Some companies actually act as their own payment processor in addition to running the front-end website components. There is nothing preventing you from processing your own payments, but we found the banking world incredibly complex, especially with regard to credit card transactions. Although we could theoretically save money by doing this work ourselves, we weren't interested in doing this work. Our Accounting department agreed.
After evaluating the options, we chose to start with a partially outsourced solution. Over time our needs changed and we became more knowledgeable; we since made the transition to a mostly in-house solution.
Initial Solution: Partial Outsourcing
Our initial solution was mostly outsourced, with us building just a few front-end components. This allowed us to get our feet wet with electronic payment without running everything. Even with a vendor doing most of the work, we encountered some challenges.
To implement this model, we built a website with a few pages that would collect personal data and track some data on the transaction. On the final page of our site, the user clicked a submit button and we shipped them off to the vendor website to enter the credit card information. This way we didn't directly deal with any credit card issues. Each night we would receive a remittance file with the day's transactions and we would reconcile these with our data using a unique identifier.
The key issues for this first phase were:
- Purchase and install a certificate from a widely accepted certificate issuer.
- Make sure all users used SSL. We simply redirected all http requests to the site to https so users couldn't accidentally use unencrypted connections.
- Accurately record a unique identifier to match up transactions when we received the remittance file from the vendor.
- Follow the vendor's protocol to integrate properly with their site.
- Understand the payment-processing system so we could date and post transactions accurately in our batch jobs.
The first two were basically normal tasks for setting up a secure server. The third required a little work, but not much. The challenge was integrating with the vendor. They had limits on the size of a unique identifier that were smaller than we wanted to provide, so we needed to shorten the value we sent. We also had concerns with how we would track down any "orphan" payments in the payment system that we couldn't track to a particular user. We were lucky that, in practice, this never occurred.
Number four also presented a challenge. My department is largely a Perl shop
and we didn't use Java on our website. During the development process, we
discovered that the vendor link required Java to encrypt the data we sent to
their system. They used Cryptix PGP with some custom code, available in Java
only. That summer at OSCON I learned about the Inline modules and I decided to
try one. Since it was only one small Java component, I followed their examples
to code the Java encryption subroutine and stuck it at the bottom of the final
Perl submission script using
Inline::Java.
We have since moved on to a different system for processing our payments, so this code is a bit dated. However, when we were using it, it looked something like this:
my $encrypt = new encrypt(); # We had several calls like this one, one for each parameter we # needed to encrypt. # Each call returned a string with the encrypted data. my $vendor_code = $encrypt->send_to_vendor($pay_code); # CGI code to create a form with the encrypted data as hidden fields. # The form also acted as a confirmation page, so the user saw # "Please review your information and click to continue". # Java code at the bottom of the file. use Inline ( Java => <<'END_OF_JAVA_CODE', import xjava.security.Cipher; import cryptix.pgp.*; class encrypt { public encrypt(){ } public String send_to_vendor (String value_string){ PGP pgpObj = new PGP(); /* Initialize the object with base values. */ pgpObj.init("/pgp_dir", "client name", "passwd","sender_key_name"); /* Add the receiver name to the list. */ pgpObj.addReceiver("vendor name"); /* Create the encoded string. */ String encvalue = pgpObj.encodeString(value_string); return (encvalue); } } END_OF_JAVA_CODE SHARED_JVM => 1, CLASSPATH => 'paths to cryptix libraries', );
The code itself is very simple. The Java code simply creates a new object,
calls a few methods, and returns the encrypted string. The interesting part is
how relatively easy it was to embed the Java code in a Perl CGI script and have
it work seamlessly. When we initially implemented this in 2001,
Inline::Java was fairly new and there was little documentation on
how to do this. Patrick LeBoutillier, the author, was very helpful as I was
trying to get it working. Now the
Inline::Java documentation has a section on using
Inline::Java in CGI scripts.
The last item on the list was far more difficult than we anticipated. For most days, it was a simple formula to tag a record with a date the money would be available. However, weekends had special rules; we saw results that were inconsistent with the documentation we were working from. To nail down the problem, we ended up having to run small test transactions at various times on various days to see when our accountants saw the money actually arrive in the UB account.
For example, if the documentation said there was a 4 p.m. cut-off time for payments on a given day, we would run a $1.11 transaction at 3:55 p.m., then another $1.12 transaction at 4:05 p.m. Our accounting people would watch the system to see when the payments came through. We needed to increment the amounts because we used the same credit card to process the transactions and there was no other way to keep them straight.
Following this method, we learned a lot about the backend banking system. Different credit card companies process things on different schedules and handle things differently. The most important thing we found was a bug in the vendor's processing system. After many weeks of running test transactions and comparing the clearing times with the vendor documentation, we finally tracked it back to a problem in the vendor system that they quickly fixed.
The final product worked well. During the first run of the final
confirmation script,
Inline::Java compiles and saves the code to
run again, so it was quite fast. The vendor received the encrypted information
correctly and could decrypt it successfully; it didn't matter to them that we
actually generated the request in Perl. Our remittance files arrived on a
regular schedule and we posted the results on a daily cycle.
The World Happens in Real Time
As described above, the vendor did much of the work, but since we passed users off to their system, we were unable to see the results of transactions until the next day. This wasn't a problem for some payments. For example, the graduate school application fee just needs to be received by a particular date. It's not crucial that we know the results of the transaction in real time.
For tuition payment, however, we needed a real-time solution. At UB and many universities, you can't register for your next semester if you have an outstanding balance from the previous semester. When a student is in this situation, called a checkstop, he needs to be able to pay and have the checkstop lifted immediately. Students often find out about their checkstop while trying to register online, so they need to be able to pay, clear their accounts, and return to the registration system while some courses are still available. What's more, this often happens on a Saturday morning when many services may not be available.
Real-Time Integration Challenges
The existing accounts system at UB runs on a mainframe, but most new development happens in our distributed Solaris environment. To allow real-time payments, we needed a vendor who could provide an immediate payment response, and we needed to apply that result to the mainframe. We also wanted to design the system to be open for future applications.
We selected VeriSign as our new vendor and they returned real-time results. To then apply real-time results to our system, we could no longer pass users off to another site; we needed to manage the entire transaction. We redesigned our system to handle this.
We currently use this redesigned system. The key steps in our current process are:
- Manage the web transaction using Perl CGI scripts and an Oracle database.
- After the user enters credit card information and clicks Submit, make a SOAP call to our internal payment server. The payment server logs all events.
- Perl scripts on the payment server call our mainframe system to determine if the student's account is eligible to receive a payment.
- If the student has an active account, we call VeriSign using their Perl module.
- Receive and log results from VeriSign.
- If VeriSign approved the payment, call the mainframe again and credit the student's account with the amount of the payment. If student paid in full, remove the checkstop from the account.
- Return the response for the initial SOAP call and present the user with the result of the transaction.
From a design perspective, most of the steps are necessary given the disparate components we're working with. Despite all of the things we do, the entire process usually takes about 10 seconds.
We have several systems that access our mainframe in real-time through Perl. The Perl part is a basic socket call to a port on the mainframe. Special software runs on the mainframe as a server that can receive formatted requests and return results after processing against mainframe systems. One major advantage of the mainframe software is that it allows us to use existing mainframe systems through a new internet front-end.
The interface with VeriSign was likewise fairly easy to integrate. They provide a Perl module to call their system and retrieve results. The majority of the work was in learning all of the non-success response codes and designing the system to handle them appropriately.
However, you may wonder why we included a SOAP call between internal
servers. When we designed the system, we tried to make all of the parts as
flexible as possible. For this phase, we built both the front-end (the user websites) and the backend (the payment server). Given a generic enough interface,
we speculated that other campus users may want to build their own front-end website and interface directly with the payment server. Constructing a SOAP
interface using
SOAP::Lite
allowed us to potentially provide a service in a technology-neutral
manner. The system has worked well and performance hasn't been a problem. Thus
far, we haven't had an external, campus-entity interface with our system, but we
have built other internal systems that use the interface.
We also have a process in place for payments that don't need immediate application (not real time). For these payments, we follow a similar process but skip the live mainframe connections. At night, we have batch Perl jobs that run and apply the changes to the mainframe.
Challenges
Most of our challenges with the ePayment system are with the user interface, specifically providing enough information. We will frequently receive a result of "declined," or "declined with possible approval." Some declines are the simple type where the user simply doesn't have enough room on their credit card. However, many credit and debit cards have a maximum charge limit per day that card holders aren't aware of. They tend to hit this limit when using a credit card to make a large charge like a tuition payment. This type of decline is conditional; the card issuer can approve the transaction after a phone call from the card holder.
The problem is that the return code from VeriSign isn't always clear enough for us to tell users exactly what they need to do. The correct response can also vary from one credit card issuer to another. The challenge is presenting them with messages that give them enough information, but no incorrect information. Even when we can provide them with a clear message such as "You need to call your credit card issuer," they often assume it's a problem with our system and contact us anyway.
Your eUniversity
UB's ePayment project has been very successful and our users have received it well. In our last payment cycle, approximately 40% of payments came through the internet, instead of the traditional methods of mailing a check, going to the payment office, or paying over the phone. Even with this level of activity, the level of support required has been very reasonable.
With so many internet payment systems available -- some specifically designed for education -- some may question custom-building so much of the system. When dealing with legacy components, however, it's often difficult to purchase a solution for just part of the process. Replacing entire systems at a university the size of UB is a much larger project than we usually want to tackle at any one time. In these situations, we've found Perl to be very flexible, allowing us to patch together disparate systems and external resources to build a cohesive, effective system.