use base qw( FS::otaker_Mixin FS::payinfo_transaction_Mixin FS::cust_main_Mixin
FS::reason_Mixin FS::Record);
use vars qw( $DEBUG $me $conf @encrypted_fields
- $unsuspendauto $ignore_noapply
+ $ignore_noapply
);
use Date::Format;
use Business::CreditCard;
#ask FS::UID to run this stuff for us later
FS::UID->install_callback( sub {
$conf = new FS::Conf;
- $unsuspendauto = $conf->exists('unsuspendauto');
} );
@encrypted_fields = ('payinfo');
Payment Information (See L<FS::payinfo_Mixin> for data format)
+=item paycardtype
+
+Credit card type, if appropriate; autodetected.
+
=item paymask
Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ my $conf = new FS::Conf;
+
my $cust_bill;
if ( $self->invnum ) {
$cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
- #false laziness w/ cust_credit::insert
- if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
- my @errors = $cust_main->unsuspend;
- #return
- # side-fx with nested transactions? upstack rolls back?
- warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
- join(' / ', @errors)
- if @errors;
- }
- #eslaf
+ # possibly trigger package unsuspend, doesn't abort transaction on failure
+ $self->unsuspend_balance if $old_balance;
#bill setup fees for voip_cdr bill_every_call packages
#some false laziness w/search in freeside-cdrd
unless (ref($reason) || !$reason) {
$reason = FS::reason->new_or_existing(
- 'class' => 'X',
+ 'class' => 'P',
'type' => 'Void payment',
'reason' => $reason
);
sub replace {
my $self = shift;
- return "Can't modify closed payment" if $self->closed =~ /^Y/i;
+ return "Can't modify closed payment"
+ if $self->closed =~ /^Y/i && !$FS::payinfo_Mixin::allow_closed_replace;
$self->SUPER::replace(@_);
}
return '' unless $conf->config_bool('payment_receipt', $cust_main->agentnum);
+ return '' if ($conf->config_bool('allow_payment_receipt_noemail', $cust_main->agentnum) && $cust_main->paymentreceipt_noemail);
+
my @invoicing_list = $cust_main->invoicing_list_emailonly;
return '' unless @invoicing_list;
|| ! $cust_bill
)
{
- my $msgnum = $conf->config('payment_receipt_msgnum', $cust_main->agentnum);
+ $error = $self->send_message_receipt(
+ 'cust_main' => $cust_main,
+ 'cust_bill' => $opt->{cust_bill},
+ 'msgnum' => $conf->config('payment_receipt_msgnum', $cust_main->agentnum)
+ );
+ #not manual and no noemail flag (here or on the customer)
+ } elsif ( ! $opt->{'noemail'} && ! $cust_main->invoice_noemail ) {
+
+ # check to see if they want to send specific message template as receipt for auto payments
+ if ( $conf->config('payment_receipt_msgnum_auto', $cust_main->agentnum) ) {
+ $error = $self->send_message_receipt(
+ 'cust_main' => $cust_main,
+ 'cust_bill' => $opt->{cust_bill},
+ 'msgnum' => $conf->config('payment_receipt_msgnum_auto', $cust_main->agentnum),
+ );
+ }
+ else {
+ my $queue = new FS::queue {
+ 'job' => 'FS::cust_bill::queueable_email',
+ 'paynum' => $self->paynum,
+ 'custnum' => $cust_main->custnum,
+ };
+
+ my %opt = (
+ 'invnum' => $cust_bill->invnum,
+ 'no_coupon' => 1,
+ );
+
+ if ( my $mode = $conf->config('payment_receipt_statement_mode') ) {
+ $opt{'mode'} = $mode;
+ } else {
+ # backward compatibility, no good fix for this yet as some people may
+ # still have "invoice_latex_statement" and such options
+ $opt{'template'} = 'statement';
+ $opt{'notice_name'} = 'Statement';
+ }
+
+ $error = $queue->insert(%opt);
+ }
+
+
+
+ }
+
+ warn "send_receipt: $error\n" if $error;
+}
+
+=item send_message_receipt
+
+sends out a message receipt.
+$error = $self->send_message_receipt(
+ 'cust_main' => $cust_main,
+ 'cust_bill' => $opt->{cust_bill},
+ 'msgnum' => $conf->config('payment_receipt_msgnum', $cust_main->agentnum)
+ );
+
+=cut
+
+sub send_message_receipt {
+ my ($self, %opt) = @_;
+ my $cust_main = $opt{'cust_main'};
+ my $cust_bill = $opt{'cust_bill'};
+ my $msgnum = $opt{'msgnum'};
+ my $error = '';
+
if ( $msgnum ) {
my %substitutions = ();
- $substitutions{invnum} = $opt->{cust_bill}->invnum if $opt->{cust_bill};
+ $substitutions{invnum} = $cust_bill->invnum if $cust_bill;
my $msg_template = qsearchs('msg_template',{ msgnum => $msgnum});
unless ($msg_template) {
$error = $cust_msg ? $cust_msg->insert : 'error preparing msg_template';
if ($error) {
warn "send_receipt: $error";
- return;
+ return $error;
}
my $queue = new FS::queue {
$error = $queue->insert( $cust_msg->custmsgnum );
} else {
-
warn "payment_receipt is on, but no payment_receipt_msgnum\n";
-
+ $error = "payment_receipt is on, but no payment_receipt_msgnum";
}
- #not manual and no noemail flag (here or on the customer)
- } elsif ( ! $opt->{'noemail'} && ! $cust_main->invoice_noemail ) {
-
- my $queue = new FS::queue {
- 'job' => 'FS::cust_bill::queueable_email',
- 'paynum' => $self->paynum,
- 'custnum' => $cust_main->custnum,
- };
-
- my %opt = (
- 'invnum' => $cust_bill->invnum,
- 'no_coupon' => 1,
- );
-
- if ( my $mode = $conf->config('payment_receipt_statement_mode') ) {
- $opt{'mode'} = $mode;
- } else {
- # backward compatibility, no good fix for this yet as some people may
- # still have "invoice_latex_statement" and such options
- $opt{'template'} = 'statement';
- $opt{'notice_name'} = 'Statement';
- }
-
- $error = $queue->insert(%opt);
-
- }
-
- warn "send_receipt: $error\n" if $error;
+ return $error;
}
=item cust_bill_pay
return '';
}
+### refund_to_unapply/unapply_refund false laziness with FS::cust_credit
+
+=item refund_to_unapply
+
+Returns L<FS::cust_pay_refund> objects that will be deleted by L</unapply_refund>
+(all currently applied refunds that aren't closed.)
+Returns empty list if payment itself is closed.
+
+=cut
+
+sub refund_to_unapply {
+ my $self = shift;
+ return () if $self->closed;
+ qsearch({
+ 'table' => 'cust_pay_refund',
+ 'hashref' => { 'paynum' => $self->paynum },
+ 'addl_from' => 'LEFT JOIN cust_refund USING (refundnum)',
+ 'extra_sql' => "AND cust_refund.closed IS NULL AND cust_refund.source_paynum IS NULL",
+ });
+}
+
+=item unapply_refund
+
+Deletes all objects returned by L</refund_to_unapply>.
+
+=cut
+
+sub unapply_refund {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ foreach my $cust_pay_refund ($self->refund_to_unapply) {
+ my $error = $cust_pay_refund->delete;
+ if ($error) {
+ dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ dbh->commit or die dbh->errstr if $oldAutoCommit;
+ return '';
+}
+
=back
=head1 CLASS METHODS
Returns an SQL fragment to retreive the unapplied amount.
-=cut
+=cut
sub unapplied_sql {
my ($class, $start, $end) = @_;
};
}
+=item SSAPI_getinfo
+
+=cut
+
+sub SSAPI_getinfo {
+ #my( $self, %opt ) = @_;
+ my $self = shift;
+
+ +{ 'paynum' => $self->paynum,
+ '_date' => $self->_date,
+ 'date' => time2str("%b %o, %Y", $self->_date),
+ 'date_short' => time2str("%m-%d-%Y", $self->_date),
+ 'paid' => sprintf('%.2f', $self->paid),
+ 'payby' => $self->payby,
+ 'paycardtype' => $self->paycardtype,
+ 'paymask' => $self->paymask,
+ 'processor' => $self->processor,
+ 'auth' => $self->auth,
+ 'order_number' => $self->order_number,
+ };
+
+}
+
+
# _upgrade_data
#
# Used by FS::Upgrade to migrate to a new database.
process_upgrade_paybatch();
}
}
+
+ ###
+ # set paycardtype
+ ###
+ $class->upgrade_set_cardtype;
+
+ # for batch payments, make sure paymask is set
+ do {
+ local $FS::payinfo_Mixin::allow_closed_replace = 1;
+ local $FS::payinfo_Mixin::ignore_masked_payinfo = 1;
+
+ my $cursor = FS::Cursor->new({
+ table => 'cust_pay',
+ extra_sql => ' WHERE paymask IS NULL AND payinfo IS NOT NULL
+ AND payby IN(\'CARD\', \'CHEK\')
+ AND batchnum IS NOT NULL',
+ });
+
+ # records from cursors for some reason don't decrypt payinfo, so
+ # call replace_old to fetch the record "normally"
+ while (my $cust_pay = $cursor->fetch) {
+ $cust_pay = $cust_pay->replace_old;
+ $cust_pay->set('paymask', $cust_pay->mask_payinfo);
+ my $error = $cust_pay->replace;
+ if ($error) {
+ die "$error (setting masked payinfo on payment#". $cust_pay->paynum.
+ ")\n"
+ }
+ }
+ };
}
sub process_upgrade_paybatch {