Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / part_export / ez_prepaid.pm
1 package FS::part_export::ez_prepaid;
2
3 use base qw( FS::part_export );
4
5 use strict;
6 use vars qw(@ISA %info $version $replace_ok_kludge $product_info);
7 use Tie::IxHash;
8 use FS::Record qw( qsearchs );
9 use FS::svc_external;
10 use SOAP::Lite;
11 use XML::Simple qw( xml_in );
12 use Data::Dumper;
13
14 $version = '01';
15
16 my $product_info;
17 my %language_id = ( English => 1, Spanish => 2 );
18
19 tie my %options, 'Tie::IxHash',
20   'site_id'     => { label => 'Site ID' },
21   'clerk_id'    => { label => 'Clerk ID' },
22 #  'product_id'  => { label => 'Product ID' }, use the 'title' field
23 #  'amount'      => { label => 'Purchase amount' },
24   'language'    => { label => 'Language',
25                      type  => 'select',
26                      options => [ 'English', 'Spanish' ],
27                     },
28
29   'debug'       => { label => 'Debug level',
30                      type  => 'select', options => [0, 1, 2 ] },
31 ;
32
33 %info = (
34   'svc'     => 'svc_external',
35   'desc'    => 'Purchase EZ-Prepaid PIN',
36   'options' => \%options,
37   'notes'   => <<'END'
38 <P>Export to the EZ-Prepaid PIN purchase service.  If the purchase is allowed,
39 the PIN will be stored as svc_external.id.</P>
40 <P>svc_external.title must contain the product ID, and should be set as a fixed
41 field in the service definition.  For a list of product IDs, see the 
42 "Merchant Info" tab in the EZ Prepaid reseller portal.</P>
43 END
44   );
45
46 $replace_ok_kludge = 0;
47
48 sub _export_insert {
49   my ($self, $svc_external) = @_;
50
51   # the name on the certificate is 'debisys.com', for some reason
52   local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=0;
53
54   my $pin = eval { $self->ez_prepaid_PinDistSale( $svc_external->title ) };
55   return $@ if $@;
56
57   local($replace_ok_kludge) = 1;
58   $svc_external->set('id', $pin);
59   $svc_external->replace;
60 }
61
62 sub _export_replace {
63   $replace_ok_kludge ? '' : "can't change PIN after purchase";
64 }
65
66 sub _export_delete {
67   "can't delete PIN after purchase";
68 }
69
70 # possibly options at some point to relate these to agentnum/usernum
71 sub site_id { $_[0]->option('site_id') }
72
73 sub clerk_id { $_[0]->option('clerk_id') }
74
75 sub ez_prepaid_PinDistSale {
76   my $self = shift;
77   my $product_id = shift;
78   $self->ez_prepaid_init; # populate product ID cache
79   my $info = $product_info->{$product_id};
80   if ( $info ) {
81     if ( $self->option('debug') ) {
82       warn "Purchasing PIN product #$product_id:\n" .
83             $info->{Description}."\n".
84             $info->{CurrencyCode} . ' ' .$info->{Amount}."\n";
85     }
86   } else { #no $info
87     die "Unknown PIN product #$product_id.\n";
88   }
89
90   my $response = $self->ez_prepaid_request(
91     'PinDistSale',
92     $version,
93     $self->site_id,
94     $self->clerk_id,
95     $product_id,
96     '', # AccountID, not used for PIN sale
97     $product_info->{$product_id}->{Amount},
98     $self->svcnum,
99     ($language_id{ $self->option('language') } || 1),
100   );
101   if ( $self->option('debug') ) {
102     warn Dumper($response);
103     # includes serial number and transaction ID, possibly useful
104     # (but we don't have a structured place to store it--maybe in 
105     # a customer note?)
106   }
107   $response->{Pin};
108 }
109
110 sub ez_prepaid_init {
111   # returns the SOAP client object
112   my $self = shift;
113   my $wsdl = 'https://webservice.ez-prepaid.com/soap/webServices.wsdl';
114
115   if ( $self->option('debug') >= 2 ) {
116     SOAP::Lite->import(+trace => [transport => \&log_transport ]);
117   }
118  
119   if ( !$self->client ) {
120     $self->set(client => SOAP::Lite->new->service($wsdl));
121     # I don't know if this can happen, but better to bail out here
122     # than go into recursion.
123     die "Error creating SOAP client\n" if !$self->client;
124   }
125
126   if ( !defined($product_info) ) {
127     # for now we only support the 'PIN' type
128     my $response = $self->ez_prepaid_request(
129       'GetTransTypeList', $version, $self->site_id, '', '', '', ''
130     );
131     my %transtype = map { $_->{Description} => $_->{TransTypeId} }
132       @{ $response->{TransType} };
133
134     if ( !exists $transtype{PIN} ) {
135       warn "'PIN' transaction type not available.\n";
136       # or else your site ID is wrong
137       return;
138     }
139
140     $response = $self->ez_prepaid_request(
141       'GetProductList',
142       $version,
143       $self->option('site_id'),
144       $transtype{PIN},
145       '', #CarrierId
146       '', #CategoryId
147       '', #ProductId
148     );
149     $product_info = +{
150       map { $_->{ProductId} => $_ }
151       @{ $response->{Product} }
152     };
153   } #!defined $product_info
154 }
155
156 sub log_transport {
157   my $in = shift;
158   if ( UNIVERSAL::can($in, 'content') ) {
159     warn $in->content."\n";
160   }
161 }
162
163 my @ForceArray = qw(TransType Product); # add others as needed
164 sub ez_prepaid_request {
165   my $self = shift;
166   # takes a method name and param list,
167   # returns a hashref containing the unpacked response
168   # or dies on error
169   
170   $self->ez_prepaid_init if !$self->client;
171
172   my $method = shift;
173   my $xml = $self->client->$method(@_);
174   # All of their response data types are one part, a string, containing 
175   # an encoded XML structure, containing the fields described in the docs.
176   my $response = xml_in($xml, ForceArray => \@ForceArray);
177   if ( exists($response->{ResponseCode}) && $response->{ResponseCode} > 0 ) {
178     die "[$method] ".$response->{ResponseMessage};
179   }
180   $response;
181 }
182
183 1;