site/cgi-bin/modules/BSE/UI/Affiliate.pm
site/cgi-bin/modules/BSE/UI/Dispatch.pm
site/cgi-bin/modules/BSE/UI/Formmail.pm
+site/cgi-bin/modules/BSE/UI/Shop.pm
site/cgi-bin/modules/BSE/UI/SiteuserCommon.pm
site/cgi-bin/modules/BSE/UI/SubAdmin.pm
site/cgi-bin/modules/BSE/UserReg.pm
site/cgi-bin/modules/DevHelp/FileUpload.pm
site/cgi-bin/modules/DevHelp/Formatter.pm
site/cgi-bin/modules/DevHelp/HTML.pm
+site/cgi-bin/modules/DevHelp/Payments/Inpho.pm
+site/cgi-bin/modules/DevHelp/Payments/Test.pm
site/cgi-bin/modules/DevHelp/Report.pm
site/cgi-bin/modules/DevHelp/Tags.pm
site/cgi-bin/modules/DevHelp/Tags/Iterate.pm
site/templates/catalog.tmpl
site/templates/catalog/multi.tmpl
site/templates/catalog/shop_subcat.tmpl
-site/templates/checkout_base.tmpl
site/templates/checkoutcard_base.tmpl
site/templates/checkoutconfirm_base.tmpl
site/templates/checkoutfinal_base.tmpl
+site/templates/checkoutnew_base.tmpl
+site/templates/checkoutpay_base.tmpl
site/templates/common/default.tmpl
site/templates/common/defsteps.tmpl
site/templates/common/downloads.tmpl
-VERSION=0.15_04
+VERSION=0.15_05
DISTNAME=bse-$(VERSION)
DISTBUILD=$(DISTNAME)
DISTTAR=../$(DISTNAME).tar
delivMobile varchar(80) not null default '',
billMobile varchar(80) not null default '',
+ -- information from online credit card processing
+ -- non-zero if we did online CC processing
+ ccOnline integer not null default 0,
+ -- non-zero if processing was successful
+ ccSuccess integer not null default 0,
+ -- receipt number
+ ccReceipt varchar(80) not null default '',
+ -- main status code (value depends on driver)
+ ccStatus integer not null default 0,
+ ccStatusText varchar(80) not null default '',
+ -- secondary status code (if any)
+ ccStatus2 integer not null default 0,
+ -- card processor transaction identifier
+ -- the ORDER_NUMBER for Inpho
+ ccTranId varchar(40) not null default '',
+
primary key (id),
index order_cchash(ccNumberHash),
index order_userId(userId, orderDate)
use vars qw(@ISA @EXPORT_OK);
require Exporter;
@ISA = qw(Exporter);
-@EXPORT_OK = qw(custom_class admin_base_url cfg_image_dir);
+@EXPORT_OK = qw(custom_class admin_base_url cfg_image_dir credit_card_class);
=head1 NAME
use BSE::CfgInfo 'custom_class';
my $class = custom_class($cfg);
+ use BSE::CfgInfo 'credit_card_class';
+ my $class = credit_card_class($cfg);
+
=head1 DESCRIPTION
This module contains functions which examine the BSE configuration and
$cfg->entry('paths', 'images', $Constants::IMAGEDIR);
}
+sub credit_card_class {
+ my ($cfg) = @_;
+
+ local @INC = @INC;
+
+ my $class = $cfg->entry('shop', 'cardprocessor')
+ or return;
+ (my $file = $class . ".pm") =~ s!::!/!g;
+
+ my $local_inc = $cfg->entry('paths', 'libraries');
+ unshift @INC, $local_inc if $local_inc;
+
+ require $file;
+
+ return $class->new($cfg);
+}
+
1;
__END__
Orders => 'select * from orders',
getOrderByPkey => 'select * from orders where id = ?',
getOrderItemByOrderId => 'select * from order_item where orderId = ?',
- addOrder => 'insert orders values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
- replaceOrder => 'replace orders values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+ addOrder => 'insert orders values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+ replaceOrder => 'replace orders values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
addOrderItem => 'insert order_item values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
getOrderByUserId => 'select * from orders where userId = ?',
$types{$type}{enabled} = 1;
}
- #use Data::Dumper;
- #print STDERR Dumper \%types;
+ # credit card payments require either encrypted emails enabled or
+ # an online CC processing module
+ if ($types{0}) {
+ my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
+ my $ccprocessor = $cfg->entry('shop', 'cardprocessor');
+
+ if ($noencrypt && !$ccprocessor) {
+ $types{0}{enabled} = 0;
+ }
+ }
return values %types;
}
customStr1 customStr2 customStr3 customStr4 customStr5
instructions billTelephone billFacsimile billEmail
siteuser_id affiliate_code shipping_cost
- delivMobile billMobile/;
+ delivMobile billMobile
+ ccOnline ccSuccess ccReceipt ccStatus ccStatusText
+ ccStatus2 ccTranId/;
}
=item siteuser
Products->getSpecial(orderProducts=>$self->{id});
}
+sub valid_fields {
+ my ($class, $cfg) = @_;
+
+ my %fields =
+ (
+ delivFirstName => { description=>'Delivery First Name', },
+ delivLastName => { description => 'Delivery Last Name' },
+ delivStreet => { description => 'Delivery Street' },
+ delivState => { description => 'Delivery State' },
+ delivSuburb => { description => 'Delivery Suburb' },
+ delivPostCode => { description => 'Delivery Post Code' },
+ delivCountry => { description => 'Delivery Country' },
+ billFirstName => { description => 'Billing First Name' },
+ billLastName => { description => 'Billing Last Name' },
+ billStreet => { description => 'Billing First Name' },
+ billSuburb => { description => 'Billing First Name' },
+ billState => { description => 'Billing First Name' },
+ billPostCode => { description => 'Billing First Name' },
+ billCountry => { description => 'Billing First Name' },
+ telephone => { description => 'Telephone Number',
+ rules => "phone" },
+ facsimile => { description => 'Facsimile Number',
+ rules => 'phone' },
+ emailAddress => { description => 'Email Address',
+ rules=>'email;required' },
+ instructions => { description => 'Instructions' },
+ billTelephone => { description => 'Billing Telephone Number',
+ rules=>'phone' },
+ billFacsimile => { description => 'Billing Facsimile Number',
+ rules=>'phone' },
+ billEmail => { description => 'Billing Email Address',
+ rules => 'email' },
+ delivMobile => { description => 'Delivery Mobile Number',
+ rules => 'phone' },
+ billMobile => { description => 'Billing Mobile Number',
+ rules=>'phone' },
+ instructions => { description => 'Instructions' },
+ );
+
+ for my $field (keys %fields) {
+ my $display = $cfg->entry('shop', "display_$field");
+ $display and $fields{$field}{description} = $display;
+ }
+
+ return %fields;
+}
+
+sub valid_rules {
+ my ($class, $cfg) = @_;
+
+ return;
+}
+
+sub valid_payment_fields {
+ my ($class, $cfg) = @_;
+
+ my %fields =
+ (
+ cardNumber =>
+ {
+ description => "Credit Card Number",
+ rules=>"creditcardnumber",
+ },
+ cardExpiry =>
+ {
+ description => "Credit Card Expiry Date",
+ rules => 'creditcardexpirysingle',
+ },
+ cardHolder => { description => "Credit Card Holder" },
+ cardType => { description => "Credit Card Type" },
+ cardVerify =>
+ {
+ description => 'Card Verification Value',
+ rules => 'creditcardcvv',
+ },
+ );
+
+ for my $field (keys %fields) {
+ my $display = $cfg->entry('shop', "display_$field");
+ $display and $fields{$field}{description} = $display;
+ }
+
+ return %fields;
+}
+
+sub valid_payment_rules {
+ return;
+}
+
1;
my $actions = $class->actions;
+ my $prefix = $class->action_prefix;
my $cgi = $req->cgi;
my $action;
for my $check (keys %$actions) {
- if ($cgi->param("a_$check")) {
+ if ($cgi->param("$prefix$check") || $cgi->param("$prefix$check.x")) {
$action = $check;
last;
}
}
+ if (!$action && $prefix ne 'a_') {
+ for my $check (keys %$actions) {
+ if ($cgi->param("a_$check") || $cgi->param("a_$check.x")) {
+ $action = $check;
+ last;
+ }
+ }
+ }
+ my @extras;
+ unless ($action) {
+ ($action, @extras) = $class->other_action($cgi);
+ }
$action ||= $class->default_action;
$class->check_action($req, $action, \$result)
or return $result;
my $method = "req_$action";
- $class->$method($req);
+ $class->$method($req, @extras);
}
sub check_secure {
return 1;
}
+sub other_action {
+ return;
+}
+
+sub action_prefix {
+ 'a_';
+}
+
1;
--- /dev/null
+package BSE::UI::Shop;
+use strict;
+use base 'BSE::UI::Dispatch';
+use DevHelp::HTML;
+use BSE::Util::SQL qw(now_sqldate now_sqldatetime);
+use BSE::Shop::Util qw(need_logon shop_cart_tags payment_types nice_options cart_item_opts basic_tags);
+use BSE::CfgInfo qw(custom_class credit_card_class);
+use BSE::TB::Orders;
+use BSE::TB::OrderItems;
+use BSE::Mail;
+use BSE::Util::Tags qw(tag_error_img);
+use Products;
+use DevHelp::Validate qw(dh_validate dh_validate_hash);
+
+use constant PAYMENT_CC => 0;
+use constant PAYMENT_CHEQUE => 1;
+use constant PAYMENT_CALLME => 2;
+
+my %actions =
+ (
+ add => 1,
+ cart => 1,
+ checkout => 1,
+ checkupdate => 1,
+ recheckout => 1,
+ confirm => 1,
+ recalc=>1,
+ recalculate => 1,
+ #purchase => 1,
+ order => 1,
+ show_payment => 1,
+ payment => 1,
+ orderdone => 1,
+ );
+
+sub actions { \%actions }
+
+sub default_action { 'cart' }
+
+sub other_action {
+ my ($class, $cgi) = @_;
+
+ for my $key ($cgi->param()) {
+ if ($key =~ /^delete_(\d+)$/) {
+ return ( remove_item => $1 );
+ }
+ }
+
+ return;
+}
+
+sub req_cart {
+ my ($class, $req, $msg) = @_;
+
+ my @cart = @{$req->session->{cart} || []};
+ my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
+ my $item_index = -1;
+ my @options;
+ my $option_index;
+
+ $req->session->{custom} ||= {};
+ my %custom_state = %{$req->session->{custom}};
+
+ my $cust_class = custom_class($req->cfg);
+ $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $req->cfg);
+ $msg = '' unless defined $msg;
+ $msg = escape_html($msg);
+
+ my %acts;
+ %acts =
+ (
+ $cust_class->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state,
+ $req->cfg),
+ shop_cart_tags(\%acts, \@cart, \@cart_prods, $req->session, $req->cgi,
+ $req->cfg, 'cart'),
+ basic_tags(\%acts),
+ msg => $msg,
+ );
+ $req->session->{custom} = \%custom_state;
+ $req->session->{order_info_confirmed} = 0;
+
+ return $req->response('cart', \%acts);
+}
+
+sub req_add {
+ my ($class, $req) = @_;
+
+ my $cgi = $req->cgi;
+
+ my $addid = $cgi->param('id');
+ $addid ||= '';
+ my $quantity = $cgi->param('quantity');
+ $quantity ||= 1;
+ my $product;
+ $product = Products->getByPkey($addid) if $addid;
+ $product or
+ return $class->req_cart($req, "Cannot find product $addid"); # oops
+
+ # collect the product options
+ my @options;
+ my @opt_names = split /,/, $product->{options};
+ my @not_def;
+ for my $name (@opt_names) {
+ my $value = $cgi->param($name);
+ push @options, $value;
+ unless (defined $value) {
+ push @not_def, $name;
+ }
+ }
+ @not_def
+ and return $class->req_cart($req, "Some product options (@not_def) not supplied");
+ my $options = join(",", @options);
+
+ # the product must be non-expired and listed
+ (my $comp_release = $product->{release}) =~ s/ .*//;
+ (my $comp_expire = $product->{expire}) =~ s/ .*//;
+ my $today = now_sqldate();
+ $comp_release le $today
+ or return $class->req_cart($req, "Product has not been released yet");
+ $today le $comp_expire
+ or return $class->req_cart($req, "Product has expired");
+ $product->{listed} or return $class->req_cart($req, "Product not available");
+
+ # used to refresh if a logon is needed
+ my $securlbase = $req->cfg->entryVar('site', 'secureurl');
+ my $r = $securlbase . $ENV{SCRIPT_NAME} . "?add=1&id=$addid";
+ for my $opt_index (0..$#opt_names) {
+ $r .= "&$opt_names[$opt_index]=".escape_uri($options[$opt_index]);
+ }
+
+ my $user = $req->siteuser;
+ # need to be logged on if it has any subs
+ if ($product->{subscription_id} != -1) {
+ if ($user) {
+ my $sub = $product->subscription;
+ if ($product->is_renew_sub_only) {
+ unless ($user->subscribed_to_grace($sub)) {
+ return show_cart("This product can only be used to renew your subscription to $sub->{title} and you are not subscribed nor within the renewal grace period");
+ }
+ }
+ elsif ($product->is_start_sub_only) {
+ if ($user->subscribed_to_grace($sub)) {
+ return show_cart("This product can only be used to start your subscription to $sub->{title} and you are already subscribed or within the grace period");
+ }
+ }
+ }
+ else {
+ return $class->_refresh_logon
+ ($req, "You must be logged on to add this product to your cart",
+ 'prodlogon', $r);
+ }
+ }
+ if ($product->{subscription_required} != -1) {
+ my $sub = $product->subscription_required;
+ if ($user) {
+ unless ($user->subscribed_to($sub)) {
+ return $class->req_cart($req, "You must be subscribed to $sub->{title} to purchase this product");
+ return;
+ }
+ }
+ else {
+ # we want to refresh back to adding the item to the cart if possible
+ return $class->_refresh_logon
+ ($req, "You must be logged on and subscribed to $sub->{title} to add this product to your cart",
+ 'prodlogonsub', $r);
+ }
+ }
+
+ # we need a natural integer quantity
+ $quantity =~ /^\d+$/
+ or return $class->req_cart($req, "Invalid quantity");
+
+ $req->session->{cart} ||= [];
+ my @cart = @{$req->session->{cart}};
+
+ # if this is is already present, replace it
+ @cart = grep { $_->{productId} ne $addid || $_->{options} ne $options }
+ @cart;
+ push @cart,
+ {
+ productId => $addid,
+ units => $quantity,
+ price=>$product->{retailPrice},
+ options=>$options
+ };
+
+ $req->session->{cart} = \@cart;
+ $req->session->{order_info_confirmed} = 0;
+
+ return $class->req_cart($req);
+}
+
+sub req_checkout {
+ my ($class, $req, $message, $olddata) = @_;
+
+ my $errors = {};
+ if (defined $message) {
+ if (ref $message) {
+ $errors = $message;
+ $message = $req->message($errors);
+ }
+ }
+ else {
+ $message = '';
+ }
+ my $cfg = $req->cfg;
+ my $cgi = $req->cgi;
+
+ $class->update_quantities($req);
+ my @cart = @{$req->session->{cart}};
+
+ @cart or return $class->req_cart($req);
+
+ my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
+
+ if (my ($msg, $id) = $class->_need_logon($req, \@cart, \@cart_prods)) {
+ return $class->_refresh_logon($req, $msg, $id);
+ return;
+ }
+
+ my $user = $req->siteuser;
+
+ $req->session->{custom} ||= {};
+ my %custom_state = %{$req->session->{custom}};
+
+ my $cust_class = custom_class($cfg);
+ $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
+
+ my $affiliate_code = $req->session->{affiliate_code};
+ defined $affiliate_code or $affiliate_code = '';
+
+ my $item_index = -1;
+ my @options;
+ my $option_index;
+ my %acts;
+ %acts =
+ (
+ shop_cart_tags(\%acts, \@cart, \@cart_prods, $req->session, $req->cgi,
+ $cfg, 'checkout'),
+ basic_tags(\%acts),
+ message => $message,
+ msg => $message,
+ old =>
+ sub {
+ my $value;
+
+ if ($olddata) {
+ $value = $cgi->param($_[0]);
+ unless (defined $value) {
+ $value = $user->{$_[0]}
+ if $user;
+ }
+ }
+ else {
+ $value = $user && defined $user->{$_[0]} ? $user->{$_[0]} : '';
+ }
+
+ defined $value or $value = '';
+ escape_html($value);
+ },
+ $cust_class->checkout_actions(\%acts, \@cart, \@cart_prods,
+ \%custom_state, $req->cgi, $cfg),
+ ifUser => defined $user,
+ user => $user ? [ \&tag_hash, $user ] : '',
+ affiliate_code => escape_html($affiliate_code),
+ error_img => [ \&tag_error_img, $cfg, $errors ],
+ );
+ $req->session->{custom} = \%custom_state;
+
+ return $req->response('checkoutnew', \%acts);
+}
+
+sub req_checkupdate {
+ my ($class, $req) = @_;
+
+ my @cart = @{$req->session->{cart}};
+ my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
+ $req->session->{custom} ||= {};
+ my %custom_state = %{$req->session->{custom}};
+ custom_class($req->cfg)
+ ->checkout_update($req->cgi, \@cart, \@cart_prods, \%custom_state, $req->cfg);
+ $req->session->{custom} = \%custom_state;
+ $req->session->{order_info_confirmed} = 0;
+
+ return $class->req_checkout($req, "", 1);
+}
+
+sub req_remove_item {
+ my ($class, $req, $index) = @_;
+ my @cart = @{$req->session->{cart}};
+ if ($index >= 0 && $index < @cart) {
+ splice(@cart, $index, 1);
+ }
+ $req->session->{cart} = \@cart;
+ $req->session->{order_info_confirmed} = 0;
+
+ return BSE::Template->get_refresh($ENV{SCRIPT_NAME}, $req->cfg);
+}
+
+my %field_map =
+ (
+ name1 => 'delivFirstName',
+ name2 => 'delivLastName',
+ address => 'delivStreet',
+ city => 'delivSuburb',
+ postcode => 'delivPostCode',
+ state => 'delivState',
+ country => 'delivCountry',
+ email => 'emailAddress',
+ cardHolder => 'ccName',
+ cardType => 'ccType',
+ );
+
+
+# saves order and refresh to payment page
+sub req_order {
+ my ($class, $req) = @_;
+
+ my $cfg = $req->cfg;
+ my $cgi = $req->cgi;
+
+ $req->session->{cart} && @{$req->session->{cart}}
+ or return $class->req_cart($req, "Your cart is empty");
+
+ my $msg;
+ $class->_validate_cfg($req, \$msg)
+ or return $class->req_cart($req, $msg);
+
+ my @products;
+ my @items = $class->_build_items($req, \@products);
+
+ my $id;
+ if (($msg, $id) = $class->_need_logon($req, \@items, \@products)) {
+ return $class->_refresh_logon($req, $msg, $id);
+ }
+
+ # some basic validation, in case the user switched off javascript
+ my $cust_class = custom_class($cfg);
+
+ my %fields = BSE::TB::Order->valid_fields($cfg);
+ my %rules = BSE::TB::Order->valid_rules($cfg);
+
+ my %errors;
+ my %values;
+ for my $name (keys %fields) {
+ ($values{$name}) = $cgi->param($name);
+ }
+
+ my @required =
+ $cust_class->required_fields($cgi, $req->session->{custom}, $cfg);
+
+ for my $name (@required) {
+ $field_map{$name} and $name = $field_map{$name};
+
+ $fields{$name}{required} = 1;
+ }
+
+ dh_validate_hash(\%values, \%errors, { rules=>\%rules, fields=>\%fields },
+ $cfg, 'Shop Order Validation');
+ keys %errors
+ and return $class->req_checkout($req, \%errors, 1);
+
+ $class->_fillout_order($req, \%values, \@items, \$msg, 'payment')
+ or return $class->req_checkout($req, $msg, 1);
+
+ $req->session->{order_info} = \%values;
+ $req->session->{order_info_confirmed} = 1;
+
+ return BSE::Template->get_refresh("$ENV{SCRIPT_NAME}?a_show_payment=1", $req->cfg);
+}
+
+sub req_show_payment {
+ my ($class, $req, $errors) = @_;
+
+ $req->session->{order_info_confirmed}
+ or return $class->req_checkout($req, 'Please proceed via the checkout page');
+
+ my $cfg = $req->cfg;
+ my $cgi = $req->cgi;
+
+ $errors ||= {};
+ my $msg = $req->message($errors);
+
+ my $order_values = $req->session->{order_info}
+ or return $class->req_checkout($req, "You need to enter order information first");
+
+ my @pay_types = payment_types($cfg);
+ my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
+ my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
+ @payment_types or @payment_types = ( PAYMENT_CALLME );
+ @payment_types = sort { $a <=> $b } @payment_types;
+ my %payment_types = map { $_=> 1 } @payment_types;
+ my $payment;
+ $errors and $payment = $cgi->param('paymentType');
+ defined $payment or $payment = $payment_types[0];
+
+ my @products;
+ my @items = $class->_build_items($req, \@products);
+
+ my %acts;
+ %acts =
+ (
+ basic_tags(\%acts),
+ message => $msg,
+ msg => $msg,
+ order => [ \&tag_hash, $order_values ],
+ shop_cart_tags(\%acts, \@items, \@products, $req->session, $req->cgi,
+ $req->cfg, 'payment'),
+ ifMultPaymentTypes => @payment_types > 1,
+ checkedPayment => [ \&tag_checkedPayment, $payment, \%types_by_name ],
+ ifPayments => [ \&tag_ifPayments, \@payment_types, \%types_by_name ],
+ error_img => [ \&tag_error_img, $cfg, $errors ],
+ );
+ for my $type (@pay_types) {
+ my $id = $type->{id};
+ my $name = $type->{name};
+ $acts{"if${name}Payments"} = exists $payment_types{$id};
+ $acts{"if${name}FirstPayment"} = $payment_types[0] == $id;
+ $acts{"checkedIfFirst$name"} = $payment_types[0] == $id ? "checked " : "";
+ $acts{"checkedPayment$name"} = $payment == $id ? 'checked="checked" ' : "";
+ }
+
+ return $req->response('checkoutpay', \%acts);
+}
+
+my %nostore =
+ (
+ cardNumber => 1,
+ cardExpiry => 1,
+ );
+
+sub req_payment {
+ my ($class, $req, $errors) = @_;
+
+ $req->session->{order_info_confirmed}
+ or return $class->req_checkout($req, 'Please proceed via the checkout page');
+
+ my $order_values = $req->session->{order_info}
+ or return $class->req_checkout($req, "You need to enter order information first");
+
+
+ my $cgi = $req->cgi;
+ my $cfg = $req->cfg;
+ my $session = $req->session;
+
+ my @pay_types = payment_types($cfg);
+ my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
+ my %pay_types = map { $_->{id} => $_ } @pay_types;
+ my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
+ @payment_types or @payment_types = ( PAYMENT_CALLME );
+ @payment_types = sort { $a <=> $b } @payment_types;
+ my %payment_types = map { $_=> 1 } @payment_types;
+
+ my $paymentType = $cgi->param('paymentType');
+ defined $paymentType or $paymentType = $payment_types[0];
+ $payment_types{$paymentType}
+ or return $class->req_show_payment($req, { paymentType => "Invalid payment type" } , 1);
+
+ my @required;
+ push @required, @{$pay_types{$paymentType}{require}};
+
+ my %fields = BSE::TB::Order->valid_payment_fields($cfg);
+ my %rules = BSE::TB::Order->valid_payment_rules($cfg);
+ for my $field (@required) {
+ if (exists $fields{$field}) {
+ $fields{$field}{required} = 1;
+ }
+ else {
+ $fields{$field} = { description => $field, required=> 1 };
+ }
+ }
+
+ my %errors;
+ dh_validate($cgi, \%errors, { rules => \%rules, fields=>\%fields },
+ $cfg, 'Shop Order Validation');
+ keys %errors
+ and return $class->req_show_payment($req, \%errors);
+
+ my @products;
+ my @items = $class->_build_items($req, \@products);
+
+ for my $field (@required) {
+ unless ($nostore{$field}) {
+ ($order_values->{$field}) = $cgi->param($field);
+ }
+ }
+
+ $order_values->{filled} = 0;
+ $order_values->{paidFor} = 0;
+
+ my $cust_class = custom_class($req->cfg);
+ eval {
+ my %custom = %{$session->{custom}};
+ $cust_class->order_save($cgi, $order_values, \@items, \@products,
+ \%custom, $cfg);
+ $session->{custom} = \%custom;
+ };
+ if ($@) {
+ return $class->req_checkout($req, $@, 1);
+ }
+
+ my @columns = BSE::TB::Order->columns;
+ my %columns;
+ @columns{@columns} = @columns;
+
+ for my $col (@columns) {
+ defined $order_values->{$col} or $order_values->{$col} = '';
+ }
+
+ $order_values->{paymentType} = $paymentType;
+
+ my @data = @{$order_values}{@columns};
+ shift @data;
+ my $order = BSE::TB::Orders->add(@data)
+ or die "Cannot add order";
+
+ my @dbitems;
+ my %subscribing_to;
+ my @item_cols = BSE::TB::OrderItem->columns;
+ for my $row_num (0..$#items) {
+ my $item = $items[$row_num];
+ my $product = $products[$row_num];
+ $item->{orderId} = $order->{id};
+ $item->{max_lapsed} = 0;
+ if ($product->{subscription_id} != -1) {
+ my $sub = $product->subscription;
+ $item->{max_lapsed} = $sub->{max_lapsed} if $sub;
+ }
+ my @data = @{$item}{@item_cols};
+
+ shift @data;
+ push(@dbitems, BSE::TB::OrderItems->add(@data));
+
+ my $sub = $product->subscription;
+ if ($sub) {
+ $subscribing_to{$sub->{text_id}} = $sub;
+ }
+ }
+
+ my $ccprocessor = $cfg->entry('shop', 'cardprocessor');
+ if ($paymentType == PAYMENT_CC && $ccprocessor) {
+ my $cc_class = credit_card_class($cfg);
+
+ $order->{ccOnline} = 1;
+
+ my $ccNumber = $cgi->param('cardNumber');
+ my $ccExpiry = $cgi->param('cardExpiry');
+ $ccExpiry =~ m!^(\d+)\D(\d+)$! or die;
+ my ($month, $year) = ($1, $2);
+ $year > 2000 or $year += 2000;
+ my $expiry = sprintf("%04d%02d", $year, $month);
+ my $verify = $cgi->param('cardVerify');
+ defined $verify or $verify = '';
+ my $result = $cc_class->payment(orderno=>$order->{id},
+ amount => $order->{total},
+ cardnumber => $ccNumber,
+ expirydate => $expiry,
+ cvv => $verify,
+ ipaddress => $ENV{REMOTE_ADDR});
+ unless ($result->{success}) {
+ use Data::Dumper;
+ print STDERR Dumper($result);
+ # failed, back to payments
+ $order->{ccSuccess} = 0;
+ $order->{ccStatus} = $result->{statuscode};
+ $order->{ccStatus2} = 0;
+ $order->{ccStatusText} = $result->{error};
+ $order->{ccTranId} = '';
+ $order->save;
+ $errors{cardNumber} = $result->{error};
+ $session->{order_work} = $order->{id};
+ return $class->req_show_payment($req, \%errors);
+ }
+
+ $order->{ccSuccess} = 1;
+ $order->{ccReceipt} = $result->{receipt};
+ $order->{ccStatus} = 0;
+ $order->{ccStatus2} = 0;
+ $order->{ccStatusText} = '';
+ $order->{ccTranId} = $result->{transactionid};
+ $order->{paidFor} = 1;
+ $order->save;
+ }
+
+ # set the order displayed by orderdone
+ $session->{order_completed} = $order->{id};
+ $session->{order_completed_at} = time;
+
+ my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
+ $class->_send_order($req, $order, \@dbitems, \@products, $noencrypt,
+ \%subscribing_to);
+
+ # empty the cart ready for the next order
+ delete @{$session}{qw/order_info order_info_confirmed cart order_work/};
+
+ return BSE::Template->get_refresh("$ENV{SCRIPT_NAME}?a_orderdone=1", $req->cfg);
+}
+
+sub req_orderdone {
+ my ($class, $req) = @_;
+
+ my $session = $req->session;
+ my $cfg = $req->cfg;
+
+ my $id = $session->{order_completed};
+ my $when = $session->{order_completed_at};
+ $id && defined $when && time < $when + 500
+ or return $class->req_cart($req);
+
+ my $order = BSE::TB::Orders->getByPkey($id)
+ or return $class->req_cart($req);
+ my @items = $order->items;
+
+ my @products = map { Products->getByPkey($_->{productId}) } @items;
+
+ my $cust_class = custom_class($req->cfg);
+
+ my @pay_types = payment_types($cfg);
+ my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
+ my %pay_types = map { $_->{id} => $_ } @pay_types;
+ my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
+
+ my $item_index = -1;
+ my @options;
+ my $option_index;
+ my %acts;
+ %acts =
+ (
+ $cust_class->purchase_actions(\%acts, \@items, \@products,
+ $session->{custom}, $cfg),
+ BSE::Util::Tags->static(\%acts, $cfg),
+ iterate_items_reset => sub { $item_index = -1; },
+ iterate_items =>
+ sub {
+ if (++$item_index < @items) {
+ $option_index = -1;
+ @options = cart_item_opts($items[$item_index],
+ $products[$item_index]);
+ return 1;
+ }
+ return 0;
+ },
+ item=> sub { escape_html($items[$item_index]{$_[0]}); },
+ product =>
+ sub {
+ my $value = $products[$item_index]{$_[0]};
+ defined $value or $value = '';
+
+ escape_html($value);
+ },
+ extended =>
+ sub {
+ my $what = $_[0] || 'retailPrice';
+ $items[$item_index]{units} * $items[$item_index]{$what};
+ },
+ order => sub { escape_html($order->{$_[0]}) },
+ money =>
+ sub {
+ my ($func, $args) = split ' ', $_[0], 2;
+ $acts{$func} || return "<: money $_[0] :>";
+ return sprintf("%.02f", $acts{$func}->($args)/100);
+ },
+ _format =>
+ sub {
+ my ($value, $fmt) = @_;
+ if ($fmt =~ /^m(\d+)/) {
+ return sprintf("%$1s", sprintf("%.2f", $value/100));
+ }
+ elsif ($fmt =~ /%/) {
+ return sprintf($fmt, $value);
+ }
+ },
+ iterate_options_reset => sub { $option_index = -1 },
+ iterate_options => sub { ++$option_index < @options },
+ option => sub { escape_html($options[$option_index]{$_[0]}) },
+ ifOptions => sub { @options },
+ options => sub { nice_options(@options) },
+ ifPayment => [ \&tag_ifPayment, $order->{paymentType}, \%types_by_name ],
+ #ifSubscribingTo => [ \&tag_ifSubscribingTo, \%subscribing_to ],
+ );
+ for my $type (@pay_types) {
+ my $id = $type->{id};
+ my $name = $type->{name};
+ $acts{"if${name}Payment"} = $order->{paymentType} == $id;
+ }
+
+ return $req->response('checkoutfinal', \%acts);
+}
+
+sub tag_ifPayment {
+ my ($payment, $types_by_name, $args) = @_;
+
+ my $type = $args;
+ if ($type !~ /^\d+$/) {
+ return '' unless exists $types_by_name->{$type};
+ $type = $types_by_name->{$type};
+ }
+
+ return $payment == $type;
+}
+
+
+sub _validate_cfg {
+ my ($class, $req, $rmsg) = @_;
+
+ my $cfg = $req->cfg;
+ my $from = $cfg->entry('shop', 'from', $Constants::SHOP_FROM);
+ unless ($from && $from =~ /.\@./) {
+ $$rmsg = "Configuration error: shop from address not set";
+ return;
+ }
+ my $toEmail = $cfg->entry('shop', 'to_email', $Constants::SHOP_TO_EMAIL);
+ unless ($toEmail && $toEmail =~ /.\@./) {
+ $$rmsg = "Configuration error: shop to_email address not set";
+ return;
+ }
+
+ return 1;
+}
+
+sub req_purchase {
+ my ($class, $req) = @_;
+
+ my $cfg = $req->cfg;
+ my $cgi = $req->cgi;
+ my $session = $req->session;
+
+ my $msg;
+ $class->_validate_cfg($req, \$msg)
+ or return $class->req_cart($req, $msg);
+
+ # some basic validation, in case the user switched off javascript
+ my $cust_class = custom_class($cfg);
+ my @required =
+ $cust_class->required_fields($cgi, $session->{custom}, $cfg);
+
+ my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
+
+ my @pay_types = payment_types($cfg);
+ my %pay_types = map { $_->{id} => $_ } @pay_types;
+ my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
+ #use Data::Dumper;
+ #print STDERR Dumper \%pay_types;
+ my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
+ if ($noencrypt) {
+ @payment_types = grep $_ ne PAYMENT_CC, @payment_types;
+ @payment_types or @payment_types = ( PAYMENT_CALLME );
+ }
+ else {
+ @payment_types or @payment_types = ( PAYMENT_CC );
+ }
+ @payment_types = sort { $a <=> $b } @payment_types;
+ my %payment_types = map { $_=> 1 } @payment_types;
+
+ my $paymentType = $cgi->param('paymentType');
+ defined $paymentType or $paymentType = $payment_types[0];
+ $payment_types{$paymentType}
+ or return $class->req_checkout($req, "Invalid payment type", 1);
+
+ push @required, @{$pay_types{$paymentType}{require}};
+
+ for my $field (@required) {
+ my $display = $cfg->entry('shop', "display_$field", $field);
+ defined($cgi->param($field)) && length($cgi->param($field))
+ or return $class->req_checkout($req, "Field $display is required", 1);
+ }
+ defined($cgi->param('email')) && $cgi->param('email') =~ /.\@./
+ or return $class->req_checkout($req, "Please enter a valid email address", 1);
+ if ($paymentType == PAYMENT_CC) {
+ defined($cgi->param('cardNumber')) && $cgi->param('cardNumber') =~ /^\d+$/
+ or return $class->req_checkout($req, "Please enter a credit card number", 1);
+ }
+
+ # map some form fields to order field names
+ my %field_map =
+ (
+ name1 => 'delivFirstName',
+ name2 => 'delivLastName',
+ address => 'delivStreet',
+ city => 'delivSuburb',
+ postcode => 'delivPostCode',
+ state => 'delivState',
+ country => 'delivCountry',
+ email => 'emailAddress',
+ cardHolder => 'ccName',
+ cardType => 'ccType',
+ );
+ # paranoia, don't store these
+ my %nostore =
+ (
+ cardNumber => 1,
+ cardExpiry => 1,
+ );
+ my %order;
+ my @cart = @{$session->{cart}};
+ @cart or return $class->req_cart($req, 'You have no items in your shopping cart');
+
+ # so we can quickly check for columns
+ my @columns = BSE::TB::Order->columns;
+ my %columns;
+ @columns{@columns} = @columns;
+
+ for my $field ($req->param()) {
+ $order{$field_map{$field} || $field} = $req->param($field)
+ unless $nostore{$field};
+ }
+
+ my $ccNumber = $req->param('cardNumber');
+ defined $ccNumber or $ccNumber = '';
+ my $ccExpiry = $req->param('cardExpiry');
+ defined $ccExpiry or $ccExpiry = '';
+ my $affiliate_code = $session->{affiliate_code};
+ defined $affiliate_code && length $affiliate_code
+ or $affiliate_code = $cgi->param('affiliate_code');
+ defined $affiliate_code or $affiliate_code = '';
+ $order{affiliate_code} = $affiliate_code;
+
+ use Digest::MD5 'md5_hex';
+ $ccNumber =~ tr/0-9//cd;
+ $order{ccNumberHash} = md5_hex($ccNumber);
+ $order{ccExpiryHash} = md5_hex($ccExpiry);
+
+ # work out totals
+ $order{total} = 0;
+ $order{gst} = 0;
+ $order{wholesale} = 0;
+ $order{shipping_cost} = 0;
+ my @products;
+ my $today = now_sqldate();
+ for my $item (@cart) {
+ my $product = Products->getByPkey($item->{productId});
+ # double check that it's still a valid product
+ if (!$product) {
+ return $class->req_cart($req, "Product $item->{productId} not found");
+ }
+ else {
+ (my $comp_release = $product->{release}) =~ s/ .*//;
+ (my $comp_expire = $product->{expire}) =~ s/ .*//;
+ $comp_release le $today
+ or return $class->req_cart($req, "'$product->{title}' has not been released yet");
+ $today le $comp_expire
+ or return $class->req_cart("'$product->{title}' has expired");
+ $product->{listed}
+ or return $class->req_cart("'$product->{title}' not available");
+ }
+ push(@products, $product); # used in page rendering
+ @$item{qw/price wholesalePrice gst/} =
+ @$product{qw/retailPrice wholesalePrice gst/};
+ $order{total} += $item->{price} * $item->{units};
+ $order{wholesale} += $item->{wholesalePrice} * $item->{units};
+ $order{gst} += $item->{gst} * $item->{units};
+ }
+
+ if (my ($msg, $id) = $class->_need_logon($req, \@cart, \@products)) {
+ return $class->_refresh_logon($req, $msg, $id);
+ }
+
+ $order{orderDate} = now_sqldatetime;
+ $order{paymentType} = $paymentType;
+ ++$session->{changed};
+
+ # blank anything else
+ for my $column (@columns) {
+ defined $order{$column} or $order{$column} = '';
+ }
+ # make sure the user can't set these behind our backs
+ $order{filled} = 0;
+ $order{paidFor} = 0;
+
+ my $user = $req->siteuser;
+ if ($user) {
+ $order{userId} = $user->{userId};
+ $order{siteuser_id} = $user->{id};
+ }
+ else {
+ $order{userId} = '';
+ $order{siteuser_id} = -1;
+ }
+
+ # this should be hard to guess
+ $order{randomId} = md5_hex(time().rand().{}.$$);
+
+ # check if a customizer has anything to do
+ # if it sets shipping cost it must also update the total
+ eval {
+ my %custom = %{$session->{custom}};
+ $cust_class->order_save($cgi, \%order, \@cart, \@products,
+ \%custom, $cfg);
+ $session->{custom} = \%custom;
+ };
+ if ($@) {
+ return $class->req_checkout($req, $@, 1);
+ }
+
+ $order{total} += $cust_class->total_extras(\@cart, \@products,
+ $session->{custom}, $cfg, 'final');
+
+ my %subscribing_to;
+
+ # load up the database
+ my @data = @order{@columns};
+ shift @data; # lose the dummy id
+ my $order = BSE::TB::Orders->add(@data)
+ or die "Cannot add order";
+ my @items;
+ my @item_cols = BSE::TB::OrderItem->columns;
+ my @prod_xfer = qw/title summary subscription_id subscription_period/;
+ for my $row_num (0..$#cart) {
+ my $row = $cart[$row_num];
+ my $product = $products[$row_num];
+ $row->{orderId} = $order->{id};
+
+ # store product data too
+ @$row{@prod_xfer} = @{$product}{@prod_xfer};
+
+ # store the lapsed value, this prevents future changes causing
+ # variation of the expiry date
+ $row->{max_lapsed} = 0;
+ if ($product->{subscription_id} != -1) {
+ my $sub = $product->subscription;
+ $row->{max_lapsed} = $sub->{max_lapsed} if $sub;
+ }
+
+ my @data = @$row{@item_cols};
+
+ shift @data;
+ push(@items, BSE::TB::OrderItems->add(@data));
+
+ my $sub = $product->subscription;
+ if ($sub) {
+ $subscribing_to{$sub->{text_id}} = $sub;
+ }
+ }
+
+ if ($user) {
+ $user->recalculate_subscriptions($cfg);
+ }
+
+ my $item_index = -1;
+ my @options;
+ my $option_index;
+ my %acts;
+ %acts =
+ (
+ $cust_class->purchase_actions(\%acts, \@items, \@products,
+ $session->{custom}, $cfg),
+ BSE::Util::Tags->static(\%acts, $cfg),
+ iterate_items_reset => sub { $item_index = -1; },
+ iterate_items =>
+ sub {
+ if (++$item_index < @items) {
+ $option_index = -1;
+ @options = cart_item_opts($items[$item_index],
+ $products[$item_index]);
+ return 1;
+ }
+ return 0;
+ },
+ item=> sub { escape_html($items[$item_index]{$_[0]}); },
+ product =>
+ sub {
+ my $value = $products[$item_index]{$_[0]};
+ defined $value or $value = '';
+
+ escape_html($value);
+ },
+ extended =>
+ sub {
+ my $what = $_[0] || 'retailPrice';
+ $items[$item_index]{units} * $items[$item_index]{$what};
+ },
+ order => sub { escape_html($order->{$_[0]}) },
+ money =>
+ sub {
+ my ($func, $args) = split ' ', $_[0], 2;
+ $acts{$func} || return "<: money $_[0] :>";
+ return sprintf("%.02f", $acts{$func}->($args)/100);
+ },
+ _format =>
+ sub {
+ my ($value, $fmt) = @_;
+ if ($fmt =~ /^m(\d+)/) {
+ return sprintf("%$1s", sprintf("%.2f", $value/100));
+ }
+ elsif ($fmt =~ /%/) {
+ return sprintf($fmt, $value);
+ }
+ },
+ iterate_options_reset => sub { $option_index = -1 },
+ iterate_options => sub { ++$option_index < @options },
+ option => sub { escape_html($options[$option_index]{$_[0]}) },
+ ifOptions => sub { @options },
+ options => sub { nice_options(@options) },
+ ifPayment => [ \&tag_ifPayments, $order->{paymentType}, \%types_by_name ],
+ #ifSubscribingTo => [ \&tag_ifSubscribingTo, \%subscribing_to ],
+ );
+ for my $type (@pay_types) {
+ my $id = $type->{id};
+ my $name = $type->{name};
+ $acts{"if${name}Payment"} = $order->{paymentType} == $id;
+ }
+ send_order($order, \@items, \@products, $noencrypt, \%subscribing_to);
+ $session->{cart} = []; # empty the cart
+
+ return req->response('checkoutfinal', \%acts);
+}
+
+sub req_recalc {
+ my ($class, $req) = @_;
+ $class->update_quantities($req);
+ $req->session->{order_info_confirmed} = 0;
+ return $class->req_cart($req);
+}
+
+sub req_recalculate {
+ my ($class, $req) = @_;
+
+ return $class->req_recalc($req);
+}
+
+sub _send_order {
+ my ($class, $req, $order, $items, $products, $noencrypt,
+ $subscribing_to) = @_;
+
+ my $cfg = $req->cfg;
+ my $cgi = $req->cgi;
+
+ my $crypto_class = $Constants::SHOP_CRYPTO;
+ my $signing_id = $Constants::SHOP_SIGNING_ID;
+ my $pgp = $Constants::SHOP_PGP;
+ my $pgpe = $Constants::SHOP_PGPE;
+ my $gpg = $Constants::SHOP_GPG;
+ my $passphrase = $Constants::SHOP_PASSPHRASE;
+ my $from = $cfg->entry('shop', 'from', $Constants::SHOP_FROM);
+ my $toName = $cfg->entry('shop', 'to_name', $Constants::SHOP_TO_NAME);
+ my $toEmail = $cfg->entry('shop', 'to_email', $Constants::SHOP_TO_EMAIL);
+ my $subject = $cfg->entry('shop', 'subject', $Constants::SHOP_MAIL_SUBJECT);
+
+ my $session = $req->session;
+ my %extras = $cfg->entriesCS('extra tags');
+ for my $key (keys %extras) {
+ # follow any links
+ my $data = $cfg->entryVar('extra tags', $key);
+ $extras{$key} = sub { $data };
+ }
+
+ my $item_index = -1;
+ my @options;
+ my $option_index;
+ my %acts;
+ %acts =
+ (
+ %extras,
+ custom_class($cfg)
+ ->order_mail_actions(\%acts, $order, $items, $products,
+ $session->{custom}, $cfg),
+ BSE::Util::Tags->static(\%acts, $cfg),
+ iterate_items_reset => sub { $item_index = -1; },
+ iterate_items =>
+ sub {
+ if (++$item_index < @$items) {
+ $option_index = -1;
+ @options = cart_item_opts($items->[$item_index],
+ $products->[$item_index]);
+ return 1;
+ }
+ return 0;
+ },
+ item=> sub { $items->[$item_index]{$_[0]}; },
+ product =>
+ sub {
+ my $value = $products->[$item_index]{$_[0]};
+ defined($value) or $value = '';
+ $value;
+ },
+ order => sub { $order->{$_[0]} },
+ extended =>
+ sub {
+ $items->[$item_index]{units} * $items->[$item_index]{$_[0]};
+ },
+ _format =>
+ sub {
+ my ($value, $fmt) = @_;
+ if ($fmt =~ /^m(\d+)/) {
+ return sprintf("%$1s", sprintf("%.2f", $value/100));
+ }
+ elsif ($fmt =~ /%/) {
+ return sprintf($fmt, $value);
+ }
+ elsif ($fmt =~ /^\d+$/) {
+ return substr($value . (" " x $fmt), 0, $fmt);
+ }
+ else {
+ return $value;
+ }
+ },
+ iterate_options_reset => sub { $option_index = -1 },
+ iterate_options => sub { ++$option_index < @options },
+ option => sub { escape_html($options[$option_index]{$_[0]}) },
+ ifOptions => sub { @options },
+ options => sub { nice_options(@options) },
+ with_wrap => \&tag_with_wrap,
+ ifSubscribingTo => [ \&tag_ifSubscribingTo, $subscribing_to ],
+ );
+
+ my $mailer = BSE::Mail->new(cfg=>$cfg);
+ # ok, send some email
+ my $confirm = BSE::Template->get_page('mailconfirm', $cfg, \%acts);
+ my $email_order = $cfg->entryBool('shop', 'email_order', $Constants::SHOP_EMAIL_ORDER);
+ if ($email_order) {
+ unless ($noencrypt) {
+ $acts{cardNumber} = $cgi->param('cardNumber');
+ $acts{cardExpiry} = $cgi->param('cardExpiry');
+ }
+ my $ordertext = BSE::Template->get_page('mailorder', $cfg, \%acts);
+
+ my $send_text;
+ if ($noencrypt) {
+ $send_text = $ordertext;
+ }
+ else {
+ eval "use $crypto_class";
+ !$@ or die $@;
+ my $encrypter = $crypto_class->new;
+
+ my $debug = $cfg->entryBool('debug', 'mail_encryption', 0);
+ my $sign = $cfg->entryBool('basic', 'sign', 1);
+
+ # encrypt and sign
+ my %opts =
+ (
+ sign=> $sign,
+ passphrase=> $passphrase,
+ stripwarn=>1,
+ debug=>$debug,
+ );
+
+ $opts{secretkeyid} = $signing_id if $signing_id;
+ $opts{pgp} = $pgp if $pgp;
+ $opts{gpg} = $gpg if $gpg;
+ $opts{pgpe} = $pgpe if $pgpe;
+ my $recip = "$toName $toEmail";
+
+ $send_text = $encrypter->encrypt($recip, $ordertext, %opts )
+ or die "Cannot encrypt ", $encrypter->error;
+ }
+ $mailer->send(to=>$toEmail, from=>$from, subject=>'New Order '.$order->{id},
+ body=>$send_text)
+ or print STDERR "Error sending order to admin: ",$mailer->errstr,"\n";
+ }
+ $mailer->send(to=>$order->{emailAddress}, from=>$from,
+ subject=>$subject . " " . localtime,
+ body=>$confirm)
+ or print STDERR "Error sending order to customer: ",$mailer->errstr,"\n";
+}
+
+sub tag_with_wrap {
+ my ($args, $text) = @_;
+
+ my $margin = $args =~ /^\d+$/ && $args > 30 ? $args : 70;
+
+ require Text::Wrap;
+ # do it twice to prevent a warning
+ $Text::Wrap::columns = $margin;
+ $Text::Wrap::columns = $margin;
+
+ return Text::Wrap::fill('', '', split /\n/, $text);
+}
+
+sub _refresh_logon {
+ my ($class, $req, $msg, $msgid, $r) = @_;
+
+ my $securlbase = $req->cfg->entryVar('site', 'secureurl');
+ my $url = $securlbase."/cgi-bin/user.pl";
+
+ $r ||= $securlbase."/cgi-bin/shop.pl?checkout=1";
+
+ my %parms;
+ $parms{r} = $r;
+ $parms{message} = $msg if $msg;
+ $parms{mid} = $msgid if $msgid;
+ $url .= "?" . join("&", map "$_=".escape_uri($parms{$_}), keys %parms);
+
+ return BSE::Template->get_refresh($url, $req->cfg);
+}
+
+sub _need_logon {
+ my ($class, $req, $cart, $cart_prods) = @_;
+
+ return need_logon($req->cfg, $cart, $cart_prods, $req->session, $req->cgi);
+}
+
+sub tag_checkedPayment {
+ my ($payment, $types_by_name, $args) = @_;
+
+ my $type = $args;
+ if ($type !~ /^\d+$/) {
+ return '' unless exists $types_by_name->{$type};
+ $type = $types_by_name->{$type};
+ }
+
+ return $payment == $type ? 'checked="checked"' : '';
+}
+
+sub tag_ifPayments {
+ my ($enabled, $types_by_name, $args) = @_;
+
+ my $type = $args;
+ if ($type !~ /^\d+$/) {
+ return '' unless exists $types_by_name->{$type};
+ $type = $types_by_name->{$type};
+ }
+
+ my @found = grep $_ == $type, @$enabled;
+
+ return scalar @found;
+}
+
+sub update_quantities {
+ my ($class, $req) = @_;
+
+ my $session = $req->session;
+ my $cgi = $req->cgi;
+ my $cfg = $req->cfg;
+ my @cart = @{$session->{cart} || []};
+ for my $index (0..$#cart) {
+ my $new_quantity = $cgi->param("quantity_$index");
+ if (defined $new_quantity) {
+ if ($new_quantity =~ /^\s*(\d+)/) {
+ $cart[$index]{units} = $1;
+ }
+ elsif ($new_quantity =~ /^\s*$/) {
+ $cart[$index]{units} = 0;
+ }
+ }
+ }
+ @cart = grep { $_->{units} != 0 } @cart;
+ $session->{cart} = \@cart;
+ $session->{custom} ||= {};
+ my %custom_state = %{$session->{custom}};
+ custom_class($cfg)->recalc($cgi, \@cart, [], \%custom_state, $cfg);
+ $session->{custom} = \%custom_state;
+}
+
+sub _build_items {
+ my ($class, $req, $products) = @_;
+
+ my $session = $req->session;
+ $session->{cart}
+ or return;
+ my @msgs;
+ my @cart = @{$req->session->{cart}}
+ or return;
+ my @items;
+ my @prodcols = Product->columns;
+ my @newcart;
+ my $today = now_sqldate();
+ for my $item (@cart) {
+ my %work = %$item;
+ my $product = Products->getByPkey($item->{productId});
+ if ($product) {
+ (my $comp_release = $product->{release}) =~ s/ .*//;
+ (my $comp_expire = $product->{expire}) =~ s/ .*//;
+ $comp_release le $today
+ or do { push @msgs, "'$product->{title}' has not been released yet";
+ next; };
+ $today le $comp_expire
+ or do { push @msgs, "'$product->{title}' has expired"; next; };
+ $product->{listed}
+ or do { push @msgs, "'$product->{title}' not available"; next; };
+
+ for my $col (@prodcols) {
+ $work{$col} = $product->{$col} unless exists $work{$col};
+ }
+ $work{extended_retailPrice} = $work{units} * $work{retailPrice};
+ $work{extended_gst} = $work{units} * $work{gst};
+ $work{extended_wholesale} = $work{units} * $work{wholesalePrice};
+
+ push @newcart, \%work;
+ push @$products, $product;
+ }
+ }
+
+ # we don't use these for anything for now
+ #if (@msgs) {
+ # @$rmsg = @msgs;
+ #}
+
+ return @newcart;
+}
+
+sub _fillout_order {
+ my ($class, $req, $values, $items, $rmsg, $how) = @_;
+
+ my $session = $req->session;
+ my $cfg = $req->cfg;
+ my $cgi = $req->cgi;
+
+ my $total = 0;
+ my $total_gst = 0;
+ my $total_wholesale = 0;
+ for my $item (@$items) {
+ $total += $item->{extended_retailPrice};
+ $total_gst += $item->{extended_gst};
+ $total_wholesale += $item->{extended_wholesale};
+ }
+ $values->{total} = $total;
+ $values->{gst} = $total_gst;
+ $values->{wholesale} = $total_wholesale;
+ $values->{shipping_cost} = 0;
+
+ my $cust_class = custom_class($cfg);
+
+ # if it sets shipping cost it must also update the total
+ eval {
+ my %custom = %{$session->{custom}};
+ $cust_class->order_save($cgi, $values, $items, $items,
+ \%custom, $cfg);
+ $session->{custom} = \%custom;
+ };
+ if ($@) {
+ $$rmsg = $@;
+ return;
+ }
+
+ $values->{total} +=
+ $cust_class->total_extras($items, $items,
+ $session->{custom}, $cfg, $how);
+
+ my $affiliate_code = $session->{affiliate_code};
+ defined $affiliate_code && length $affiliate_code
+ or $affiliate_code = $cgi->param('affiliate_code');
+ defined $affiliate_code or $affiliate_code = '';
+ $values->{affiliate_code} = $affiliate_code;
+
+ my $user = $req->siteuser;
+ if ($user) {
+ $values->{userId} = $user->{userId};
+ $values->{siteuser_id} = $user->{id};
+ }
+ else {
+ $values->{userId} = '';
+ $values->{siteuser_id} = -1;
+ }
+
+ $values->{orderDate} = now_sqldatetime;
+
+ # this should be hard to guess
+ $values->{randomId} ||= md5_hex(time().rand().{}.$$);
+
+ return 1;
+}
+
+sub action_prefix { '' }
+
+1;
--- /dev/null
+package DevHelp::Payments::Inpho;
+use strict;
+use Carp 'confess';
+use LWP::UserAgent;
+use DevHelp::HTML;
+
+sub new {
+ my ($class, $cfg) = @_;
+
+ return bless { cfg => $cfg }, $class;
+}
+
+sub payment {
+ my ($self, %args) = @_;
+
+ my $cfg = $self->{cfg};
+
+ my $conf = $self->_conf;
+
+ for my $name (qw/orderno amount cardnumber expirydate ipaddress cvv/) {
+ defined $args{$name}
+ or confess "Missing $name argument";
+ }
+
+ my ($expyear, $expmonth) = $args{expirydate} =~ /^(\d\d\d\d)(\d\d)$/
+ or return
+ {
+ success => 0,
+ statuscode => 1,
+ error => 'Invalid expiry date',
+ };
+
+ my $currency = $args{currency};
+ if (defined $currency && $currency ne 'AUD') {
+ return
+ {
+ success => 0,
+ error => 'Unknown currency',
+ statuscode => 5,
+ };
+ }
+ $currency = 'AUD';
+
+ my $url = $conf->{url};
+ my %url_args =
+ (
+ PAN => $args{cardnumber},
+ expiry_month => $expmonth,
+ expiry_year => sprintf("%02d", $expyear % 100),
+ cardVerificationData => $args{cvv},
+ currency_type => $currency,
+ currency_amount => $args{amount},
+ cardholderIP => $args{ipaddress},
+ user_name => $conf->{user},
+ user_password => $conf->{password},
+ );
+ $url .= '?' . join("&", map "$_=" . escape_uri($url_args{$_}), keys %url_args);
+
+ my $ua = LWP::UserAgent->new;
+ my $response = $ua->get($url);
+ unless ($response->is_success) {
+ return
+ {
+ success => 0,
+ statuscode => 99,
+ error => 'Error making request: '.$response->status_line,
+ };
+ }
+ my $content = $response->content;
+ if ($content eq '0') {
+ return
+ {
+ success => 0,
+ statuscode => 98,
+ error => "Invalid request or invalid merchant user/password",
+ };
+ }
+
+ # extract the fields from it
+ my %result;
+ for my $line (split /,/, $content) {
+ if ($line =~ /^(\w+)=(.*)$/) {
+ $result{$1} = $2;
+ }
+ }
+
+ if ($result{SUMMARY_RESPONSE_CODE} != 0) {
+ return
+ {
+ success => 0,
+ statuscode => $result{RESPONSE_CODE},
+ error => $result{RESPONSE_TEXT},
+ };
+ }
+
+ # should be success
+ return
+ {
+ success => 1,
+ statuscode => $result{RESPONSE_CODE},
+ error => $result{RESPONSE_TEXT},
+ receipt => $result{RECEIPT_NUMBER},
+ transactionid => $result{ORDER_NUMBER},
+ };
+}
+
+my %norm_defs =
+ (
+ url => 'https://extranet.inpho.com.au/cc_ssl/process',
+ );
+
+my %test_defs =
+ (
+ );
+
+sub _conf {
+ my ($self) = @_;
+
+ my $cfg = $self->{cfg};
+ my $test = $cfg->entryBool('inpho', 'test');
+ my $prefix = $test ? 'test_' : '';
+ my $defs = $test ? \%test_defs : \%norm_defs;
+ my %conf;
+ for my $key (qw(url user password)) {
+ my $value = $cfg->entry('inpho', $prefix.$key, $defs->{$key});
+ defined $value
+ or confess "Key $prefix$key not defined in [inpho] for credit card processing";
+ $conf{$key} = $value;
+ }
+
+ \%conf;
+}
+
+1;
--- /dev/null
+package DevHelp::Payments::Test;
+use strict;
+use Carp 'confess';
+
+sub new {
+ my ($class, $cfg) = @_;
+
+ return bless { cfg => $cfg }, $class;
+}
+
+sub payment {
+ my ($self, %args) = @_;
+
+ my $cfg = $self->{cfg};
+
+ for my $name (qw/orderno amount cardnumber expirydate ipaddress cvv/) {
+ defined $args{$name}
+ or confess "Missing $name argument";
+ }
+
+ my $currency = $args{currency};
+ if (defined $currency && $currency ne 'AUD') {
+ return
+ {
+ success => 0,
+ error => 'Unknown currency',
+ statuscode => 5,
+ };
+ }
+
+ if (my $result = $cfg->entry('test payments', 'result')) {
+ my ($code, $error) = split /;/, $result, 2;
+ return
+ {
+ success => 0,
+ statuscode => $code,
+ error => $error,
+ };
+ }
+
+ my ($expyear, $expmonth) = $args{expirydate} =~ /^(\d\d\d\d)(\d\d)$/
+ or return
+ {
+ success => 0,
+ statuscode => 1,
+ error => 'Invalid expiry date',
+ };
+
+ my ($nowyear, $nowmonth) = (localtime)[5, 4];
+ $nowyear += 1900;
+ ++$nowmonth;
+
+ if ($expyear < $nowyear || $expyear == $nowyear && $expmonth < $nowmonth) {
+ return
+ {
+ success => 0,
+ statuscode => 1,
+ error => 'Card expired',
+ };
+ }
+
+ unless ($args{amount} =~ /^\d+$/ && $args{amount} > 0) {
+ return
+ {
+ success => 0,
+ statuscode => 2,
+ error => 'Invalid amount',
+ };
+ }
+
+ $args{cardnumber} =~ tr/0-9//cd;
+ if ($args{cardnumber} eq '4111111111111100') {
+ return
+ {
+ success => 1,
+ statuscode => 0,
+ error => '',
+ receipt => "R$args{orderno}",
+ transactionid => '',
+ };
+ }
+ elsif ($args{cardnumber} =~ /(\d\d)$/) {
+ return
+ {
+ success => 0,
+ statuscode => 0+$1,
+ error => "Synthetic error $1",
+ };
+ }
+}
+
+1;
+
+=head1 NAME
+
+DevHelp::Payments::Test - test payments driver
+
+=head1 SYNOPSIS
+
+ my $obj = DevHelp::Payments::Test->new($cfg);
+
+ my $result = $obj->payment(orderno=>$order_number,
+ amount => $amount_in_cents,
+ cardnumber => $cc_number,
+ expirydate => $yyyymm,
+ ipaddress => $user_ip,
+ cvv => $cvv);
+ if ($result->{success}) {
+ print "Receipt: $result->{receipt}\n";
+ }
+ else {
+ print "Error: $result->{error}\n";
+ }
+
+=head1 TESTING
+
+This module provides mechanisms for testing credit card transactions.
+
+To use it add
+
+ cardprocessor=DevHelp::Payments::Test
+
+to the [shop] section of bse.cfg (or an includes config file.)
+
+The amount, currency type, and card expiry date are all validated.
+
+If the credit card number supplied is: 4111111111111100 the
+transaction will succeed.
+
+Otherwise the last 2 digits of the credit card number are used to
+synthesize an error.
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=back
+
{
ccexpiry => 1,
},
+ creditcardexpirysingle =>
+ {
+ ccexpirysingle => 1,
+ },
+ creditcardcvv =>
+ {
+ match => qr/^(\d){3,4}$/,
+ error => '$n is the 3 or 4 digit code on the back of your card',
+ },
miaa =>
{
match => qr/^\s*\d{1,6}\s*$/,
last RULE;
}
}
+ if ($rule->{ccexpirysingle}) {
+ unless ($data =~ m!^\s*(\d+)\s*/\s*(\d+)+\s*$!) {
+ $errors->{$field} = _make_error($field, $info, $rule,
+ q!$n must be in MM/YY format!);
+ last RULE;
+ }
+ my ($month, $year) = ($1, $2);
+ $year += 2000;
+ if ($month < 1 || $month > 12) {
+ $errors->{$field} = _make_error($field, $info, $rule,
+ q!$n month must be between 1 and 12!);
+ last RULE;
+ }
+ my ($now_year, $now_month) = (localtime)[5, 4];
+ $now_year += 1900;
+ ++$now_month;
+ if ($year < $now_year || $year == $now_year && $month < $now_month) {
+ $errors->{$field} = _make_error($field, $info, $rule,
+ q!$n is in the past, your card has expired!);
+ last RULE;
+ }
+ }
}
}
}
my ($articles) = @_;
my @pages =
(
- 'cart', 'checkout', 'checkoutfinal', 'checkoutcard', 'checkoutconfirm',
+ 'cart', 'checkoutnew', 'checkoutfinal', 'checkoutcard', 'checkoutconfirm',
+ 'checkoutpay',
);
require 'Generate/Article.pm';
my $shop = $articles->getByPkey($SHOPID);
-#!/usr/bin/perl -w
-# -d:ptkdb
+#!/usr/bin/perl -w -d:ptkdb
BEGIN { $ENV{DISPLAY} = '192.168.32.15:0.0' }
use strict;
use FindBin;
use lib "$FindBin::Bin/modules";
use CGI ':standard';
-use Products;
-use Product;
-use Constants qw(:shop $CGI_URI);
+use BSE::Request;
+use BSE::UI::Shop;
use BSE::Template;
-use CGI::Cookie;
-use BSE::WebUtil qw(refresh_to);
-use BSE::CfgInfo qw(custom_class);
-use BSE::Mail;
-use BSE::Shop::Util qw/shop_cart_tags cart_item_opts nice_options total
- basic_tags load_order_fields need_logon get_siteuser
- payment_types/;
-use BSE::Session;
-use BSE::Cfg;
-use BSE::Util::Tags qw(tag_hash);
-use DevHelp::HTML;
-
-my $cfg = BSE::Cfg->new();
-
-my $subject = $cfg->entry('shop', 'subject', $SHOP_MAIL_SUBJECT);
-
-# our PGP passphrase
-my $passphrase = $SHOP_PASSPHRASE;
-
-# the class we use to perform encryption
-# we can change this to switch between GnuPG and PGP
-my $crypto_class = $SHOP_CRYPTO;
-
-# id of the private key to use for signing
-# leave as undef to use your default key
-my $signing_id = $SHOP_SIGNING_ID;
-
-# location of PGP
-my $pgpe = $SHOP_PGPE;
-my $pgp = $SHOP_PGP;
-my $gpg = $SHOP_GPG;
-
-my $from = $cfg->entry('shop', 'from', $SHOP_FROM);
-
-my $toName = $cfg->entry('shop', 'to_name', $SHOP_TO_NAME);
-my $toEmail= $cfg->entry('shop', 'to_email', $SHOP_TO_EMAIL);
-
-use constant PAYMENT_CC => 0;
-use constant PAYMENT_CHEQUE => 1;
-use constant PAYMENT_CALLME => 2;
-
-my $urlbase = $cfg->entryVar('site', 'url');
-my $securlbase = $cfg->entryVar('site', 'secureurl');
-my %session;
-BSE::Session->tie_it(\%session, $cfg);
-
-# this shouldn't be necessary, but it stopped working elsewhere and this
-# fixed it
-END {
- untie %session;
-}
-
-if (!exists $session{cart}) {
- $session{cart} = [];
-}
-
-# the keys here are the names of the buttons on the various forms
-# we also have 'delete_<number>' buttons.
-my %steps =
- (
- add=>\&add_item,
- cart=>\&show_cart,
- checkout=>\&checkout,
- checkupdate => \&checkupdate,
- recheckout => sub { checkout('', 1); },
- confirm => \&checkout_confirm,
- recalc=>\&recalc,
- recalculate=>\&recalc,
- purchase=>\&purchase,
- #prePurchase=>\&prePurchase,
- );
-
-for my $key (keys %steps) {
- if (param($key) or param("$key.x")) {
- $steps{$key}->();
- exit;
- }
-}
-
-for my $key (param()) {
- if ($key =~ /^delete_(\d+)/) {
- remove_item($1);
- exit;
- }
-}
-
-show_cart();
-
-sub add_item {
- my $addid = param('id');
- $addid ||= '';
- my $quantity = param('quantity');
- $quantity ||= 1;
- my $product;
- $product = Products->getByPkey($addid) if $addid;
- $product or return show_cart("Cannot find product $addid"); # oops
-
- # collect the product options
- my @options;
- my @opt_names = split /,/, $product->{options};
- my @not_def;
- for my $name (@opt_names) {
- my $value = param($name);
- push @options, $value;
- unless (defined $value) {
- push @not_def, $name;
- }
- }
- @not_def
- and return show_cart("Some product options (@not_def) not supplied");
- my $options = join(",", @options);
+use Carp 'confess';
+
+$SIG{__DIE__} = sub { confess $@ };
+
+my $req = BSE::Request->new;
+my $result = BSE::UI::Shop->dispatch($req);
+BSE::Template->output_result($req, $result);
+
+
+
+# use Products;
+# use Product;
+# use Constants qw(:shop $CGI_URI);
+# use BSE::Template;
+# use CGI::Cookie;
+# use BSE::WebUtil qw(refresh_to);
+# use BSE::CfgInfo qw(custom_class);
+# use BSE::Mail;
+# use BSE::Shop::Util qw/shop_cart_tags cart_item_opts nice_options total
+# basic_tags load_order_fields need_logon get_siteuser
+# payment_types/;
+# use BSE::Session;
+# use BSE::Cfg;
+# use BSE::Util::Tags qw(tag_hash);
+# use DevHelp::HTML;
+
+# my $cfg = BSE::Cfg->new();
+
+# my $subject = $cfg->entry('shop', 'subject', $SHOP_MAIL_SUBJECT);
+
+# # our PGP passphrase
+# my $passphrase = $SHOP_PASSPHRASE;
+
+# # the class we use to perform encryption
+# # we can change this to switch between GnuPG and PGP
+# my $crypto_class = $SHOP_CRYPTO;
+
+# # id of the private key to use for signing
+# # leave as undef to use your default key
+# my $signing_id = $SHOP_SIGNING_ID;
+
+# # location of PGP
+# my $pgpe = $SHOP_PGPE;
+# my $pgp = $SHOP_PGP;
+# my $gpg = $SHOP_GPG;
+
+# my $from = $cfg->entry('shop', 'from', $SHOP_FROM);
+
+# my $toName = $cfg->entry('shop', 'to_name', $SHOP_TO_NAME);
+# my $toEmail= $cfg->entry('shop', 'to_email', $SHOP_TO_EMAIL);
+
+# use constant PAYMENT_CC => 0;
+# use constant PAYMENT_CHEQUE => 1;
+# use constant PAYMENT_CALLME => 2;
+
+# my $urlbase = $cfg->entryVar('site', 'url');
+# my $securlbase = $cfg->entryVar('site', 'secureurl');
+# my %session;
+# BSE::Session->tie_it(\%session, $cfg);
+
+# # this shouldn't be necessary, but it stopped working elsewhere and this
+# # fixed it
+# END {
+# untie %session;
+# }
+
+# if (!exists $session{cart}) {
+# $session{cart} = [];
+# }
+
+# # the keys here are the names of the buttons on the various forms
+# # we also have 'delete_<number>' buttons.
+# my %steps =
+# (
+# add=>\&add_item,
+# cart=>\&show_cart,
+# checkout=>\&checkout,
+# checkupdate => \&checkupdate,
+# recheckout => sub { checkout('', 1); },
+# confirm => \&checkout_confirm,
+# recalc=>\&recalc,
+# recalculate=>\&recalc,
+# purchase=>\&purchase,
+# #prePurchase=>\&prePurchase,
+# );
+
+# for my $key (keys %steps) {
+# if (param($key) or param("$key.x")) {
+# $steps{$key}->();
+# exit;
+# }
+# }
+
+# for my $key (param()) {
+# if ($key =~ /^delete_(\d+)/) {
+# remove_item($1);
+# exit;
+# }
+# }
+
+# show_cart();
+
+# sub add_item {
+# my $addid = param('id');
+# $addid ||= '';
+# my $quantity = param('quantity');
+# $quantity ||= 1;
+# my $product;
+# $product = Products->getByPkey($addid) if $addid;
+# $product or return show_cart("Cannot find product $addid"); # oops
+
+# # collect the product options
+# my @options;
+# my @opt_names = split /,/, $product->{options};
+# my @not_def;
+# for my $name (@opt_names) {
+# my $value = param($name);
+# push @options, $value;
+# unless (defined $value) {
+# push @not_def, $name;
+# }
+# }
+# @not_def
+# and return show_cart("Some product options (@not_def) not supplied");
+# my $options = join(",", @options);
- # the product must be non-expired and listed
- use BSE::Util::SQL qw(now_sqldate);
- (my $comp_release = $product->{release}) =~ s/ .*//;
- (my $comp_expire = $product->{expire}) =~ s/ .*//;
- my $today = now_sqldate();
- $comp_release le $today
- or return show_cart("Product has not been released yet");
- $today le $comp_expire
- or return show_cart("Product has expired");
- $product->{listed} or return show_cart("Product not available");
+# # the product must be non-expired and listed
+# use BSE::Util::SQL qw(now_sqldate);
+# (my $comp_release = $product->{release}) =~ s/ .*//;
+# (my $comp_expire = $product->{expire}) =~ s/ .*//;
+# my $today = now_sqldate();
+# $comp_release le $today
+# or return show_cart("Product has not been released yet");
+# $today le $comp_expire
+# or return show_cart("Product has expired");
+# $product->{listed} or return show_cart("Product not available");
- # used to refresh if a logon is needed
- my $r = $securlbase . $ENV{SCRIPT_NAME} . "?add=1&id=$addid";
- for my $opt_index (0..$#opt_names) {
- $r .= "&$opt_names[$opt_index]=".escape_uri($options[$opt_index]);
- }
+# # used to refresh if a logon is needed
+# my $r = $securlbase . $ENV{SCRIPT_NAME} . "?add=1&id=$addid";
+# for my $opt_index (0..$#opt_names) {
+# $r .= "&$opt_names[$opt_index]=".escape_uri($options[$opt_index]);
+# }
- my $user = get_siteuser(\%session, $cfg, $CGI::Q);
- # need to be logged on if it has any subs
- if ($product->{subscription_id} != -1) {
- if ($user) {
- my $sub = $product->subscription;
- if ($product->is_renew_sub_only) {
- unless ($user->subscribed_to_grace($sub)) {
- return show_cart("This product can only be used to renew your subscription to $sub->{title} and you are not subscribed nor within the renewal grace period");
- }
- }
- elsif ($product->is_start_sub_only) {
- if ($user->subscribed_to_grace($sub)) {
- return show_cart("This product can only be used to start your subscription to $sub->{title} and you are already subscribed or within the grace period");
- }
- }
- }
- else {
- refresh_logon("You must be logged on to add this product to your cart",
- 'prodlogon', $r);
- return;
- }
- }
- if ($product->{subscription_required} != -1) {
- my $sub = $product->subscription_required;
- if ($user) {
- unless ($user->subscribed_to($sub)) {
- show_cart("You must be subscribed to $sub->{title} to purchase this product");
- return;
- }
- }
- else {
- # we want to refresh back to adding the item to the cart if possible
- refresh_logon("You must be logged on and subscribed to $sub->{title} to add this product to your cart",
- 'prodlogonsub', $r);
- return;
- }
- }
-
- # we need a natural integer quantity
- $quantity =~ /^\d+$/
- or return show_cart("Invalid quantity");
-
- my @cart = @{$session{cart}};
+# my $user = get_siteuser(\%session, $cfg, $CGI::Q);
+# # need to be logged on if it has any subs
+# if ($product->{subscription_id} != -1) {
+# if ($user) {
+# my $sub = $product->subscription;
+# if ($product->is_renew_sub_only) {
+# unless ($user->subscribed_to_grace($sub)) {
+# return show_cart("This product can only be used to renew your subscription to $sub->{title} and you are not subscribed nor within the renewal grace period");
+# }
+# }
+# elsif ($product->is_start_sub_only) {
+# if ($user->subscribed_to_grace($sub)) {
+# return show_cart("This product can only be used to start your subscription to $sub->{title} and you are already subscribed or within the grace period");
+# }
+# }
+# }
+# else {
+# refresh_logon("You must be logged on to add this product to your cart",
+# 'prodlogon', $r);
+# return;
+# }
+# }
+# if ($product->{subscription_required} != -1) {
+# my $sub = $product->subscription_required;
+# if ($user) {
+# unless ($user->subscribed_to($sub)) {
+# show_cart("You must be subscribed to $sub->{title} to purchase this product");
+# return;
+# }
+# }
+# else {
+# # we want to refresh back to adding the item to the cart if possible
+# refresh_logon("You must be logged on and subscribed to $sub->{title} to add this product to your cart",
+# 'prodlogonsub', $r);
+# return;
+# }
+# }
+
+# # we need a natural integer quantity
+# $quantity =~ /^\d+$/
+# or return show_cart("Invalid quantity");
+
+# my @cart = @{$session{cart}};
- # if this is is already present, replace it
- @cart = grep { $_->{productId} ne $addid || $_->{options} ne $options }
- @cart;
- push(@cart, { productId => $addid, units => $quantity,
- price=>$product->{retailPrice},
- options=>$options });
-
- $session{cart} = \@cart;
- show_cart();
-}
-
-sub show_cart {
- my ($msg) = @_;
- my @cart = @{$session{cart}};
- my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
- my $item_index = -1;
- my @options;
- my $option_index;
+# # if this is is already present, replace it
+# @cart = grep { $_->{productId} ne $addid || $_->{options} ne $options }
+# @cart;
+# push(@cart, { productId => $addid, units => $quantity,
+# price=>$product->{retailPrice},
+# options=>$options });
+
+# $session{cart} = \@cart;
+# show_cart();
+# }
+
+# sub show_cart {
+# my ($msg) = @_;
+# my @cart = @{$session{cart}};
+# my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
+# my $item_index = -1;
+# my @options;
+# my $option_index;
- $session{custom} ||= {};
- my %custom_state = %{$session{custom}};
-
- my $cust_class = custom_class($cfg);
- $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
- $msg = '' unless defined $msg;
- $msg = CGI::escapeHTML($msg);
-
- my %acts;
- %acts =
- (
- $cust_class->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state,
- $cfg),
- shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
- 'cart'),
- basic_tags(\%acts),
- msg => $msg,
- );
- $session{custom} = \%custom_state;
-
- page('cart.tmpl', \%acts);
-}
-
-sub update_quantities {
- my @cart = @{$session{cart}};
- for my $index (0..$#cart) {
- my $new_quantity = param("quantity_$index");
- if (defined $new_quantity) {
- if ($new_quantity =~ /^\s*(\d+)/) {
- $cart[$index]{units} = $1;
- }
- elsif ($new_quantity =~ /^\s*$/) {
- $cart[$index]{units} = 0;
- }
- }
- }
- @cart = grep { $_->{units} != 0 } @cart;
- $session{cart} = \@cart;
- $session{custom} ||= {};
- my %custom_state = %{$session{custom}};
- custom_class($cfg)->recalc($CGI::Q, \@cart, [], \%custom_state, $cfg);
- $session{custom} = \%custom_state;
-}
-
-sub recalc {
- update_quantities();
- show_cart();
-}
-
-sub remove_item {
- my ($index) = @_;
- my @cart = @{$session{cart}};
- if ($index >= 0 && $index < @cart) {
- splice(@cart, $index, 1);
- }
- $session{cart} = \@cart;
-
- refresh_to($ENV{SCRIPT_NAME});
-}
-
-sub checkupdate {
- my @cart = @{$session{cart}};
- my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
- $session{custom} ||= {};
- my %custom_state = %{$session{custom}};
- custom_class($cfg)
- ->checkout_update($CGI::Q, \@cart, \@cart_prods, \%custom_state, $cfg);
- $session{custom} = \%custom_state;
+# $session{custom} ||= {};
+# my %custom_state = %{$session{custom}};
+
+# my $cust_class = custom_class($cfg);
+# $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
+# $msg = '' unless defined $msg;
+# $msg = CGI::escapeHTML($msg);
+
+# my %acts;
+# %acts =
+# (
+# $cust_class->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state,
+# $cfg),
+# shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
+# 'cart'),
+# basic_tags(\%acts),
+# msg => $msg,
+# );
+# $session{custom} = \%custom_state;
+
+# page('cart.tmpl', \%acts);
+# }
+
+# sub update_quantities {
+# my @cart = @{$session{cart}};
+# for my $index (0..$#cart) {
+# my $new_quantity = param("quantity_$index");
+# if (defined $new_quantity) {
+# if ($new_quantity =~ /^\s*(\d+)/) {
+# $cart[$index]{units} = $1;
+# }
+# elsif ($new_quantity =~ /^\s*$/) {
+# $cart[$index]{units} = 0;
+# }
+# }
+# }
+# @cart = grep { $_->{units} != 0 } @cart;
+# $session{cart} = \@cart;
+# $session{custom} ||= {};
+# my %custom_state = %{$session{custom}};
+# custom_class($cfg)->recalc($CGI::Q, \@cart, [], \%custom_state, $cfg);
+# $session{custom} = \%custom_state;
+# }
+
+# sub recalc {
+# update_quantities();
+# show_cart();
+# }
+
+# sub remove_item {
+# my ($index) = @_;
+# my @cart = @{$session{cart}};
+# if ($index >= 0 && $index < @cart) {
+# splice(@cart, $index, 1);
+# }
+# $session{cart} = \@cart;
+
+# refresh_to($ENV{SCRIPT_NAME});
+# }
+
+# sub checkupdate {
+# my @cart = @{$session{cart}};
+# my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
+# $session{custom} ||= {};
+# my %custom_state = %{$session{custom}};
+# custom_class($cfg)
+# ->checkout_update($CGI::Q, \@cart, \@cart_prods, \%custom_state, $cfg);
+# $session{custom} = \%custom_state;
- checkout("", 1);
-}
-
-sub tag_checkedPayment {
- my ($payment, $types_by_name, $args) = @_;
-
- my $type = $args;
- if ($type !~ /^\d+$/) {
- return '' unless $types_by_name->{$type};
- $type = $types_by_name->{$type};
- }
-
- return $payment == $type ? 'checked="checked"' : '';
-}
-
-sub tag_ifPayments {
- my ($enabled, $types_by_name, $args) = @_;
-
- my $type = $args;
- if ($type !~ /^\d+$/) {
- return '' unless $types_by_name->{$type};
- $type = $types_by_name->{$type};
- }
-
- my @found = grep $_ == $type, @$enabled;
-
- return scalar @found;
-}
-
-# display the checkout form
-# can also be called with an error message and a flag to fillin the old
-# values for the form elements
-sub checkout {
- my ($message, $olddata) = @_;
-
- $message = '' unless defined $message;
-
- update_quantities();
- my @cart = @{$session{cart}};
-
- @cart or return show_cart();
-
- my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
-
- if (my ($msg, $id) = need_logon($cfg, \@cart, \@cart_prods, \%session, $CGI::Q)) {
- refresh_logon($msg, $id);
- return;
- }
-
- my $user = get_siteuser(\%session, $cfg, $CGI::Q);
-
- $session{custom} ||= {};
- my %custom_state = %{$session{custom}};
-
- my $cust_class = custom_class($cfg);
- $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
-
- my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
-
- my @pay_types = payment_types($cfg);
- my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
- my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
- if ($noencrypt) {
- @payment_types = grep $_ ne PAYMENT_CC, @payment_types;
- @payment_types or @payment_types = ( PAYMENT_CALLME );
- }
- else {
- @payment_types or @payment_types = ( PAYMENT_CC );
- }
- @payment_types = sort { $a <=> $b } @payment_types;
- my %payment_types = map { $_=> 1 } @payment_types;
- my $payment;
- $olddata and $payment = param('paymentType');
- defined $payment or $payment = $payment_types[0];
-
- my $affiliate_code = $session{affiliate_code};
- defined $affiliate_code or $affiliate_code = '';
-
- my $item_index = -1;
- my @options;
- my $option_index;
- my %acts;
- %acts =
- (
- shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
- 'checkout'),
- basic_tags(\%acts),
- message => sub { $message },
- old =>
- sub {
- my $value;
-
- if ($olddata) {
- $value = param($_[0]);
- unless (defined $value) {
- $value = $user->{$_[0]}
- if $user;
- }
- }
- else {
- $value = $user && defined $user->{$_[0]} ? $user->{$_[0]} : '';
- }
+# checkout("", 1);
+# }
+
+# sub tag_checkedPayment {
+# my ($payment, $types_by_name, $args) = @_;
+
+# my $type = $args;
+# if ($type !~ /^\d+$/) {
+# return '' unless $types_by_name->{$type};
+# $type = $types_by_name->{$type};
+# }
+
+# return $payment == $type ? 'checked="checked"' : '';
+# }
+
+# sub tag_ifPayments {
+# my ($enabled, $types_by_name, $args) = @_;
+
+# my $type = $args;
+# if ($type !~ /^\d+$/) {
+# return '' unless $types_by_name->{$type};
+# $type = $types_by_name->{$type};
+# }
+
+# my @found = grep $_ == $type, @$enabled;
+
+# return scalar @found;
+# }
+
+# # display the checkout form
+# # can also be called with an error message and a flag to fillin the old
+# # values for the form elements
+# sub checkout {
+# my ($message, $olddata) = @_;
+
+# $message = '' unless defined $message;
+
+# update_quantities();
+# my @cart = @{$session{cart}};
+
+# @cart or return show_cart();
+
+# my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
+
+# if (my ($msg, $id) = need_logon($cfg, \@cart, \@cart_prods, \%session, $CGI::Q)) {
+# refresh_logon($msg, $id);
+# return;
+# }
+
+# my $user = get_siteuser(\%session, $cfg, $CGI::Q);
+
+# $session{custom} ||= {};
+# my %custom_state = %{$session{custom}};
+
+# my $cust_class = custom_class($cfg);
+# $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
+
+# my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
+
+# my @pay_types = payment_types($cfg);
+# my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
+# my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
+# if ($noencrypt) {
+# @payment_types = grep $_ ne PAYMENT_CC, @payment_types;
+# @payment_types or @payment_types = ( PAYMENT_CALLME );
+# }
+# else {
+# @payment_types or @payment_types = ( PAYMENT_CC );
+# }
+# @payment_types = sort { $a <=> $b } @payment_types;
+# my %payment_types = map { $_=> 1 } @payment_types;
+# my $payment;
+# $olddata and $payment = param('paymentType');
+# defined $payment or $payment = $payment_types[0];
+
+# my $affiliate_code = $session{affiliate_code};
+# defined $affiliate_code or $affiliate_code = '';
+
+# my $item_index = -1;
+# my @options;
+# my $option_index;
+# my %acts;
+# %acts =
+# (
+# shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
+# 'checkout'),
+# basic_tags(\%acts),
+# message => sub { $message },
+# old =>
+# sub {
+# my $value;
+
+# if ($olddata) {
+# $value = param($_[0]);
+# unless (defined $value) {
+# $value = $user->{$_[0]}
+# if $user;
+# }
+# }
+# else {
+# $value = $user && defined $user->{$_[0]} ? $user->{$_[0]} : '';
+# }
- defined $value or $value = '';
- CGI::escapeHTML($value);
- },
- $cust_class->checkout_actions(\%acts, \@cart, \@cart_prods,
- \%custom_state, $CGI::Q, $cfg),
- ifMultPaymentTypes => @payment_types > 1,
- ifUser => defined $user,
- user => $user ? [ \&tag_hash, $user ] : '',
- checkedPayment => [ \&tag_checkedPayment, $payment, \%types_by_name ],
- ifPayments => [ \&tag_ifPayments, \@payment_types, \%types_by_name ],
- affiliate_code => escape_html($affiliate_code),
- );
- for my $type (@pay_types) {
- my $id = $type->{id};
- my $name = $type->{name};
- $acts{"if${name}Payments"} = exists $payment_types{$id};
- $acts{"if${name}FirstPayment"} = $payment_types[0] == $id;
- $acts{"checkedIfFirst$name"} = $payment_types[0] == $id ? "checked " : "";
- $acts{"checkedPayment$name"} = $payment == $id ? 'checked="checked" ' : "";
- }
- $session{custom} = \%custom_state;
-
- page('checkout.tmpl', \%acts);
-}
-
-# displays the data entered by the user so they can either confirm the
-# details or redisplay the checkout page
-sub checkout_confirm {
- my %order;
- my $error;
-
- my @cart_prods;
- unless (load_order_fields(0, $CGI::Q, \%order, \%session, \@cart_prods,
- \$error)) {
- return checkout($error, 1);
- }
- ++$session{changed};
- my @cart = @{$session{cart}};
- # display the confirmation page
- my %acts;
- %acts =
- (
- order => sub { CGI::escapeHTML($order{$_[0]}) },
- shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
- 'confirm'),
- basic_tags(\%acts),
- old =>
- sub {
- my $value = param($_[0]);
- defined $value or $value = '';
- CGI::escapeHTML($value);
- },
- );
- page('checkoutconfirm.tmpl', \%acts);
-}
-
-sub tag_ifPayment {
- my ($payment, $types_by_name, $args) = @_;
-
- my $type = $args;
- if ($type !~ /^\d+$/) {
- return '' unless $types_by_name->{$type};
- $type = $types_by_name->{$type};
- }
-
- return $payment == $type;
-}
-
-# the real work
-sub purchase {
- $from && $from =~ /.\@./
- or return checkout("Configuration error: shop from address not set", 1);
- $toEmail && $toEmail =~ /.\@./
- or return checkout("Configuration error: shop to_email address not set", 1);
-
- # some basic validation, in case the user switched off javascript
- my $cust_class = custom_class($cfg);
- my @required =
- $cust_class->required_fields($CGI::Q, $session{custom}, $cfg);
-
- my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
-
- my @pay_types = payment_types($cfg);
- my %pay_types = map { $_->{id} => $_ } @pay_types;
- my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
- #use Data::Dumper;
- #print STDERR Dumper \%pay_types;
- my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
- if ($noencrypt) {
- @payment_types = grep $_ ne PAYMENT_CC, @payment_types;
- @payment_types or @payment_types = ( PAYMENT_CALLME );
- }
- else {
- @payment_types or @payment_types = ( PAYMENT_CC );
- }
- @payment_types = sort { $a <=> $b } @payment_types;
- my %payment_types = map { $_=> 1 } @payment_types;
-
- my $paymentType = param('paymentType');
- defined $paymentType or $paymentType = $payment_types[0];
- $payment_types{$paymentType}
- or return checkout("Invalid payment type");
-
- push @required, @{$pay_types{$paymentType}{require}};
-
- for my $field (@required) {
- my $display = $cfg->entry('shop', "display_$field", $field);
- defined(param($field)) && length(param($field))
- or return checkout("Field $display is required", 1);
- }
- defined(param('email')) && param('email') =~ /.\@./
- or return checkout("Please enter a valid email address", 1);
- if ($paymentType == PAYMENT_CC) {
- defined(param('cardNumber')) && param('cardNumber') =~ /^\d+$/
- or return checkout("Please enter a credit card number", 1);
- }
-
- use BSE::TB::Orders;
- use BSE::TB::OrderItems;
-
- # map some form fields to order field names
- my %field_map =
- (
- name1 => 'delivFirstName',
- name2 => 'delivLastName',
- address => 'delivStreet',
- city => 'delivSuburb',
- postcode => 'delivPostCode',
- state => 'delivState',
- country => 'delivCountry',
- email => 'emailAddress',
- cardHolder => 'ccName',
- cardType => 'ccType',
- );
- # paranoia, don't store these
- my %nostore =
- (
- cardNumber => 1,
- cardExpiry => 1,
- );
- my %order;
- my @cart = @{$session{cart}};
- @cart or return show_cart('You have no items in your shopping cart');
-
- # so we can quickly check for columns
- my @columns = BSE::TB::Order->columns;
- my %columns;
- @columns{@columns} = @columns;
-
- for my $field (param()) {
- $order{$field_map{$field} || $field} = param($field)
- unless $nostore{$field};
- }
-
- my $ccNumber = param('cardNumber');
- defined $ccNumber or $ccNumber = '';
- my $ccExpiry = param('cardExpiry');
- defined $ccExpiry or $ccExpiry = '';
- my $affiliate_code = $session{affiliate_code};
- defined $affiliate_code && length $affiliate_code
- or $affiliate_code = param('affiliate_code');
- defined $affiliate_code or $affiliate_code = '';
- $order{affiliate_code} = $affiliate_code;
-
- use Digest::MD5 'md5_hex';
- $ccNumber =~ tr/0-9//cd;
- $order{ccNumberHash} = md5_hex($ccNumber);
- $order{ccExpiryHash} = md5_hex($ccExpiry);
-
- # work out totals
- $order{total} = 0;
- $order{gst} = 0;
- $order{wholesale} = 0;
- $order{shipping_cost} = 0;
- my @products;
- my $today = now_sqldate();
- for my $item (@cart) {
- my $product = Products->getByPkey($item->{productId});
- # double check that it's still a valid product
- if (!$product) {
- return show_cart("Product $item->{productId} not found");
- }
- else {
- (my $comp_release = $product->{release}) =~ s/ .*//;
- (my $comp_expire = $product->{expire}) =~ s/ .*//;
- $comp_release le $today
- or return show_cart("'$product->{title}' has not been released yet");
- $today le $comp_expire
- or return show_cart("'$product->{title}' has expired");
- $product->{listed}
- or return show_cart("'$product->{title}' not available");
- }
- push(@products, $product); # used in page rendering
- @$item{qw/price wholesalePrice gst/} =
- @$product{qw/retailPrice wholesalePrice gst/};
- $order{total} += $item->{price} * $item->{units};
- $order{wholesale} += $item->{wholesalePrice} * $item->{units};
- $order{gst} += $item->{gst} * $item->{units};
- }
-
- if (my ($msg, $id) = need_logon($cfg, \@cart, \@products, \%session, $CGI::Q)) {
- refresh_logon($msg, $id);
- return;
- }
-
- use BSE::Util::SQL qw(now_sqldatetime);
- $order{orderDate} = now_sqldatetime;
- $order{paymentType} = $paymentType;
- ++$session{changed};
-
- # blank anything else
- for my $column (@columns) {
- defined $order{$column} or $order{$column} = '';
- }
- # make sure the user can't set these behind our backs
- $order{filled} = 0;
- $order{paidFor} = 0;
-
- my $user = get_siteuser(\%session, $cfg, $CGI::Q);
- if ($user) {
- $order{userId} = $user->{userId};
- $order{siteuser_id} = $user->{id};
- }
- else {
- $order{userId} = '';
- $order{siteuser_id} = -1;
- }
-
- # this should be hard to guess
- $order{randomId} = md5_hex(time().rand().{}.$$);
-
- # check if a customizer has anything to do
- # if it sets shipping cost it must also update the total
- eval {
- my %custom = %{$session{custom}};
- $cust_class->order_save($CGI::Q, \%order, \@cart, \@products,
- \%custom, $cfg);
- $session{custom} = \%custom;
- };
- if ($@) {
- return checkout($@, 1);
- }
-
- $order{total} += $cust_class->total_extras(\@cart, \@products,
- $session{custom}, $cfg, 'final');
-
- my %subscribing_to;
-
- # load up the database
- my @data = @order{@columns};
- shift @data; # lose the dummy id
- my $order = BSE::TB::Orders->add(@data)
- or die "Cannot add order";
- my @items;
- my @item_cols = BSE::TB::OrderItem->columns;
- my @prod_xfer = qw/title summary subscription_id subscription_period/;
- for my $row_num (0..$#cart) {
- my $row = $cart[$row_num];
- my $product = $products[$row_num];
- $row->{orderId} = $order->{id};
-
- # store product data too
- @$row{@prod_xfer} = @{$product}{@prod_xfer};
-
- # store the lapsed value, this prevents future changes causing
- # variation of the expiry date
- $row->{max_lapsed} = 0;
- if ($product->{subscription_id} != -1) {
- my $sub = $product->subscription;
- $row->{max_lapsed} = $sub->{max_lapsed} if $sub;
- }
-
- my @data = @$row{@item_cols};
+# defined $value or $value = '';
+# CGI::escapeHTML($value);
+# },
+# $cust_class->checkout_actions(\%acts, \@cart, \@cart_prods,
+# \%custom_state, $CGI::Q, $cfg),
+# ifMultPaymentTypes => @payment_types > 1,
+# ifUser => defined $user,
+# user => $user ? [ \&tag_hash, $user ] : '',
+# checkedPayment => [ \&tag_checkedPayment, $payment, \%types_by_name ],
+# ifPayments => [ \&tag_ifPayments, \@payment_types, \%types_by_name ],
+# affiliate_code => escape_html($affiliate_code),
+# );
+# for my $type (@pay_types) {
+# my $id = $type->{id};
+# my $name = $type->{name};
+# $acts{"if${name}Payments"} = exists $payment_types{$id};
+# $acts{"if${name}FirstPayment"} = $payment_types[0] == $id;
+# $acts{"checkedIfFirst$name"} = $payment_types[0] == $id ? "checked " : "";
+# $acts{"checkedPayment$name"} = $payment == $id ? 'checked="checked" ' : "";
+# }
+# $session{custom} = \%custom_state;
+
+# page('checkout.tmpl', \%acts);
+# }
+
+# # displays the data entered by the user so they can either confirm the
+# # details or redisplay the checkout page
+# sub checkout_confirm {
+# my %order;
+# my $error;
+
+# my @cart_prods;
+# unless (load_order_fields(0, $CGI::Q, \%order, \%session, \@cart_prods,
+# \$error)) {
+# return checkout($error, 1);
+# }
+# ++$session{changed};
+# my @cart = @{$session{cart}};
+# # display the confirmation page
+# my %acts;
+# %acts =
+# (
+# order => sub { CGI::escapeHTML($order{$_[0]}) },
+# shop_cart_tags(\%acts, \@cart, \@cart_prods, \%session, $CGI::Q, $cfg,
+# 'confirm'),
+# basic_tags(\%acts),
+# old =>
+# sub {
+# my $value = param($_[0]);
+# defined $value or $value = '';
+# CGI::escapeHTML($value);
+# },
+# );
+# page('checkoutconfirm.tmpl', \%acts);
+# }
+
+# sub tag_ifPayment {
+# my ($payment, $types_by_name, $args) = @_;
+
+# my $type = $args;
+# if ($type !~ /^\d+$/) {
+# return '' unless $types_by_name->{$type};
+# $type = $types_by_name->{$type};
+# }
+
+# return $payment == $type;
+# }
+
+# # the real work
+# sub purchase {
+# $from && $from =~ /.\@./
+# or return checkout("Configuration error: shop from address not set", 1);
+# $toEmail && $toEmail =~ /.\@./
+# or return checkout("Configuration error: shop to_email address not set", 1);
+
+# # some basic validation, in case the user switched off javascript
+# my $cust_class = custom_class($cfg);
+# my @required =
+# $cust_class->required_fields($CGI::Q, $session{custom}, $cfg);
+
+# my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
+
+# my @pay_types = payment_types($cfg);
+# my %pay_types = map { $_->{id} => $_ } @pay_types;
+# my %types_by_name = map { $_->{name} => $_->{id} } @pay_types;
+# #use Data::Dumper;
+# #print STDERR Dumper \%pay_types;
+# my @payment_types = map $_->{id}, grep $_->{enabled}, @pay_types;
+# if ($noencrypt) {
+# @payment_types = grep $_ ne PAYMENT_CC, @payment_types;
+# @payment_types or @payment_types = ( PAYMENT_CALLME );
+# }
+# else {
+# @payment_types or @payment_types = ( PAYMENT_CC );
+# }
+# @payment_types = sort { $a <=> $b } @payment_types;
+# my %payment_types = map { $_=> 1 } @payment_types;
+
+# my $paymentType = param('paymentType');
+# defined $paymentType or $paymentType = $payment_types[0];
+# $payment_types{$paymentType}
+# or return checkout("Invalid payment type");
+
+# push @required, @{$pay_types{$paymentType}{require}};
+
+# for my $field (@required) {
+# my $display = $cfg->entry('shop', "display_$field", $field);
+# defined(param($field)) && length(param($field))
+# or return checkout("Field $display is required", 1);
+# }
+# defined(param('email')) && param('email') =~ /.\@./
+# or return checkout("Please enter a valid email address", 1);
+# if ($paymentType == PAYMENT_CC) {
+# defined(param('cardNumber')) && param('cardNumber') =~ /^\d+$/
+# or return checkout("Please enter a credit card number", 1);
+# }
+
+# use BSE::TB::Orders;
+# use BSE::TB::OrderItems;
+
+# # map some form fields to order field names
+# my %field_map =
+# (
+# name1 => 'delivFirstName',
+# name2 => 'delivLastName',
+# address => 'delivStreet',
+# city => 'delivSuburb',
+# postcode => 'delivPostCode',
+# state => 'delivState',
+# country => 'delivCountry',
+# email => 'emailAddress',
+# cardHolder => 'ccName',
+# cardType => 'ccType',
+# );
+# # paranoia, don't store these
+# my %nostore =
+# (
+# cardNumber => 1,
+# cardExpiry => 1,
+# );
+# my %order;
+# my @cart = @{$session{cart}};
+# @cart or return show_cart('You have no items in your shopping cart');
+
+# # so we can quickly check for columns
+# my @columns = BSE::TB::Order->columns;
+# my %columns;
+# @columns{@columns} = @columns;
+
+# for my $field (param()) {
+# $order{$field_map{$field} || $field} = param($field)
+# unless $nostore{$field};
+# }
+
+# my $ccNumber = param('cardNumber');
+# defined $ccNumber or $ccNumber = '';
+# my $ccExpiry = param('cardExpiry');
+# defined $ccExpiry or $ccExpiry = '';
+# my $affiliate_code = $session{affiliate_code};
+# defined $affiliate_code && length $affiliate_code
+# or $affiliate_code = param('affiliate_code');
+# defined $affiliate_code or $affiliate_code = '';
+# $order{affiliate_code} = $affiliate_code;
+
+# use Digest::MD5 'md5_hex';
+# $ccNumber =~ tr/0-9//cd;
+# $order{ccNumberHash} = md5_hex($ccNumber);
+# $order{ccExpiryHash} = md5_hex($ccExpiry);
+
+# # work out totals
+# $order{total} = 0;
+# $order{gst} = 0;
+# $order{wholesale} = 0;
+# $order{shipping_cost} = 0;
+# my @products;
+# my $today = now_sqldate();
+# for my $item (@cart) {
+# my $product = Products->getByPkey($item->{productId});
+# # double check that it's still a valid product
+# if (!$product) {
+# return show_cart("Product $item->{productId} not found");
+# }
+# else {
+# (my $comp_release = $product->{release}) =~ s/ .*//;
+# (my $comp_expire = $product->{expire}) =~ s/ .*//;
+# $comp_release le $today
+# or return show_cart("'$product->{title}' has not been released yet");
+# $today le $comp_expire
+# or return show_cart("'$product->{title}' has expired");
+# $product->{listed}
+# or return show_cart("'$product->{title}' not available");
+# }
+# push(@products, $product); # used in page rendering
+# @$item{qw/price wholesalePrice gst/} =
+# @$product{qw/retailPrice wholesalePrice gst/};
+# $order{total} += $item->{price} * $item->{units};
+# $order{wholesale} += $item->{wholesalePrice} * $item->{units};
+# $order{gst} += $item->{gst} * $item->{units};
+# }
+
+# if (my ($msg, $id) = need_logon($cfg, \@cart, \@products, \%session, $CGI::Q)) {
+# refresh_logon($msg, $id);
+# return;
+# }
+
+# use BSE::Util::SQL qw(now_sqldatetime);
+# $order{orderDate} = now_sqldatetime;
+# $order{paymentType} = $paymentType;
+# ++$session{changed};
+
+# # blank anything else
+# for my $column (@columns) {
+# defined $order{$column} or $order{$column} = '';
+# }
+# # make sure the user can't set these behind our backs
+# $order{filled} = 0;
+# $order{paidFor} = 0;
+
+# my $user = get_siteuser(\%session, $cfg, $CGI::Q);
+# if ($user) {
+# $order{userId} = $user->{userId};
+# $order{siteuser_id} = $user->{id};
+# }
+# else {
+# $order{userId} = '';
+# $order{siteuser_id} = -1;
+# }
+
+# # this should be hard to guess
+# $order{randomId} = md5_hex(time().rand().{}.$$);
+
+# # check if a customizer has anything to do
+# # if it sets shipping cost it must also update the total
+# eval {
+# my %custom = %{$session{custom}};
+# $cust_class->order_save($CGI::Q, \%order, \@cart, \@products,
+# \%custom, $cfg);
+# $session{custom} = \%custom;
+# };
+# if ($@) {
+# return checkout($@, 1);
+# }
+
+# $order{total} += $cust_class->total_extras(\@cart, \@products,
+# $session{custom}, $cfg, 'final');
+
+# my %subscribing_to;
+
+# # load up the database
+# my @data = @order{@columns};
+# shift @data; # lose the dummy id
+# my $order = BSE::TB::Orders->add(@data)
+# or die "Cannot add order";
+# my @items;
+# my @item_cols = BSE::TB::OrderItem->columns;
+# my @prod_xfer = qw/title summary subscription_id subscription_period/;
+# for my $row_num (0..$#cart) {
+# my $row = $cart[$row_num];
+# my $product = $products[$row_num];
+# $row->{orderId} = $order->{id};
+
+# # store product data too
+# @$row{@prod_xfer} = @{$product}{@prod_xfer};
+
+# # store the lapsed value, this prevents future changes causing
+# # variation of the expiry date
+# $row->{max_lapsed} = 0;
+# if ($product->{subscription_id} != -1) {
+# my $sub = $product->subscription;
+# $row->{max_lapsed} = $sub->{max_lapsed} if $sub;
+# }
+
+# my @data = @$row{@item_cols};
- shift @data;
- push(@items, BSE::TB::OrderItems->add(@data));
-
- my $sub = $product->subscription;
- if ($sub) {
- $subscribing_to{$sub->{text_id}} = $sub;
- }
- }
-
- if ($user) {
- $user->recalculate_subscriptions($cfg);
- }
-
- my $item_index = -1;
- my @options;
- my $option_index;
- my %acts;
- %acts =
- (
- $cust_class->purchase_actions(\%acts, \@items, \@products,
- $session{custom}, $cfg),
- BSE::Util::Tags->static(\%acts, $cfg),
- iterate_items_reset => sub { $item_index = -1; },
- iterate_items =>
- sub {
- if (++$item_index < @items) {
- $option_index = -1;
- @options = cart_item_opts($items[$item_index],
- $products[$item_index]);
- return 1;
- }
- return 0;
- },
- item=> sub { CGI::escapeHTML($items[$item_index]{$_[0]}); },
- product =>
- sub {
- my $value = $products[$item_index]{$_[0]};
- defined $value or $value = '';
-
- escape_html($value);
- },
- extended =>
- sub {
- my $what = $_[0] || 'retailPrice';
- $items[$item_index]{units} * $items[$item_index]{$what};
- },
- order => sub { CGI::escapeHTML($order->{$_[0]}) },
- money =>
- sub {
- my ($func, $args) = split ' ', $_[0], 2;
- $acts{$func} || return "<: money $_[0] :>";
- return sprintf("%.02f", $acts{$func}->($args)/100);
- },
- _format =>
- sub {
- my ($value, $fmt) = @_;
- if ($fmt =~ /^m(\d+)/) {
- return sprintf("%$1s", sprintf("%.2f", $value/100));
- }
- elsif ($fmt =~ /%/) {
- return sprintf($fmt, $value);
- }
- },
- iterate_options_reset => sub { $option_index = -1 },
- iterate_options => sub { ++$option_index < @options },
- option => sub { CGI::escapeHTML($options[$option_index]{$_[0]}) },
- ifOptions => sub { @options },
- options => sub { nice_options(@options) },
- ifPayment => [ \&tag_ifPayment, $order->{paymentType}, \%types_by_name ],
- #ifSubscribingTo => [ \&tag_ifSubscribingTo, \%subscribing_to ],
- );
- for my $type (@pay_types) {
- my $id = $type->{id};
- my $name = $type->{name};
- $acts{"if${name}Payment"} = $order->{paymentType} == $id;
- }
- send_order($order, \@items, \@products, $noencrypt, \%subscribing_to);
- $session{cart} = []; # empty the cart
- page('checkoutfinal.tmpl', \%acts);
-}
-
-sub tag_ifSubscribingTo {
- my ($subscribing_to, $args) = @_;
-
- exists $subscribing_to->{$args};
-}
-
-sub tag_with_wrap {
- my ($args, $text) = @_;
-
- my $margin = $args =~ /^\d+$/ && $args > 30 ? $args : 70;
-
- require Text::Wrap;
- # do it twice to prevent a warning
- $Text::Wrap::columns = $margin;
- $Text::Wrap::columns = $margin;
-
- return Text::Wrap::fill('', '', split /\n/, $text);
-}
-
-# sends the email order confirmation and the PGP encrypted
-# email to the site owner
-sub send_order {
- my ($order, $items, $products, $noencrypt, $subscribing_to) = @_;
-
- my %extras = $cfg->entriesCS('extra tags');
- for my $key (keys %extras) {
- # follow any links
- my $data = $cfg->entryVar('extra tags', $key);
- $extras{$key} = sub { $data };
- }
-
- my $item_index = -1;
- my @options;
- my $option_index;
- my %acts;
- %acts =
- (
- %extras,
- custom_class($cfg)
- ->order_mail_actions(\%acts, $order, $items, $products,
- $session{custom}, $cfg),
- BSE::Util::Tags->static(\%acts, $cfg),
- iterate_items_reset => sub { $item_index = -1; },
- iterate_items =>
- sub {
- if (++$item_index < @$items) {
- $option_index = -1;
- @options = cart_item_opts($items->[$item_index],
- $products->[$item_index]);
- return 1;
- }
- return 0;
- },
- item=> sub { $items->[$item_index]{$_[0]}; },
- product =>
- sub {
- my $value = $products->[$item_index]{$_[0]};
- defined($value) or $value = '';
- $value;
- },
- order => sub { $order->{$_[0]} },
- extended =>
- sub {
- $items->[$item_index]{units} * $items->[$item_index]{$_[0]};
- },
- _format =>
- sub {
- my ($value, $fmt) = @_;
- if ($fmt =~ /^m(\d+)/) {
- return sprintf("%$1s", sprintf("%.2f", $value/100));
- }
- elsif ($fmt =~ /%/) {
- return sprintf($fmt, $value);
- }
- elsif ($fmt =~ /^\d+$/) {
- return substr($value . (" " x $fmt), 0, $fmt);
- }
- else {
- return $value;
- }
- },
- iterate_options_reset => sub { $option_index = -1 },
- iterate_options => sub { ++$option_index < @options },
- option => sub { CGI::escapeHTML($options[$option_index]{$_[0]}) },
- ifOptions => sub { @options },
- options => sub { nice_options(@options) },
- with_wrap => \&tag_with_wrap,
- ifSubscribingTo => [ \&tag_ifSubscribingTo, $subscribing_to ],
- );
-
- my $mailer = BSE::Mail->new(cfg=>$cfg);
- # ok, send some email
- my $confirm = BSE::Template->get_page('mailconfirm', $cfg, \%acts);
- my $email_order = $cfg->entryBool('shop', 'email_order', $SHOP_EMAIL_ORDER);
- if ($email_order) {
- unless ($noencrypt) {
- $acts{cardNumber} = sub { param('cardNumber') };
- $acts{cardExpiry} = sub { param('cardExpiry') };
- }
- my $ordertext = BSE::Template->get_page('mailorder', $cfg, \%acts);
+# shift @data;
+# push(@items, BSE::TB::OrderItems->add(@data));
+
+# my $sub = $product->subscription;
+# if ($sub) {
+# $subscribing_to{$sub->{text_id}} = $sub;
+# }
+# }
+
+# if ($user) {
+# $user->recalculate_subscriptions($cfg);
+# }
+
+# my $item_index = -1;
+# my @options;
+# my $option_index;
+# my %acts;
+# %acts =
+# (
+# $cust_class->purchase_actions(\%acts, \@items, \@products,
+# $session{custom}, $cfg),
+# BSE::Util::Tags->static(\%acts, $cfg),
+# iterate_items_reset => sub { $item_index = -1; },
+# iterate_items =>
+# sub {
+# if (++$item_index < @items) {
+# $option_index = -1;
+# @options = cart_item_opts($items[$item_index],
+# $products[$item_index]);
+# return 1;
+# }
+# return 0;
+# },
+# item=> sub { CGI::escapeHTML($items[$item_index]{$_[0]}); },
+# product =>
+# sub {
+# my $value = $products[$item_index]{$_[0]};
+# defined $value or $value = '';
+
+# escape_html($value);
+# },
+# extended =>
+# sub {
+# my $what = $_[0] || 'retailPrice';
+# $items[$item_index]{units} * $items[$item_index]{$what};
+# },
+# order => sub { CGI::escapeHTML($order->{$_[0]}) },
+# money =>
+# sub {
+# my ($func, $args) = split ' ', $_[0], 2;
+# $acts{$func} || return "<: money $_[0] :>";
+# return sprintf("%.02f", $acts{$func}->($args)/100);
+# },
+# _format =>
+# sub {
+# my ($value, $fmt) = @_;
+# if ($fmt =~ /^m(\d+)/) {
+# return sprintf("%$1s", sprintf("%.2f", $value/100));
+# }
+# elsif ($fmt =~ /%/) {
+# return sprintf($fmt, $value);
+# }
+# },
+# iterate_options_reset => sub { $option_index = -1 },
+# iterate_options => sub { ++$option_index < @options },
+# option => sub { CGI::escapeHTML($options[$option_index]{$_[0]}) },
+# ifOptions => sub { @options },
+# options => sub { nice_options(@options) },
+# ifPayment => [ \&tag_ifPayment, $order->{paymentType}, \%types_by_name ],
+# #ifSubscribingTo => [ \&tag_ifSubscribingTo, \%subscribing_to ],
+# );
+# for my $type (@pay_types) {
+# my $id = $type->{id};
+# my $name = $type->{name};
+# $acts{"if${name}Payment"} = $order->{paymentType} == $id;
+# }
+# send_order($order, \@items, \@products, $noencrypt, \%subscribing_to);
+# $session{cart} = []; # empty the cart
+# page('checkoutfinal.tmpl', \%acts);
+# }
+
+# sub tag_ifSubscribingTo {
+# my ($subscribing_to, $args) = @_;
+
+# exists $subscribing_to->{$args};
+# }
+
+# sub tag_with_wrap {
+# my ($args, $text) = @_;
+
+# my $margin = $args =~ /^\d+$/ && $args > 30 ? $args : 70;
+
+# require Text::Wrap;
+# # do it twice to prevent a warning
+# $Text::Wrap::columns = $margin;
+# $Text::Wrap::columns = $margin;
+
+# return Text::Wrap::fill('', '', split /\n/, $text);
+# }
+
+# # sends the email order confirmation and the PGP encrypted
+# # email to the site owner
+# sub send_order {
+# my ($order, $items, $products, $noencrypt, $subscribing_to) = @_;
+
+# my %extras = $cfg->entriesCS('extra tags');
+# for my $key (keys %extras) {
+# # follow any links
+# my $data = $cfg->entryVar('extra tags', $key);
+# $extras{$key} = sub { $data };
+# }
+
+# my $item_index = -1;
+# my @options;
+# my $option_index;
+# my %acts;
+# %acts =
+# (
+# %extras,
+# custom_class($cfg)
+# ->order_mail_actions(\%acts, $order, $items, $products,
+# $session{custom}, $cfg),
+# BSE::Util::Tags->static(\%acts, $cfg),
+# iterate_items_reset => sub { $item_index = -1; },
+# iterate_items =>
+# sub {
+# if (++$item_index < @$items) {
+# $option_index = -1;
+# @options = cart_item_opts($items->[$item_index],
+# $products->[$item_index]);
+# return 1;
+# }
+# return 0;
+# },
+# item=> sub { $items->[$item_index]{$_[0]}; },
+# product =>
+# sub {
+# my $value = $products->[$item_index]{$_[0]};
+# defined($value) or $value = '';
+# $value;
+# },
+# order => sub { $order->{$_[0]} },
+# extended =>
+# sub {
+# $items->[$item_index]{units} * $items->[$item_index]{$_[0]};
+# },
+# _format =>
+# sub {
+# my ($value, $fmt) = @_;
+# if ($fmt =~ /^m(\d+)/) {
+# return sprintf("%$1s", sprintf("%.2f", $value/100));
+# }
+# elsif ($fmt =~ /%/) {
+# return sprintf($fmt, $value);
+# }
+# elsif ($fmt =~ /^\d+$/) {
+# return substr($value . (" " x $fmt), 0, $fmt);
+# }
+# else {
+# return $value;
+# }
+# },
+# iterate_options_reset => sub { $option_index = -1 },
+# iterate_options => sub { ++$option_index < @options },
+# option => sub { CGI::escapeHTML($options[$option_index]{$_[0]}) },
+# ifOptions => sub { @options },
+# options => sub { nice_options(@options) },
+# with_wrap => \&tag_with_wrap,
+# ifSubscribingTo => [ \&tag_ifSubscribingTo, $subscribing_to ],
+# );
+
+# my $mailer = BSE::Mail->new(cfg=>$cfg);
+# # ok, send some email
+# my $confirm = BSE::Template->get_page('mailconfirm', $cfg, \%acts);
+# my $email_order = $cfg->entryBool('shop', 'email_order', $SHOP_EMAIL_ORDER);
+# if ($email_order) {
+# unless ($noencrypt) {
+# $acts{cardNumber} = sub { param('cardNumber') };
+# $acts{cardExpiry} = sub { param('cardExpiry') };
+# }
+# my $ordertext = BSE::Template->get_page('mailorder', $cfg, \%acts);
- my $send_text;
- if ($noencrypt) {
- $send_text = $ordertext;
- }
- else {
- eval "use $crypto_class";
- !$@ or die $@;
- my $encrypter = $crypto_class->new;
+# my $send_text;
+# if ($noencrypt) {
+# $send_text = $ordertext;
+# }
+# else {
+# eval "use $crypto_class";
+# !$@ or die $@;
+# my $encrypter = $crypto_class->new;
- my $debug = $cfg->entryBool('debug', 'mail_encryption', 0);
- my $sign = $cfg->entryBool('basic', 'sign', 1);
+# my $debug = $cfg->entryBool('debug', 'mail_encryption', 0);
+# my $sign = $cfg->entryBool('basic', 'sign', 1);
- # encrypt and sign
- my %opts =
- (
- sign=> $sign,
- passphrase=> $passphrase,
- stripwarn=>1,
- debug=>$debug,
- );
+# # encrypt and sign
+# my %opts =
+# (
+# sign=> $sign,
+# passphrase=> $passphrase,
+# stripwarn=>1,
+# debug=>$debug,
+# );
- $opts{secretkeyid} = $signing_id if $signing_id;
- $opts{pgp} = $pgp if $pgp;
- $opts{gpg} = $gpg if $gpg;
- $opts{pgpe} = $pgpe if $pgpe;
- my $recip = "$toName $toEmail";
-
- $send_text = $encrypter->encrypt($recip, $ordertext, %opts )
- or die "Cannot encrypt ", $encrypter->error;
- }
- $mailer->send(to=>$toEmail, from=>$from, subject=>'New Order '.$order->{id},
- body=>$send_text)
- or print STDERR "Error sending order to admin: ",$mailer->errstr,"\n";
- }
- $mailer->send(to=>$order->{emailAddress}, from=>$from,
- subject=>$subject . " " . localtime,
- body=>$confirm)
- or print STDERR "Error sending order to customer: ",$mailer->errstr,"\n";
-}
-
-sub page {
- my ($template, $acts) = @_;
-
- BSE::Template->show_page($template, $cfg, $acts);
-}
-
-# convert an epoch time to sql format
-sub epoch_to_sql {
- use POSIX 'strftime';
- my ($time) = @_;
-
- return strftime('%Y-%m-%d', localtime $time);
-}
-
-sub refresh_logon {
- my ($msg, $msgid, $r) = @_;
- my $url = $securlbase."/cgi-bin/user.pl";
-
- $r ||= $securlbase."/cgi-bin/shop.pl?checkout=1";
+# $opts{secretkeyid} = $signing_id if $signing_id;
+# $opts{pgp} = $pgp if $pgp;
+# $opts{gpg} = $gpg if $gpg;
+# $opts{pgpe} = $pgpe if $pgpe;
+# my $recip = "$toName $toEmail";
+
+# $send_text = $encrypter->encrypt($recip, $ordertext, %opts )
+# or die "Cannot encrypt ", $encrypter->error;
+# }
+# $mailer->send(to=>$toEmail, from=>$from, subject=>'New Order '.$order->{id},
+# body=>$send_text)
+# or print STDERR "Error sending order to admin: ",$mailer->errstr,"\n";
+# }
+# $mailer->send(to=>$order->{emailAddress}, from=>$from,
+# subject=>$subject . " " . localtime,
+# body=>$confirm)
+# or print STDERR "Error sending order to customer: ",$mailer->errstr,"\n";
+# }
+
+# sub page {
+# my ($template, $acts) = @_;
+
+# BSE::Template->show_page($template, $cfg, $acts);
+# }
+
+# # convert an epoch time to sql format
+# sub epoch_to_sql {
+# use POSIX 'strftime';
+# my ($time) = @_;
+
+# return strftime('%Y-%m-%d', localtime $time);
+# }
+
+# sub refresh_logon {
+# my ($msg, $msgid, $r) = @_;
+# my $url = $securlbase."/cgi-bin/user.pl";
+
+# $r ||= $securlbase."/cgi-bin/shop.pl?checkout=1";
- my %parms;
- $parms{r} = $r;
- $parms{message} = $msg if $msg;
- $parms{mid} = $msgid if $msgid;
- $url .= "?" . join("&", map "$_=".CGI::escape($parms{$_}), keys %parms);
+# my %parms;
+# $parms{r} = $r;
+# $parms{message} = $msg if $msg;
+# $parms{mid} = $msgid if $msgid;
+# $url .= "?" . join("&", map "$_=".CGI::escape($parms{$_}), keys %parms);
- refresh_to($url);
-}
+# refresh_to($url);
+# }
__END__
=head1 CHANGES
+=head2 0.15_05
+
+WARNING: this release makes major changes to the way the shop works.
+Make sure you test B<BEFORE> you deploy.
+
+This release may introduce incompatibilities with older BSE::Custom
+modules, if you come across any of these, please let me know.
+
+To deploy this with a custom template set the following templates will
+need to be updated:
+
+ - checkoutnew_base.tmpl - new checkout page
+ - checkoutpay_base.tmpl - new payment page
+ - mailconfirm.tmpl - handle CC processing
+ - mailorder.tmpl - handle CC processing
+ - checkoutfinal_base.tmpl - display credit card receipt number
+
+This is primarily intended as a test release, currently it has three
+known problems:
+
+=over
+
+=item *
+
+the checkout page won't load the saved order values if you go back to
+it after having gone through it without finishing the order.
+
+=item *
+
+each failed online credit card transaction results in a new order in
+the database (marked as failed)
+
+=item *
+
+some admin side templates still need updating
+
+=back
+
+The changes:
+
+=over
+
+=item *
+
+major changes to the structure of the shop, There is no longer a
+purchase action, this has been split into an order action, which saves
+user information, and a payment action, which attempts to process a
+payment.
+
+The initial checkout page now uses the checkoutnew template rather
+than the checkout template. The payment page uses the checkoutpay
+template.
+
+=item *
+
+the final order display is now a separate request from the
+purchase/payment action, you can now make changes to the template and
+test them without having to create a new order each time. The final
+order display page will only display the last successful order for 5
+minutes.
+
+=item *
+
+the shop can now process credit card transactions online through the
+Inpho credit card gateway. There is also a also a "test" gateway
+module that allows for offline testing. See [shop].cardprocessor in
+config.pod
+
+=item *
+
+the shop (and other scripts that use the general dispatcher, like
+affiliate.pl and fmail.pl) should now work with image buttons
+submitting the form.
+
+=item *
+
+conditions to check for payments types on the shop pages that accepted
+a payment type name (like <:ifPayments Name:>) would always return
+value for the CC type, even when it was enabled.
+
+=item *
+
+fields that were marked as required previously are now actually
+required, this most likely a problem for the cardType field. I may
+end up marking this as not required.
+
+=item *
+
+a new credit card field value is accepted and passed to the online
+credit card processor, the cardVerify field.
+
+=item *
+
+fields on the checkout page now must use the name defined in the order
+record. The older names are no longer usable, except in the required
+method of BSE::Custom.
+
+=item *
+
+the error_img tag is now available on the checkout and payment pages
+
+=back
+
=head2 0.15_04
=over
Used to translate the stored order field name into a presentation name
suitable for error messages.
+=item cardprocessor
+
+The name of a class to load to process credit card transactions online.
+
+Currently this can be either DevHelp::Payments::Test or
+DevHelp::Payments::Inpho.
+
=back
+=head2 [Shop Order Validation]
+
+This section can contain extra order validation information, including
+specifying required fields, display names and extra validation rules.
+
=head2 [fields]
=over
These defaults are used when creating an article where no value is
supplied, they can also be accessed via the <:default I<name>:> tag.
+=head2 [inpho]
+
+This is used to configure the DevHelp::Payments::Inpho module.
+
+=over
+
+=item test
+
+If this is set then the test parameters are used instead of the
+product values.
+
+=item url
+
+The URL to process requests through.
+
+Default: https://extranet.inpho.com.au/cc_ssl/process
+
+=item user
+
+Inpho supplied user name.
+
+=item password
+
+Inpho supplied password.
+
+=item test_url
+
+The URL to process test requests through.
+
+=item test_user
+
+The user to supply to test requests.
+
+=item test_password
+
+The password to supply to test requests.
+
+=back
+
=head1 AUTHOR
Tony Cook <tony@develop-help.com>
var typeEl = MM_findObj('paymentType');
var type = typeEl.value;
if (type == 0) {
- MM_validateForm('name1','','R','name2','','R','address','','R','city','','R','postcode','','R','state','','R','country','','R','email','','RisEmail','cardHolder','','R','cardNumber','','R','cardExpiry','','R');
+ MM_validateForm('delivFirstName','','R','delivLastName','','R','delivStreet','','R','delivSuburb','','R','delivPostCode','','R','delivState','','R','delivCountry','','R','email','','RisEmail','cardHolder','','R','cardNumber','','R','cardExpiry','','R');
}
else {
- MM_validateForm('name1','','R','name2','','R','address','','R','city','','R','postcode','','R','state','','R','country','','R','email','','RisEmail');
+ MM_validateForm('delivFirstName','','R','delivLastName','','R','delivStreet','','R','delivSuburb','','R','delivPostCode','','R','delivState','','R','delivCountry','','R','email','','RisEmail');
}
}
<tr>
<td colspan=2>
<p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:message:></font></p>
- <br>
</td>
</tr>
<:or Message:><:eif Message:>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> First
Name:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="name1" size=34 value="<:old name1:>">
+ <input type="Text" name="delivFirstName" size=34 value="<:old delivFirstName:>"><:error_img delivFirstName:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Last Name:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="name2" size=34 value="<:old name2:>">
+ <input type="Text" name="delivLastName" size=34 value="<:old delivLastName:>"><:error_img delivLastName:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Address:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="address" size=34 value="<:old address:>">
+ <input type="Text" name="delivStreet" size=34 value="<:old delivStreet:>" /><:error_img delivStreet:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> City:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="city" size=34 value="<:old city:>">
+ <input type="Text" name="delivSuburb" size=34 value="<:old delivSuburb:>" /><:error_img delivSuburb:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Postcode:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="postcode" size=10 value="<:old postcode:>">
+ <input type="Text" name="delivPostCode" size=10 value="<:old delivPostCode:>" /><:error_img delivPostCode:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> State:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="state" size=10 value="<:old state:>">
+ <input type="Text" name="delivState" size=10 value="<:old delivState:>" /><:error_img delivState:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Country:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="country" size=20 value="<:old country:>">
+ <input type="Text" name="delivCountry" size=20 value="<:old delivCountry:>" /><:error_img delivCountry:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Telephone:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="telephone" size=20 value="<:old telephone:>">
+ <input type="Text" name="telephone" size=20 value="<:old telephone:>" /><:error_img telephone:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Mobile:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="delivMobile" size=20 value="<:old delivMobile:>">
+ <input type="Text" name="delivMobile" size=20 value="<:old delivMobile:>" /><:error_img delivMobile:>
</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Facsimile:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="facsimile" size=20 value="<:old facsimile:>">
+ <input type="Text" name="facsimile" size=20 value="<:old facsimile:>" /><:error_img facsimile:>
</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> E-mail:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="email" size=34 value="<:old email:>">
+ <input type="Text" name="email" size=34 value="<:old email:>"><:error_img email:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> First
Name:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billFirstName" size=34 value="<:old billFirstName:>">
+ <input type="Text" name="billFirstName" size=34 value="<:old billFirstName:>"><:error_img billFirstName:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Last Name:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billLastName" size=34 value="<:old billLastName:>">
+ <input type="Text" name="billLastName" size=34 value="<:old billLastName:>"><:error_img billLastName:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Address:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billStreet" size=34 value="<:old billStreet:>">
+ <input type="Text" name="billStreet" size=34 value="<:old billStreet:>"><:error_img billStreet:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> City:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billSuburb" size=34 value="<:old billSuburb:>">
+ <input type="Text" name="billSuburb" size=34 value="<:old billSuburb:>"><:error_img billSuburb:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Postcode:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billPostCode" size=10 value="<:old billPostCode:>">
+ <input type="Text" name="billPostCode" size=10 value="<:old billPostCode:>"><:error_img billPostCode:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> State:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billState" size=10 value="<:old billState:>">
+ <input type="Text" name="billState" size=10 value="<:old billState:>"><:error_img billState:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Country:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billCountry" size=20 value="<:old billCountry:>">
+ <input type="Text" name="billCountry" size=20 value="<:old billCountry:>"><:error_img billCountry:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Email:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billEmail" size=20 value="<:old billEmail:>">
+ <input type="Text" name="billEmail" size=20 value="<:old billEmail:>"><:error_img billEmail:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Telephone:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billTelephone" size=20 value="<:old billTelephone:>">
+ <input type="Text" name="billTelephone" size=20 value="<:old billTelephone:>"><:error_img billTelephone:>
*</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Mobile:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billMobile" size=20 value="<:old billMobile:>">
+ <input type="Text" name="billMobile" size=20 value="<:old billMobile:>"><:error_img billMobile:>
</font></td>
</tr>
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Facsimile:</font></td>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- <input type="Text" name="billFacsimile" size=20 value="<:old billFacsimile:>">
+ <input type="Text" name="billFacsimile" size=20 value="<:old billFacsimile:>"><:error_img billFacsimile:>
*</font></td>
</tr>
</table>
</table>
<p>
<input type="submit" value="Update" name="checkupdate" />
- <input type="submit" value="Purchase Now" name="purchase">
+ <input type="submit" value="Purchase Now" name="a_order">
<input type="reset" value="Reset Form" name="reset">
</p>
</form>
<p> The <:siteName:> store is run on a secure encrypted server, your details are
safe with us.<br>
</p>
-<:if Payment CC:><:or Payment:><:eif Payment:>
+<:if Payment CC:><p>Paid by credit card.</p><:if Order ccOnline:><p>Credit Card Receipt Number: <:order ccReceipt:></p><:or Order:><:eif Order:><:or Payment:><:eif Payment:>
<:if Payment Cheque:>
<p>Please send your cheque to:</p>
<ul><:cfg shop address1 |h:><br>
--- /dev/null
+<:wrap base.tmpl:>
+<script language="JavaScript">
+<!--
+function MM_findObj(n, d) { //v4.01
+ var p,i,x; if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
+ d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
+ if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
+ for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document);
+ if(!x && d.getElementById) x=d.getElementById(n); return x;
+}
+
+function MM_validateForm() { //v4.0
+ var i,p,q,nm,test,num,min,max,errors='',args=MM_validateForm.arguments;
+ for (i=0; i<(args.length-2); i+=3) { test=args[i+2]; val=MM_findObj(args[i]);
+ if (val) { nm=val.name; if ((val=val.value)!="") {
+ if (test.indexOf('isEmail')!=-1) { p=val.indexOf('@');
+ if (p<1 || p==(val.length-1)) errors+='- '+nm+' must contain an e-mail address.\n';
+ } else if (test!='R') {
+ if (isNaN(val)) errors+='- '+nm+' must contain a number.\n';
+ if (test.indexOf('inRange') != -1) { p=test.indexOf(':');
+ min=test.substring(8,p); max=test.substring(p+1);
+ if (val<min || max<val) errors+='- '+nm+' must contain a number between '+min+' and '+max+'.\n';
+ } } } else if (test.charAt(0) == 'R') errors += '- '+nm+' is required.\n'; }
+ } if (errors) alert('The following error(s) occurred:\n'+errors);
+ document.MM_returnValue = (errors == '');
+}
+
+function BSE_validateForm {
+ var typeEl = MM_findObj('paymentType');
+ var type = typeEl.value;
+ if (type == 0) {
+ MM_validateForm('delivFirstName','','R','delivLastName','','R','delivStreet','','R','delivSuburb','','R','delivPostCode','','R','delivState','','R','delivCountry','','R','emailAddress','','RisEmail','cardHolder','','R','cardNumber','','R','cardExpiry','','R');
+ }
+ else {
+ MM_validateForm('delivFirstName','','R','delivLastName','','R','delivStreet','','R','delivSuburb','','R','delivPostCode','','R','delivState','','R','delivCountry','','R','emailAddress','','RisEmail');
+ }
+}
+
+//-->
+</script>
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td width="80%" height="24"> <font face="Arial, Helvetica, sans-serif" size="4" color="#FF7F00"><b><:title:></b></font></td>
+ <td height="24"> </td>
+ </tr>
+ <tr>
+ <td bgcolor="#999999" colspan="2" height="1"><img src="/images/trans_pixel.gif" width="24" height="1" border="0"></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td width="100"><img src="/images/trans_pixel.gif" width="100" height="10" border="0"></td>
+ <td bgcolor="#999999" width="100%"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2">/
+ <a href="<:ifAdmin:>/cgi-bin/admin/admin.pl?id=1<:or:>/<:eif:>"><font color="#FFFFFF">Home</font></a>
+ / <a href="/shop/index.html"><font color="#FFFFFF"><:article title:></font></a>
+ /</font></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+<p> <b><font face="Verdana, Arial, Helvetica, sans-serif" size="3"> Thank you
+ for shopping at <:siteName:></font></b></p>
+<font class="article_body_text" face="Verdana, Arial, Helvetica, sans-serif" size="2">
+<p> The <:siteName:> store is run on a secure encrypted server, your details are
+ safe with us.<br>
+</p>
+</font>
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td align="center" bgcolor="#CCCCCC" width="100%" height="18"> <font size="2" face="Verdana, Arial, Helvetica, sans-serif">
+ <b>Shopping Cart Items</b></font></td>
+ </tr>
+</table>
+<table border="0" cellspacing="0" cellpadding="1" width="100%" bgcolor="#666666">
+ <tr valign="middle" align="center">
+ <td width="100%">
+ <table width="100%" border="0" cellspacing="1" cellpadding="2" bgcolor="#EEEEEE">
+ <tr valign="middle" align="center" bgcolor="#666666">
+ <td width="100%" align="left" height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Item:</b></font> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF">(All
+ prices in AUD – includes GST and shipping costs where applicable)</font></td>
+ <td nowrap height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Qty:</b></font> </td>
+ <td height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Price:</b></font> </td>
+ </tr>
+ <:iterator begin items:>
+ <tr valign="middle" align="center" bgcolor="#FFFFFF">
+ <td width="100%" align="left"> <a href="<:item link:>"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
+ summary:> <:options:></font></a></td>
+ <td nowrap align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
+ units:></font></td>
+ <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>$<:
+ money item retailPrice :></b></font></td>
+ </tr>
+ <:iterator end items:>
+ </table>
+ </td>
+ </tr>
+</table>
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td> </td>
+ <td height="20"> </td>
+ <td height="20" bgcolor="#666666"> </td>
+ <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF">
+ <b>GRAND TOTAL</b></font></td>
+ <td height="20" bgcolor="#666666"> </td>
+ </tr>
+ <tr>
+ <td width="50%" valign="MIDDLE"><a href="/shop/index.html"><img src="/images/store/browse_more.gif" width="133" height="21" border="0" alt="Browse More"></a></td>
+ <td NOWRAP width="50%">
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr></tr>
+ </table>
+ </td>
+ <td><img src="/images/store/left_bottom_corner_line.gif" width="26" height="31"></td>
+ <td align="center" bgcolor="#FFFFFF" height="100%" NOWRAP> <font size="3" face="Verdana, Arial, Helvetica, sans-serif">
+ <b>$<:money total:></b></font></td>
+ <td><img src="/images/store/right_bottom_corner_line.gif" width="26" height="31"></td>
+ </tr>
+ <tr>
+ <td width="50%"></td>
+ <td width="50%"></td>
+ <td></td>
+ <td bgcolor="#666666"><img src="/images/trans_pixel.gif" width="1" height="1"></td>
+ <td></td>
+ </tr>
+</table>
+<:if User:>
+<p> </p>
+<:or User:>
+ <br>
+ <table bgcolor="#EEEEEE" border="0" cellspacing="0" cellpadding="10" width="100%">
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">If you wish to track the status of your order you must either <a href="/cgi-bin/user.pl?show_register=1&r=/cgi-bin/shop.pl?checkout=1"><b>Register</b></a> or <a href="/cgi-bin/user.pl?show_logon=1&r=/cgi-bin/shop.pl?checkout=1"><b>Logon</b></a> before you continue with this purchase.</font></td>
+ </tr>
+ </table>
+ <br>
+<:eif User:>
+<form action="/cgi-bin/shop.pl" method="POST" onSubmit="BSE_validateForm();return document.MM_returnValue">
+ <font face="Verdana, Arial, Helvetica, sans-serif" size="3"> <b>Shipping Details:</b></font>
+ <hr noshade size="1">
+ <table border="0" cellspacing="0" cellpadding="0">
+ <:if Message:>
+ <tr>
+ <td colspan=2>
+ <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:message:></font></p>
+ </td>
+ </tr>
+ <:or Message:><:eif Message:>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> First
+ Name:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivFirstName" size=34 value="<:old delivFirstName:>"><:error_img delivFirstName:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Last Name:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivLastName" size=34 value="<:old delivLastName:>"><:error_img delivLastName:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Address:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivStreet" size=34 value="<:old delivStreet:>" /><:error_img delivStreet:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> City:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivSuburb" size=34 value="<:old delivSuburb:>" /><:error_img delivSuburb:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Postcode:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivPostCode" size=10 value="<:old delivPostCode:>" /><:error_img delivPostCode:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> State:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivState" size=10 value="<:old delivState:>" /><:error_img delivState:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Country:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivCountry" size=20 value="<:old delivCountry:>" /><:error_img delivCountry:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Telephone:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="telephone" size=20 value="<:old telephone:>" /><:error_img telephone:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Mobile:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="delivMobile" size=20 value="<:old delivMobile:>" /><:error_img delivMobile:>
+ </font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Facsimile:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="facsimile" size=20 value="<:old facsimile:>" /><:error_img facsimile:>
+ </font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> E-mail:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="emailAddress" size=34 value="<:old emailAddress:>"><:error_img emailAddress:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td valign="top"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Special<br />Instructions:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <textarea name="instructions" rows="5" cols="40" wrap="virtual"><:old instructions:></textarea></font><:error_img instructions:></td>
+ </tr>
+ <tr>
+ <td colspan="2"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ * Required information for order to be shipped</font></td>
+ </tr>
+ </table>
+ <p> </p>
+ <:if Cgi need_billing:>
+ <font face="Verdana, Arial, Helvetica, sans-serif" size="3"><input type="checkbox" name="need_billing" checked="checked" onClick="this.form.checkupdate.click()" /> <b>Billing Details:</b></font>
+
+ <hr size="1" noshade>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> First
+ Name:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billFirstName" size=34 value="<:old billFirstName:>"><:error_img billFirstName:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Last Name:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billLastName" size=34 value="<:old billLastName:>"><:error_img billLastName:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Address:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billStreet" size=34 value="<:old billStreet:>"><:error_img billStreet:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> City:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billSuburb" size=34 value="<:old billSuburb:>"><:error_img billSuburb:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Postcode:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billPostCode" size=10 value="<:old billPostCode:>"><:error_img billPostCode:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> State:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billState" size=10 value="<:old billState:>"><:error_img billState:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Country:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billCountry" size=20 value="<:old billCountry:>"><:error_img billCountry:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Email:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billEmail" size=20 value="<:old billEmail:>"><:error_img billEmail:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Telephone:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billTelephone" size=20 value="<:old billTelephone:>"><:error_img billTelephone:>
+ *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Mobile:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billMobile" size=20 value="<:old billMobile:>"><:error_img billMobile:>
+ </font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Facsimile:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="billFacsimile" size=20 value="<:old billFacsimile:>" /><:error_img billFacsimile:>
+ *</font></td>
+ </tr>
+ </table>
+ <p> </p>
+ <:or Cgi:>
+ <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="checkbox" name="need_billing" onClick="this.form.checkupdate.click()" /> Billing details different to shipping</font></p>
+ <:eif Cgi:>
+<:include custom/checkout.include optional:>
+ <p>
+ <input type="submit" value="Update" name="checkupdate" />
+ <input type="submit" value="Purchase Now" name="a_order">
+ <input type="reset" value="Reset Form" name="reset">
+ </p>
+ </form>
--- /dev/null
+<:wrap base.tmpl:>
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td width="80%" height="24"> <font face="Arial, Helvetica, sans-serif" size="4" color="#FF7F00"><b><:title:></b></font></td>
+ <td height="24"> </td>
+ </tr>
+ <tr>
+ <td bgcolor="#999999" colspan="2" height="1"><img src="/images/trans_pixel.gif" width="24" height="1" border="0"></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td width="100"><img src="/images/trans_pixel.gif" width="100" height="10" border="0"></td>
+ <td bgcolor="#999999" width="100%"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2">/
+ <a href="<:ifAdmin:>/cgi-bin/admin/admin.pl?id=1<:or:>/<:eif:>"><font color="#FFFFFF">Home</font></a>
+ / <a href="/shop/index.html"><font color="#FFFFFF"><:article title:></font></a>
+ /</font></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+<p> <b><font face="Verdana, Arial, Helvetica, sans-serif" size="3"> Thank you
+ for shopping at <:siteName:></font></b></p>
+<font class="article_body_text" face="Verdana, Arial, Helvetica, sans-serif" size="2">
+<p> The <:siteName:> store is run on a secure encrypted server, your details are
+ safe with us.</p></font>
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td align="center" bgcolor="#CCCCCC" width="100%" height="18"> <font size="2" face="Verdana, Arial, Helvetica, sans-serif">
+ <b>Shopping Cart Items</b></font></td>
+ </tr>
+</table>
+<table border="0" cellspacing="0" cellpadding="1" width="100%" bgcolor="#666666">
+ <tr valign="middle" align="center">
+ <td width="100%">
+ <table width="100%" border="0" cellspacing="1" cellpadding="2" bgcolor="#EEEEEE">
+ <tr valign="middle" align="center" bgcolor="#666666">
+ <td width="100%" align="left" height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Item:</b></font> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF">(All
+ prices in AUD – includes GST and shipping costs where applicable)</font></td>
+ <td nowrap height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Qty:</b></font> </td>
+ <td height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Price:</b></font> </td>
+ </tr>
+ <:iterator begin items:>
+ <tr valign="middle" align="center" bgcolor="#FFFFFF">
+ <td width="100%" align="left"> <a href="<:item link:>"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
+ summary:> <:options:></font></a></td>
+ <td nowrap align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
+ units:></font></td>
+ <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>$<:
+ money item retailPrice :></b></font></td>
+ </tr>
+ <:iterator end items:>
+ </table>
+ </td>
+ </tr>
+</table>
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td> </td>
+ <td height="20"> </td>
+ <td height="20" bgcolor="#666666"> </td>
+ <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF">
+ <b>GRAND TOTAL</b></font></td>
+ <td height="20" bgcolor="#666666"> </td>
+ </tr>
+ <tr>
+ <td width="50%" valign="MIDDLE"><a href="/shop/index.html"><img src="/images/store/browse_more.gif" width="133" height="21" border="0" alt="Browse More"></a></td>
+ <td NOWRAP width="50%">
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr></tr>
+ </table>
+ </td>
+ <td><img src="/images/store/left_bottom_corner_line.gif" width="26" height="31"></td>
+ <td align="center" bgcolor="#FFFFFF" height="100%" NOWRAP> <font size="3" face="Verdana, Arial, Helvetica, sans-serif">
+ <b>$<:money total:></b></font></td>
+ <td><img src="/images/store/right_bottom_corner_line.gif" width="26" height="31"></td>
+ </tr>
+ <tr>
+ <td width="50%"></td>
+ <td width="50%"></td>
+ <td></td>
+ <td bgcolor="#666666"><img src="/images/trans_pixel.gif" width="1" height="1"></td>
+ <td></td>
+ </tr>
+</table>
+<form action="/cgi-bin/shop.pl" method="post">
+ <font face="Verdana, Arial, Helvetica, sans-serif" size="3"> <b>Payment Details:</b></font>
+ <hr size="1" noshade>
+<:ifMsg:><p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><:msg:></font></b></p><:or:><:eif:>
+ <:if Payments CC :>
+ <:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><input type="radio" name="paymentType" value="0" <:checkedPayment CC:>> Credit Card</font></p><:or MultPaymentTypes:><input type=hidden name=paymentType value=0 <:checkedPayment CC:>><:eif MultPaymentTypes:>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Name on
+ Card: </font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="cardHolder" size=30 value="<:old cardHolder:>"><:error_img cardHolder:>
+ (As per card) *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Card Number:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="cardNumber" size=16 maxlength="16" value="<:old cardNumber:>"><:error_img cardNumber:>*
+ CVV: <input type="text" name="cardVerify" size="4" maxlength="4" value="<:old cardVerify:>" /><:error_img cardVerify:> (no spaces)</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Expiry
+ Date:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <input type="Text" name="cardExpiry" size=5 maxlength="5" value="<:old cardExpiry:>"><:error_img cardExpiry:>
+ (eg: 09/01) *</font></td>
+ </tr>
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Card Type:</font></td>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <select name="cardType">
+ <option value="">Choose type</option>
+ <option value="Visa" <:ifEq [cgi cardType] "Visa":>selected="selected"<:or:><:eif:>>Visa</option>
+ <option value="Mastercard" <:ifEq [cgi cardType] "Mastercard":>selected="selected"<:or:><:eif:>>Mastercard</option>
+ <option value="Bankcard" <:ifEq [cgi cardType] "Bankcard":>selected="selected"<:or:><:eif:>>Bankcard</option>
+ </select><:error_img cardType:>
+ *</font></td>
+ </tr>
+ </table>
+ <:or Payments:><:eif Payments:>
+ <:if Payments Cheque:>
+ <:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type=radio name=paymentType value=1 <:checkedPayment Cheque:>/>
+ Cheque</font></p><:or MultPaymentTypes:><input type=hidden name=paymentType value=1><:eif MultPaymentTypes:>
+ <p> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Please send your cheque to:</font></p>
+ <ul> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <:cfg shop address1:><br />
+ <:cfg shop address2:><br />
+ <:cfg shop address3:></font></ul>
+ <:or Payments:><:eif Payments:>
+ <:if Payments CallMe:>
+ <:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><input type=radio name=paymentType value=2 <:checkedPayment CallMe:>/> Contact me for billing details</font></p>
+ <:or MultPaymentTypes:>
+ <input type=hidden name=paymentType value=2>
+ <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">We will call you to arrange for payment.</font></p>
+ <:eif MultPaymentTypes:>
+ <:or Payments:>
+
+ <:eif Payments:>
+ <:include custom/payment_type.include optional:>
+ <p> </p>
+ <font face="Verdana, Arial, Helvetica, sans-serif" size="3"> <b>Tax Invoice
+ / Receipt & Delivery Costs:</b></font>
+ <hr size="1" noshade>
+ <table border="0" cellspacing="0" cellpadding="0" width="375">
+ <tr>
+ <td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">We will
+ include a tax invoice / receipt with your order, clearly showing the GST
+ and delivery components of the purchase price.</font></td>
+ </tr>
+ </table>
+ <p>
+ <input type="submit" value="Update" name="checkupdate" />
+ <input type="submit" value="Purchase Now" name="payment">
+ <input type="reset" value="Reset Form" name="reset">
+ </p>
+ </form>
Total: <:order total |m10:>
GST: <:order gst |m10:>
-<:ifEq [order paymentType] "0" :>Paid by credit card.<:or:><:eif
+<:ifEq [order paymentType] "0" :>Paid by credit card.<:if Order ccOnline
+:>Processed online.
+Receipt No. : <:order ccReceipt:>
+<:or Order
+:><:eif Order:><:or:><:eif
:><:ifEq [order paymentType] "1" :>Will be paid by cheque<:or
:><:eif:><:ifEq [order paymentType] "2"
:>We will call you to arrange for payment<:or:><:eif:><:
GST: <:order gst |m10:>
<:ifEq [order paymentType] "0":>Paid by credit card:
-Card No. : <:cardNumber:>
+<:if Order ccOnline
+:>Processed online.
+Receipt No. : <:order ccReceipt:>
+<:or Order
+:>Card No. : <:cardNumber:>
Expires : <:cardExpiry:>
Name on Card : <:order ccName:>
-Card Type : <:order ccType :><:or:><:eif
+Card Type : <:order ccType :><:eif Order:><:or:><:eif
:><:ifEq [order paymentType] "1" :>Will be paid by cheque<:or
:><:eif:><:ifEq [order paymentType] "2"
:>Please call the customer to arrange for payment<:or:><:eif:><:
#custom.user_auth=1
# product fields.retailPrice=Dealer Price Inc GST
#paths.local_templates=/home/tony/dev/bse/tandb_dealer/cvs/templates
-shop.payment_types=1,2,10,11,12
+#shop.payment_types=1,2,10,11,12
payment type names.10=DirectDeposit
payment type names.11=FaxProForma
payment type names.12=EmailProForma
article defaults.body=<set the body>
product defaults.title=<set the product title>
catalog defaults.title=<set the catalog title>
+
+#shop.cardprocessor=DevHelp::Payments::Test
+shop.cardprocessor=DevHelp::Payments::Inpho
+inpho.user=theowww
+inpho.password=s1tz^^mD1
+inpho.test=1
+inpho.test_url=http://www.develop-help.com/cgi-bin/inphotest.pl
+inpho.test_user=test
+inpho.test_password=test