in reply to using Business::PayPal
I did however, across the Christmas break a couple of years back, bust it to deliver for a New Year launch an online order form using this module.
The first distinction it is important to understand is between the PayPal services. Essentially they offer two options: WPS (Website Payments Standard) which hands the transaction off to PayPal for the collection of credit card data, before returning your user to your own site; and WPP (Website Payments Pro), which operates more like a traditional merchant account and is suitable for a PCI compliant, locked down server which accepts credit card data only across an encrypted connection. I wasted all sorts of time before I figured out that one.
Here is the other distinction: WPS will cost you transaction fees. WPP will cost you transaction fees, plus $30 / month. Add another $30 / month if you want to handle recurring transactions through WPP. You can do recurring transactions with WPS, but your clients will be required to have or create a paypal account to do business with you that way. I regularly heard complaints about that from my clients, even before they pulled the rug out from under wikileaks.
At any rate, this is the code I wrote, back when I did the Winter jam:
There was other code in that project to interact with the user, to validate their data, to prepare parameters to hand off to templates and such. I wound up coding only the DoExpressCheckoutPayment option, because my client was not ready to incur the monthly overhead for WPP. I'm pretty sure those were all the PayPal interactions I used. I found using their name-value pairs API far easier to wrap my head around than the alternative, whose name escapes me now. I spent much time reading and re-reading their API documentation.sub _instantiate_pp_nvp { my $self = shift; if(defined($self->{'pp_nvp'}) && $self->{'pp_nvp'}->isa('Business::P +ayPal::NVP')){ return; } my $pp_branch; if($self->{'cfg'}->param("paypal.pp_use_sandbox")){ $pp_branch = 'test'; } else { $pp_branch = 'live'; } $self->{'pp_nvp'} = Business::PayPal::NVP->new( Version => '57.0', test => { user => $self->{'cfg'}->param("paypal.pp_sandbox_username" +), pwd => $self->{'cfg'}->param("paypal.pp_sandbox_password" +), sig => $self->{'cfg'}->param("paypal.pp_sandbox_signature +") }, live => { user => $self->{'cfg'}->param("paypal.pp_username"), pwd => $self->{'cfg'}->param("paypal.pp_password"), sig => $self->{'cfg'}->param("paypal.pp_signature") }, branch => $pp_branch ); return; } sub _SetExpressCheckout { my $self = shift; my $fields = shift; my $gross_sales = $self->_compute_invoice($fields); $self->_instantiate_pp_nvp(); $fields->{'AMT'} = $gross_sales; $fields->{'DESC'} = $self->{'cfg'}->param("inventory.product_name"); $fields->{'CURRENCYCODE'} = 'USD'; $fields->{'PAYMENTACTION'} = 'Sale'; $fields->{'RETURNURL'} = $self->{'cfg'}->param("paypal.pp_return_url +"); $fields->{'CANCELURL'} = $self->{'cfg'}->param("paypal.pp_cancel_url +"); $fields->{'VERSION'} = $self->{'cfg'}->param("paypal.pp_api_version" +); $fields->{'METHOD'} = 'SetExpressCheckout'; $fields->{'REQCONFIRMSHIPPING'} = '0'; $fields->{'ADDROVERRIDE'} = '0'; $fields->{'NOSHIPPING'} = '0'; $fields->{'ALLOWNOTE'} = '0'; $fields->{'LOCALECODE'} = 'US'; $fields->{'HDRBORDERCOLOR'} = $self->{'cfg'}->param("paypal.pp_hdrbo +rdercolor"); $fields->{'HDRBACKCOLOR'} = $self->{'cfg'}->param("paypal.pp_hdrback +color"); $fields->{'PAYFLOWCOLOR'} = $self->{'cfg'}->param("paypal.pp_payflow +color"); # $fields->{'SOLUTIONTYPE'} = 'Mark'; # $fields->{'LANDINGPAGE'} = 'Login'; # $fields->{'CHANNELTYPE'} = 'Merchant'; my %response = $self->{'pp_nvp'}->SetExpressCheckout( %{$fields} ); # $self->log('DEBUG','->get_paypal_token_nvp() says API server said: + ',\%response); my %details = $self->{'pp_nvp'}->GetExpressCheckoutDetails( 'Token' +=> $response{'TOKEN'} ); $self->{'paypal_details'} = \%details; # $self->log('DEBUG','Express Checkout Details include: ',$self->{'p +aypal_details'}); return %details; } sub _GetExpressCheckoutDetails { my $self = shift; my $token = shift; $self->_instantiate_pp_nvp(); my %response; $response{'TOKEN'} = $token; my %details = $self->{'pp_nvp'}->GetExpressCheckoutDetails( 'Token' +=> $response{'TOKEN'} ); $self->{'paypal_details'} = \%details; print STDERR '->_GetExpressCheckoutDetails() says the details are: ' + . Dumper(\%details); return \%details; } sub _DoExpressCheckoutPayment { my $self = shift; # my $fields = shift; # return unless(defined($self->{'paypal_details'})); # $self->log('DEBUG','$ymd->DoExpressCheckoutPayment got these incom +ing arguments: ',$fields); # my $gross_sales = $self->_compute_invoice($fields); $self->_instantiate_pp_nvp(); my %args = ( token => $self->{'s'}->param('token'), TOKEN => $self->{'s'}->param('token'), PayerID => $self->{'s'}->param('payerid'), PAYERID => $self->{'s'}->param('payerid'), # AMT => '1.00', AMT => $self->{'s'}->{'AMT'}, # OrderTotal => '1.00', OrderTotal => $self->{'s'}->{'AMT'}, CURRENCYCODE => 'USD', PAYMENTACTION => 'Sale', METHOD => 'DoExpressCheckoutPayment' ); print STDERR 'DoExpressCheckoutPayment got these arguments: ' . Dump +er(\%args); my %response = $self->{'pp_nvp'}->DoExpressCheckoutPayment( %args ); print STDERR 'DoExpressCheckoutPayment responds: ' . Dumper(\%respon +se); if($response{'ACK'} eq 'Success' && defined($response{'TRANSACTIONID +'})){ my $sql = $self->{'cfg'}->param("sql.insert_pp_transaction"); my $sth = $self->{'dbh'}->prepare($sql); $sth->execute( $response{'TRANSACTIONID'}, $response{'TOKEN'}, $response{'ACK'}, $response{'PAYMENTSTATUS'}, $response{'AMT'}, $response{'FEEAMT'}, $response{'TAXAMT'}, $response{'CURRENCYCODE'}, $response{'PAYMENTTYPE'}, $response{'TRANSACTIONTYPE'}, $response{'REASONCODE'}, $response{'PENDINGREASON'}, $response{'ORDERTIME'}, $response{'TIMESTAMP'}, $response{'VERSION'}, $response{'BUILD'}, $response{'CORRELATIONID'} ); } return \%response; }
In the best tradition of feature creep, this stand alone form was suddenly required to sport content around it. At the risk of bringing down the wrath of the perl monks, I installed a (php-based) drupal cms to handle that. The next time my client asked to extend the online order form, and now with a cms in place to provide a framework, I ripped out my custom code and installed ubercart and its paypal payment module in its place. In two or four hours (don't really remember now), I had that set up to replicate the functionality of my custom code, providing whatever the additional requested feature may have been, plus all the other features built into ubercart.
Just a week or two ago I have installed ubercart and uc_recurring on my own consulting firm's (drupal-based) site to handle subscriptions for our hosting service and one time payments for other products. It took me a couple of days of reading and a conversation with one of the principal contributors to figure out that drupal's ubercart will handle recurring payments fairly easily with WPP, but that the version available now still requires a patch (available on a comment in the issues queue) to make it work with WPS.
Trust that is helpful.
-- Hugh
if( $insurance->rationing() ) { $people->die(); }