[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
mark.
Files modified:
Add PayPal 'Buy Now' button support to enable credit card purchases without
a PayPal account.
Patch by
![[staff profile]](https://www.dreamwidth.org/img/silk/identity/user_staff.png)
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. --------------------------------------------------------------------------------