From c91676b1ad87daa4da59ed8b44d9d5a5e0dce31f Mon Sep 17 00:00:00 2001 From: Joeri de Bruin Date: Wed, 13 May 2009 20:07:09 +0000 Subject: [PATCH] add PayPal paydriver --- docs/changelog/7.x.x.txt | 1 + docs/gotcha.txt | 2 + docs/upgrades/upgrade_7.7.5-7.7.6.pl | 11 + etc/WebGUI.conf.original | 3 +- lib/WebGUI/Shop/PayDriver/PayPal/PayPalStd.pm | 445 ++++++++++++++++++ .../i18n/English/PayDriver_PayPalStd.pm | 176 +++++++ sbin/testEnvironment.pl | 1 + www/extras/PayPal/PayPal_mark_37x23.gif | Bin 0 -> 812 bytes www/extras/PayPal/btn_xpressCheckout.gif | Bin 0 -> 3091 bytes 9 files changed, 638 insertions(+), 1 deletion(-) create mode 100644 lib/WebGUI/Shop/PayDriver/PayPal/PayPalStd.pm create mode 100644 lib/WebGUI/i18n/English/PayDriver_PayPalStd.pm create mode 100644 www/extras/PayPal/PayPal_mark_37x23.gif create mode 100644 www/extras/PayPal/btn_xpressCheckout.gif diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 028963c61..a5fef3fb1 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -29,6 +29,7 @@ - rfe #9906: Inbox Filtering - rfe #9911: Inbox: Copy Sender - rfe #9907: Inbox: Notifications + - Added PayPal paydriver. (Thanks to Paul Wrightson) 7.7.5 - Adding StoryManager. diff --git a/docs/gotcha.txt b/docs/gotcha.txt index c5d1caf18..c734505eb 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -12,6 +12,8 @@ save you many hours of grief. -------------------------------------------------------------------- * WebGUI now requires Business::Tax::VAT::Validation. + * WebGUI now requires Crypt::SSLeay 0.57 or greater. + 7.7.5 -------------------------------------------------------------------- * Due to a long standing bug in the Profile system, if the type of a diff --git a/docs/upgrades/upgrade_7.7.5-7.7.6.pl b/docs/upgrades/upgrade_7.7.5-7.7.6.pl index e00eafb80..b96575eb7 100644 --- a/docs/upgrades/upgrade_7.7.5-7.7.6.pl +++ b/docs/upgrades/upgrade_7.7.5-7.7.6.pl @@ -39,6 +39,7 @@ addListingsCacheTimeoutToMatrix( $session ); addSurveyFeedbackTemplateColumn( $session ); installCopySender($session); installNotificationsSettings($session); +addPayDrivers($session); finish($session); @@ -162,6 +163,16 @@ sub installNotificationsSettings { $session->setting->add('inboxNotificationTemplateId', 'b1316COmd9xRv4fCI3LLGA'); } +#---------------------------------------------------------------------------- +# Describe what our function does +sub addPayDrivers { + my $session = shift; + print "\tAdding PayPal driver checking..." unless $quiet; + $session->config->addToArray('paymentDrivers', 'WebGUI::Shop::PayDriver::PayPal::PayPalStd'); + print "DONE!\n" unless $quiet; +} + + # -------------- DO NOT EDIT BELOW THIS LINE -------------------------------- #---------------------------------------------------------------------------- diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original index 814a864ac..c8bce636a 100644 --- a/etc/WebGUI.conf.original +++ b/etc/WebGUI.conf.original @@ -176,7 +176,8 @@ "paymentDrivers" : [ "WebGUI::Shop::PayDriver::Cash", - "WebGUI::Shop::PayDriver::ITransact" + "WebGUI::Shop::PayDriver::ITransact", + "WebGUI::Shop::PayDriver::PayPal::PayPalStd" ], # List the shipping drivers you have installed and wish to be diff --git a/lib/WebGUI/Shop/PayDriver/PayPal/PayPalStd.pm b/lib/WebGUI/Shop/PayDriver/PayPal/PayPalStd.pm new file mode 100644 index 000000000..290012324 --- /dev/null +++ b/lib/WebGUI/Shop/PayDriver/PayPal/PayPalStd.pm @@ -0,0 +1,445 @@ +package WebGUI::Shop::PayDriver::PayPal::PayPalStd; + +=head1 LEGAL + ------------------------------------------------------------------- + PayPal Standard payment driver for WebGUI. + Copyright (C) 2009 Invicta Services, LLC. + ------------------------------------------------------------------- + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ------------------------------------------------------------------- +=cut + +use strict; +use LWP::UserAgent; +use Crypt::SSLeay; + +use base qw/WebGUI::Shop::PayDriver::PayPal/; + +=head1 NAME + +PayPal Website payments standard + +=head1 DESCRIPTION + +A PayPal Website payments standard handler for WebGUI. Provides an interface to PayPal with cart contents +and transaction information on return. + +=head1 SYNOPSIS + + Add "WebGUI::Shop::PayDriver::PayPal::PayPalStd" to the paymentDrivers list in your WebGUI site config file. + Re-start the WebGUI modperl and modproxy web servers. + +=cut + +#------------------------------------------------------------------- +# local subs + +#------------------------------------------------------------------- + +=head2 handlesRecurring + +Tells the commerce system that this payment plugin can handle recurring payments. +1 = yes, 0 = no. This module == no. +=cut + +sub handlesRecurring { + return 0; +} + +#------------------------------------------------------------------- + +=head2 canCheckOutCart ( ) + +Returns whether the cart can be checked out by this plugin. + +=cut + +sub canCheckoutCart { + my $self = shift; + my $cart = $self->getCart; + + return 0 unless $cart->readyForCheckout; + return 0 if $cart->requiresRecurringPayment; + + return 1; +} + +#------------------------------------------------------------------- + +# Recurring TX stuff removed, for now. + +#------------------------------------------------------------------- +sub definition { + my $class = shift; + my $session = shift; + WebGUI::Error::InvalidParam->throw( error => q{Must provide a session variable} ) + unless ref $session eq 'WebGUI::Session'; + my $definition = shift; + + my $i18n = WebGUI::International->new( $session, 'PayDriver_PayPalStd' ); + + tie my %fields, 'Tie::IxHash'; + %fields = ( + vendorId => { + fieldType => 'text', + label => $i18n->get('vendorId'), + hoverHelp => $i18n->get('vendorId help'), + }, + signature => { + fieldType => 'textarea', + label => $i18n->get('signature'), + hoverHelp => $i18n->get('signature help'), + }, + currency => { + fieldType => 'selectBox', + label => $i18n->get('currency'), + hoverHelp => $i18n->get('currency help'), + defaultValue => 'USD', + options => $class->getPaymentCurrencies(), + }, + useSandbox => { + fieldType => 'yesNo', + label => $i18n->get('use sandbox'), + hoverHelp => $i18n->get('use sandbox help'), + defaultValue => 1, + }, + buttonImage => { + fieldType => 'text', + label => $i18n->get('button image'), + hoverHelp => $i18n->get('button image help'), + defaultValue => '', + }, + emailMessage => { + fieldType => 'textarea', + label => $i18n->get('emailMessage'), + hoverHelp => $i18n->get('emailMessage help'), + }, + ); + + push @{$definition}, + { + name => $i18n->get('PayPal'), + properties => \%fields, + }; + + return $class->SUPER::definition( $session, $definition ); +} + +#------------------------------------------------------------------- +sub getButton { + my $self = shift; + my $session = $self->session; + my $i18n = WebGUI::International->new( $session, 'PayDriver_PayPalStd' ); + + my $payForm = WebGUI::Form::formHeader($session) . $self->getDoFormTags('pay'); + + if ( $self->get('buttonImage') ) { + my $button = $self->get('buttonImage'); + WebGUI::Macro::process( $session, \$button ); + $payForm + .= ' '; + } + else { + $payForm .= WebGUI::Form::submit( $session, { value => $i18n->get('PayPal') } ); + } + + $payForm .= WebGUI::Form::formFooter($session); + + return $payForm; +} + +#------------------------------------------------------------------- + +=head2 processTransaction ( [ paymentAddress ] ) + +This method is responsible for handling success or failure from the payment processor, completing or denying the transaction, and sending out notification and receipt emails. Returns a WebGUI::Shop::Transaction object. +This method is overridden from the parent class to allow asynchronous completion / denial of PayPal payments. + +=head3 paymentAddress + +A reference to a WebGUI::Shop::Address object that should be attached as payment information. Not required. + +=cut + +sub processTransaction { + my ( $self, $paymentAddress ) = @_; + + my $cart = $self->getCart; + + # Setup tranasction properties + my $transactionProperties; + $transactionProperties->{paymentMethod} = $self; + $transactionProperties->{cart} = $cart; + $transactionProperties->{paymentAddress} = $paymentAddress if defined $paymentAddress; + $transactionProperties->{isRecurring} = $cart->requiresRecurringPayment; + + # Create a transaction... + my $transaction = WebGUI::Shop::Transaction->create( $self->session, $transactionProperties ); + + # And handle the payment for it + my $session = $self->session; + my $config = $session->config; + + my $f = WebGUI::HTMLForm->new( + $session, + action => ( $self->get('useSandbox') ? $self->getPayPalSandboxUrl() : $self->getPayPalUrl() ), + extras => 'name="paypal_form"' + ); + + $f->hidden( name => 'business', value => $self->get('vendorId') ); + $f->hidden( name => 'cmd', value => '_cart' ); + $f->hidden( name => 'site_url', value => $session->setting->get("companyURL") ); + $f->hidden( name => 'image_url', value => '' ); + $f->hidden( + name => 'return', + value => + $session->url->page( "shop=pay;method=do;do=completeTransaction;paymentGatewayId=" . $self->getId, 1 ) + ); ## PayPal says OK for now + $f->hidden( + name => 'cancel_return', + value => + $session->url->page( "shop=pay;method=do;do=cancelTransaction;paymentGatewayId=" . $self->getId, 1 ) + ); ## Error / user cancel + +# $f->hidden(name=>'notify_url', value=>$session->url->page("shop=pay;method=do;do=IPNnotifyTransaction;paymentGatewayId=".$self->getId, 1)); + $f->hidden( name => 'notify_url', value => '' ); ##no IPN for now, get OK from PDT auto-return + $f->hidden( name => 'rm', value => '2' ); ## use POST + $f->hidden( name => 'currency_code', value => $self->get('currency') ); + $f->hidden( name => 'lc', value => 'US' ); + $f->hidden( name => 'bn', value => 'toolkit-perl' ); + $f->hidden( name => 'cbt', value => 'Continue >>' ); + + # + $f->hidden( name => 'no_shipping', value => '1' ); # do not display shipping addr + $f->hidden( name => 'no_note', value => '0' ); + $f->hidden( name => 'cn', value => 'Comments' ); + $f->hidden( name => 'cs', value => '' ); + + # + # does not get used for uploaded carts + $f->hidden( name => 'item_name', value => 'WebGUI cart' ); + $f->hidden( + name => 'amount', + value => $transaction->get('amount') - $transaction->get('taxes') - $transaction->get('shippingPrice') + ); + + # + $f->hidden( name => 'upload', value => '1' ); + my $itemList = $transaction->getItems; + my $itemNum = 0; + foreach my $item ( @{$itemList} ) { + + # items numbered 1++ + $itemNum++; + + # glue item number to WebGUI itemId + $f->hidden( name => 'item_number_' . $itemNum, value => $item->get('itemId') ); + $f->hidden( name => 'item_name_' . $itemNum, value => $item->get('configuredTitle') ); + $f->hidden( name => 'quantity_' . $itemNum, value => $item->get('quantity') ); + $f->hidden( name => 'amount_' . $itemNum, value => $item->get('price') ); + } + + # + $f->hidden( name => 'shipping', value => $transaction->get('shippingPrice') ); + $f->hidden( name => 'shipping2', value => '' ); # no individual shipping + $f->hidden( name => 'handling_cart', value => '0.00' ); # no separate handling + $f->hidden( name => 'tax_cart', value => $transaction->get('taxes') ); # no separate taxes + $f->hidden( name => 'custom', value => '' ); + $f->hidden( name => 'invoice', value => $transaction->getId ) + ; # need to identify OUR TX so we can update it later + + # + $f->hidden( name => 'address_override', value => 1 ); + + $f->hidden( + name => 'first_name', + value => substr( + $transaction->get('shippingAddressName'), 0, + rindex( $transaction->get('shippingAddressName'), ' ' ) + ) + ); + $f->hidden( + name => 'last_name', + value => substr( + $transaction->get('shippingAddressName'), + rindex( $transaction->get('shippingAddressName'), ' ' ) + 1 + ) + ); + + $f->hidden( name => 'address1', value => $transaction->get('shippingAddress1') ); + $f->hidden( name => 'address2', value => $transaction->get('shippingAddress2') ); + $f->hidden( name => 'city', value => $transaction->get('shippingCity') ); + $f->hidden( name => 'state', value => $transaction->get('shippingState') ); + $f->hidden( name => 'zip', value => $transaction->get('shippingCode') ); + $f->hidden( name => 'country', value => $self->getPaypalCountry( $transaction->get('shippingCountry') ) ); + + if ( $session->user->profileField('email') ) { + $f->hidden( name => 'email', value => $session->user->profileField('email') ); + } + $f->hidden( name => 'night_phone_a', value => $transaction->get('shippingPhoneNumber') ); + $f->hidden( name => 'night_phone_b', value => '' ); + $f->hidden( name => 'night_phone_c', value => '' ); + + return + $f->print + . '
Processing Transaction . . .
' + . ''; + +} + +#------------------------------------------------------------------- +sub www_cancelTransaction { + my $self = shift; + my $session = $self->session; + + my %pdt; + my $retstr = ''; + foreach my $input_name ( $self->session->request->param ) { + $pdt{$input_name} = $self->session->request->param($input_name); + $retstr .= $input_name . ":" . $self->session->request->param($input_name) . "
"; + } + + my $transaction = eval { WebGUI::Shop::Transaction->newByGatewayId( $session, $pdt{invoice}, $self->getId ) }; + + # First check whether the original transaction actualy exists + if ( WebGUI::Error->caught || !( defined $transaction ) ) { + $session->errorHandler->warn("PayPal Standard: No transaction ID: $pdt{invoice}"); + return; + } + $transaction->denyPurchase( $pdt{invoice}, 0, $pdt{payment_status} ); + return $self->displayPaymentError($transaction); +} + +#------------------------------------------------------------------- +sub www_completeTransaction { + my $self = shift; + my $session = $self->session; + + my $paypal_url; + + my %paypal; ## return variables from PDT + +## find TX key from PayPal PDT + my $tx = $self->session->form->get("tx"); + + if ($tx) { + + # found a tx, re-present it for all the TX details + $paypal_url = $self->get('useSandbox') ? $self->getPayPalSandboxUrl() : $self->getPayPalUrl(); + + my $query = join( "&", "cmd=_notify-synch", "tx=" . $tx, "at=" . $self->get('signature') ); + my $user_agent = new LWP::UserAgent; + my $request = new HTTP::Request( "POST", $paypal_url ); + + $request->content_type("application/x-www-form-urlencoded"); + $request->content($query); + + # Make the request + my $result = $user_agent->request($request); + + if ( $result->is_error ) { + $session->errorHandler->warn("PayPal Standard: PayPal server seems offline."); + return; + } + + # Decode the response into individual lines and unescape any HTML escapes + my @response = split( "\n", $self->session->url->unescape( $result->content ) ); + + # The status is always the first line of the response. + my $status = shift @response; + + foreach my $response_line (@response) { + my ( $key, $value ) = split "=", $response_line; + $paypal{$key} = $value; + } + + my $transaction = eval { WebGUI::Shop::Transaction->new( $session, $paypal{invoice} ) }; + + # First check whether the original transaction actualy exists + if ( WebGUI::Error->caught || !( defined $transaction ) ) { + $session->errorHandler->warn( + "PayPal Standard: No WebGUI transaction ID: $paypal{invoice}," . $self->getId ); + return; + } + + if ( $status eq "SUCCESS" ) { + $transaction->completePurchase( $paypal{invoice}, 1, $paypal{payment_status} ); + my $cart = $self->getCart; + $cart->onCompletePurchase; + $self->sendNotifications($transaction); + } + elsif ( $status eq "FAIL" ) { + $transaction->denyPurchase( $paypal{invoice}, 0, $paypal{payment_status} ); + } + + if ( $transaction->get('isSuccessful') ) { + return $transaction->thankYou(); + } + else { + return $self->displayPaymentError($transaction); + } + } + else { ## no tx from paypal + $session->errorHandler->warn("PayPal Standard: No transaction ID"); + } +} + +#------------------------------------------------------------------- + +=head2 www_edit ( ) + +Generates an edit form. + +=cut + +sub www_edit { + my $self = shift; + my $session = $self->session; + my $admin = WebGUI::Shop::Admin->new($session); + my $i18n = WebGUI::International->new( $session, 'PayDriver_PayPalStd' ); + + return $session->privilege->insufficient() unless $admin->canManage; + + my $form = $self->getEditForm; + + $form->submit; + + # adds instructions for IPN etc. + my $output = '
'; + $output + .= $i18n->get('extra info') + . '

' + . 'https://' + . $session->config->get("sitename")->[0] + . '/?shop=pay;method=do;do=completeTransaction;paymentGatewayId=' + . $self->getId . ''; + + return $admin->getAdminConsole->render( $form->print . $output, $i18n->get( 'payment methods', 'PayDriver' ) ); +} + +#------------------------------------------------------------------- +sub www_pay { + my $self = shift; + my $session = $self->session; + + # Payment time! + return $self->processTransaction(); +} + +1; + diff --git a/lib/WebGUI/i18n/English/PayDriver_PayPalStd.pm b/lib/WebGUI/i18n/English/PayDriver_PayPalStd.pm new file mode 100644 index 000000000..5e856b620 --- /dev/null +++ b/lib/WebGUI/i18n/English/PayDriver_PayPalStd.pm @@ -0,0 +1,176 @@ +package WebGUI::i18n::English::PayDriver_PayPalStd; + +=head1 LEGAL + ------------------------------------------------------------------- + PayPal Standard payment driver for WebGUI. + Copyright (C) 2009 Invicta Services, LLC. + ------------------------------------------------------------------- + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ------------------------------------------------------------------- +=cut + +use strict; + +our $I18N = { + 'error occurred message' => { + message => q|The following errors occurred:|, + lastUpdated => 0, + context => q|The message that tell the user that there were some errors in their submitted credentials.|, + }, + 'PayPal' => { + message => q|PayPal|, + lastUpdated => 0, + context => q|The name of the PayPal Website Payments Standard plugin|, + }, + 'label' => { + message => q|PayPal|, + lastUpdated => 0, + context => q|Default PayPal payment gateway label| + }, + + 'vendorId' => { + message => q|PayPal Account|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'vendorId help' => { + message => q|Fill in the email address that identifies your PayPal account.|, + lastUpdated => 0, + context => q|Hover help for vendor id in the configuration form of the PayPal module.| + }, + + 'emailMessage' => { + message => q|Email message|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'emailMessage help' => { + message => q|The message that will be appended to the email user will receive from PayPal.|, + lastUpdated => 0, + context => q|Hover help for the email message field in the configuration form of the PayPal module.| + }, + + 'password' => { + message => q|Password|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'password help' => { + message => q|The password for your PayPal account.|, + lastUpdated => 0, + context => q|Hover help for the password field in the configuration form of the PayPal module.| + }, + + 'signature' => { + message => q|Signature|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'signature help' => { + message => q|The account signature for your PayPal account.|, + lastUpdated => 0, + context => q|Hover help for the signature field in the configuration form of the PayPal module.| + }, + + 'currency' => { + message => q|Currency|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'currency help' => { + message => q|The currency for your transactions with your PayPal account.|, + lastUpdated => 0, + context => q|Hover help for the signature field in the configuration form of the PayPal module.| + }, + + 'use sandbox' => { + message => q|Use Sandbox|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'use sandbox help' => { + message => + q|Set this option to yes if you want to use the PayPal SANDBOX development (i.e. NOT production) environment. Recommended for testing.|, + lastUpdated => 0, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'button image' => { + message => q|PayPal Button image URL|, + lastUpdated => 1241986933, + context => q|Form label in the configuration form of the PayPal module.| + }, + 'button image help' => { + message => q|Set this option to use PayPal images for checkout buttons.|, + lastUpdated => 1241986933, + context => q|Form label in the configuration form of the PayPal module.| + }, + + 'module name' => { + message => q|PayPal|, + lastUpdated => 0, + context => q|The displayed name of the payment module.| + }, + + 'invalid firstName' => { + message => q|You have to enter a valid first name.|, + lastUpdated => 0, + context => q|An error indicating that an invalid first name has been entered.| + }, + 'invalid lastName' => { + message => q|You have to enter a valid last name.|, + lastUpdated => 0, + context => q|An error indicating that an invalid last name has been entered.| + }, + 'invalid address' => { + message => q|You have to enter a valid address.|, + lastUpdated => 0, + context => q|An error indicating that an invalid street has been entered.| + }, + 'invalid city' => { + message => q|You have to enter a valid city.|, + lastUpdated => 0, + context => q|An error indicating that an invalid city has been entered.| + }, + 'invalid zip' => { + message => q|You have to enter a valid zipcode.|, + lastUpdated => 0, + context => q|An error indicating that an invalid zipcode has been entered.| + }, + 'invalid email' => { + message => q|You have to enter a valid email address.|, + lastUpdated => 0, + context => q|An error indicating that an invalid email address has been entered.| + }, + 'PayPal' => { + message => q|PayPal|, + lastUpdated => 0, + context => q|Name of the gateway from the definition| + }, + 'no description' => { + message => q|No description|, + lastUpdated => 0, + context => q|The default description of purchase of users.| + }, + 'extra info' => { + message => + q|Remember to set both "Payment Data Transfer" and "Auto Return" ON in the Website Payments section of your PayPal Profile.
+Additionally, set the "Return URL" to:|, + lastUpdated => 0, + context => q|An informational message that's shown in the configuration form of this plugin.| + }, +}; + +1; + diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 4803a4cba..892662d94 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -127,6 +127,7 @@ checkModule('HTML::Packer', "0.4" ); checkModule('JavaScript::Packer', '0.02' ); checkModule('CSS::Packer', '0.2' ); checkModule('Business::Tax::VAT::Validation', '0.20' ); +checkModule('Crypt::SSLeay', '0.57' ); failAndExit("Required modules are missing, running no more checks.") if $missingModule; diff --git a/www/extras/PayPal/PayPal_mark_37x23.gif b/www/extras/PayPal/PayPal_mark_37x23.gif new file mode 100644 index 0000000000000000000000000000000000000000..25333b17c33e5f88aab877b66e6fb5da10e19db0 GIT binary patch literal 812 zcmV+{1JnFRNk%w1VI=?;0OkMy|CuuzHe~+!@BjV#|NQs+;dLIyS~21$bN{J@a4MG000000000000000A^8LV00000EC2ui z03`qy000O7fB=GngoTEOh=K`=jE#Odrb6ge6K zj2Iz;*flUJwF-$Z70Yb@^24`M0*wdkdpbQ_haj3C`gElPLVg|WGu0fG3 z9>`s3P$Gj-K@_sth(lpUfD>s>1Td2)+Xf0h8bE15fky)mEy6Sq(qIjRvw~Oz*f60j zjchiF*;2{IV#2t#6ReCYF|J1gAb17A34ua_VF^0$Xdx`&OMp3sF}OG^A&M6alDS}D za#+xV&!9tpP7$C@3el)jKM>@gwd>cd$JBuVySDAtf)?oB&AYen-@gSN7*4#n@#Dym q3u3^$x%21HqemB{pt|+z*t2WbE~tR_@8H9WA3r{k`SXYZ0RTJP=(0Ef literal 0 HcmV?d00001 diff --git a/www/extras/PayPal/btn_xpressCheckout.gif b/www/extras/PayPal/btn_xpressCheckout.gif new file mode 100644 index 0000000000000000000000000000000000000000..3eb68d2fa33a1786301c97c25fbafc4afc0781b1 GIT binary patch literal 3091 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 literal 0 HcmV?d00001