summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormark <mark>2011-07-12 07:54:22 +0000
committermark <mark>2011-07-12 07:54:22 +0000
commit0b81782a6257456e04fb8a5a7faf0dbfbf7bc166 (patch)
tree91be38b299086b665d00b07c3152ba9c46465448
parent970dfae19db81ed3afdd9f7e15637907f84d5341 (diff)
credit card expiration event, #13202
-rw-r--r--FS/FS/cust_main.pm54
-rw-r--r--FS/FS/cust_main/Billing.pm2
-rw-r--r--FS/FS/msg_template.pm9
-rw-r--r--FS/FS/part_event/Condition/cust_paydate_within.pm46
-rw-r--r--FS/FS/part_event/Condition/once_every.pm2
5 files changed, 106 insertions, 7 deletions
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 95ade20..1b059e6 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -2915,6 +2915,60 @@ sub paydate_monthyear {
}
}
+=item paydate_epoch
+
+Returns the exact time in seconds corresponding to the payment method
+expiration date. For CARD/DCRD customers this is the end of the month;
+for others (COMP is the only other payby that uses paydate) it's the start.
+Returns 0 if the paydate is empty or set to the far future.
+
+=cut
+
+sub paydate_epoch {
+ my $self = shift;
+ my ($month, $year) = $self->paydate_monthyear;
+ return 0 if !$year or $year >= 2037;
+ if ( $self->payby eq 'CARD' or $self->payby eq 'DCRD' ) {
+ $month++;
+ if ( $month == 13 ) {
+ $month = 1;
+ $year++;
+ }
+ return timelocal(0,0,0,1,$month-1,$year) - 1;
+ }
+ else {
+ return timelocal(0,0,0,1,$month-1,$year);
+ }
+}
+
+=item paydate_epoch_sql
+
+Class method. Returns an SQL expression to obtain the payment expiration date
+as a number of seconds.
+
+=cut
+
+# Special expiration date behavior for non-CARD/DCRD customers has been
+# carefully preserved. Do we really use that?
+sub paydate_epoch_sql {
+ my $class = shift;
+ my $table = shift || 'cust_main';
+ my ($case1, $case2);
+ if ( driver_name eq 'Pg' ) {
+ $case1 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) + INTERVAL '1 month') - 1";
+ $case2 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) )";
+ }
+ elsif ( lc(driver_name) eq 'mysql' ) {
+ $case1 = "UNIX_TIMESTAMP( DATE_ADD( CAST( $table.paydate AS DATETIME ), INTERVAL 1 month ) ) - 1";
+ $case2 = "UNIX_TIMESTAMP( CAST( $table.paydate AS DATETIME ) )";
+ }
+ else { return '' }
+ return "CASE WHEN $table.payby IN('CARD','DCRD')
+ THEN ($case1)
+ ELSE ($case2)
+ END"
+}
+
=item tax_exemption TAXNAME
=cut
diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm
index 0133164..4b9c3d3 100644
--- a/FS/FS/cust_main/Billing.pm
+++ b/FS/FS/cust_main/Billing.pm
@@ -1663,7 +1663,7 @@ sub do_cust_event {
if $DEBUG > 1;
#if ( my $error = $cust_event->do_event(%options) ) { #XXX %options?
- if ( my $error = $cust_event->do_event() ) {
+ if ( my $error = $cust_event->do_event( 'time' => $time ) ) {
#XXX wtf is this? figure out a proper dealio with return value
#from do_event
return $error;
diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index e90cffd..65acd9a 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -249,7 +249,7 @@ sub prepare {
}
}
}
- $_ = encode_entities($_) foreach values(%hash);
+ $_ = encode_entities($_ || '') foreach values(%hash);
###
@@ -386,12 +386,11 @@ sub substitutions {
cust_status ucfirst_cust_status cust_statuscolor
signupdate dundate
- expdate
packages recurdates
),
- # expdate is a special case
- [ signupdate_ymd => sub { time2str('%Y-%m-%d', shift->signupdate) } ],
- [ dundate_ymd => sub { time2str('%Y-%m-%d', shift->dundate) } ],
+ [ expdate => sub { shift->paydate_epoch } ], #compatibility
+ [ signupdate_ymd => sub { $ymd->(shift->signupdate) } ],
+ [ dundate_ymd => sub { $ymd->(shift->dundate) } ],
[ paydate_my => sub { sprintf('%02d/%04d', shift->paydate_monthyear) } ],
[ otaker_first => sub { shift->access_user->first } ],
[ otaker_last => sub { shift->access_user->last } ],
diff --git a/FS/FS/part_event/Condition/cust_paydate_within.pm b/FS/FS/part_event/Condition/cust_paydate_within.pm
new file mode 100644
index 0000000..4808e90
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_paydate_within.pm
@@ -0,0 +1,46 @@
+package FS::part_event::Condition::cust_paydate_within;
+
+use strict;
+use base qw( FS::part_event::Condition );
+use FS::Record qw( str2time_sql str2time_sql_closing );
+use Time::Local 'timelocal';
+
+sub description {
+ 'Credit card expires within upcoming interval';
+}
+
+# Run the event when the customer's credit card expiration
+# date is less than X days in the future.
+# Combine this with a "once_every" condition so that the event
+# won't repeat every day until the expiration date.
+
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 0,
+ };
+}
+
+sub option_fields {
+ (
+ 'within' => { 'label' => 'Expiration date within',
+ 'type' => 'freq',
+ },
+ );
+}
+
+sub condition {
+ my( $self, $cust_main, %opt ) = @_;
+ my $expire_time = $cust_main->paydate_epoch or return 0;
+ $opt{'time'} >= $self->option_age_from('within', $expire_time);
+}
+
+sub condition_sql {
+ my ($self, $table, %opt) = @_;
+ my $expire_time = FS::cust_main->paydate_epoch_sql or return 'true';
+ $opt{'time'} . ' >= ' .
+ $self->condition_sql_option_age_from('within', $expire_time);
+}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/once_every.pm b/FS/FS/part_event/Condition/once_every.pm
index 2921b3a..ef28078 100644
--- a/FS/FS/part_event/Condition/once_every.pm
+++ b/FS/FS/part_event/Condition/once_every.pm
@@ -31,7 +31,7 @@ sub condition {
'eventpart' => $self->eventpart,
'tablenum' => $tablenum,
'status' => { op=>'!=', value=>'failed' },
- '_date' => { op=>'>=', value=>$max_date },
+ '_date' => { op=>'>', value=>$max_date },
},
'extra_sql' => ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/
? " AND eventnum != $1 "