Added information on common failure causes to the perldoc.
[freeside.git] / FS / FS / part_export / domreg_opensrs.pm
1 package FS::part_export::domreg_opensrs;
2
3 use vars qw(@ISA %info %options $conf);
4 use Tie::IxHash;
5 use FS::Record qw(qsearchs qsearch);
6 use FS::Conf;
7 use FS::part_export::null;
8 use FS::svc_domain;
9 use FS::part_pkg;
10
11 =head1 NAME
12
13 FS::part_export::domreg_opensrs - Register or transfer domains with Tucows OpenSRS
14
15 =head1 DESCRIPTION
16
17 This module handles registering and transferring domains using a registration service provider (RSP) account
18 at Tucows OpenSRS, an ICANN-approved domain registrar.
19
20 As a part_export, this module can be designated for use with svc_domain services.  When the svc_domain object
21 is inserted into the Freeside database, registration or transferring of the domain may be initiated, depending
22 on the setting of the svc_domain's action field.
23
24 =over 4
25
26 =item N - Register the domain
27
28 =item M - Transfer the domain
29
30 =item I - Ignore the domain for registration purposes
31
32 =back
33
34 This export uses Net::OpenSRS.  Registration and transfer attempts will fail unless Net::OpenSRS is installed
35 and LWP::UserAgent is able to make HTTPS posts.  You can turn on debugging messages and use the OpenSRS test
36 gateway when setting up this export.
37
38 =cut
39
40 @ISA = qw(FS::part_export::null);
41
42 my @tldlist = qw/com net org biz info name mobi at be ca cc ch cn de dk es eu fr it mx nl tv uk us/;
43
44 tie %options, 'Tie::IxHash',
45   'username'     => { label => 'Reseller user name at OpenSRS',
46                       },
47   'privatekey'   => { label => 'Private key',
48                       },
49   'password'     => { label => 'Password for management account',
50                       },
51   'masterdomain' => { label => 'Master domain at OpenSRS',
52                       },
53   'debug_level'  => { label => 'Net::OpenSRS debug level',
54                       type => 'select',
55                       options => [ 0, 1, 2, 3 ],
56                       default => 0 },
57 #  'register'     => { label => 'Use for registration',
58 #                      type => 'checkbox',
59 #                      default => '1' },
60 #  'transfer'     => { label => 'Use for transfer',
61 #                      type => 'checkbox',
62 #                      default => '1' },
63   'tlds'         => { label => 'Use this export for these top-level domains (TLDs)',
64                       type => 'select',
65                       multi => 1,
66                       size => scalar(@tldlist),
67                       options => [ @tldlist ],
68                       default => 'com net org' },
69 ;
70
71 %info = (
72   'svc'     => 'svc_domain',
73   'desc'    => 'Domain registration via Tucows OpenSRS',
74   'options' => \%options,
75   'notes'   => <<'END'
76 Registers and transfers domains via the <a href="http://opensrs.com/">Tucows OpenSRS</a> registrar (using <a href="http://search.cpan.org/dist/Net-OpenSRS">Net::OpenSRS</a>).
77 All of the Net::OpenSRS restrictions apply:
78 <UL>
79   <LI>You must have a reseller account with Tucows.
80   <LI>You must add the public IP address of the Freeside server to the 'Script API allow' list in the OpenSRS web interface.
81   <LI>You must generate an API access key in the OpenSRS web interface and enter it below.
82   <LI>All domains are managed using the same user name and password, but you can create sub-accounts for clients.
83   <LI>The user name must be the same as your OpenSRS reseller ID.
84   <LI>You must enter a master domain that all other domains are associated with.  That domain must be registered through your OpenSRS account.
85 </UL>
86 Some top-level domains offered by OpenSRS have additional business rules not supported by this export. These TLDs cannot be registered or transfered with this export.
87 <BR><BR>Use these buttons for some useful presets:
88 <UL>
89   <LI>
90     <INPUT TYPE="button" VALUE="OpenSRS Live System (rr-n1-tor.opensrs.net)" onClick='
91       document.dummy.machine.value = "rr-n1-tor.opensrs.net";
92       this.form.machine.value = "rr-n1-tor.opensrs.net";
93     '>
94   <LI>
95     <INPUT TYPE="button" VALUE="OpenSRS Test System (horizon.opensrs.net)" onClick='
96       document.dummy.machine.value = "horizon.opensrs.net";
97       this.form.machine.value = "horizon.opensrs.net";
98     '>
99 </UL>
100 END
101 );
102
103 install_callback FS::UID sub { 
104   $conf = new FS::Conf;
105 };
106
107 =head1 METHODS
108
109 =over 4
110
111 =item format_tel
112
113 Reformats a phone number according to registry rules.  Currently Freeside stores phone numbers
114 in NANPA format and the registry prefers "+CCC.NPANPXNNNN"
115
116 =cut
117
118 sub format_tel {
119   my $tel = shift;
120
121   #if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})( x(\d+))?$/) {
122   if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})$/) {
123     $tel = "+1.$1$2$3";
124 #    if $tel .= "$4" if $4;
125   }
126   return $tel;
127 }
128
129 sub gen_contact_info
130 {
131   my ($co)=@_;
132
133   my @invoicing_list = $co->invoicing_list_emailonly;
134   if ( $conf->exists('emailinvoiceautoalways')
135        || $conf->exists('emailinvoiceauto') && ! @invoicing_list
136        || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
137     push @invoicing_list, $co->all_emails;
138   }
139
140   my $email = ($conf->exists('business-onlinepayment-email-override'))
141               ? $conf->config('business-onlinepayment-email-override')
142               : $invoicing_list[0];
143
144   my $c = {
145     firstname => $co->first,
146     lastname  => $co->last,
147     company   => $co->company,
148     address   => $co->address1,
149     city      => $co->city(),
150     state     => $co->state(),
151     zip       => $co->zip(),
152     country   => uc($co->country()),
153     email     => $email,
154     #phone     => format_tel($co->daytime()),
155     phone     => $co->daytime() || $co->night,
156   };
157   return $c;
158 }
159
160 sub validate_contact_info {
161   my $c = shift;
162
163   my %fields = (
164     firstname => "first name",
165     lastname => "last name",
166     address => "street address",
167     city => "city", 
168     state => "state",
169     zip => "ZIP/postal code",
170     country => "country",
171     email => "email address",
172     phone => "phone number",
173   );
174   my @err = ();
175   foreach (keys %fields) {
176     if (!defined($c->{$_}) || !$c->{$_}) {
177       push @err, $fields{$_};
178     }
179   }
180   if (scalar(@err) > 0) {
181     return "Contact information needs: " . join(', ', @err);
182   }
183   undef;
184 }
185
186 sub testmode {
187   my $self = shift;
188
189   return 'live' if $self->machine eq "rr-n1-tor.opensrs.net";
190   return 'test' if $self->machine eq "horizon.opensrs.net";
191   undef;
192 }
193
194 sub _export_insert {
195   my( $self, $svc_domain ) = ( shift, shift );
196
197   return if $svc_domain->action eq 'I';  # Ignoring registration, just doing DNS
198
199   eval "use Net::OpenSRS;";
200   return $@ if $@;
201
202   # Get the TLD of the new domain
203   my @bits = split /\./, $svc_domain->domain;
204
205   return "Can't register subdomains: " . $svc_domain->domain if scalar(@bits) != 2;
206
207   my $tld = pop @bits;
208
209   # See if it's one this export supports
210   my @tlds = split /\s+/, $self->option('tlds');
211   @tlds =  map { s/\.//; $_ } @tlds;
212   return "Can't register top-level domain $tld, restricted to: " . $self->option('tlds') if ! grep { $_ eq $tld } @tlds;
213
214   my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
215
216   my $c = gen_contact_info($cust_main);
217
218   my $err = validate_contact_info($c);
219   return $err if $err;
220
221   my $srs = Net::OpenSRS->new();
222
223   $srs->debug_level( $self->option('debug_level') ); # Output should be in the Apache error log
224
225   $srs->environment( $self->testmode() );
226   $srs->set_key( $self->option('privatekey') );
227
228   $srs->set_manage_auth( $self->option('username'), $self->option('password') );
229
230   my $cookie = $srs->get_cookie( $self->option('masterdomain') );
231   if (!$cookie) {
232      return "Unable to get cookie at OpenSRS: " . $srs->last_response();
233   }
234
235   if ($svc_domain->action eq 'N') {
236 #    return "Domain registration not enabled" if !$self->option('register');
237     return $srs->last_response() if !$srs->register_domain( $svc_domain->domain, $c);
238   } elsif ($svc_domain->action eq 'M') {
239 #    return "Domain transfer not enabled" if !$self->option('transfer');
240     return $srs->last_response() if !$srs->transfer_domain( $svc_domain->domain, $c);
241   } else {
242     return "Unknown domain action " . $svc_domain->action;
243   }
244
245   return ''; # Should only get here if register or transfer succeeded
246
247 }
248
249 ## Domain registration exports do nothing on replace.  Mainly because we haven't decided what they should do.
250 #sub _export_replace {
251 #  my( $self, $new, $old ) = (shift, shift, shift);
252 #
253 #  return '';
254 #
255 #}
256
257 ## Domain registration exports do nothing on delete.  You're just removing the domain from Freeside, not the registry
258 #sub _export_delete {
259 #  my( $self, $svc_domain ) = ( shift, shift );
260 #
261 #  return '';
262 #}
263
264 sub registrar {
265   return {
266         name => 'OpenSRS',
267   };
268 }
269
270 =back
271
272 =head1 SEE ALSO
273
274 L<Net::OpenSRS>, L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_domain>,
275 L<FS::Record>, schema.html from the base documentation.
276
277
278 =cut
279
280 1;
281