
interchange-cvs at icdevgroup
Feb 8, 2010, 7:25 AM
Post #1 of 1
(1802 views)
Permalink
|
|
[interchange] CyberSource SOAP toolkit payment module
|
|
commit 627df179b11d3252783477a347939adc2473b119 Author: Mark Johnson <mark [at] endpoint> Date: Mon Feb 8 10:20:56 2010 -0500 CyberSource SOAP toolkit payment module Provides full clientless (as in no Simple or SCMP API) support for the following CyberSource services: * Standard credit card * Bill Me Later * Paypal Express Checkout * Electronic check Squashed commit of the following: commit eb6e31d62115670cc2a0abe063cc3ff73831f986 Author: Mark Johnson <mark [at] endpoint> Date: Mon Feb 8 10:13:03 2010 -0500 Complete CyberSource documentation Fill in documentation missing for the newer Paypal and Electronic Check features. commit d5623a7287254d6df1f563221936ff4e287aa2b9 Author: Mark Johnson <mark [at] endpoint> Date: Tue Oct 20 21:53:06 2009 -0400 Fixed glitch on return_ec_set handling. * paypal_token was being wiped out since CYBS wasn't echoing the token in its reply. commit 1b6902c04914b784f6ad920c8c46068083148374 Author: Mark Johnson <mark [at] endpoint> Date: Mon Aug 17 12:49:54 2009 -0400 Converted check_num -> check_number The former was in standard, but the latter in map_actual(), which I took to be authoritative. commit 79f0292a912d6a95a66413b0c43aa085508b85fe Author: Mark Johnson <mark [at] endpoint> Date: Mon Aug 17 10:56:36 2009 -0400 Adding electronic-check support. commit 27a0f38fb5c00aa8b698e506905e38577a31ab69 Author: Mark Johnson <mark [at] endpoint> Date: Wed Aug 12 10:31:00 2009 -0400 Adjustments from testing implementation * Added support for passing in generalized transaction_id, distinct from CYBS's transaction ID, that Paypal requires for follow-on transactions. So, CYBS tid = origid, PP tid = transaction_id. * Added documentation for transaction_id param, noting the relationship of the various PP services for its use. * Converted from using $Session to $::Session. * CYBS will enforce a conditional set of required features on the shipTo_* fields if any of them are present, despite them being technically optional. Since actual() tends to suck in anything floating around in $Values, added an address_ok requirement for using any address data at all. * Added conversion in request for the typically used UK to instead use GB for any UK address. * Restricted resetting $Session->{paypal_token} to only EC set transactions. Failures on dopayment attempts were (at least sometimes) coming back without echoing the token, which was killing the paypal session. commit fe2029d6869813962863d286c6a327806c089f46 Author: Mark Johnson <mark [at] endpoint> Date: Tue Aug 4 11:52:18 2009 -0400 Paypal documentation added. commit 694e1d7dcefd6d027810471ac7e56cd04fc74333 Author: Mark Johnson <mark [at] endpoint> Date: Tue Aug 4 10:28:35 2009 -0400 Paypal Support in Vend::Payment::CyberSource commit a6a7169bd3107506d88087b2c9221594c14185de Author: Mark Johnson <mark [at] endpoint> Date: Mon Jul 20 10:07:05 2009 -0400 Initial load of Vend::Payment::CyberSource lib/Vend/Payment/CyberSource.pm | 2469 +++++++++++++++++++++++++++++++++++++++ 1 files changed, 2469 insertions(+), 0 deletions(-) --- diff --git a/lib/Vend/Payment/CyberSource.pm b/lib/Vend/Payment/CyberSource.pm new file mode 100644 index 0000000..5050f5f --- /dev/null +++ b/lib/Vend/Payment/CyberSource.pm @@ -0,0 +1,2469 @@ +# Vend::Payment::CyberSource - Interchange Cybersource SOAP Toolkit Support +# +# Copyright (C) 2009 Interchange Development Group and others +# +# Written by Mark Johnson <mark [at] endpoint> +# based on code by Sonny Cook <sonny [at] endpoint> +# and Mike Heins <mike [at] perusion> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA. + +package Vend::Payment::CyberSource; + +=head1 Interchange CyberSource Support + +Vend::Payment::CyberSource Revision: 1.0 + +=head1 SYNOPSIS + + &charge=cybersource + + or + + [charge gateway=cybersource param1=value1 param2=value2] + + or, with a route named foo with gateway => cybersource + + [charge route=foo] + +While you have free will, B<please> use a route. See below. + +=head1 PREREQUISITES + + SOAP::Lite + XML::Pastor::Schema::Parser + LWP::Simple + OpenSSL + +=head1 DESCRIPTION + +The Vend::Payment::CyberSource module implements the cybersource() routine for +use with Interchange. It is compatible on a call level with the other +Interchange payment modules. + +To enable this module, place this directive in C<interchange.cfg>: + + Require module Vend::Payment::CyberSource + +This I<must> be in interchange.cfg or a file included from it. + +Make sure CreditCardAuto is off (default in Interchange demos). + +This module supersedes Vend::Payment::ICS. With only minor adjustments to the +payment route and some changes to the keys in $Session->{payment_result}, +Vend::Payment::CyberSource should function as a drop-in replacement for +Vend::Payment::ICS. If you have legacy support for the SCMP API and don't need +any of the new features supported in this module, you do not need to change. +However, all new CyberSource clients will have to use this module as they do +not allow new clients to use SCMP. + +If you do choose to replace the current use of Vend::Payment::ICS, you will +have to abandon the merchant keys generated by ecert() (I know, it's a +devastating blow) and instead generate a transaction key from within the online +merchant account. That new transaction key will be added to your payment route. + +Whenever possible, please set your payment parameters using a payment route or +passed as arguments to [charge] directly. No special effort has been made to +consistently fall back on the charge_param() function, which plucks values out +of the MV_PAYMENT_* variable space. It is not possible to isolate variables to +a specific charge routine using charge_param() and, as such, it should be +viewed with suspicion, rather than being used as a crutch. In particular, this +module makes the assumption that certain critical route parameters will be +present in $opt, and the best way to ensure they are in there is to use a +payment route. + +=head2 Simple made Simple + +You may treat this gateway module as though it were the Simple API. The +construction of the requests and responses within the Vend::Payment namespace +will correspond either virtually (in the case of SOAP errors, it's probably +different) or exactly (in the case of successful requests, even if the +transaction itself is not successful) to the request/response name/value pairs +documented for the Simple API. + +Unlike its name may imply, the Simple API is in fact not simple to install. +Difficult becomes Impossible if you have a 64-bit OS. CyberSource as of this +module's writing has not seen fit to port either of their client APIs (SCMP or +Simple) to a 64-bit version. However, because this is actually the SOAP +clientless toolkit wrapped to impersonate Simple, you get to by-pass the pain +you'd otherwise have to feel to install Simple into your environment. + +B<What this means:> do I<NOT> install the Simple API! It will likely be a big +pain and I<it is useless> for the purposes of this module. You only need to +ensure the modules/software indicated in PREREQUISITES are installed. + +You can find the documentation for the Simple API at the following URL: + +http://apps.cybersource.com/library/documentation/sbc/api_guide/html/ + +Again, do I<NOT> install the Simple API; just use the docs as though it is +already installed. + +=head2 Options + +=over + +=item B<merchant_id> + +ID obtained from CyberSource. Likely the same value you use to log in to the +online merchant interface for your account. + +=item B<transaction_key> + +Key generated from the CyberSource online merchant interface. + +=item B<api_version> + +Which version of Simple to use. When setting up, just pick the current one, and +updates are likely unneeded unless new features come out that you need. + +You can see the full list of versions, and what features they each support (if +you like reading XSD) here: + +https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/ + +The opt should be just the number, something like 1.44 + +=item B<live> + +Calls go to production environment when value is 'true' (as in, the string +true, and not just perly true, to give those already familiar with the actual +Simple API warm fuzzy feelings). Any other value (or no value) is assumed to go +to the test environment. + +=item B<xsd_cache_dir> + +Directory relative to catroot for caching the XSD files for each API version and +from each environment (live or test). If not set, no caching will occur, but this +is I<not> recommended. + +The module makes an initial request to CyberSource to get the XSD for whatever +version of the API you're using, so that it knows what request values to look +for and are valid. Ideally, you don't want every single request repeating this +process. + +If this option is set, the module will first look for the appropriate XSD +associated with the correct version and environment in the cache dir. If it's +not found, only then will it request the XSD from CyberSource. Once it's +obtained from from CyberSource, it will cache it in the same location it +initially looked, so all subsequent requests using the same API version in the +same environment can use the cached XSD instead. + +There should be no reason to expire the cache, but if you wish to you'll have +to do so manually. There's no built-in expiration mechanism. Because a +published API will be static, the only reason you would need to expire the +cached version is if it somehow became corrupted. + +Also, don't manually replace files in the cache. The XSD retrieved from +CyberSource must be slightly tweaked to work with the XSD parsing module. That +tweak is frozen into the cache. If you wish to refresh the cache, just remove +the cache files and let the module itself pull down the correct version and +cache it for you. + +=item B<acct_type> + +The type of the account for the customer's payment instrument. Currently +supported: + + * cc (standard credit card, the default) + * bml (Bill Me Later) + * pp (Paypal Express Checkout) + * ec (Electronic Check) + +=item B<transaction> + +Type of transaction to run. + +acct_type cc or bml: + + * auth (or authorize, A) + * settle (or capture, D) + * sale (or S) - auth and settle in same request + * credit (or C) + * auth_reversal (or R) (not widely supported; use with caution) + * void (or V) + +For additional information on acct_type 'bml' transactions, see full B<Bill Me +Later> section below. + +acct_type pp: + + * pp_set (Exp. checkout set service) + * pp_get (Exp. checkout get service) + * pp_dopmt (Exp. Checkout do payment) + * pp_ord_setup (Exp. Checkout order setup) + * pp_auth (auth service) + * pp_bill (capture service) + * pp_sale (auth and capture together) + * pp_authrev (reverse auth) + * pp_refund (refund service) + +See B<Paypal> section below for full details + +acct_type ec: + + * ec_debit + * ec_credit + +See B<Electronic Check> section below for full details + +=item B<origid> + +Original transaction ID referenced for follow-on transactions. E.g., the +requestID of an auth that is to be captured. + +=item B<order_id or order_number> + +Passed over as merchantReferenceCode with request. Also very likely needed for +any use of B<items_sub> (see below). + +=item B<apps> + +The list of allowable applications, space- or comma-separated, for this +request. Added as a security measure to restrict use on any applications that +aren't needed and might be a risk (such as credits if they'll never be issued +through Interchange). All ics_* apps apply to cc and bml account types. pp_* +and ec_* apply to account types pp and ec, respectively: + + * ics_auth (needed for auth, sale) + * ics_auth_reversal (needed for auth_reversal) + * ics_bill (needed for settle, sale) + * ics_credit (needed for credit) + * ics_void (needed for void) + * pp_ec_set (needed for pp_set) + * pp_ec_get (needed for pp_get) + * pp_ec_dopmt (needed for pp_dopmt, pp_sale) + * pp_ec_ord_setup (needed for pp_ord_setup) + * pp_auth (needed for pp_auth) + * pp_capture (needed for pp_bill, pp_sale) + * pp_authrev (needed for pp_authrev) + * pp_refund (needed for pp_refund) + * ec_debit (needed for ec_debit) + * ec_credit (needed for ec_credit) + +It is recommended to exclude any apps you will not use from the list. + +=item B<ship_map> + +List of mv_shipmode values to map to the various allowable values in +shipTo_shippingMethod. List is optional and defaults to 'lowcost'. + +The list should be in a form that is appropriate for perl's qw() to put the +list into a hash. E.g.,: + + GNDRES lowcost + 2DAY twoday + ... + +List of possible CyberSource values: + + * sameday + * oneday + * twoday + * threeday + * lowcost + * pickup + * other + * none + +=item B<mv_credit_card_number> + +Unlike many (if not the rest) of the gateway modules, you can pass in the card +number as an option. However, the code prefers the value from the traditional +source from $CGI via the actual opt. This option will mostly be unneeded. + +=item B<any Simple request keys> + +The code will take any of the Simple key names directly through $opt. However, +it will prefer any defined values associated with actual() over $opt, which may +not be what you expect. + +=item B<items_sub> + +Custom subroutine that can be used to construct the order sent over to +CyberSource. By default, module uses $Vend::Items. Routine will be essential +for any processing of payments that are not directly associated with the +immediate session. E.g., any payment interface developed for the admin to +process transactions after the order already exists. + +Routine should return an array that matches the basics of an Interchange cart. +Most likely use would be to construct a cart out of an order already in the +database, but as long as the routine returns a cart-style array, it doesn't +matter how it's constructed. + +Each line item needs the following attributes: + + * code + * quantity + +To determine cost, each line-item hash is passed to Vend::Data::item_price(). +So, if your pricing demands more line-item attributes to calculate correctly, +you'll need to ensure they are present. + +Routine can be either a catalog or global sub, with the code preferring the +catalog sub. Args: + + * Reference to copy of request hash + * $opt + +=item B<check_sub> + +Post-reply routine that can be used to alter the status and/or response hash +returned from cybersource(). Examples of usage: + +=over + +=item * + +Auth succeeds, but we want it treat as a failure because of AVS response. + +=item * + +Auth fails, but error is a communication failure, so we want to treat it as +success and follow up manually with customer to resolve. + +=item * + +Decision Manager result raises concerns, so we add a response parameter that +indicates the order should not be fulfilled automatically, but rather funnel to +a queue for manual review. + +=back + +Routine should return 'success' or 'failed' if it is to be authoritative as to +that determination. Otherwise, it should return undef and let the code +calculate 'success' or 'failed' based on the value of the 'decision' key in the +response hash. + +It can be either a catalog or global sub, with the code preferring the catalog +sub. Args: + + * Reference to response hash (so it can be modified directly) + * Reference to a copy of the request hash + * $opt + +=item B<ip_address> + +IP to supply to CyberSource for the transaction. Optional and defaults to +$Session->{shost} if defined, or $Session->{ohost} otherwise. + +=item B<shipping> + +Amount to supply CyberSource for shipping costs. Defaults to [shipping]. + +=item B<amount> + +Amount to supply to CyberSource to apply to the transaction. + +Note that this value supersedes all the costs provided along with the order and +shipping amounts. They do not have to agree. Supplying I<amount> means +CyberSource will use that specific amount. If I<amount> is not supplied, I +believe that CyberSource will construct a transaction amount from the sum of +the order and shipping, but I do not recommend this. + +By default, I<amount> will be derived from [total-cost]. + +=item B<timeout> + +Number of seconds for a request without a response before the process is +killed. + +=item B<merrmsg, merrmsg_bml> + +Override the default error messages presented to users in the event of a failed +transaction attempt, for B<acct_type> cc and bml, respectively. B<merrmsg> will +run through sprintf() and can have the reason code and reason message, in that +order, embedded in message with the use of %s as a placeholder. B<merrmsg_bml> +has no such provision because the errors that come back from Paymentech for BML +failures range between cryptic and useless. + +=back + +The following options are valid for Paypal usage only: + +=over + +=item B<returnurl> + +URL to which the customer's browser is returned after choosing to pay with +PayPal. + +=item B<cancelurl> + +URL to which customers are returned if they do not approve the use of PayPal +for payment. + +=item B<maxamount> + +Expected maximum total amount of the entire order, including shipping costs and +tax charges. + +=item B<order_desc> + +Description of items the customer is purchasing. + +=item B<confirmshipping> + +Flag that indicates if you require the customer's shipping address on file with +PayPal to be a confirmed address. 0 (default) not confirmed, 1 confirmed. + +=item B<noshipping> + +Flag that indicates if the shipping address should be displayed on the PayPal +Web pages. 0 (default) show shipping, 1 suppress shipping display. + +=item B<addressoverride> + +Customer-supplied address sent in the SetExpressCheckout request rather than +the address on file with PayPal for this customer. + +You can use this field only with the payment method, not with the shortcut +method. See Overview of PayPal Express Checkout for a description of the PayPal +methods. + +Possible values: + +0 (default): Display the address on file with PayPal. The customer cannot edit +this address. + +1: Display the customer-supplied address. The customer can edit this in PayPal +Express Checkout. + +=item B<locale> + +Locale of pages displayed by PayPal during Express Checkout. + +=item B<headerbackcolor> + +Background color for the header of the payment page. Format: HTML Hexadecimal +color. + +=item B<headerbordercolor> + +Border color around the header of the payment page. Format: HTML Hexadecimal +color. + +=item B<headerimg> + +URL for the image that will be displayed in the upper left area of the payment +page. + +=item B<payflowcolor> + +Background color for the payment page. Format: HTML Hexadecimal color. + +=item B<pp_token> + +Timestamped token by which you identify to PayPal that you are processing this +payment with Express Checkout. Corresponds with paypalToken description in +CyberSource docs. Normally not needed as module will handle internally for most +typical scenarios. + +=item B<pp_payer_id> + +Unique PayPal customer account identification number that was returned in the +payPalEcGetDetailsService reply message. Corresponds with paypalPayerId +description in CyberSource docs. Normally not needed as module will handle +internally for most typical scenarios. + +=item B<return_ec_set> + +Boolean to indicate that an EC set call is to be issued as a return call; that +is, a call to amend the user's Paypal data returned in a subsequent EC get call +on the same session established by an initial EC set call. + +The same can be accomplished by calling EC set while explicitly supplying the +paypal token and CyberSource request ID and token of the original call to EC +set establishing that session. + +=item B<transaction_id> + +For use in captures, refunds, and auth reversals. Maps to the +I<paypal-specific> transaction ID of the original request, and not the +RequestID, which is the CyberSource transaction ID. + +Specifically, it should relate in the following manner: + I<Maps to Service Param> I<Obtained From> + payPalDoCaptureService_paypalAuthorizationId payPalEcDoPaymentReply_transactionId + payPalAuthReversalService_paypalAuthorizationId payPalAuthorizationReply_transactionId + payPalRefundService_paypalCaptureId payPalDoCaptureReply_transactionId + +=back + +=head2 Bill Me Later + +This module provides full support for Bill Me Later (BML) transactions. Some +important notes on using BML with this module: + +See CyberSource's full BML documentation for details: + +http://apps.cybersource.com/library/documentation/dev_guides/CC_Svcs_IG_BML_Supplement/html/ + +=over + +=item * + +You'll want to add in the extra fields collected for BML. Suggested process is +to use the remap feature in Vend::Payment::charge(). Thus, add to your route: + + Route foo remap <<EOV + bml_customer_registration_date bml_customer_registration_date + bml_customer_type_flag bml_customer_type_flag + bml_item_category bml_item_category + bml_product_delivery_type_indicator bml_product_delivery_type_indicator + bml_tc_version bml_tc_version + customer_ssn customer_ssn + date_of_birth date_of_birth + b_phone b_phone + EOV + +This will allow those fields to be used directly from your form and be picked +up by map_actual() for you. The names listed above correspond with the values +as managed through SCMP. You can just as easily use, instead, those documented +with Simple. + +=item * + +Set acct_type option to 'bml'. + +=item * + +On a failed BML attempt, the module sets [value suppress_bml] to 1. It's +recommended you use this session setting to disable the BML option so as to +encourage your customers to try to complete their order with a credit card. +Chances are extremely remote that a subsequent BML attempt will succeed after a +failure. However, this is not a BML requirement; you may choose to let +customers try again and again to check out with BML. + +=item * + +On a successful BML authorization, you can find the customer's BML account +number (which looks like a credit card starting with the digits 5049) in +$Session->{payment_result}{ccAuthReply_bmlAccountNumber}. Using this account +number for repeat customers will speed up the authorization process, but it is +not required. Without it, your BML customers will have to re-enter their +last-four SSN and DOB each time. + +Note that BML does not consider this account number sensitive. It can be freely +stored unencrypted in the customer's user record. + +=back + +=head2 Paypal + +This module also fully implements CyberSource's integration for Express +Checkout services. Note that CyberSource doesn't appear to support all Paypal +services, so depending on your needs (e.g., Mass Pay), you may need another +option. However, for typical Express Checkout usage, it is fully supported. + +See CyberSource's full Paypal documentation for details: + +http://apps.cybersource.com/library/documentation/dev_guides/PayPal_Express_IG/html/ + +=over + +=item * + +Set acct_type option to 'pp' + +=item * + +Module is set up to handle most standard cases with minimal demand on the +developer. Paypal requires toting around the paypal session ID and, in the case +of dopayment, the payer ID. Further, CyberSource requires that the request ID +and token from the EC set be passed back on each subsequent request associated +with the same paypal session. Finally, using address override needs to be +tracked between a set call and subsequent get call. The module will track these +values in the user's IC session and most likely "do the right thing". You can, +however, override any of these explicitly if you have the need or wish to +control it explicitly. + +=item * + +The reply fields are stripped down to their canonical values for convenience. +Many times, the same param merely differs in its reply field based on the +application under which it was processed. E.g., the "amount" returned will come +back in one of the following forms: + + payPalEcSetReply_amount + payPalEcDoPaymentReply_amount + payPalEcOrderSetupReply_amount + payPalAuthorizationReply_amount + payPalDoCaptureReply_amount + +Any of these will be stripped down to just "amount" in the payment_result hash, +but the fully qualified parameter is left in place, too, if needed. + +=item * + +It is recommended that you utilize B<check_sub> in conjunction with an EC get +request for migrating response values from Paypal into the Interchange session. +The following is a sample sub that works in conjunction with a typical +configuration based off the standard demo: + + Sub load_values <<EOS + sub { + my ($resp, $req, $opt) = @_; + + return unless $resp->{decision} eq 'ACCEPT'; + + my $b_pre = $Values->{pp_use_billing_address} + ? 'b_' + : '' + ; + + $Values->{$b_pre . 'phone_day'} = $resp->{payerPhone}; + + $Values->{email} = $resp->{payer}; + $Values->{payerid} = $resp->{PayerId}; + $Values->{payerstatus} = $resp->{payerStatus}; + $Values->{payerbusiness} = $resp->{payerBusiness}; + $Values->{salutation} = $resp->{payerSalutation}; + $Values->{mname} = $resp->{payerMiddlename}; + $Values->{suffix} = $resp->{payerSuffix}; + $Values->{address_status} = $resp->{addressStatus}; + $Values->{countryname} = $resp->{countryName}; + + unless ($Session->{paypal_override}) { + $Values->{$b_pre . 'fname'} = $resp->{payerFirstname}; + $Values->{$b_pre . 'lname'} = $resp->{payerLastname}; + $Values->{$b_pre . 'address1'} = $resp->{shipToAddress1}; + $Values->{$b_pre . 'address2'} = $resp->{shipToAddress2}; + $Values->{$b_pre . 'city'} = $resp->{shipToCity}; + $Values->{$b_pre . 'state'} = $resp->{shipToState}; + $Values->{$b_pre . 'zip'} = $resp->{shipToZip}; + $Values->{$b_pre . 'country'} = $resp->{shipToCountry}; + } + + return; + } + EOS + +=item * + +When making an EC set call through [charge], the module will return the +appropriate URL to which to redirect the user's browser rather than the typical +value of the transaction ID. Thus, when running an EC set, it is appropriate to +capture and test the return value from [charge] to determine both if the +request was successful and, if so, where to direct the user. + +Example mv_click code: + + [button + text="Checkout with Paypal" + form=basket + ] + [tmp redirect][charge route=paypal_set][/tmp] + [if scratch redirect] + [bounce href="[scratch redirect]"] + [/if] + [/button] + +=back + +=head2 Electronic Checks + +This module fully implements CyberSource's electronic check support. See +documentation for full details: + +http://apps.cybersource.com/library/documentation/dev_guides/Electronic_Checks_IG/html/ + +=over + +=item * + +Set acct_type option to 'ec' + +=item * + +Includes mappings to use the standard demo's default check payment option. + +=back + +=head2 Troubleshooting + +Try the instructions above in test mode (live set to anything but string +'true'). A test order should complete. + +Switch to production mode, then try an auth or sale with the card number C<4111 +1111 1111 1111> and a valid expiration date. The transaction should be denied, +and the reason should be in [data session payment_error]. + +If nothing works: + +=over 4 + +=item * + +Make sure you "Require"d the module in interchange.cfg: + + Require module Vend::Payment::CyberSource + +=item * + +Make sure all the modules/applications listed in PREREQUISITES are installed +and working. You can test to see whether your Perl thinks they are: + + perl -MSOAP::Lite \ + -MXML::Pastor::Schema::Parser \ + -MLWP::Simple \ + -e 'print "It works\n"' + +If it prints "It works." and returns to the prompt you should be OK (presuming +they are in working order otherwise). + +=item * + +Check the error logs, both catalog and global. Also set debugging on in +C<interchange.cfg> and check the debug log! + +=item * + +Make sure you set your payment parameters properly, and of course you used a +payment route, right? + +=item * + +Try an order, then put this code in a page: + + <XMP> + [calc] + my $string = $Tag->uneval( { ref => $Session->{payment_result} }); + $string =~ s/{/{\n/; + $string =~ s/,/,\n/g; + return $string; + [/calc] + </XMP> + +That should show what happened. + +=item * + +If all else fails, consultants are available to help with integration for a fee. +See http://www.icdevgroup.org/ for mailing lists and other information. + +=back + +=head1 BUGS + +Naturally none. OK, at least none known. Be sure to post any found to the IC +user list or you can send them to the author directly. + +=head1 AUTHOR + +Mark Johnson <mark [at] endpoint> +(Based primarily off of Vend::Payment::ICS by Sonny Cook <sonny [at] endpoint>) + +=cut + +::logGlobal('Loading module ' . __PACKAGE__); + +package Vend::Payment; +use strict; + +my $debug_scrub = sub { + # Utility to scrub out any mv_credit_card_* or + # *_ssn values from a hash containing those data for + # the purpose of writing to the debug log. + + local $_ = ::uneval(shift); + + my $scrub_keys = + join ( + '|', + qw/ + card_cvNumber + card_accountNumber + card_pin + mv_credit_card_[^']+ + [^']+_ssn + / + ) + ; + + s{ + ( + ' + (?: $scrub_keys ) + ' \s+ => \s+ ' + ) + ( + (?: + \\' + | \\ + | [^\\']* + )* + ) + (') + }{$1 . ('X' x length($2)) . $3}xsmge; + + return $_; +}; + +sub cybersource { +#::logDebug("cybersource called--in the begining"); + my ($opt) = @_; + $opt->{order_number} ||= $opt->{order_id}; + +::logDebug("cybersource opt hash: %s", $debug_scrub->($opt)); + + my %type_map = qw/ + sale auth_bill + auth auth + auth_reversal auth_reversal + authorize auth + void void + settle bill + capture bill + credit credit + S auth_bill + C credit + D bill + V void + A auth + R auth_reversal + pp_set pp_set + pp_get pp_get + pp_dopmt pp_dopmt + pp_ord_setup pp_ord_setup + pp_auth pp_auth + pp_bill pp_bill + pp_sale pp_sale + pp_authrev pp_authrev + pp_refund pp_refund + ec_debit ec_debit + ec_credit ec_credit + /; + + my %inv_trans_map = qw/ + auth A + auth_bill S + credit C + auth_reversal R + void V + bill D + /; + + my %app_map = ( + auth => [qw/ ics_auth /], + auth_bill => [qw/ ics_auth ics_bill/], + auth_reversal => [qw/ ics_auth_reversal /], + bill => [qw/ ics_bill /], + credit => [qw/ ics_credit /], + void => [qw/ ics_void /], + pp_set => [qw/ pp_ec_set /], + pp_get => [qw/ pp_ec_get /], + pp_dopmt => [qw/ pp_ec_dopmt /], + pp_ord_setup => [qw/ pp_ec_ord_setup /], + pp_auth => [qw/ pp_auth /], + pp_bill => [qw/ pp_capture /], + pp_sale => [qw/ pp_ec_dopmt pp_capture/], + pp_authrev => [qw/ pp_authrev /], + pp_refund => [qw/ pp_refund /], + ec_debit => [qw/ ec_debit /], + ec_credit => [qw/ ec_credit /], + ); + + my $transtype = $opt->{transaction} + || charge_param('transaction') + || 'auth'; + +::logDebug("transaction type: $transtype"); + + $transtype = $type_map{$transtype} + or return ( + MStatus => 'failure-hard', + MErrMsg => errmsg('Unrecognized transaction: %s', $transtype), + ) + ; + + my %acct_type_map = qw/ + bml bml + cc cc + pp pp + ec ec + /; + + my $acct_type = $acct_type_map{ lc ($opt->{acct_type}) || 'cc' } + or return ( + MStatus => 'failure-hard', + MErrMsg => errmsg('Unrecognized acct_type: %s', $opt->{acct_type}), + ) + ; + + ## get list of applications to use + my (@apps,%opt_apps); + @opt_apps{ grep { /\S/ } split ( /[,\s]+/, lc ($opt->{apps}) ) } = (); + + for ( @{ $app_map{$transtype} } ) { + push (@apps, $_) + if exists $opt_apps{ lc ($_) }; + } +::logDebug ("Applications: " . ::uneval \@apps); + + ## Allow IC-relative page paths for return and cancel URLs + if ($acct_type eq 'pp' && grep { /^pp_ec_set$/ } @apps) { + $opt->{$_} = $Tag->area({ href => $opt->{$_}, secure => 1 }) + for (qw/returnurl cancelurl/); + } + + ## Apps that require (or benefit from) a cart + my %items_apps = qw/ + ics_auth 1 + pp_ec_set 1 + pp_ec_dopmt 1 + pp_ec_ord_setup 1 + pp_auth 1 + ec_debit 1 + /; + + ## Required fields per app + my %required_map = ( + all => + [. qw/ + merchantID + merchantReferenceCode + purchaseTotals_currency + / + ], + ics_auth => + [. qw/ + ccAuthService_run + billTo_street1 + billTo_city + billTo_country + billTo_state + billTo_postalCode + card_expirationMonth + card_expirationYear + card_accountNumber + billTo_email + billTo_firstName + billTo_lastName + shipTo_street1 + shipTo_city + shipTo_country + shipTo_state + shipTo_postalCode + / + ], + ics_auth_reversal => + [. qw/ + ccAuthReversalService_run + ccAuthReversalService_authRequestID + ccAuthReversalService_authRequestToken + / + ], + ics_bill => + [. qw/ + ccCaptureService_run + ccCaptureService_authRequestID + ccCaptureService_authRequestToken + / + ], + ics_credit => + [. qw/ + ccCreditService_run + billTo_street1 + billTo_city + billTo_country + billTo_state + billTo_postalCode + card_expirationMonth + card_expirationYear + card_accountNumber + billTo_email + billTo_firstName + billTo_lastName + / + ], + ics_void => + [. qw/ + voidService_run + voidService_voidRequestID + voidService_voidRequestToken + / + ], + pp_ec_set => + [. qw/ + payPalEcSetService_run + payPalEcSetService_paypalReturn + payPalEcSetService_paypalCancelReturn + / + ], + pp_ec_get => + [. qw/ + payPalEcGetDetailsService_run + payPalEcGetDetailsService_paypalToken + payPalEcGetDetailsService_paypalEcSetRequestID + payPalEcGetDetailsService_paypalEcSetRequestToken + / + ], + pp_ec_dopmt => + [. qw/ + payPalEcDoPaymentService_run + payPalEcDoPaymentService_paypalToken + payPalEcDoPaymentService_paypalPayerId + payPalEcDoPaymentService_paypalEcSetRequestID + payPalEcDoPaymentService_paypalEcSetRequestToken + payPalEcDoPaymentService_paypalCustomerEmail + / + ], + pp_ec_ord_setup => + [. qw/ + payPalEcOrderSetupService_run + payPalEcOrderSetupService_paypalToken + payPalEcOrderSetupService_paypalPayerId + payPalEcOrderSetupService_paypalEcSetRequestID + payPalEcOrderSetupService_paypalEcSetRequestToken + payPalEcOrderSetupService_paypalCustomerEmail + / + ], + pp_auth => + [. qw/ + payPalAuthorizationService_run + payPalAuthorizationService_paypalCustomerEmail + payPalAuthorizationService_paypalEcOrderSetupRequestID + payPalAuthorizationService_paypalEcOrderSetupRequestToken + / + ], + pp_capture => + [. qw/ + payPalDoCaptureService_run + payPalDoCaptureService_completeType + payPalDoCaptureService_paypalAuthorizationId + / + ], + pp_authrev => + [ qw/ + payPalAuthReversalService_run + payPalAuthReversalService_paypalAuthorizationId + / + ], + pp_refund => + [. qw/ + payPalRefundService_run + payPalRefundService_paypalCaptureId + payPalRefundService_paypalDoCaptureRequestID + payPalRefundService_paypalDoCaptureRequestToken + / + ], + ec_debit => + [. qw/ + billTo_city + billTo_country + billTo_email + billTo_firstName + billTo_lastName + billTo_phoneNumber + billTo_postalCode + billTo_state + billTo_street1 + check_accountNumber + check_accountType + check_bankTransitNumber + ecDebitService_run + / + ], + ec_credit => + [ qw/ + ecCreditService_run + / + ], + ); + + my %exempt_map = ( + billing_intl => + [. qw/ + billTo_street1 + billTo_city + billTo_country + billTo_state + billTo_postalCode + / + ], + shipping_intl => + [. qw/ + shipTo_street1 + shipTo_city + shipTo_country + shipTo_state + shipTo_postalCode + / + ], + ); + + ## These fields are not necessarily optional on our end, + ## they are just optional on CyberSource's end. + my %optional_map = ( + all => + [ qw/ + timeout + / + ], + ics_auth => + [. qw/ + billTo_phoneNumber + billTo_street2 + billTo_company + businessRules_declineAVSFlags + shipTo_street2 + card_cvNumber + businessRules_ignoreAVSResult + businessRules_ignoreCVResult + invoiceHeader_merchantDescriptor + invoiceHeader_merchantDescriptorContact + shipTo_shippingMethod + / + ], + ics_auth_reversal => [], + ics_bill => + [ qw/ + invoiceHeader_merchantDescriptor + invoiceHeader_merchantDescriptorContact + / + ], + ics_credit => + [. qw/ + invoiceHeader_merchantDescriptor + invoiceHeader_merchantDescriptorContact + ccCreditService_captureRequestID + ccCreditService_captureRequestToken + / + ], + ics_void => [], + pp_ec_set => + [. qw/ + payPalEcSetService_paypalEcSetRequestID + payPalEcSetService_paypalEcSetRequestToken + payPalEcSetService_invoiceNumber + payPalEcSetService_paypalAddressOverride + payPalEcSetService_paypalCustomerEmail + payPalEcSetService_paypalDesc + payPalEcSetService_paypalHdrbackcolor + payPalEcSetService_paypalHdrbordercolor + payPalEcSetService_paypalHdrimg + payPalEcSetService_paypalLc + payPalEcSetService_paypalMaxamt + payPalEcSetService_paypalNoshipping + payPalEcSetService_paypalPayflowcolor + payPalEcSetService_paypalReqconfirmshipping + payPalEcSetService_paypalToken + payPalEcSetService_promoCode0 + payPalEcSetService_requestBillingAddress + shipTo_street1 + shipTo_street2 + shipTo_city + shipTo_country + shipTo_state + shipTo_phone + shipTo_postalCode + shipTo_firstName + shipTo_lastName + / + ], + pp_ec_get => [], + pp_ec_dopmt => + [. qw/ + payPalEcDoPaymentService_invoiceNumber + payPalEcDoPaymentService_paypalAddressOverride + payPalEcDoPaymentService_paypalDesc + payPalEcDoPaymentService_promoCode0 + shipTo_street1 + shipTo_street2 + shipTo_city + shipTo_country + shipTo_state + shipTo_phone + shipTo_postalCode + shipTo_firstName + shipTo_lastName + / + ], + pp_ec_ord_setup => + [. qw/ + payPalEcOrderSetupService_invoiceNumber + payPalEcOrderSetupService_paypalDesc + payPalEcOrderSetupService_promoCode0 + / + ], + pp_auth => + [ qw/ + payPalAuthorizationService_paypalOrderId + / + ], + pp_capture => + [. qw/ + payPalDoCaptureService_invoiceNumber + payPalDoCaptureService_paypalAuthorizationRequestID + payPalDoCaptureService_paypalAuthorizationRequestToken + payPalDoCaptureService_paypalEcDoPaymentRequestID + payPalDoCaptureService_paypalEcDoPaymentRequestToken + / + ], + pp_authrev => + [. qw/ + payPalAuthReversalService_paypalEcDoPaymentRequestID + payPalAuthReversalService_paypalEcOrderSetupRequestID + payPalAuthReversalService_paypalAuthorizationRequestID + payPalAuthReversalService_paypalEcDoPaymentRequestToken + payPalAuthReversalService_paypalEcOrderSetupRequestToken + payPalAuthReversalService_paypalAuthorizationRequestToken + / + ], + pp_refund => + [ qw/ + payPalRefundService_paypalNote + / + ], + ec_debit => + [. qw/ + billTo_company + billTo_companyTaxID + billTo_dateOfBirth + billTo_driversLicenseNumber + billTo_driversLicenseState + billTo_ipAddress + billTo_street2 + businessRules_declineAVSFlags + check_accountEncoderID + check_checkNumber + check_secCode + ecDebitService_commerceIndicator + ecDebitService_partialPaymentID + ecDebitService_paymentMode + ecDebitService_referenceNumber + ecDebitService_settlementMethod + ecDebitService_verificationLevel + invoiceHeader_merchantDescriptor + recurringSubscriptionInfo_subscriptionID + / + ], + ec_credit => + [. qw/ + billTo_city + billTo_country + billTo_dateOfBirth + billTo_email + billTo_firstName + billTo_ipAddress + billTo_lastName + billTo_phoneNumber + billTo_postalCode + billTo_state + billTo_street1 + billTo_street2 + check_accountEncoderID + check_accountNumber + check_accountType + check_bankTransitNumber + check_checkNumber + ecCreditService_commerceIndicator + ecCreditService_debitRequestID + ecCreditService_debitRequestToken + ecCreditService_partialPaymentID + ecCreditService_referenceNumber + ecCreditService_settlementMethod + invoiceHeader_merchantDescriptor + orderRequestToken + recurringSubscriptionInfo_subscriptionID + / + ], + ); + + my %default_map = qw/ + timeout 20 + businessRules_ignoreAVSResult true + businessRules_ignoreCVResult true + purchaseTotals_currency usd + bml_itemCategory 3700 + bml_productDeliveryTypeIndicator shipping_and_handling + ccAuthService_run true + ccAuthReversalService_run true + ccCaptureService_run true + ccCreditService_run true + voidService_run true + payPalEcSetService_run true + payPalEcGetDetailsService_run true + payPalEcDoPaymentService_run true + payPalEcOrderSetupService_run true + payPalAuthorizationService_run true + payPalDoCaptureService_run true + payPalAuthReversalService_run true + payPalRefundService_run true + payPalDoCaptureService_completeType Complete + ecDebitService_run true + ecCreditService_run true + check_accountType C + /; + + my %actual_map = qw/ + billTo_street1 b_address1 + billTo_street2 b_address2 + billTo_city b_city + billTo_country b_country + billTo_state b_state + billTo_postalCode b_zip + card_expirationMonth mv_credit_card_exp_month + card_expirationYear mv_credit_card_exp_year + card_accountNumber mv_credit_card_number + card_cvNumber mv_credit_card_cvv2 + billTo_email email + billTo_firstName b_fname + billTo_lastName b_lname + billTo_phoneNumber b_phone + shipTo_street1 address1 + shipTo_street2 address2 + shipTo_city city + shipTo_country country + shipTo_state state + shipTo_postalCode zip + shipTo_firstName fname + shipTo_lastName lname + shipTo_phoneNumber phone_day + shipTo_shippingMethod mv_shipmode + + payPalEcDoPaymentService_paypalCustomerEmail email + payPalEcOrderSetupService_paypalCustomerEmail email + payPalAuthorizationService_paypalCustomerEmail email + payPalEcSetService_paypalCustomerEmail email + + bml_customerRegistrationDate bml_customer_registration_date + bml_customerTypeFlag bml_customer_type_flag + bml_itemCategory bml_item_category + bml_productDeliveryTypeIndicator bml_product_delivery_type_indicator + bml_tcVersion bml_tc_version + billTo_ssn customer_ssn + billTo_dateOfBirth date_of_birth + + check_accountNumber check_account + check_bankTransitNumber check_routing + check_checkNumber check_number + /; + + my %opt_map = qw/ + merchantID merchant_id + invoiceHeader_merchantDescriptor merchant_descriptor + invoiceHeader_merchantDescriptorContact merchant_descriptor_contact + merchantReferenceCode order_number + + ccAuthReversalService_authRequestID origid + ccCaptureService_authRequestID origid + ccAuthReversalService_authRequestToken request_token + ccCaptureService_authRequestToken request_token + ccCreditService_captureRequestID origid + ccCreditService_captureRequestToken request_token + voidService_voidRequestID origid + voidService_voidRequestToken request_token + ecCreditService_debitRequestID origid + orderRequestToken request_token + + payPalEcGetDetailsService_paypalEcSetRequestID origid + payPalEcGetDetailsService_paypalEcSetRequestToken request_token + payPalEcDoPaymentService_paypalEcSetRequestID origid + payPalEcDoPaymentService_paypalEcSetRequestToken request_token + payPalEcOrderSetupService_paypalEcSetRequestID origid + payPalEcOrderSetupService_paypalEcSetRequestToken request_token + payPalAuthorizationService_paypalEcOrderSetupRequestID origid + payPalAuthorizationService_paypalEcOrderSetupRequestToken request_token + payPalRefundService_paypalDoCaptureRequestID origid + payPalRefundService_paypalDoCaptureRequestToken request_token + + payPalEcSetService_invoiceNumber order_number + payPalEcDoPaymentService_invoiceNumber order_number + payPalEcOrderSetupService_invoiceNumber order_number + payPalDoCaptureService_invoiceNumber order_number + + payPalDoCaptureService_paypalAuthorizationId transaction_id + payPalAuthReversalService_paypalAuthorizationId transaction_id + payPalRefundService_paypalCaptureId transaction_id + + payPalEcSetService_paypalReturn returnurl + payPalEcSetService_paypalCancelReturn cancelurl + payPalEcSetService_paypalMaxamt maxamount + + payPalEcSetService_paypalDesc order_desc + payPalEcDoPaymentService_paypalDesc order_desc + payPalEcOrderSetupService_paypalDesc order_desc + + payPalEcSetService_paypalReqconfirmshipping confirmshipping + payPalEcSetService_paypalNoshipping noshipping + + payPalEcSetService_paypalAddressOverride addressoverride + payPalEcDoPaymentService_paypalAddressOverride addressoverride + + payPalEcSetService_paypalLc locale + payPalEcSetService_paypalHdrbackcolor headerbackcolor + payPalEcSetService_paypalHdrbordercolor headerbordercolor + payPalEcSetService_paypalHdrimg headerimg + payPalEcSetService_paypalPayflowcolor payflowcolor + + payPalEcGetDetailsService_paypalToken pp_token + payPalEcDoPaymentService_paypalToken pp_token + payPalEcOrderSetupService_paypalToken pp_token + payPalEcSetService_paypalToken pp_token + + payPalEcDoPaymentService_paypalPayerId pp_payer_id + payPalEcOrderSetupService_paypalPayerId pp_payer_id + /; + + # Shipping Types Map - ours to Cybersource's + my %ship_method = $opt->{ship_map} =~ /\S/ + ? $Vend::Interpolate::ready_safe->reval(qq{ qw( $opt->{ship_map} ) }) + : () + ; + + ::logError("Error in ship_map route param: $@") + if $@; + +::logDebug('%%ship_method: %s', ::uneval(\%ship_method)); + + ## Special Cases + $required_map{ics_bill} = [ 'ccCaptureService_run' ] + if $transtype eq 'auth_bill'; + + $required_map{pp_capture} = + [ qw/ + payPalDoCaptureService_run + payPalDoCaptureService_completeType + / + ] + if $transtype eq 'pp_sale'; + + my %actual = $opt->{actual} ? %{ $opt->{actual} } : map_actual(); + $actual{mv_credit_card_number} ||= $opt->{mv_credit_card_number}; + +::logDebug('%%actual: %s', $debug_scrub->(\%actual)); + + my @required_keys = ( + @{ $required_map{all} }, + ); + + push (@required_keys, @{ $required_map{$_} }) + for @apps; + + my @optional_keys = ( + @{ $optional_map{all} }, + ); + + push (@optional_keys, @{ $optional_map{$_} }) + for @apps; + +#::logDebug('@required_keys: %s', ::uneval(\@required_keys)); +#::logDebug('@optional_keys: %s', ::uneval(\@optional_keys)); +#::logDebug('$opt: %s', $debug_scrub->($opt)); + + # BML + if ($acct_type eq 'bml') { + + delete @actual{qw/mv_credit_card_exp_month mv_credit_card_exp_year/}; + + push (@required_keys, qw/ + bml_customerTypeFlag + bml_itemCategory + bml_productDeliveryTypeIndicator + card_cardType + /); + + push (@optional_keys, qw/ + billTo_phoneNumber + bml_billToPhoneType + bml_customerBillingAddressChange + bml_customerEmailChange + bml_customerHasCheckingAccount + bml_customerHasSavingsAccount + bml_customerPasswordChange + bml_customerPhoneChange + bml_employerCity + bml_employerCompanyName + bml_employerCountry + bml_employerPhoneNumber + bml_employerPhoneType + bml_employerPostalCode + bml_employerState + bml_employerStreet1 + bml_employerStreet2 + bml_grossHouseholdIncome + bml_householdIncomeCurrency + bml_merchantPromotionCode + bml_preapprovalNumber + bml_residenceStatus + bml_shipToPhoneType + bml_yearsAtCurrentResidence + bml_yearsWithCurrentEmployer + shipTo_email + shipTo_firstName + shipTo_lastName + shipTo_phoneNumber + /); + + $default_map{bml_customerRegistrationDate} = + POSIX::strftime('%Y%m%d',localtime(time)); + $default_map{bml_customerTypeFlag} = 'N'; + $default_map{card_cardType} = '028'; + $default_map{card_expirationMonth} = '12'; + $default_map{card_expirationYear} = '2021'; + $default_map{card_accountNumber} = '5049900000000000'; + + if ( $opt->{origid} || $actual{mv_credit_card_number} ) { + push (@optional_keys, qw/ + bml_customerRegistrationDate + bml_tcVersion + /); + $default_map{bml_customerTypeFlag} = 'E'; + } + + else { + push (@required_keys, qw/ + billTo_dateOfBirth + billTo_ssn + bml_customerRegistrationDate + bml_tcVersion + /); + } + } + + if ($acct_type eq 'pp') { + delete @{ $::Session }{qw/paypal_token paypal_request_id paypal_request_token/} + if $transtype eq 'pp_set' + && ! $opt->{return_ec_set}; + + $::Session->{paypal_override} = $opt->{addressoverride} + if grep { /^pp_ec_set$/ } @apps; + + $opt->{pp_token} ||= $::Session->{paypal_token}; + $opt->{origid} ||= $::Session->{paypal_request_id}; + $opt->{request_token} ||= $::Session->{paypal_request_token}; + $opt->{pp_payer_id} ||= $::Session->{paypal_payer_id}; + } + +#::logDebug('After BML special block'); +#::logDebug('@required_keys: %s', ::uneval(\@required_keys)); +#::logDebug('@optional_keys: %s', ::uneval(\@optional_keys)); + + ## Build Request + my %request_keys; + my @request_keys = (@required_keys, @optional_keys); + @request_keys{ @request_keys } = (); + + ## Only allow shipTo_* on 'pp' requests when + ## address_ok + if ($acct_type eq 'pp' && ! $opt->{address_ok}) { + delete $request_keys{$_} + for grep { /^shipTo_/ } keys %request_keys; + } + + my %request; + for (keys %request_keys) { + + next if $transtype eq 'auth_bill' && /^invoiceHeader/; + + $request{$_} = $actual{$_} + if defined $actual{$_}; + + $request{$_} = $actual{ $actual_map{$_} } + if defined $actual{ $actual_map{$_} }; + + $request{$_} = $opt->{$_} + if defined $opt->{$_}; + + $request{$_} = $opt->{ $opt_map{$_} } + if defined $opt->{ $opt_map{$_} }; + } + + # Uses the {purchaseTotals_currency} -> MV_PAYMENT_CURRENCY options if set + $request{purchaseTotals_currency} = charge_param('currency') + || ($Vend::Cfg->{Locale} && $Vend::Cfg->{Locale}{currency_code}) + || 'usd'; + + # Fix the shipmode + $request{shipTo_shippingMethod} = + $ship_method{ $request{shipTo_shippingMethod} } + || 'lowcost' + unless $acct_type eq 'pp'; + + ## Add defaults + for (@request_keys) { + next if length ($request{$_}); + next unless defined $default_map{$_}; + $request{$_} = $default_map{$_}; + } + + ## build exempt keys hash + my %exempt_keys = (); + for (keys %exempt_map) { + ## these keys apply only if we are shipping outside the + ## US or CA + next if $_ eq 'billing_intl' + && ( + lc $request{billTo_country} eq 'us' + || + lc $request{billTo_country} eq 'ca' + ) + ; + + next if $_ eq 'shipping_intl' + && ( + lc $request{shipTo_country} eq 'us' + || + lc $request{shipTo_country} eq 'ca' + ) + ; + + $exempt_keys{$_} = 1 + for @{ $exempt_map{$_} }; + } + + ## make sure that we have ALL required fields filled + ## exempt fields can be present, but are not required + + for (@required_keys) { + unless ( defined $request{$_} || $exempt_keys{$_} ) { + return ( + MStatus => 'failure-hard', + MErrMsg => errmsg("Missing value for >$_< field"), + ); + } + } + + my $idx = 0; + if ( scalar grep { $items_apps{$_} } @apps ) { + my @items; + if (my $sub_name = $opt->{items_sub}) { + my $sub = $Vend::Cfg->{Sub}{$sub_name} + || $Global::GlobalSub->{$sub_name}; + if (ref ($sub) eq 'CODE') { + @items = $sub->( { %request }, $opt); + } + else { + ::logError('cybersource: non-existent items_sub routine %s', $sub_name); + } + } + @items = @$Vend::Items + unless @items; + +#::logDebug('building item lines based on @items'); + + for ( @items ) { + my $cost = Vend::Data::item_price($_); + $request{"item_${idx}_productCode"} = 'default'; + $request{"item_${idx}_productSKU"} = $_->{code}; + $request{"item_${idx}_unitPrice"} = + sprintf ('%0.2f', $cost); + $request{"item_${idx}_quantity"} = $_->{quantity}; + + ++$idx; + } + + $request{billTo_ipAddress} = + $opt->{ip_address} + || $::Session->{shost} + || $::Session->{ohost} + unless $request{bml_tcVersion} eq '12103' + || $acct_type eq 'pp'; + + $opt->{shipping} ||= $Tag->shipping({ noformat => 1 }); + $opt->{handling} ||= $Tag->handling({ noformat => 1 }); + } + + # Adding shipping offer per BML requirement + if ($opt->{shipping} > 0) { + $request{"item_${idx}_productCode"} = 'shipping_only'; + $request{"item_${idx}_unitPrice"} = + sprintf ('%0.2f', $opt->{shipping}); + $request{"item_${idx}_quantity"} = 1; + ++$idx; + } + + # Handling, if any + if ($opt->{handling} > 0) { + $request{"item_${idx}_productCode"} = 'handling_only'; + $request{"item_${idx}_unitPrice"} = + sprintf ('%0.2f', $opt->{handling}); + $request{"item_${idx}_quantity"} = 1; + ++$idx; + } + + ## declare total cost + $request{purchaseTotals_grandTotalAmount} = $opt->{total_cost}; + + ## Specific data filters for BML that pass CS validation + ## and other miscellaneous hacks that have nothing to do + ## with CyberSource generally. + if ($acct_type eq 'bml') { + + for ( grep { /phone.*number/i } keys %request ) { + $request{$_} =~ s/\D+//g; + $request{$_} =~ s/^[10]+//; + } + + for ( grep { /date/i } keys %request ) { + $request{$_} =~ s/-//g; + } + + if ( $request{shipTo_country} !~ /^(?:US)?$/ ) { + + for (keys %request) { + my ($stuff) = /^billTo_(.*)/ or next; + next unless exists $request_keys{"shipTo_$stuff"}; + $request{"shipTo_$stuff"} = $request{$_}; + } + + $request{shipTo_street2} = 'Foreign Delivery'; + } + } + + # Strip any trailing digits from email fields + $request{$_} =~ s/\d+$// + for (grep { /email/i && defined $request{$_} } keys %request); + + if ( length ($request{card_expirationYear}) == 2 ) { + $request{card_expirationYear} += + $request{card_expirationYear} < 70 + ? 2000 + : 1900 + ; + } + + $request{bml_productDeliveryTypeIndicator} = 'electronic_software' + if $request{bml_itemCategory} eq '4700'; + + my $cybs = Vend::Payment::CyberSource->new($opt); + my %resp; + + # Wrapping full call code to APIs in eval to + # implement interaction time limit on response. + eval { + + my $timeout = delete $request{timeout}; + $timeout = $opt->{timeout} if $opt->{timeout}; + + my $should_have_died; + my $sigalrm_die_msg = 'alarm-induced gateway timeout'; + + ## Scrounge up any country fields for PayPal requests + ## and convert them from UK -> GB. If you want to convert + ## back, you'll almost certainly want to write a check_sub + if ($acct_type eq 'pp') { + $request{$_} = 'GB' + for grep + { /country/i && lc ($request{$_}) eq 'uk' } + keys %request + ; + } + + local ($SIG{ALRM}) = sub { + $should_have_died = 1; + die $sigalrm_die_msg; + }; + +::logDebug("cybersource Sending Request...\n%s", $debug_scrub->(\%request)); + + my $start = time; + alarm $timeout; + + my $rv = $cybs->send(\%request, \%resp); + + alarm 0; + die $sigalrm_die_msg if $should_have_died; + + my $end = time; + +::logDebug("cybersource Response (%ds):\n%s", $end - $start, ::uneval(\%resp)); + + # Initiate a timeout payment if the gateway response + # itself was a timeout error + die 'API-induced gateway timeout' + if $resp{reasonCode} == 151 + || $resp{reasonCode} == 250; + + }; # End eval + + if ($@) { + ::logDebug("eval trapped die in gateway call: $@ "); + } + + my %reason_code_map = ( + 100 => 'Successful transaction', + 101 => 'The request is missing one or more required fields', + 102 => 'One or more fields in the request contains invalid data', + 150 => 'General system failure', + 151 => 'The request was received but there was a server timeout', + 152 => 'The request was received, but a service did not finish running in time', + 200 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the Address Verification Service (AVS) check', + 201 => 'The issuing bank has questions about the request', + 202 => 'Card is expired or date is a mismatch with the date the issuing bank has on file', + 203 => 'Card is declined', + 204 => 'Insufficient funds in the account', + 205 => 'Stolen or lost card', + 207 => 'Issuing bank unavailable', + 208 => 'Inactive card or card not authorized for card-not-present transactions', + 209 => 'American Express Card Identification Digits (CID) did not match', + 210 => 'The card has reached the credit limit', + 211 => 'Invalid card verification number', + 221 => "The customer matched an entry on the processor's negative file", + 223 => 'PayPal rejected the transaction', + 230 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification (CV) check', + 231 => 'Invalid account number', + 232 => 'The card type is not accepted by the payment processor', + 233 => 'General decline by the processor', + 234 => 'There is a problem with the merchant configuration', + 235 => 'The requested amount exceeds the originally authorized amount', + 236 => 'Processor failure', + 237 => 'The authorization has already been reversed', + 238 => 'The authorization has already been captured', + 239 => 'The requested transaction amount must match the previous transaction amount', + 240 => 'The card type sent is invalid or does not correlate with the credit card number', + 241 => 'The request ID is invalid', + 242 => 'Capture request has no corresponding, unsettled authorization record', + 243 => 'The transaction has already been settled or reversed', + 246 => 'The capture or credit is not voidable', + 247 => 'You requested a credit for a capture that was previously voided', + 250 => 'The request was received, but there was a timeout at the payment processor', + 387 => 'PayPal authorization failed', + ); + + # Stripping out irritating prefix of each service run from each different + # type of transaction. Will leave in the unadulterated versions too, in + # case anyone actually needs that distinction present. + + for (grep { /_run$/ && $request{$_} eq 'true'} keys %request) { + my ($base) = /(.*)Service_run/; + $base .= 'Reply_'; + for (keys %resp) { + my ($ckey) = /^$base(.*)/ + or next; + $resp{$ckey} ||= $resp{$_}; + } + } + + if ($acct_type eq 'pp') { + + if (grep { /^pp_ec_set$/ } @apps) { + $::Session->{paypal_request_id} = $resp{requestID}; + $::Session->{paypal_request_token} = $resp{requestToken}; + $::Session->{paypal_token} = $resp{paypalToken} + unless $opt->{return_ec_set}; + } + elsif (grep { /^pp_ec_get$/ } @apps) { + $::Session->{paypal_payer_id} = $resp{payerId}; + } + + $resp{pp_redirect} = sprintf ($resp{pp_redirect}, $resp{paypalToken} || $::Session->{paypal_token}); + } + + if ($transtype =~ /^auth(?:_bill)?$/) { + my ($avs_addr, $avs_zip) = $cybs->handle_avs($resp{ccAuthReply_avsCode}); + my $cv = $cybs->handle_cv($resp{ccAuthReply_cvCode}); + $resp{AVSADDR} = $avs_addr; + $resp{AVSZIP} = $avs_zip; + $resp{CVV2MATCH} = $cv; + } + + my $status; + if (my $sub_name = $opt->{check_sub}) { + my $sub = $Vend::Cfg->{Sub}{$sub_name} + || $Global::GlobalSub->{$sub_name}; + if (ref ($sub) eq 'CODE') { + $status = $sub->(\%resp, { %request }, $opt); + } + else { + ::logError('cybersource: non-existent check_sub routine %s', $sub_name); + } + } + + if (!$status) { + $status = $resp{decision} eq 'ACCEPT' + ? 'success' + : 'failed' + ; + } + + $resp{MStatus} = $status; + $resp{'order-id'} = $transtype eq 'pp_set' + ? $resp{pp_redirect} + : $resp{requestID} + ; + $resp{PNREF} = $resp{requestID}; + $resp{transtype} = $inv_trans_map{$transtype} || $transtype; + $resp{acct_type} = $acct_type; + $resp{rc_msg} = $reason_code_map{ $resp{reasonCode} } || 'Unknown'; + +::logDebug("decision: $status, reason code: $resp{reasonCode}"); + + if ($status ne 'success') { + if ($acct_type eq 'bml') { + + $::Values->{suppress_bml} = 1; + + $resp{MErrMsg} = errmsg($opt->{merrmsg_bml} || <<EOP); +We're sorry, Bill Me Later<sup>®</sup> was unable to authorize your +transaction. Please accept our sincerest apologies. You are welcome +to complete your order using any of our other payment methods. +EOP + } + else { + + my $str = $opt->{merrmsg} || <<'EOP'; +Error code: %s (%s). Please call in your order or try again. +EOP + my $msg = errmsg( + $str, + $resp{faultCode} + || $resp{reasonCode} + || 'no details available' + , + $resp{faultDetail} + || $resp{rc_msg} + || 'unknown error' + , + ); + $resp{MErrMsg} = $resp{'pop.error-message'} = $msg; + } + } + +::logDebug("Returning %%resp: %s",::uneval(\%resp)); + return %resp; +} + +package Vend::Payment::CyberSource; + +BEGIN { + my @mod_failures; + eval { + require SOAP::Lite; + }; + push (@mod_failures, "SOAP::Lite ($@)") + if $@; + + eval { + require XML::Pastor::Schema::Parser; + }; + push (@mod_failures, "XML::Pastor::Schema::Parser ($@)") + if $@; + + eval { + require LWP::Simple; + }; + push (@mod_failures, "LWP::Simple ($@)") + if $@; + + if (@mod_failures) { + my $list = join ("\n\n* ", @mod_failures); + die <<EOP; +Following required modules failed to load: + +* $list +EOP + } +} + +# new() expects to be called with a standard payment-route +# $opt hash. If the route has been sufficiently defined (and one +# doesn't rely on the unwise practice of charge_param()), the +# constructor will be able to build the object data with no special +# effort on the part of the caller. +sub new { + my $class = shift; + my $self = bless ({}, $class); + $self->init(shift); + return $self; +} + +# Set up object attributes and helper objects from +# XML::Pastor::Schema::Parser +sub init { + my $self = shift; + my $opt = shift; + %$self = ( + MERCHANT_ID => $opt->{merchant_id}, + TRANSACTION_KEY => $opt->{transaction_key}, + CYBS_HOST => $opt->{live} eq 'true' ? 'ics2ws.ic3.com' : 'ics2wstest.ic3.com', + PP_302_HOST => $opt->{live} eq 'true' ? 'www.paypal.com' : 'www.sandbox.paypal.com', + CYBS_VERSION => $opt->{api_version}, + WSSE_NSURI => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', + WSSE_PREFIX => 'wsse', + PASSWORD_TEXT => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText', + ); + + my ($xsd, $file); + if ($opt->{xsd_cache_dir}) { + (my $dir = $opt->{xsd_cache_dir}) =~ s{/+$}{}; + $file = "$dir/" + . $self->attr('CYBS_HOST') + . "/cybs_v" + . $self->attr('CYBS_VERSION') + . ".xsd"; + + $xsd = Vend::File::readfile($file); +::logDebug("cybersource: found cached xsd for version " . $self->attr('CYBS_VERSION') . " at $file") if $xsd; + } + + unless ($xsd) { + $xsd = + LWP::Simple::get( + sprintf ( + 'https://%s/commerce/1.x/transactionProcessor/CyberSourceTransaction_%s.xsd', + $self->attr('CYBS_HOST'), + $self->attr('CYBS_VERSION'), + ) + ) + ; + + # XML::Pastor::Schema::Parser doesn't know what to do with xsd:any. + # Since it doesn't matter to us, just coerce it to xsd:element. + $xsd =~ s!(?<=<xsd:)any !element !g; + + Vend::File::writefile( + ">$file", + \$xsd, + { auto_create_dir => 1 } + ) + if $file; + } + + $self->{_model} = + XML::Pastor::Schema::Parser + -> new + -> parse( schema => $xsd ) + ; + + # Top-level RequestMessage object is our primary interest. + $self->{_rmsg} = $self->model->xml_item('RequestMessage'); + + return; +} + +# Results of address verification. This field will contain one of the following values: + +# * A: Street number matches, but 5-digit ZIP code and 9-digit ZIP code do not match. +# * B: Street address match for non-U.S. AVS transaction. Postal code not verified. +# * C: Street address and postal code not verified for non-U.S. AVS transaction. +# * D: Street address and postal code match for non-U.S. AVS transaction. +# * E: AVS data is invalid. +# * G: Non-U.S. card issuing bank does not support AVS. +# * I: Address information not verified for non-U.S. AVS transaction. +# * M: Street address and postal code match for non-U.S. AVS transaction. +# * N: Street number, 5-digit ZIP code, and 9-digit ZIP code do not match. +# * P: Postal code match for non-U.S. AVS transaction. Street address not verified. +# * R: System unavailable. +# * S: Issuing bank does not support AVS. +# * U: Address information unavailable. Returned if non-U.S. AVS is not available or if the AVS in a U.S. bank is not functioning properly. +# * W: Street number does not match, but 5-digit ZIP code and 9-digit ZIP code match. +# * X: Exact match. Street number, 5-digit ZIP code, and 9-digit ZIP code match. +# * Y: Both street number and 5-digit ZIP code match. +# * Z: 5-digit ZIP code matches. +# * 1: CyberSource does not support AVS for this processor or card type. +# * 2: The processor returned an unrecognized value for the AVS response. + +sub handle_avs { + my $self = shift; + my $c = shift; + ## returns (address, zip) + ## D,M,H,V,X,Y + return ('Y', 'Y') if $c =~ /^[DMHVXY]$/; + + ## A,O + return ('Y', 'N') if ($c eq 'A' || $c eq 'O'); + + ## L,W,Z + return ('N', 'Y') if $c =~ /^[LWZ]$/; + + ## P,F + return ('', 'Y') if ($c eq 'P' || $c eq 'F'); + + ## B,T + return ('Y', '') if ($c eq 'B' || $c eq 'T'); + + ## N,K + return ('N', 'N') if ($c eq 'N' || $c eq 'K'); + + ## C,E,G,I,R,S,U,1,2,3,4 + return ('', ''); +} + +# Result of processing the card verification number. This field will contain one of the following values: + +# * M: Card verification number matched. +# * N: Card verification number not matched. +# * P: Card verification number not processed. +# * S: Card verification number is on the card but was not included in the request. +# * U: Card verification is not supported by the issuing bank. +# * X: Card verification is not supported by the card association. +# * <space>: Deprecated. Ignore this value. +# * 1: CyberSource does not support card verification for this processor or card type. +# * 2: The processor returned an unrecognized value for the card verification response. +# * 3: The processor did not return a card verification result code. + +sub handle_cv { + my $self = shift; + my $c = shift; + return 'Y' if $c eq 'M'; + return 'N' if $c eq 'N'; + return ''; +} + +# Main function to send request to CyberSource and process response. The +# request and response constructions should virtually or exactly match the +# request/response value descriptions of CyberSource's "Simple" API. Note, +# however, this does not depend on the Simple API, but is actually using the +# SOAP toolkit. Thus, please do NOT install any of the Simple API as it is +# /not/ Simple, and not necessary. + +sub send { + my $self = shift; + my ($req, $resp) = @_; + + my $service = + SOAP::Lite + -> uri( + sprintf ( + 'urn:schemas-cybersource-com:transaction-data-%s', + $self->attr('CYBS_VERSION') + ) + ) + -> proxy( + sprintf ( + 'https://%s/commerce/1.x/transactionProcessor', + $self->attr('CYBS_HOST') + ) + ) + -> autotype(0) + ; + +#::logDebug('$service: %s', ::uneval($service)); + + my $header = $self->form_header; + + my @request; + my (%items, %service); + $self->other( + { + clientLibrary => 'Perl', + clientLibraryVersion => $], + clientEnvironment => $^O, + } + ); + +# Services and Items are two nodes that deviate from CyberSource's standard of +# indicating complex data relations with an underscore in the key name. Thus, +# they have to be handled specially, outside the underscore/complex-data +# relationship. For the remainder, we assume the underscore/complex-data +# relationship and store them away using other() for standard recursive +# processing. + + while (my ($k,$v) = each %$req) { + if ($k =~ /^item_(\d+)_(.*)/) { + $items{$1}{$2} = $v; + } + elsif ($k =~ /^([^_]+Service)_(.*)/) { + $service{$1}{$2} = $v; + } + else { + $self->other($k => $v); + } + } + + my @service_keys = keys %service; + for (@service_keys) { + next if delete ($service{$_}{run}) eq 'true'; + delete $service{$_}; + } + + for my $term (@{ $self->rmsg->elements }) { + if ($service{$term}) { + my @service; + my $hsh = $service{$term}; + my $type = $self->rmsg->elementInfo->{$term}->type; + if (! defined ($type) ) { + ::logError("cybersource: No element info returned for service type $term. Check API version. Skipping"); + next; + } + my $list = $self->model + ->xml_item(split (/[|]/, $type, 2)) + ->elements; + for my $k (@$list) { + $self->add_field(\@service, $k, $hsh->{$k}) + if exists $hsh->{$k}; + } + $self->add_service(\@request, $term, \@service, 'true'); + } + elsif ($term eq 'item') { + my $type = $self->rmsg->elementInfo->{$term}->type; + if (! defined ($type) ) { + ::logError("cybersource: No element info returned for term $term. Check API version. Skipping"); + next; + } + my $list = $self->model->xml_item(split (/[|]/, $type, 2))->elements; + for my $idx (sort {$a <=> $b} keys %items) { + my @item; + my $hsh = $items{$idx}; + for my $k (@$list) { + $self->add_field(\@item, $k, $hsh->{$k}) + if exists $hsh->{$k}; + } + $self->add_item(\@request, $idx, \@item); + } + } + else { + $self->resolve_term(\@request, [ $term ]); + } + } + +#::logDebug('@request: %s', ::uneval(\@request)); +#::logDebug('$header: %s', ::uneval($header)); + + my $reply = + $service->call( + requestMessage => @request, + $header + ) + ; + +#::logDebug('raw $reply: %s', ::uneval($reply)); + + my $reply_hash; + if ($reply->fault) { + $resp = { + faultCode => $reply->faultcode, + faultString => $reply->faultstring, + faultDetail => $reply->faultdetail, + }; + } + else { + $reply_hash = $reply->valueof('//Body/replyMessage'); + $self->resolve_reply($resp, $reply_hash ); + } + +#::logDebug('valueof on replyMessage: %s', ::uneval ($reply_hash)); + return 1; +} + +# Handle standard underscore/complex-data relationships to convert +# them into appropriate SOAP calls. Recursion allows function to +# handle arbitrarily deep complex data. Essentially converts standard +# Simple API calls to SOAP toolkit calls. +sub resolve_term { + my $self = shift; + my $request = shift; + my $list = shift; + my $node = shift || $self->rmsg; + my $pre = shift || ''; + + for my $name (@$list) { + my $type = $node->elementInfo->{$name}->type; + my $subnode = defined $type + ? $self->model->xml_item(split (/[|]/, $type, 2)) + : undef + ; + my $key = $pre . $name; + if (! defined ($subnode) || $subnode->contentType eq 'simple') { + # We are at a scalar node. Check for its existence in caller's + # request and, if present, add it to $request + $self->add_field($request, $name, $self->other($key)) + if defined $self->other($key); + } + elsif ($subnode->contentType eq 'complex') { + # Start a recursion for a complex element. + my @complex; + $self->resolve_term( + \@complex, + $subnode->elements, + $subnode, + $key . '_' + ); + # Only add if, somewhere within the complex + # type, at least one scalar was added. + $self->add_complex_type( + $request, + $name, + \@complex + ) + if @complex; + } + else { + die sprintf ( + "cybersource: resolve_term() failed on $key. Obj dump: %s", + ::uneval($subnode) + ); + } + } + return; +} + +# Store and retrieve Simple-API key/value pairs that will +# later be dissected by resolve_term() +sub other { + my $self = shift; + my $k = shift; + + if (ref $k) { + $self->{_other} = $k; + return; + } + + $self->{_other} = {} + unless ref $self->{_other}; + + if (@_) { + $self->{_other}{$k} = shift; + } + + return $self->{_other}{$k}; +} + +sub model { + return $_[0]->{_model}; +} + +sub rmsg { + return $_[0]->{_rmsg}; +} + +sub attr { + return $_[0]->{$_[1]}; +} + +sub resolve_reply { + my $self = shift; + my $resp = shift; + my $reply = shift; + my $pre = shift || ''; + + while (my ($k, $v) = each %$reply) { + my $key = $pre . $k; + if (ref $v) { + $self->resolve_reply( + $resp, + $v, + $key . '_' + ); + } + else { + $resp->{$key} = $v; + } + } + $resp->{pp_redirect} = + 'https://' + . $self->attr('PP_302_HOST') + . '/cgi-bin/webscr?cmd=_express-checkout&token=%s' + ; + return; +} + +# Following subs extracted out of CyberSource's sample processing script. +#------------------------------------------------------------------------------ +# adds a tag to the referenced parent element with $name as the tagname and +# $val its content. $val may be either text or a reference to a non-leaf node. +#------------------------------------------------------------------------------ +sub add_field { + my $self = shift; + my ($parentRef, $name, $val) = @_; + push(@$parentRef, SOAP::Data->name($name => $val)); +} + +#------------------------------------------------------------------------------ +# adds a complex type (i.e. non-leaf node) to the referenced parent element +# with $name as the tagname and $complexTypeRef the reference to the complex +# type. +#------------------------------------------------------------------------------ +sub add_complex_type { + my $self = shift; + my ($parentRef, $name, $complexTypeRef) = @_; + $self->add_field( + $parentRef, + $name, + \SOAP::Data->value(@$complexTypeRef) + ); +} + +#------------------------------------------------------------------------------ +# adds a line item to the referenced parent element with $index as the id +# attribute and $itemRef as the reference to the item node. +#------------------------------------------------------------------------------ +sub add_item { + my $self = shift; + my ($parentRef, $index, $itemRef) = @_; + # note the leading space in ' id'. Without it, the id attribute would not + # be added to the item tag. SOAP::Lite seems to be using the "id" attribute + # for its own tracking/identification purposes. + push( + @$parentRef, + SOAP::Data + -> name( item => \SOAP::Data->value(@$itemRef) ) + -> attr({' id' => $index}) + ); +} + +#------------------------------------------------------------------------------ +# adds a service node to the referenced parent element with $name as the +# tagname and $serviceRef as the reference to the service node. $run must be +# either 'true' or 'false'. +#------------------------------------------------------------------------------ +sub add_service { + my $self = shift; + my ($parentRef, $name, $serviceRef, $run) = @_; + push( + @$parentRef, + SOAP::Data + -> name( $name => \SOAP::Data->value(@$serviceRef) ) + -> attr({run => $run}) + ); +} + +#------------------------------------------------------------------------------ +# forms the SOAP header with the UsernameToken in it. +#------------------------------------------------------------------------------ +sub form_header { + my $self = shift; + my %tokenHash; + $tokenHash{Username} = + SOAP::Data + -> type('' => $self->attr('MERCHANT_ID')) + -> prefix($self->attr('WSSE_PREFIX')) + ; + + $tokenHash{Password} = + SOAP::Data + -> type('' => $self->attr('TRANSACTION_KEY')) + -> attr({'Type' => $self->attr('PASSWORD_TEXT')}) + -> prefix($self->attr('WSSE_PREFIX')) + ; + + my $usernameToken = + SOAP::Data + -> name( 'UsernameToken' => \%tokenHash ) + -> prefix($self->attr('WSSE_PREFIX')) + ; + + my $header = + SOAP::Header + -> name( + Security => { + UsernameToken => + SOAP::Data->type( + '' => $usernameToken + ) + } + ) + -> uri($self->attr('WSSE_NSURI')) + -> prefix($self->attr('WSSE_PREFIX')) + ; + + return $header; +} + +1; _______________________________________________ interchange-cvs mailing list interchange-cvs [at] icdevgroup http://www.icdevgroup.org/mailman/listinfo/interchange-cvs
|