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.
 
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org