site/cgi-bin/modules/BSE/Passwords.pm
site/cgi-bin/modules/BSE/Permissions.pm
site/cgi-bin/modules/BSE/ProductImportXLS.pm
+site/cgi-bin/modules/BSE/PayPal.pm
site/cgi-bin/modules/BSE/Report.pm
site/cgi-bin/modules/BSE/Request.pm
site/cgi-bin/modules/BSE/Request/Base.pm
site/templates/admin/helpicon.tmpl Help icon template for admin templates
site/templates/admin/image_edit.tmpl Edit a single image
site/templates/admin/interestemail.tmpl
+site/templates/admin/include/audithead.tmpl
+site/templates/admin/include/auditentry.tmpl
site/templates/admin/logon.tmpl
site/templates/admin/memberupdate/import.tmpl
site/templates/admin/memberupdate/preview.tmpl
-- trace of the request and response
shipping_trace text null,
+ -- paypal stuff
+ -- token from SetExpressCheckout
+ paypal_token varchar(255) null,
+
+ paypal_tran_id varchar(255) null,
+
primary key (id),
index order_cchash(ccNumberHash),
index order_userId(userId, orderDate)
#!/usr/bin/perl -w
# -d:ptkdb
-BEGIN { $ENV{DISPLAY} = '192.168.32.51:0.0'; }
+BEGIN { $ENV{DISPLAY} = '192.168.32.54:0.0'; }
use strict;
use FindBin;
return $single->entry('html', 'charset', 'iso-8859-1');
}
+=item user_url($script, $target)
+
+=cut
+
+sub user_url {
+ my ($cfg, $script, $target, @options) = @_;
+
+ my $base = $script eq 'shop' ? $cfg->entryVar('site', 'secureurl') : '';
+ my $template;
+ if ($target) {
+ if ($script eq 'nuser') {
+ $template = "/cgi-bin/nuser.pl/user/TARGET";
+ }
+ else {
+ $template = "$base/cgi-bin/$script.pl?a_TARGET=1";
+ }
+ $template = $cfg->entry('targets', $script, $template);
+ $template =~ s/TARGET/$target/;
+ }
+ else {
+ if ($script eq 'nuser') {
+ $template = "/cgi-bin/nuser.pl/user";
+ }
+ else {
+ $template = "$base/cgi-bin/$script.pl";
+ }
+ $template = $cfg->entry('targets', $script.'_n', $template);
+ }
+ if (@options) {
+ $template .= $template =~ /\?/ ? '&' : '?';
+ my @entries;
+ while (my ($key, $value) = splice(@options, 0, 2)) {
+ require BSE::Util::HTML;
+ push @entries, "$key=" . BSE::Util::HTML::escape_uri($value);
+ }
+ $template .= join '&', @entries;
+ }
+
+ return $template;
+}
+
1;
=head1 AUTHOR
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,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
replaceOrderItem => 'replace order_item values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
getOrderByUserId => 'select * from orders where userId = ?',
--- /dev/null
+package BSE::PayPal;
+use strict;
+use BSE::Cfg;
+use BSE::Util::HTML;
+use BSE::Shop::Util qw(:payment);
+use Carp qw(confess);
+
+use constant DEF_TEST_WS_URL => "https://api-3t.sandbox.paypal.com/nvp";
+use constant DEF_TEST_REFRESH_URL => "https://www.sandbox.paypal.com/webscr";
+
+use constant DEF_LIVE_WS_URL => "https://api-3t.paypal.com/nvp";
+use constant DEF_LIVE_REFRESH_URL => "https://www.paypal.com/cgibin/webscr";
+
+my %defs =
+ (
+ test_ws_url => DEF_TEST_WS_URL,
+ test_refresh_url => DEF_TEST_REFRESH_URL,
+
+ live_ws_url => DEF_LIVE_WS_URL,
+ live_refresh_url => DEF_LIVE_REFRESH_URL,
+ );
+
+sub _test {
+ my ($cfg) = @_;
+
+ return $cfg->entry("paypal", "test", 1);
+}
+
+sub _cfg {
+ my ($cfg, $key) = @_;
+
+ my $realkey = _test($cfg) ? "test_$key" : "live_$key";
+ if (exists $defs{$realkey}) {
+ return $cfg->entry("paypal", $realkey, $defs{$realkey});
+ }
+ else {
+ return $cfg->entryErr("paypal", $realkey);
+ }
+}
+
+sub _base_ws_url {
+ my ($cfg) = @_;
+
+ return _cfg($cfg, "ws_url");
+}
+
+sub _base_refresh_url {
+ my ($cfg) = @_;
+
+ return _cfg($cfg, "refresh_url");
+}
+
+sub _api_signature {
+ my ($cfg) = @_;
+
+ return _cfg($cfg, "api_signature");
+}
+
+sub _api_username {
+ my ($cfg) = @_;
+
+ return _cfg($cfg, "api_username");
+}
+
+sub _api_password {
+ my ($cfg) = @_;
+
+ return _cfg($cfg, "api_password");
+}
+
+sub _format_amt {
+ my ($price) = @_;
+
+ return sprintf("%d.%02d", int($price / 100), $price % 100);
+}
+
+sub _order_amt {
+ my ($order) = @_;
+
+ return _format_amt($order->total);
+}
+
+sub _order_currency {
+ my ($order) = @_;
+
+ return BSE::Cfg->single->entry("shop", "currency", "AUD")
+}
+
+sub payment_url {
+ my ($class, %opts) = @_;
+
+ my $order = delete $opts{order}
+ or confess "Missing order";
+ my $rmsg = delete $opts{msg}
+ or confess "Missing msg";
+ my $who = delete $opts{user} || "U";
+ my $cfg = BSE::Cfg->single;
+
+ my %info = _set_express_checkout($cfg, $order, $who, $rmsg)
+ or return;
+
+ $order->set_paypal_token($info{TOKEN});
+ $order->save;
+
+ my $url = _make_url(_base_refresh_url($cfg),
+ {
+ cmd => "_express-checkout",
+ token => $info{TOKEN},
+ useraction => "confirm",
+ AMT => _order_amt($order),
+ CURRENCYCODE => _order_currency($order)
+ }
+ );
+
+# BSE::TB::AuditLog->log
+# (
+# component => "shop:paypal:paymenturl",
+# level => "debug",
+# object => $order,
+# actor => $who,
+# msg => "URL $url",
+# );
+
+ return $url;
+}
+
+# the _api_*() functions will die if not configured
+sub configured {
+ my $cfg = BSE::Cfg->single;
+
+ return eval
+ {
+ _api_username($cfg) && _api_password($cfg) && _api_signature($cfg);
+ 1;
+ };
+}
+
+sub pay_order {
+ my ($class, %opts) = @_;
+
+ my $order = delete $opts{order}
+ or confess "Missing order";
+ my $req = delete $opts{req}
+ or confess "Missing req";
+ my $rmsg = delete $opts{msg}
+ or confess "Missing msg";
+
+ my $cgi = $req->cgi;
+ my $cfg = $req->cfg;
+ my $token = $cgi->param("token");
+ unless ($token) {
+ $$rmsg = $req->catmsg("msg:bse/shop/paypal/notoken");
+ return;
+ }
+ my $payerid = $cgi->param("PayerID");
+ unless ($payerid) {
+ $$rmsg = $req->catmsg("msg:bse/shop/paypal/nopayerid");
+ return;
+ }
+ unless ($token eq $order->paypal_token) {
+ print STDERR "cgi $token order ", $order->paypal_token, "\n";
+ $$rmsg = $req->catmsg("msg:bse/shop/paypal/badtoken");
+ return;
+ }
+
+ my %info;
+ $DB::single = 1;
+ if (_do_express_checkout_payment
+ ($cfg, $rmsg, $order, scalar($req->siteuser), $token, $payerid, \%info)) {
+ $order->set_paypal_tran_id($info{TRANSACTIONID});
+
+ }
+ elsif (keys %info) {
+ unless ($info{L_ERRORCODE}
+ && $info{L_ERRORCODE} == 10415
+ && $info{CHECKOUTSTATUS}
+ && $info{CHECKOUTSTATUS} eq "PaymentActionCompleted"
+ && $info{PAYMENTREQUEST_0_TRANSACTIONID}) {
+ return; # something else went wrong
+ }
+
+ # already processed, maybe there was an error when the user first
+ # returned, treat it as completed
+ $order->set_paypal_tran_id($info{PAYMENTREQUEST_0_TRANSACTIONID});
+ }
+ $order->set_paypal_token("");
+ $order->set_paidFor(1);
+ $order->set_paymentType(PAYMENT_PAYPAL);
+ $order->set_complete(1);
+ $order->save;
+ BSE::TB::AuditLog->log
+ (
+ component => "shop:paypal:pay",
+ level => "notice",
+ object => $order,
+ actor => scalar($req->siteuser) || "U",
+ msg => "Order paid via Paypal, transaction ".$order->paypal_tran_id,
+ );
+
+ return 1;
+}
+
+sub refund_order {
+ my ($class, %opts) = @_;
+
+ my $order = delete $opts{order}
+ or confess "Missing order";
+ my $rmsg = delete $opts{msg}
+ or confess "Missing msg";
+ my $req = delete $opts{req}
+ or confess "Missing req";
+
+ unless ($order->paymentType eq PAYMENT_PAYPAL) {
+ $$rmsg = "This order was not paid by paypal";
+ return;
+ }
+
+ my $cfg = BSE::Cfg->single;
+ my %info = _do_refund_transaction($cfg, $rmsg, $order, scalar($req->user))
+ or return;
+
+ $order->set_paidFor(0);
+ $order->save;
+
+ BSE::TB::AuditLog->log
+ (
+ component => "shop:paypal:refund",
+ level => "notice",
+ object => $order,
+ actor => scalar($req->user) || "U",
+ msg => "PayPal payment refunded, transaction $info{REFUNDTRANSACTIONID}",
+ );
+
+ return 1;
+}
+
+sub _do_refund_transaction {
+ my ($cfg, $rmsg, $order, $who) = @_;
+
+ my %params =
+ (
+ VERSION => "62.0",
+ TRANSACTIONID => $order->paypal_tran_id,
+ REFUNDTYPE => "Full",
+ );
+
+ my %info = _api_req($cfg, $rmsg, $order, $who, "RefundTransaction", \%params)
+ or return;
+
+ return %info;
+}
+
+sub _make_qparam {
+ my ($param) = @_;
+
+ return join("&", map { "$_=".escape_uri($param->{$_}) } sort keys %$param);
+}
+
+sub _make_url {
+ my ($base, $param) = @_;
+
+ my $sep = $base =~ /\?/ ? "&" : "?";
+
+ return $base . $sep . _make_qparam($param);
+}
+
+sub _shop_url {
+ my ($cfg, $action, @params) = @_;
+
+ return $cfg->user_url("shop", $action, @params);
+}
+
+sub _populate_from_order {
+ my ($params, $order, $cfg) = @_;
+
+ $params->{AMT} = _order_amt($order);
+ $params->{CURRENCYCODE} = _order_currency($order);
+
+ my $index = 0;
+ for my $item ($order->items) {
+ $params->{"L_NAME$index"} = $item->title;
+ $params->{"L_AMT$index"} = _format_amt($item->price);
+ $params->{"L_QTY$index"} = $item->units;
+ $params->{"L_NUMBER$index"} = $item->product_code
+ if $item->product_code;
+ ++$index;
+ }
+ $params->{SHIPPINGAMT} = _format_amt($order->shipping_cost)
+ if $order->shipping_cost;
+
+ # use our shipping information
+ my $country_code = $order->deliv_country_code;
+ if ($country_code && $cfg->entry("paypal", "shipping", 1)) {
+ $params->{SHIPTONAME} = $order->delivFirstName . " " . $order->delivLastName;
+ $params->{SHIPTOSTREET} = $order->delivStreet;
+ $params->{SHIPTOSTREET2} = $order->delivStreet2;
+ $params->{SHIPTOCITY} = $order->delivSuburb;
+ $params->{SHIPTOSTATE} = $order->delivState;
+ $params->{SHIPTOZIP} = $order->delivPostCode;
+ $params->{SHIPTOCOUNTRYCODE} = $country_code;
+ $params->{ADDROVERRIDE} = 1;
+ }
+ else {
+ $params->{NOSHIPPING} = 1;
+ }
+}
+
+sub _set_express_checkout {
+ my ($cfg, $order, $who, $rmsg) = @_;
+
+ my %params =
+ (
+ $cfg->entriesCS("paypal custom"),
+ VERSION => "62.0",
+ RETURNURL => _shop_url($cfg, "paypalret", order => $order->randomId),
+ CANCELURL => _shop_url($cfg, "paypalcan", order => $order->randomId),
+ PAYMENTACTION => "Sale",
+ );
+
+ _populate_from_order(\%params, $order, $cfg);
+
+ my %info = _api_req($cfg, $rmsg, $order, $who,"SetExpressCheckout",
+ \%params)
+ or return;
+
+ unless ($info{TOKEN}) {
+ $$rmsg = "No token returned by PayPal";
+ return;
+ }
+
+ return %info;
+}
+
+ sub _get_express_checkout_details {
+ my ($cfg, $order, $who, $rmsg, $token) = @_;
+
+ my %params =
+ (
+ TOKEN => $token,
+ VERSION => "62.0",
+ );
+
+ my %info = _api_req($cfg, $rmsg, $order, $who, "GetExpressCheckoutDetails",
+ \%params)
+ or return;
+
+ return %info;
+}
+
+sub _do_express_checkout_payment {
+ my ($cfg, $rmsg, $order, $who, $token, $payerid, $info) = @_;
+
+ my %params =
+ (
+ VERSION => "62.0",
+ PAYMENTACTION => "Sale",
+ TOKEN => $token,
+ PAYERID => $payerid,
+ );
+
+ _populate_from_order(\%params, $order, $cfg);
+
+ my %info = _api_req($cfg, $rmsg, $order, $who || "U", "DoExpressCheckoutPayment",
+ \%params, $info)
+ or return;
+
+ return %info;
+}
+
+# Low level API request
+sub _api_req {
+ my ($cfg, $rmsg, $order, $who, $method, $param, $info) = @_;
+
+ $who ||= "U";
+
+ require LWP::UserAgent;
+ my $ua = LWP::UserAgent->new;
+ $param->{METHOD} = $method;
+ $param->{USER} = _api_username($cfg);
+ $param->{PWD} = _api_password($cfg);
+ $param->{SIGNATURE} = _api_signature($cfg);
+
+ my $post = _make_qparam($param);
+
+ my $req = HTTP::Request->new(POST => _base_ws_url($cfg));
+ $req->content($post);
+
+ my $result = $ua->request($req);
+
+ require BSE::TB::AuditLog;
+ BSE::TB::AuditLog->log
+ (
+ component => "shop:paypal",
+ function => $method,
+ level => "info",
+ object => $order,
+ actor => $who,
+ msg => "PayPal $method request",
+ dump => "Request:<<\n" . $req->as_string . "\n>>\n\nResult:<<\n" . $result->as_string . "\n>>",
+ );
+
+ my %info;
+ for my $entry (split /&/, $result->decoded_content) {
+ my ($key, $value) = split /=/, $entry, 2;
+ $info{$key} = unescape_uri($value);
+ }
+
+ %$info = %info if $info;
+ unless ($info{ACK} =~ /^Success/) {
+ BSE::TB::AuditLog->log
+ (
+ component => "shop:paypal",
+ function => $method,
+ level => "crit",
+ object => $order,
+ actor => $who,
+ msg => "PayPal $method failure",
+ dump => $result->as_string,
+ );
+ $$rmsg = $info{L_LONGMESSAGE0};
+ return;
+ }
+
+ return %info;
+}
+
+1;
BSE::Template->output_result($req, $result);
}
+# one day, this will mark the message as an error
+sub flash_error {
+ my ($self, @msg) = @_;
+
+ return $self->flash(@msg);
+}
+
sub flash {
my ($self, @msg) = @_;
$self->session->{flash} = \@flash;
}
+sub _str_msg {
+ my ($req, $msg) = @_;
+
+ if ($msg =~ /^(msg:[\w-]+(?:\/[\w-]+)+)(?::(.*))?$/) {
+ my $id = $1;
+ my $params = $2;
+ my @params = defined $params ? split(/:/, $params) : ();
+ $msg = $req->catmsg($id, \@params);
+ }
+
+ return $msg;
+}
+
sub message {
my ($req, $errors) = @_;
my @msgs = ref $errors->{$key} ? @{$errors->{$key}} : $errors->{$key};
for my $msg (@msgs) {
- if ($msg =~ /^(msg:[\w-]+(?:\/[\w-]+)+)(?::(.*))?$/) {
- my $id = $1;
- my $params = $2;
- my @params = defined $params ? split(/:/, $params) : ();
- $msg = $req->catmsg($id, \@params);
- }
+ $msg = $req->_str_msg($msg);
}
$errors->{$key} = ref $errors->{$key} ? \@msgs : $msgs[0];
}
push @lines, @{$req->session->{flash}};
delete $req->session->{flash};
}
- $msg = join "<br />", map escape_html($_), @lines;
- if (!$msg && $req->cgi->param('m')) {
- $msg = join(' ', $req->cgi->param('m'));
- $msg = escape_html($msg);
+ if (!@lines && $req->cgi->param('m')) {
+ push @lines, map $req->_str_msg($_), $req->cgi->param("m");
}
- return $msg;
+ return join "<br />", map escape_html($_), @lines;
}
sub dyn_response {
sub user_url {
my ($req, $script, $target, @options) = @_;
- my $cfg = $req->cfg;
- my $base = $script eq 'shop' ? $cfg->entryVar('site', 'secureurl') : '';
- my $template;
- if ($target) {
- if ($script eq 'nuser') {
- $template = "/cgi-bin/nuser.pl/user/TARGET";
- }
- else {
- $template = "$base/cgi-bin/$script.pl?a_TARGET=1";
- }
- $template = $cfg->entry('targets', $script, $template);
- $template =~ s/TARGET/$target/;
- }
- else {
- if ($script eq 'nuser') {
- $template = "/cgi-bin/nuser.pl/user";
- }
- else {
- $template = "$base/cgi-bin/$script.pl";
- }
- $template = $cfg->entry('targets', $script.'_n', $template);
- }
- if (@options) {
- $template .= $template =~ /\?/ ? '&' : '?';
- my @entries;
- while (my ($key, $value) = splice(@options, 0, 2)) {
- push @entries, "$key=" . escape_uri($value);
- }
- $template .= join '&', @entries;
- }
-
- return $template;
+ return $req->cfg->user_url($script, $target, @options);
}
sub admin_tags {
@ISA = qw/Exporter/;
@EXPORT_OK = qw/shop_cart_tags cart_item_opts nice_options shop_nice_options
total shop_total load_order_fields basic_tags need_logon
- get_siteuser payment_types order_item_opts/;
+ get_siteuser payment_types order_item_opts
+ PAYMENT_CC PAYMENT_CHEQUE PAYMENT_CALLME PAYMENT_MANUAL PAYMENT_PAYPAL/;
+
+our %EXPORT_TAGS =
+ (
+ payment => [ grep /^PAYMENT_/, @EXPORT_OK ],
+ );
+
use Constants qw/:shop/;
use BSE::Util::SQL qw(now_sqldate);
use BSE::Util::Tags;
use Carp 'confess';
use BSE::Util::HTML qw(escape_html);
+use constant PAYMENT_CC => 0;
+use constant PAYMENT_CHEQUE => 1;
+use constant PAYMENT_CALLME => 2;
+use constant PAYMENT_MANUAL => 3;
+use constant PAYMENT_PAYPAL => 4;
+
=item shop_cart_tags($acts, $cart, $cart_prods, $req, $stage)
Returns a list of tags which display the cart details
sub payment_types {
my ($cfg) = @_;
- my %types =
+ my @types =
(
- 0 => {
- id => 0,
- name => 'CC',
- desc => 'Credit Card',
- require => [ qw/cardNumber cardExpiry cardHolder/ ],
- },
- 1 => {
- id => 1,
- name => 'Cheque',
- desc => 'Cheque',
- require => [],
- },
- 2 => {
- id => 2,
- name => 'CallMe',
- desc => 'Call customer for payment',
- require => [],
- },
+ {
+ id => PAYMENT_CC,
+ name => 'CC',
+ desc => 'Credit Card',
+ require => [ qw/cardNumber cardExpiry cardHolder/ ],
+ },
+ {
+ id => PAYMENT_CHEQUE,
+ name => 'Cheque',
+ desc => 'Cheque',
+ require => [],
+ },
+ {
+ id => PAYMENT_CALLME,
+ name => 'CallMe',
+ desc => 'Call customer for payment',
+ require => [],
+ },
+ {
+ id => PAYMENT_PAYPAL,
+ name => "PayPal",
+ desc => "PayPal",
+ require => [],
+ },
);
+ my %types = map { $_->{id} => $_ } @types;
my @payment_types = split /,/, $cfg->entry('shop', 'payment_types', '0');
my $name = $cfg->entry('payment type names', $type, $hash->{name});
my $desc = $cfg->entry('payment type descs', $type,
$hash->{desc} || $name);
+ my $enabled = !$cfg->entry('payment type disable', $hash->{name}, 0);
my @require = $hash->{require} ? @{$hash->{require}} : ();
@require = split /,/, $cfg->entry('payment type required', $type,
join ",", @require);
# credit card payments require either encrypted emails enabled or
# an online CC processing module
- if ($types{0}) {
+ if ($types{+PAYMENT_CC}) {
my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
my $ccprocessor = $cfg->entry('shop', 'cardprocessor');
if ($noencrypt && !$ccprocessor) {
- $types{0}{enabled} = 0;
+ $types{+PAYMENT_CC}{enabled} = 0;
+ $types{+PAYMENT_CC}{message} =
+ "No card processor configured and encryption disabled";
+ }
+ }
+
+ # paypal requires api confguration
+ if ($types{+PAYMENT_PAYPAL} && $types{+PAYMENT_PAYPAL}{enabled}) {
+ require BSE::PayPal;
+
+ unless (BSE::PayPal->configured) {
+ $types{+PAYMENT_PAYPAL}{enabled} = 0;
+ $types{+PAYMENT_PAYPAL}{message} = "No API configuration";
}
}
"bse_audit_log";
}
+sub level_name {
+ my ($self) = @_;
+
+ return BSE::TB::AuditLog->level_id_to_name($self->level);
+}
+
+sub actor_name {
+ my ($self) = @_;
+
+ my $type = $self->actor_type;
+ if ($type eq "U") {
+ return "Unknown";
+ }
+ elsif ($type eq "E") {
+ return "Error";
+ }
+ elsif ($type eq "S") {
+ return "System";
+ }
+ elsif ($type eq "A") {
+ require BSE::TB::AdminUsers;
+ my $admin = BSE::TB::AdminUsers->getByPkey($self->actor_id);
+ if ($admin) {
+ return "Admin: ".$admin->logon;
+ }
+ else {
+ return "Admin: " . $self->actor_id. " (not found)";
+ }
+ }
+ elsif ($type eq "M") {
+ require SiteUsers;
+ my $user = SiteUsers->getByPkey($self->actor_id);
+ if ($user) {
+ return "Member: ".$user->userId;
+ }
+ else {
+ return "Member: ".$self->actor_id . " (not found)";
+ }
+ }
+ else {
+ return "Unknown type $type";
+ }
+}
+
1;
use vars qw(@ISA $VERSION);
@ISA = qw(Squirrel::Table);
use BSE::TB::AuditEntry;
-use Scalar::Util;
+use Scalar::Util qw(blessed);
sub rowClass {
return 'BSE::TB::AuditEntry';
=item *
-level - level of event, one of
+level - level of event, one of emerg, alert, crit, error, warning,
+notice, info, debug.
+
+=item *
+
+actor - the entity performing the actor, one of a SiteUser object, an
+AdminUser object, "S" for system, "U" for an unknown public user.
+
+=item *
+
+msg - a brief message
+
+=item *
+
+ip_address - the actor's IP address (optional, loaded from $REMOTE_ADDR).
+
+=item *
+
+object - an optional object being acted upon.
+
+=item *
+
+dump - an optional dump of debugging data
=back
if ($component =~ /^(\w+):(\w*):(.+)$/) {
@entry{qw/component module function/} = ( $1, $2, $3 );
}
+ elsif ($component =~ /^(\w+):(\w*)$/) {
+ @entry{qw/component module/} = ( $1, $2 );
+ $entry{function} = delete $opts{function} || delete $opts{action}
+ or $class->crash("Missing function parameter");
+ }
else {
$entry{component} = $component;
$entry{module} = delete $opts{module} || '';
$entry{actor_type} = "A";
}
else {
- $entry{actor_type} = "A";
+ $entry{actor_type} = "M";
}
$entry{actor_id} = $actor->id;
}
else {
- if ($actor eq "S") {
- $entry{actor_type} = "S";
+ if ($actor =~ /^[US]$/) {
+ $entry{actor_type} = $actor;
}
else {
- $entry{actor_type} = "U";
+ $entry{actor_type} = "E";
}
$entry{actor_id} = undef;
}
ccOnline ccSuccess ccReceipt ccStatus ccStatusText
ccStatus2 ccTranId complete delivOrganization billOrganization
delivStreet2 billStreet2 purchase_order shipping_method
- shipping_name shipping_trace/;
+ shipping_name shipping_trace
+ paypal_token paypal_tran_id/;
}
sub defaults {
);
}
+sub table { "orders" }
+
sub address_columns {
return qw/
delivFirstName delivLastName delivStreet delivSuburb delivState
return BSE::TB::OrderItems->make(%item);
}
+sub deliv_country_code {
+ my ($self) = @_;
+
+ my $use_codes = BSE::Cfg->single->entry("shop", "country_code", 0);
+ if ($use_codes) {
+ return $self->delivCountry;
+ }
+ else {
+ require BSE::Countries;
+ return BSE::Countries::bse_country_code($self->delivCountry);
+ }
+}
+
1;
BSE::TB::OrderItemOptions->getBy(order_item_id => $self->{id});
}
+sub product {
+ my ($self) = @_;
+
+ $self->productId == -1
+ and return;
+ require Products;
+ return Products->getByPkey($self->productId);
+}
+
1;
use BSE::WebUtil 'refresh_to_admin';
use BSE::Util::HTML qw(:default popup_menu);
use BSE::Arrows;
-use BSE::Shop::Util qw(order_item_opts nice_options);
+use BSE::Shop::Util qw(:payment order_item_opts nice_options);
my %actions =
(
order_unpaid => 'shop_order_unpaid',
product_detail => '',
product_list => '',
+ paypal_refund => 'bse_shop_order_refund_paypal',
);
sub actions {
my $order = BSE::TB::Orders->getByPkey($id)) {
if ($order->paidFor != $value) {
if ($value) {
- $order->set_paymentType(3);
+ $order->set_paymentType(PAYMENT_MANUAL);
}
else {
- $order->paymentType == 3
+ $order->paymentType == PAYMENT_MANUAL
or return $class->req_order_detail($req, "You can only unpay manually paid orders");
}
}
}
+sub req_paypal_refund {
+ my ($self, $req) = @_;
+
+ my $id = $req->cgi->param('id');
+ if ($id and
+ my $order = BSE::TB::Orders->getByPkey($id)) {
+ require BSE::PayPal;
+ my $msg;
+ unless (BSE::PayPal->refund_order(order => $order,
+ req => $req,
+ msg => \$msg)) {
+ return $self->req_order_detail($req, $msg);
+ }
+
+ return $req->get_refresh($req->url(shopadmin => { "a_order_detail" => 1, id => $id }));
+ }
+ else {
+ $req->flash_error("Missing or invalid order id");
+ return $self->req_order_list($req);
+ }
+}
+
#####################
# utilities
# perhaps some of these belong in a class...
}
unless ($action) {
for my $check (keys %$actions) {
- if ($cgi->param("$prefix$check") || $cgi->param("$prefix$check.x")) {
+ if ($cgi->param("a_$check") || $cgi->param("a_$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")) {
+ if ($cgi->param("$prefix$check") || $cgi->param("$prefix$check.x")) {
$action = $check;
last;
}
use base 'BSE::UI::Dispatch';
use BSE::Util::HTML qw(:default popup_menu);
use BSE::Util::SQL qw(now_sqldate now_sqldatetime);
-use BSE::Shop::Util qw(need_logon shop_cart_tags payment_types nice_options
+use BSE::Shop::Util qw(:payment need_logon shop_cart_tags payment_types nice_options
cart_item_opts basic_tags order_item_opts);
use BSE::CfgInfo qw(custom_class credit_card_class bse_default_country);
use BSE::TB::Orders;
use Digest::MD5 'md5_hex';
use BSE::Shipping;
use BSE::Countries qw(bse_country_code);
-
-use constant PAYMENT_CC => 0;
-use constant PAYMENT_CHEQUE => 1;
-use constant PAYMENT_CALLME => 2;
+use BSE::Util::Secure qw(make_secret);
use constant MSG_SHOP_CART_FULL => 'Your shopping cart is full, please remove an item and try adding an item again';
payment => 1,
orderdone => 1,
location => 1,
+ paypalret => 1,
+ paypalcan => 1,
);
my %field_map =
ifMultPaymentTypes => @payment_types > 1,
checkedPayment => [ \&tag_checkedPayment, $payment, \%types_by_name ],
ifPayments => [ \&tag_ifPayments, \@payment_types, \%types_by_name ],
+ paymentTypeId => [ \&tag_paymentTypeId, \%types_by_name ],
error_img => [ \&tag_error_img, $cfg, $errors ],
total => $order->{total},
delivery_in => $order->{delivery_in},
}
}
+ $order->set_randomId(make_secret($cfg));
$order->{ccOnline} = 0;
my $ccprocessor = $cfg->entry('shop', 'cardprocessor');
$order->{ccExpiryHash} = md5_hex($ccExpiry);
}
}
+ elsif ($paymentType == PAYMENT_PAYPAL) {
+ require BSE::PayPal;
+ my $msg;
+ my $url = BSE::PayPal->payment_url(order => $order,
+ user => $user,
+ msg => \$msg);
+ unless ($url) {
+ $session->{order_work} = $order->{id};
+ my %errors;
+ $errors{_} = "PayPal error: $msg" if $msg;
+ return $class->req_show_payment($req, \%errors);
+ }
+
+ # have to mark it complete so it doesn't get used by something else
+ return BSE::Template->get_refresh($url, $req->cfg);
+ }
# order complete
$order->{complete} = 1;
$order->save;
+ $class->_finish_order($order, $req);
+
+ return BSE::Template->get_refresh($req->user_url(shop => 'orderdone'), $req->cfg);
+}
+
+# do final processing of an order after payment
+sub _finish_order {
+ my ($self, $req, $order) = @_;
+
+
my $custom = custom_class($req->cfg);
$custom->can("order_complete")
and $custom->order_complete($req->cfg, $order);
# set the order displayed by orderdone
- $session->{order_completed} = $order->{id};
- $session->{order_completed_at} = time;
+ $req->session->{order_completed} = $order->{id};
+ $req->session->{order_completed_at} = time;
- my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
- $class->_send_order($req, $order, \@dbitems, \@products, $noencrypt,
- \%subscribing_to);
+ $self->_send_order($req, $order);
# empty the cart ready for the next order
- delete @{$session}{qw/order_info order_info_confirmed cart order_work/};
-
- return BSE::Template->get_refresh($req->user_url(shop => 'orderdone'), $req->cfg);
+ delete @{$req->session}{qw/order_info order_info_confirmed cart order_work/};
}
sub req_orderdone {
return $payment == $type;
}
+sub tag_paymentTypeId {
+ my ($types_by_name, $args) = @_;
+
+ if (exists $types_by_name->{$args}) {
+ return $types_by_name->{$args};
+ }
+
+ return '';
+}
+
sub _validate_cfg {
my ($class, $req, $rmsg) = @_;
}
sub _send_order {
- my ($class, $req, $order, $items, $products, $noencrypt,
- $subscribing_to) = @_;
+ my ($class, $req, $order) = @_;
my $cfg = $req->cfg;
my $cgi = $req->cgi;
+ my $noencrypt = $cfg->entryBool('shop', 'noencrypt', 0);
my $crypto_class = $cfg->entry('shop', 'crypt_module',
$Constants::SHOP_CRYPTO);
my $signing_id = $cfg->entry('shop', 'crypt_signing_id',
$extras{$key} = sub { $data };
}
+ my @items = $order->items;
+ my @products = map $_->product, @items;
+ my %subscribing_to;
+ for my $product (@products) {
+ my $sub = $product->subscription;
+ if ($sub) {
+ $subscribing_to{$sub->{text_id}} = $sub;
+ }
+ }
+
my $item_index = -1;
my @options;
my $option_index;
(
%extras,
custom_class($cfg)
- ->order_mail_actions(\%acts, $order, $items, $products,
+ ->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) {
+ if (++$item_index < @items) {
$option_index = -1;
@options = order_item_opts($req,
- $items->[$item_index],
- $products->[$item_index]);
+ $items[$item_index],
+ $products[$item_index]);
return 1;
}
return 0;
},
- item=> sub { $items->[$item_index]{$_[0]}; },
+ item=> sub { $items[$item_index]{$_[0]}; },
product =>
sub {
- my $value = $products->[$item_index]{$_[0]};
+ 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]};
+ $items[$item_index]{units} * $items[$item_index]{$_[0]};
},
_format =>
sub {
ifOptions => sub { @options },
options => sub { nice_options(@options) },
with_wrap => \&tag_with_wrap,
- ifSubscribingTo => [ \&tag_ifSubscribingTo, $subscribing_to ],
+ ifSubscribingTo => [ \&tag_ifSubscribingTo, \%subscribing_to ],
);
my $mailer = BSE::Mail->new(cfg=>$cfg);
$values->{orderDate} = now_sqldatetime;
# this should be hard to guess
- $values->{randomId} ||= md5_hex(time().rand().{}.$$);
+ $values->{randomId} = md5_hex(time().rand().{}.$$);
return 1;
}
return 1;
}
+sub _paypal_order {
+ my ($self, $req, $rmsg) = @_;
+
+ my $id = $req->cgi->param("order");
+ unless ($id) {
+ $$rmsg = $req->catmsg("msg:bse/shop/paypal/noorderid");
+ return;
+ }
+ my ($order) = BSE::TB::Orders->getBy(randomId => $id);
+ unless ($order) {
+ $$rmsg = $req->catmsg("msg:bse/shop/paypal/unknownorderid");
+ return;
+ }
+
+ return $order;
+}
+
+=item paypalret
+
+Handles PayPal returning control.
+
+Expects:
+
+=over
+
+=item *
+
+order - the randomId of the order
+
+=item *
+
+token - paypal token we originally supplied to paypal. Supplied by
+PayPal.
+
+=item *
+
+PayerID - the paypal user who paid the order. Supplied by PayPal.
+
+=back
+
+=cut
+
+sub req_paypalret {
+ my ($self, $req) = @_;
+
+ require BSE::PayPal;
+ BSE::PayPal->configured
+ or return $self->req_cart($req, { _ => "msg:bse/shop/paypal/unconfigured" });
+
+ my $msg;
+ my $order = $self->_paypal_order($req, \$msg)
+ or return $self->req_show_payment($req, { _ => $msg });
+
+ $order->complete
+ and return $self->req_cart($req, { _ => "msg:bse/shop/paypal/alreadypaid" });
+
+ unless (BSE::PayPal->pay_order(req => $req,
+ order => $order,
+ msg => \$msg)) {
+ return $self->req_show_payment($req, { _ => $msg });
+ }
+
+ $self->_finish_order($req, $order);
+
+ return $req->get_refresh($req->user_url(shop => "orderdone"));
+}
+
+sub req_paypalcan {
+ my ($self, $req) = @_;
+
+ require BSE::PayPal;
+ BSE::PayPal->configured
+ or return $self->req_cart($req, { _ => "msg:bse/shop/paypal/unconfigured" });
+
+ my $msg;
+ my $order = $self->_paypal_order($req, \$msg)
+ or return $self->req_show_payment($req, { _ => $msg });
+
+ $req->flash("msg:bse/shop/paypal/cancelled");
+
+ my $url = $req->user_url(shop => "show_payment");
+ return $req->get_refresh($url);
+}
+
1;
}
sub admin {
- my ($class, $acts, $cfg) = @_;
+ my ($class, $acts, $cfg, $req) = @_;
+ my $oit = BSE::Util::Iterate::Objects->new(cfg => $cfg);
return
(
help => [ \&tag_help, $cfg, 'admin' ],
+ $oit->make
+ (
+ single => "auditentry",
+ plural => "auditlog",
+ code => [ iter_auditlog => $class, $req ],
+ ),
);
}
return $cfg->entry("help style $style", $name, $default);
}
+sub iter_auditlog {
+ my ($class, $req, $args, $acts, $funcname, $templater) = @_;
+
+ my (@args) = DevHelp::Tags->get_parms($args, $acts, $templater);
+ require BSE::TB::AuditLog;
+ return sort { $b->id cmp $a->id }
+ BSE::TB::AuditLog->getBy(@args);
+}
+
sub tag_help {
my ($cfg, $defstyle, $args) = @_;
id: bse/admin/message/badmultiline
description: Multiple lines of text supplied when creating or saving a single line message
+id: bse/shop/
+description: Shop messages
+
+id: bse/shop/paypal/
+description: PayPal messages
+
+id: bse/shop/paypal/noorderid
+description: No order value was passed back from PayPal
+
+id: bse/shop/paypal/notoken
+description: No token value was passed back from PayPal
+
+id: bse/shop/paypal/nopayerid
+description: No PayerID value was passed back from PayPal
+
+id: bse/shop/paypal/unknownorderid
+description: Order id supplied by PayPal is unknown
+
+id: bse/shop/paypal/cancelled
+description: Payment through PayPal was cancelled by the user
+
+id: bse/shop/paypal/unconfigured
+description: Entry via one of the PayPal entries when PayPal isn't configured
+
+id: bse/shop/paypal/alreadypaid
+description: Returned from PayPal for an order that was already paid. The payment through PayPal won't be completed.
+
+id: bse/shop/paypal/badtoken
+description: The transaction token supplied by PayPal to paypalret doesn't match the token value stored in the order
id: bse/admin/message/badmultiline
message: Message $1:s may contain only a single line of text
+id: bse/shop/paypal/notoken
+message: No token supplied by PayPal
+
+id: bse/shop/paypal/nopayerid
+message: No PayerID supplied by PayPal
+
+id: bse/shop/paypal/noorderid
+message: No order ID supplied by PayPal
+
+id: bse/shop/paypal/unknownorderid
+message: Unknown order ID supplied by PayPal
+
+id: bse/shop/paypal/unconfigured
+message: PayPal payments cannot be processed, PayPal support is not configured
+
+id: bse/shop/paypal/alreadypaid
+message: Order has already been paid
+
+id: bse/shop/paypal/cancelled
+message: PayPal payment cancelled
+
+id: bse/shop/paypal/badtoken
+message: The token returned by PayPal doesn't match the token generated for your order.
+
id: test/test/multiline
message: <<TEXT
This message has
2 - contact customer for details
+=item *
+
+3 - paypal - see L<paypal.pod>
+
=back
Other types can be added by adding entries to the [payment type names]
distinct products (with options) in the cart, not the total
quantities. Default: Unlimited.
+=item currency
+
+The shop currency as a 3-letter currency code. Default: AUD.
+Currencies other than "AUD" aren't supported by most of the system.
+
=back
=head2 [shipping]
background-color: #888;
}
+#menu_wrapper .column {
+ float: left;
+ width: 20em;
+ margin: 5px;
+ background-color: #FFF;
+}
+
+#menu_wrapper .title {
+ font-weight: bold;
+ text-align: left;
+ padding: 5px;
+ font-size: 120%;
+}
+
+#menu_wrapper .column li {
+ list-style: none;
+ margin-top: 5px;
+ /*margin-bottom: 5px;*/
+}
+
+#menu_wrapper .column>ul ul {
+ padding-left: 1em;
+ padding-right: 0px;
+ margin-left: 0px;
+ margin-right: 0px;
+}
+
+#menu_wrapper .column>ul {
+ padding-left: 0px;
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+/*#menu_wrapper .column>ul>li {
+ margin-top: 10px;
+}*/
+
+#menu_wrapper .column li a:link,
+#menu_wrapper .column li a:visited {
+ display: block;
+ border: 1px dotted #8080FF;
+ padding: 5px;
+ text-decoration: none;
+ background-color: #CCCCFF;
+ color: #000;
+}
+
+#menu_wrapper .column li a:hover {
+ background-color: #8080FF;
+}
+
+#menu_wrapper li>ul.submenu {
+ display: none;
+}
+
+#menu_wrapper li:hover>ul.submenu {
+ display: block;
+ position: absolute;
+ background: #FFF;
+ width: 17em;
+ padding-right: 1em;
+ border: 1px solid #8080ff;
+}
+
+#auditlog .col_when_at,
+#auditlog .col_who {
+ white-space: nowrap;
+}
--- /dev/null
+<tr class="audit<:auditentry level:>">
+ <td class="col_when_at"><:date "%H:%M %d/%m/%Y" auditentry when_at:></td>
+ <td class="col_level"><:auditentry level_name:></td>
+ <td class="col_actor"><:auditentry actor_name:>
+</td>
+ <td class="col_what"><:auditentry component:>/<:auditentry module:>/<:auditentry function:></td>
+ <td class="col_msg"><:auditentry msg:></td>
+</tr>
\ No newline at end of file
--- /dev/null
+<tr>
+ <th>When</th>
+ <th>Level</th>
+ <th>Who</th>
+ <th>What</th>
+ <th>Message</th>
+</tr>
<:ifSiteuser id:><a href="/cgi-bin/admin/siteusers.pl?a_edit=1&id=<:siteuser id:>">Edit Member</a> |
<a href="/cgi-bin/admin/siteusers.pl?a_edit=1&id=<:siteuser id:>&_t=orders">Other member orders</a> |<:or:><:eif:>
</p>
+<:ifMessage:><div class="message"><:message:></div><:or:><:eif:>
<h2>Order details - No: #<:order id:></h2>
<:ifOrder complete:><:or:><p><b><font size="+1">This order is incomplete and should not be filled.</font></b></p><:eif:>
<table cellpadding="6" cellspacing="1" border="0">
<td align=right nowrap><:money order gst:></td>
</tr>
</table>
-</td>
-</tr>
-</table>
<:switch:>
<:case Eq [order paidFor] "0":>
<p>This order hasn't been paid</p>
<:case Eq [order paymentType] "1":><p>Payment will be made by cheque.</p>
<:case Eq [order paymentType] "2":><p>Contact the customer to arrange for payment.</p>
<:case Eq [order paymentType] "3":><p>Paid manually (staff marked this order paid) - you can <a href="<:script:>?a_order_unpaid=1&id=<:order id:>">unmark it</a></p>
+<:case Eq [order paymentType] "4":><p>Paid via PayPal, transaction id <:order paypal_tran_id:><:ifUserCan bse_shop_order_refund_paypal:> <a href="<:script:>?a_paypal_refund=1&id=<:order id:>">Refund</a><:or:><:eif:></p>
<:endswitch:>
<:include custom/order_detail_payment.include optional:>
<:if Order filled:>
<:if Order instructions:>
<p style="white-space: pre-wrap;"><:order instructions:></p>
<:or Order:><:eif Order:>
+
+<table class="editform" id="auditlog">
+<:include admin/include/audithead.tmpl:>
+<:iterator begin auditlog object_id [order id] object_type BSE::TB::Order:>
+<:include admin/include/auditentry.tmpl:>
+<:iterator end auditlog:>
+</table>
+
+</table>
</body></html>
<:if Payment CallMe:>
<p>We will call you to arrange payment.</p>
<:or Payment:><:eif Payment:>
+<:if Payment PayPal:>
+<p>Paid via PayPal, transaction ID <:order paypal_tran_id:></p>
+<:or Payment:><:eif Payment:>
<:include custom/checkout_final_payments.include optional:>
</font>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<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:>
+ <:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><input type="radio" name="paymentType" value="<:paymentTypeId CC:>" <:checkedPayment CC:>> Credit Card</font></p><:or MultPaymentTypes:><input type=hidden name=paymentType value="<:paymentTypeId CC:>" <:checkedPayment CC:>><:eif MultPaymentTypes:>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Name on
</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:>
+ <:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="radio" name="paymentType" value="<:paymentTypeId Cheque:>" <:checkedPayment Cheque:>/>
+ Cheque</font></p><:or MultPaymentTypes:><input type="hidden" name="paymentType" value="<:paymentTypeId Cheque:>"><: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>
+ <:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><input type="radio" name="paymentType" value="<:paymentTypeId CallMe:>" <:checkedPayment CallMe:>/> Contact me for billing details</font></p>
<:or MultPaymentTypes:>
- <input type=hidden name=paymentType value=2>
+ <input type="hidden" name="paymentType" value="<:paymentTypeId CallMe:>">
<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:>
+ <:if Payments PayPal:>
+<:if MultPaymentTypes:><p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type=radio name=paymentType value="<:paymentTypeId PayPal:>" <:checkedPayment PayPal:>/>
+<img src="https://www.paypal.com/en_AU/i/logo/PayPal_mark_37x23.gif" align="absmiddle" style="margin-right:7px;"><span style="font-size:11px; font-family: Arial, Verdana;">The safer, easier way to pay.</span></font></p><:or MultPaymentTypes:><input type=hidden name=paymentType value=1><: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
:><: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:><:
+:>We will call you to arrange for payment<:or:><:eif:><:ifEq [order paymentType] "4"
+:>Paid by PayPal, transaction id <:order paypal_tran_id:><:or:><:eif:><:
include custom/payment_type_email.include:>
To be shipped by: <:order shipping_method:>
<:if Order instructions:>
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:><:
+:>Please call the customer to arrange for payment<:or:><:eif:><:ifEq [order paymentType] "4"
+:>Paid by PayPal, transaction id <:order paypal_tran_id:><:or:><:eif:><:
include custom/payment_type_email.include:>
To be shipped by: <:order shipping_method:>
<:if Order instructions:>
Column shipping_method;varchar(64);NO;;
Column shipping_name;varchar(40);NO;;
Column shipping_trace;text;YES;NULL;
+Column paypal_token;varchar(255);YES;NULL;
+Column paypal_tran_id;varchar(255);YES;NULL;
Index PRIMARY;1;[id]
Index order_cchash;0;[ccNumberHash]
Index order_userId;0;[userId;orderDate]