mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-05-15 12:46 am

[dw-free] Add PayPal 'Buy Now' button support to enable credit card purchases without a PayPal accou

[commit: http://hg.dwscoalition.org/dw-free/rev/c4f5f857fb42]

Add PayPal 'Buy Now' button support to enable credit card purchases without
a PayPal account.

Patch by [staff profile] mark.

Files modified:
  • bin/upgrading/en.dat
  • bin/upgrading/update-db-general.pl
  • cgi-bin/DW/Shop.pm
  • cgi-bin/DW/Shop/Engine.pm
  • cgi-bin/DW/Shop/Engine/CreditCardPP.pm
  • cgi-bin/DW/Shop/Engine/PayPal.pm
  • cgi-bin/LJ/Widget/ShopCart.pm
  • htdocs/shop/creditcard.bml
  • htdocs/shop/creditcard.bml.text
  • htdocs/shop/receipt.bml.text
--------------------------------------------------------------------------------
diff -r 444427ab0545 -r c4f5f857fb42 bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Wed May 13 16:26:50 2009 +0000
+++ b/bin/upgrading/en.dat	Fri May 15 00:46:43 2009 +0000
@@ -4457,7 +4457,9 @@ widget.shopcart.paymentmethod=Payment Me
 
 widget.shopcart.paymentmethod.checkmoneyorder=Check/Money Order
 
-widget.shopcart.paymentmethod.paypal=PayPal/Credit Card
+widget.shopcart.paymentmethod.creditcardpp=Credit Card
+
+widget.shopcart.paymentmethod.paypal=PayPal Account
 
 widget.shopcart.total=Total:
 
diff -r 444427ab0545 -r c4f5f857fb42 bin/upgrading/update-db-general.pl
--- a/bin/upgrading/update-db-general.pl	Wed May 13 16:26:50 2009 +0000
+++ b/bin/upgrading/update-db-general.pl	Fri May 15 00:46:43 2009 +0000
@@ -3225,6 +3225,7 @@ register_tablecreate('pp_log', <<'EOC');
 register_tablecreate('pp_log', <<'EOC');
 CREATE TABLE pp_log (
     ppid int unsigned not null,
+    ip varchar(15) not null,
     transtime int unsigned not null,
     req_content text not null,
     res_content text not null,
@@ -4072,6 +4073,11 @@ register_alter(sub {
                   q{ALTER TABLE shop_carts ADD COLUMN email VARCHAR(255) AFTER userid} );
     }
 
+    unless ( column_type( 'pp_log', 'ip' ) =~ /varcahr/ ) {
+        do_alter( 'pp_log',
+                  q{ALTER TABLE pp_log ADD COLUMN ip VARCHAR(15) NOT NULL AFTER ppid} );
+    }
+
 });
 
 
diff -r 444427ab0545 -r c4f5f857fb42 cgi-bin/DW/Shop.pm
--- a/cgi-bin/DW/Shop.pm	Wed May 13 16:26:50 2009 +0000
+++ b/cgi-bin/DW/Shop.pm	Fri May 15 00:46:43 2009 +0000
@@ -33,6 +33,7 @@ our $STATE_PEND_REFUND = 6;    # refund 
 our $STATE_PEND_REFUND = 6;    # refund is approved but unissued
 our $STATE_REFUNDED    = 7;    # we have refunded this cart and reversed it
 our $STATE_CLOSED      = 8;    # carts can go from OPEN -> CLOSED
+our $STATE_DECLINED    = 9;    # payment entity declined the fundage
 
 # documentation of valid state transitions...
 #
@@ -62,6 +63,9 @@ our $STATE_CLOSED      = 8;    # carts c
 #                        cart.  i.e., it hasn't been touched in a while so we
 #                        decide the user isn't coming back.
 #
+#   PEND_PAID -> DECLINED  happens when we try to capture funds from a remote
+#                          entity and they decline for some reason.
+#
 # any other state transition is hereby considered null and void.
 
 
@@ -76,6 +80,10 @@ our %PAYMENTMETHODS = (
     checkmoneyorder => {
         id => 2,
         class => 'CheckMoneyOrder',
+    },
+    creditcardpp => {
+        id => 3,
+        class => 'CreditCardPP',
     },
 );
 
diff -r 444427ab0545 -r c4f5f857fb42 cgi-bin/DW/Shop/Engine.pm
--- a/cgi-bin/DW/Shop/Engine.pm	Wed May 13 16:26:50 2009 +0000
+++ b/cgi-bin/DW/Shop/Engine.pm	Fri May 15 00:46:43 2009 +0000
@@ -18,6 +18,7 @@ package DW::Shop::Engine;
 
 use strict;
 use DW::Shop::Engine::CheckMoneyOrder;
+use DW::Shop::Engine::CreditCardPP;
 use DW::Shop::Engine::PayPal;
 
 # get( $method, $cart )
@@ -25,6 +26,7 @@ use DW::Shop::Engine::PayPal;
 # returns the proper subclass for the given payment method, if one exists
 sub get {
     return DW::Shop::Engine::PayPal->new( $_[2] ) if $_[1] eq 'paypal';
+    return DW::Shop::Engine::CreditCardPP->new( $_[2] ) if $_[1] eq 'creditcardpp';
     return DW::Shop::Engine::CheckMoneyOrder->new( $_[2] ) if $_[1] eq 'checkmoneyorder';
 
     warn "Payment method '$_[1]' not supported.\n";
diff -r 444427ab0545 -r c4f5f857fb42 cgi-bin/DW/Shop/Engine/CreditCardPP.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Shop/Engine/CreditCardPP.pm	Fri May 15 00:46:43 2009 +0000
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+#
+# DW::Shop::Engine::CreditCardPP
+#
+# This is a very simple payment method, it generates one of those fancy PayPal
+# buttons which the user can use to pay.
+#
+# Authors:
+#      Mark Smith <mark@dreamwidth.org>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself.  For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+package DW::Shop::Engine::CreditCardPP;
+
+use strict;
+use Carp qw/ croak confess /;
+use Digest::MD5 qw/ md5_hex /;
+use Storable qw/ nfreeze thaw /;
+
+use base qw/ DW::Shop::Engine /;
+
+
+# new( $cart )
+#
+# instantiates a new PayPal engine for the given cart
+sub new {
+    return bless { cart => $_[1] }, $_[0];
+}
+
+
+# checkout_url()
+#
+# this is simple, send them to the page for entering their credit card information
+sub checkout_url {
+    my $self = $_[0];
+
+    # make sure that the cart contains something that costs something.  since
+    # this check should have been done above, we die hardcore here.
+    my $cart = $self->cart;
+    die "Constraints not met: cart && cart->has_items && cart->has_total > 0.00.\n"
+        unless $cart && $cart->has_items && $cart->total > 0.00;
+
+    # and, just in case something terrible happens, make sure our state is good
+    die "Cart not in valid state!\n"
+        unless $cart->state == $DW::Shop::STATE_OPEN;
+
+    # return URL to cc entry
+    return "$LJ::SITEROOT/shop/creditcard";
+}
+
+
+# accessors
+sub cart { $_[0]->{cart} }
+
+
+1;
diff -r 444427ab0545 -r c4f5f857fb42 cgi-bin/DW/Shop/Engine/PayPal.pm
--- a/cgi-bin/DW/Shop/Engine/PayPal.pm	Wed May 13 16:26:50 2009 +0000
+++ b/cgi-bin/DW/Shop/Engine/PayPal.pm	Fri May 15 00:46:43 2009 +0000
@@ -372,9 +372,9 @@ sub _pp_req {
         if ( ref $self && ( my $ppid = $self->ppid ) ) {
             if ( my $dbh = DW::Pay::get_db_writer() ) {
                 $dbh->do( q{
-                        INSERT INTO pp_log (ppid, transtime, req_content, res_content)
-                        VALUES (?, UNIX_TIMESTAMP(), ?, ?)
-                    }, undef, $ppid, $reqct, $res->content );
+                        INSERT INTO pp_log (ppid, ip, transtime, req_content, res_content)
+                        VALUES (?, ?, UNIX_TIMESTAMP(), ?, ?)
+                    }, undef, $ppid, BML::get_remote_ip(), $reqct, $res->content );
                 warn $dbh->errstr
                     if $dbh->err;
             }
@@ -395,12 +395,31 @@ sub process_ipn {
     my $dbh = DW::Pay::get_db_writer()
         or die "failed, please retry later\n";
     $dbh->do(
-        q{INSERT INTO pp_log (ppid, transtime, req_content, res_content)
-          VALUES (0, UNIX_TIMESTAMP(), ?, '')},
-        undef, nfreeze( $form )
+        q{INSERT INTO pp_log (ppid, ip, transtime, req_content, res_content)
+          VALUES (0, ?, UNIX_TIMESTAMP(), ?, '')},
+        undef, BML::get_remote_ip(), nfreeze( $form )
     );
     die "failed to insert\n"
         if $dbh->err;
+
+    # if this is a confirmation of a payment from a CC/other button type payment, then
+    # mark the item as being paid
+    if ( $form->{payment_status} eq 'Completed' && $form->{transaction_subject} =~ /Order #(\d+)$/ ) {
+        my $cart = DW::Shop::Cart->get_from_cartid( $1 );
+
+        # we must have a cart, and it must be in the right state and
+        # actually a credit card cart, and the price must match to prevent
+        # someone trying to spoof our button
+        return 1
+            unless $cart &&
+                   $cart->state == $DW::Shop::STATE_PEND_PAID &&
+                   $cart->paymentmethod eq 'creditcardpp';
+                   $cart->display_total == $form->{payment_gross};
+
+        # looks good, mark it paid
+        $cart->paymentmethod( 'creditcardpp' );
+        $cart->state( $DW::Shop::STATE_PAID );
+    }
 
     return 1;
 }
diff -r 444427ab0545 -r c4f5f857fb42 cgi-bin/LJ/Widget/ShopCart.pm
--- a/cgi-bin/LJ/Widget/ShopCart.pm	Wed May 13 16:26:50 2009 +0000
+++ b/cgi-bin/LJ/Widget/ShopCart.pm	Fri May 15 00:46:43 2009 +0000
@@ -99,7 +99,10 @@ sub render_body {
         $ret .= "<p>" . $class->html_submit( removeselected => $class->ml( 'widget.shopcart.btn.removeselected' ) ) . " ";
         $ret .= $class->html_submit( discard => $class->ml( 'widget.shopcart.btn.discard' ) ) . "</p>";
 
-        my @paypal_option = ( paypal => $class->ml( 'widget.shopcart.paymentmethod.paypal' ) )
+        my @paypal_option = (
+            paypal => $class->ml( 'widget.shopcart.paymentmethod.paypal' ),
+            creditcardpp => $class->ml( 'widget.shopcart.paymentmethod.creditcardpp' ),
+        )
             if keys %LJ::PAYPAL_CONFIG;
         $ret .= "<p>" . $class->ml( 'widget.shopcart.paymentmethod' ) . " ";
         $ret .= $class->html_select(
@@ -124,12 +127,8 @@ sub handle_post {
     my ( $class, $post, %opts ) = @_;
 
     # check out
-    if ( $post->{checkout} ) {
-        my $method = 'paypal';
-        $method = 'checkmoneyorder' if $post->{paymentmethod} eq 'checkmoneyorder' || !keys %LJ::PAYPAL_CONFIG;
-
-        return BML::redirect( "$LJ::SITEROOT/shop/checkout?method=$method" );
-    }
+    return BML::redirect( "$LJ::SITEROOT/shop/checkout?method=$post->{paymentmethod}" )
+        if $post->{checkout};
 
     # remove selected items
     if ( $post->{removeselected} ) {
diff -r 444427ab0545 -r c4f5f857fb42 htdocs/shop/creditcard.bml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/creditcard.bml	Fri May 15 00:46:43 2009 +0000
@@ -0,0 +1,94 @@
+<?_c
+#
+# shop/creditcard.bml
+#
+# Generates HTML for a PayPal "Buy Now" button... maybe in the future this will
+# do something far more amazing, with super robot powers.
+#
+# Authors:
+#      Mark Smith <mark@dreamwidth.org>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself. For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+_c?><?page
+body<=
+<?_code
+{
+    use strict;
+    use vars qw/ %GET %POST $title /;
+
+    return BML::redirect( "$LJ::SITEROOT/" )
+        unless LJ::is_enabled( 'payments' );
+
+    $title = $ML{'.title'};
+
+    # this page uses new style JS
+    LJ::need_res( 'stc/shop.css' );
+    LJ::set_active_resource_group( 'jquery' );
+
+    # get their shop/cart
+    my $cart = DW::Shop->get->cart;
+    return $ML{'.error.nocart'}
+        unless $cart;
+    return $ML{'.error.emptycart'}
+        unless $cart->has_items;
+
+    # if state is NOT open, then just redirect them to the wait page,
+    # which will do the Right Thing.  this typically is used in the case
+    # that the user double clicks on the form, or hits back and clicks
+    # submit again...
+    return BML::redirect( "$LJ::SITEROOT/shop/receipt?ordernum=" . $cart->ordernum )
+        unless $cart->state == $DW::Shop::STATE_OPEN;
+
+    # FIXME: if they have a $0 cart, we don't support that yet
+    return $ML{'.error.zerocart'}
+        if $cart->total == 0.00;
+
+    # looks good, set the payment method and state
+    $cart->paymentmethod( 'creditcardpp' );
+    $cart->state( $DW::Shop::STATE_PEND_PAID );
+
+    # values we need
+    my $cartid = $cart->id;
+    my $cost = $cart->display_total;
+
+    # okay, render out the button :-)
+    return qq|
+<?p <?_ml .info _ml?> p?>
+
+<form action="$LJ::PAYPAL_CONFIG{cc_url}" method="post"> 
+
+<!-- Identify your business so that you can collect the payments. --> 
+<input type="hidden" name="business" value="$LJ::PAYPAL_CONFIG{account}" />
+
+<!-- Specify a Buy Now button. --> 
+<input type="hidden" name="cmd" value="_xclick" />
+<input type="hidden" name="bn" value="${LJ::SITENAMESHORT}_BuyNow_WPS_US" />
+<input type="hidden" name="notify_url" value="$LJ::SITEROOT/shop/pp_notify" />
+
+<!-- Specify details about the item that buyers will purchase. --> 
+<input type="hidden" name="item_name" value="$LJ::SITECOMPANY Order #$cartid" />
+<input type="hidden" name="amount" value="$cost" />
+<input type="hidden" name="currency_code" value="USD" /> 
+<input type="hidden" name="invoice" value="$cartid" />
+
+<!-- Other configuration -->
+<input type="hidden" name="no_shipping" value="1" />
+<input type="hidden" name="no_note" value="1" />
+
+<!-- Display the payment button. --> 
+<input type="image" name="submit" border="0" src="https://www.paypal.com/en_US/i/btn/btn_buynow_LG.gif"
+    alt="PayPal - The safer, easier way to pay online" /> 
+<img alt="" border="0" width="1" height="1" src="https://www.paypal.com/en_US/i/scr/pixel.gif" /> 
+
+</form>
+    |;
+}
+_code?>
+<=body
+title=><?_code return $title; _code?>
+page?>
diff -r 444427ab0545 -r c4f5f857fb42 htdocs/shop/creditcard.bml.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/creditcard.bml.text	Fri May 15 00:46:43 2009 +0000
@@ -0,0 +1,15 @@
+;; -*- coding: utf-8 -*-
+
+.error.badcartstate=Your shopping cart has already been checked out.  If your payment failed, you will need to build a new shopping cart to try again.  We're sorry for the inconvenience.
+
+.error.emptycart=Your cart is empty.
+
+.error.invalidpaymentmethod=The payment method you chose is not currently supported.
+
+.error.nocart=You do not have a shopping cart.
+
+.error.zerocart=Sorry, we don't support $0 carts yet.
+
+.info=If you'd like to use a credit card to purchase your account, use the button below. This will take you to PayPal, our merchant processor. You won't need a PayPal account to use this payment option.
+
+.title=Purchase by Credit Card
diff -r 444427ab0545 -r c4f5f857fb42 htdocs/shop/receipt.bml.text
--- a/htdocs/shop/receipt.bml.text	Wed May 13 16:26:50 2009 +0000
+++ b/htdocs/shop/receipt.bml.text	Fri May 15 00:46:43 2009 +0000
@@ -27,7 +27,9 @@
 
 .cart.paymentmethod.checkmoneyorder.extra=Please make your check or money order out to: [[sitecompany]]</p><p>Mail it to: [[address]]
 
-.cart.paymentmethod.paypal=PayPal
+.cart.paymentmethod.creditcardpp=Credit Card
+
+.cart.paymentmethod.paypal=PayPal Account
 
 .error.invalidordernum=Your order number is invalid.
 
--------------------------------------------------------------------------------