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