File Coverage

blib/lib/Marketplace/Rakuten.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Marketplace::Rakuten;
2              
3 1     1   45958 use strict;
  1         2  
  1         21  
4 1     1   3 use warnings;
  1         1  
  1         18  
5              
6 1     1   420 use Moo;
  1         8382  
  1         3  
7 1     1   1447 use MooX::Types::MooseLike::Base qw(Str);
  1         4197  
  1         60  
8 1     1   541 use HTTP::Tiny;
  1         33864  
  1         31  
9 1     1   395 use Marketplace::Rakuten::Response;
  0            
  0            
10             use Marketplace::Rakuten::Order;
11             use Marketplace::Rakuten::Utils;
12             use namespace::clean;
13              
14             =head1 NAME
15              
16             Marketplace::Rakuten - Interface to http://webservice.rakuten.de/documentation
17              
18             =head1 VERSION
19              
20             Version 0.04
21              
22             =head1 CATEGORIES
23              
24             The list of categories for the German marketplace can be downloaded from
25             the L<http://api.rakuten.de/categories/csv/download>.
26              
27             When adding or editing a product, you can pass the
28             C<rakuten_category_id> to C<add_product> or C<edit_product>
29              
30             =cut
31              
32             our $VERSION = '0.04';
33              
34              
35             =head1 SYNOPSIS
36              
37             use Marketplace::Rakuten;
38             my $rakuten = Marketplace::Rakuten->new(key => 'xxxxxxx',
39             endpoint => 'http://webservice.rakuten.de/merchants/'
40             );
41             my $res = $rakuten->get_key_info;
42              
43             =head1 ACCESSORS
44              
45             =head2 key
46              
47             The API key (required).
48              
49             =head2 endpoint
50              
51             The URL of the endpoint. Default to http://webservice.rakuten.de/merchants/
52              
53             If you need a specific api version, it could be something like http://webservice.rakuten.de/v2.05/merchants
54              
55             =head2 ua
56              
57             The user-agent. Built lazily.
58              
59             =cut
60              
61             has key => (is => 'ro',
62             required => 1,
63             isa => Str);
64              
65             has endpoint => (is => 'ro',
66             default => sub { 'http://webservice.rakuten.de/merchants/' });
67              
68             has ua => (is => 'lazy');
69              
70             sub _build_ua {
71             return HTTP::Tiny->new;
72             }
73              
74              
75             =head1 METHODS
76              
77             =head2 api_call($method, $call, $data);
78              
79             Generic method to do any call. The first argument is the HTTP request
80             method (get or post). The second argument is the path of the api, e.g.
81             (C<misc/getKeyInfo>). The third is the structure to send (an hashref).
82              
83             Return a Marketplace::Rakuten::Response object.
84              
85             The key is injected into the data hashref in any case.
86              
87             =head2 API CALLS
88              
89             =head3 Miscellaneous
90              
91             =over 4
92              
93             =item get_key_info
94              
95             =back
96              
97             =head3 Product creation
98              
99             =over 4
100              
101             =item add_product(\%data)
102              
103             L<http://webservice.rakuten.de/documentation/method/add_product>
104              
105             Mandatory params: C<name>, C<price>, C<description>
106              
107             Recommended: C<product_art_no> (the sku)
108              
109             Return hashref: C<product_id>
110              
111             =item add_product_image(\%data)
112              
113             L<http://webservice.rakuten.de/documentation/method/add_product_image>
114              
115             Mandatory params: C<product_art_no> (the sku) or C<product_id>
116             (rakuten id).
117              
118             Return hashref: C<image_id>
119              
120             =item add_product_variant(\%data)
121              
122             L<http://webservice.rakuten.de/documentation/method/add_product_variant>
123              
124             Mandatory params: C<product_art_no> (the sku) or C<product_id>,
125             C<price>, C<color>, C<label>
126              
127             Return hashref: C<variant_id>
128              
129             =item add_product_variant_definition
130              
131             L<http://webservice.rakuten.de/documentation/method/add_product_variant>
132              
133             Mandatory params: C<product_art_no> (the sku) or C<product_id>
134              
135             Recommended: C<variant_1> Size C<variant_2> Color
136              
137             You need to call this method before trying to upload variants. The
138             data returned are however just a boolean (kind of) success.
139              
140             =item add_product_multi_variant
141              
142             L<http://webservice.rakuten.de/documentation/method/add_product_multi_variant>
143              
144             Mandatory params: C<product_art_no> (the sku) or C<product_id>, C<variation1_type>, C<variation1_value>, C<price>
145              
146             Recommended: C<variant_art_no>
147              
148             Return hashref: C<variant_id>
149              
150             =item add_product_link
151              
152             L<http://webservice.rakuten.de/documentation/method/add_product_link>
153              
154             Mandatory params: C<product_art_no> (the sku) or C<product_id>,
155             C<name> (100 chars), C<url> (255 chars)
156              
157             Return hashref: C<link_id>
158              
159             =item add_product_attribute
160              
161             L<http://webservice.rakuten.de/documentation/method/add_product_attribute>
162              
163             Mandatory params: C<product_art_no> (the sku) or C<product_id>,
164             C<title> (50 chars), C<value>
165              
166             Return hashref: C<attribute_id>
167              
168             =back
169              
170             =cut
171              
172             sub api_call {
173             my ($self, $method, $call, $data) = @_;
174             die "Missing method argument" unless $method;
175             die "Missing call name" unless $call;
176              
177             # populate the data and inject the key
178             $data ||= {};
179             $data->{key} = $self->key;
180              
181              
182             my $ua = $self->ua;
183             my $url = $self->endpoint;
184             $url =~ s/\/$//;
185             $call =~ s/^\///;
186             $url .= '/' . $call;
187              
188             my $response;
189             $method = lc($method);
190              
191             if ($method eq 'get') {
192             my $params = $ua->www_form_urlencode($data);
193             $url .= '?' . $params;
194             $response = $ua->get($url);
195             }
196             elsif ($method eq 'post') {
197             $response = $ua->post_form($url, $data);
198             }
199             else {
200             die "Unsupported method $method";
201             }
202             die "HTTP::Tiny failed to provide a response!" unless $response;
203             return Marketplace::Rakuten::Response->new(%$response);
204             }
205              
206             sub get_key_info {
207             my ($self, $data) = @_;
208             $self->api_call(get => 'misc/getKeyInfo')
209             }
210              
211             sub add_product {
212             my ($self, $data) = @_;
213             $self->api_call(post => 'products/addProduct', $data);
214             }
215              
216             sub add_product_image {
217             my ($self, $data) = @_;
218             $self->api_call(post => 'products/addProductImage', $data);
219             }
220              
221             sub add_product_variant {
222             my ($self, $data) = @_;
223             $self->api_call(post => 'products/addProductVariant', $data);
224             }
225              
226             sub add_product_variant_definition {
227             my ($self, $data) = @_;
228             $self->api_call(post => 'products/addProductVariantDefinition', $data);
229             }
230              
231             sub add_product_multi_variant {
232             my ($self, $data) = @_;
233             $self->api_call(post => 'products/addProductMultiVariant', $data);
234             }
235              
236             sub add_product_link {
237             my ($self, $data) = @_;
238             $self->api_call(post => 'products/addProductLink', $data);
239             }
240              
241             sub add_product_attribute {
242             my ($self, $data) = @_;
243             $self->api_call(post => 'products/addProductAttribute', $data);
244             }
245              
246             # unclear what cross-selling is, so leaving it out.
247              
248             # addProductToShopCategory is referring to an unknown
249             # shop_category_id, leaving it out for now.
250              
251              
252             =head3 Product editing
253              
254             The following methods require and id passed (sku or id) and work the
255             same as for product adding, but no argument is mandatory.
256              
257             They all return just a structure with a boolean success (-1 is
258             failure, 1 is success).
259              
260             =over 4
261              
262             =item edit_product(\%data)
263              
264             Requires C<product_id> or C<product_art_no>
265              
266             =item edit_product_variant(\%data);
267              
268             Requires C<variant_id> or C<variant_art_no>
269              
270             =item edit_product_variant_definition(\%data)
271              
272             Requires C<product_id> or C<product_art_no>
273              
274             =item edit_product_multi_variant(\%data);
275              
276             Requires C<variant_id> or C<variant_art_no>
277              
278             =item edit_product_attribute
279              
280             Requires C<attribute_id>, C<title>, C<value>
281              
282             =back
283              
284             =cut
285              
286             sub edit_product {
287             my ($self, $data) = @_;
288             $self->api_call(post => 'products/editProduct', $data);
289             }
290              
291             sub edit_product_variant {
292             my ($self, $data) = @_;
293             $self->api_call(post => 'products/editProductVariant', $data);
294             }
295              
296             sub edit_product_variant_definition {
297             my ($self, $data) = @_;
298             $self->api_call(post => 'products/editProductVariantDefinition', $data);
299             }
300              
301             sub edit_product_multi_variant {
302             my ($self, $data) = @_;
303             $self->api_call(post => 'products/editProductMultiVariant', $data);
304             }
305              
306             sub edit_product_attribute {
307             my ($self, $data) = @_;
308             $self->api_call(post => 'products/editProductAttribute', $data);
309             }
310              
311             =head3 Product deletion
312              
313             The following methods require just an id or sku. Return a boolean success.
314              
315             =over 4
316              
317             =item delete_product(\%data)
318              
319             Requires C<product_id> or C<product_art_no>
320              
321             =item delete_product_variant(\%data);
322              
323             Requires C<variant_id> or C<variant_art_no>
324              
325             =item delete_product_image(\%data)
326              
327             Requires C<image_id> (returned by add_product_image).
328              
329             =item delete_product_link(\%data);
330              
331             Requires C<link_id> (returned by add_product_link).
332              
333             =item delete_product_attribute
334              
335             Requires C<attribute_id> (returned by add_product_attribute).
336              
337             =back
338              
339             =cut
340              
341             sub delete_product {
342             my ($self, $data) = @_;
343             $self->api_call(post => 'products/deleteProduct', $data);
344             }
345              
346             sub delete_product_variant {
347             my ($self, $data) = @_;
348             $self->api_call(post => 'products/deleteProductVariant', $data);
349             }
350              
351             sub delete_product_image {
352             my ($self, $data) = @_;
353             $self->api_call(post => 'products/deleteProductImage', $data);
354             }
355              
356             sub delete_product_link {
357             my ($self, $data) = @_;
358             $self->api_call(post => 'products/deleteProductLink', $data);
359             }
360              
361             sub delete_product_attribute {
362             my ($self, $data) = @_;
363             $self->api_call(post => 'products/deleteProductAttribute', $data);
364             }
365              
366             =head3 Orders
367              
368             =over 4
369              
370             =item get_orders(\%params)
371              
372             No argument is required, but you probably want to pass something like
373              
374             { status => pending }
375              
376             See L<http://webservice.rakuten.de/documentation/method/get_orders>
377             for the full list of options.
378              
379             List of statuses:
380              
381             =over 4
382              
383             =item pending
384              
385             =item editable
386              
387             This is what you want to get for importing.
388              
389             =item shipped
390              
391             =item payout
392              
393             Order is paid. Unclear when the status switches to this one.
394              
395             =item cancelled
396              
397             =back
398              
399             The response is paginated and from here you get only the raw data.
400              
401             Use get_parsed_orders to get the full list of objects.
402              
403             =item get_parsed_orders(\%params)
404              
405             The hashref with the parameters is the same of C<get_orders> (which
406             get called). This method takes care of the pagination and return a
407             list of L<Marketplace::Rakuten::Order> objects. You can access the raw
408             structures with $object->order.
409              
410             =item get_pending_orders
411              
412             Shortcut for $self->get_parsed_orders({ status => 'pending' });
413              
414             =item get_editable_orders
415              
416             Shortcut for $self->get_parsed_orders({ status => 'editable' });
417              
418             =cut
419              
420              
421             sub get_orders {
422             my ($self, $data) = @_;
423             $self->api_call(get => 'orders/getOrders', $data);
424             }
425              
426             sub get_pending_orders {
427             my ($self) = @_;
428             return $self->get_parsed_orders({ status => 'pending' });
429             }
430              
431             sub get_editable_orders {
432             my ($self) = @_;
433             return $self->get_parsed_orders({ status => 'editable' });
434             }
435              
436              
437             sub get_parsed_orders {
438             my ($self, $params) = @_;
439             $params ||= {};
440             my $res = $self->get_orders($params);
441             die "Couldn't retrieve orders: " . Dumper($res->errors)
442             unless $res->is_success;
443             my $data = $res->data;
444             my @orders = $self->_parse_orders($data);
445             # print Dumper(\@orders);
446             # print Dumper($res);
447             while ($data->{orders} and
448             $data->{orders}->{paging}->{pages} and
449             $data->{orders}->{paging}->{page} and
450             $data->{orders}->{paging}->{per_page} and
451             $data->{orders}->{paging}->{pages} > $data->{orders}->{paging}->{page}) {
452             my $next_page = $data->{orders}->{paging}->{page} + 1;
453             my $per_page = $data->{orders}->{paging}->{per_page};
454             $res = $self->get_orders({
455             %$params,
456             page => $next_page,
457             per_page => $per_page
458             });
459             $data = $res->data;
460             push @orders, $self->_parse_orders($data);
461             }
462             return @orders;
463             }
464              
465             sub _parse_orders {
466             my ($self, $struct) = @_;
467             my @out;
468             if ($struct->{orders}) {
469             if (my $orders = $struct->{orders}->{order}) {
470             foreach my $order (@$orders) {
471             my $struct = { %$order };
472             Marketplace::Rakuten::Utils::turn_empty_hashrefs_into_empty_strings($struct);
473             push @out, Marketplace::Rakuten::Order->new(order => $struct);
474             }
475             }
476             }
477             return @out;
478             }
479              
480              
481             =item set_order_shipped(\%params)
482              
483             Required parameter: order_no (the rakuten's order number).
484              
485             Accepted parameters:
486              
487             =over 4
488              
489             =item dhl (boolean, true if Rakuten-DHL-Rahmenvertrag is used)
490              
491             =item carrier
492              
493             =item tracking_number
494              
495             =item tracking_url
496              
497             =back
498              
499             =cut
500              
501             sub set_order_shipped {
502             my ($self, $data) = @_;
503             $self->api_call(post => 'orders/setOrderShipped', $data);
504             }
505              
506             =item set_order_cancelled(\%params)
507              
508             Required paramater: C<order_no> (rakuten's order number)
509              
510             Optional: C<comment>
511              
512             =cut
513              
514             sub set_order_cancelled {
515             my ($self, $data) = @_;
516             $self->api_call(post => 'orders/setOrderCancelled', $data);
517             }
518              
519             =item set_order_returned(\%params)
520              
521             Required paramater: C<order_no> (rakuten's order number) and C<type>
522             (C<fully> or C<partly>):
523              
524             L<http://webservice.rakuten.de/documentation/method/set_order_returned>
525              
526             =back
527              
528             =cut
529              
530              
531             sub set_order_returned {
532             my ($self, $data) = @_;
533             $self->api_call(post => 'orders/setOrderReturned', $data);
534             }
535              
536             =head3 Category management
537              
538             =over 4
539              
540             =item add_shop_category(\%params)
541              
542             Required parameter: C<name>
543              
544             L<http://webservice.rakuten.de/documentation/method/add_shop_category>
545              
546             Returned: C<shop_category_id>
547              
548             =item edit_shop_category(\%params)
549              
550             Required parameter C<shop_category_id> or C<external_shop_category_id>
551              
552             L<http://webservice.rakuten.de/documentation/method/edit_shop_category>
553              
554             =item get_shop_categories
555              
556             No mandatory parameter.
557              
558             L<http://webservice.rakuten.de/documentation/method/get_shop_categories>
559              
560             =item delete_shop_category
561              
562             Required parameter C<shop_category_id> or C<external_shop_category_id>
563              
564             L<http://webservice.rakuten.de/documentation/method/delete_shop_category>
565              
566             =item add_product_to_shop_category
567              
568             Required parameters: C<shop_category_id> or C<external_shop_category_id>,
569             C<product_id> or C<product_art_no> (Rakuten's id or our sku).
570              
571             L<http://webservice.rakuten.de/documentation/method/add_product_to_shop_category>
572              
573             =back
574              
575             =cut
576              
577             sub add_shop_category {
578             my ($self, $data) = @_;
579             $self->api_call(post => 'categories/addShopCategory', $data);
580             }
581              
582             sub edit_shop_category {
583             my ($self, $data) = @_;
584             $self->api_call(post => 'categories/editShopCategory', $data);
585             }
586              
587             sub get_shop_categories {
588             my ($self, $data) = @_;
589             $self->api_call(get => 'categories/getShopCategories', $data);
590             }
591              
592             sub delete_shop_category {
593             my ($self, $data) = @_;
594             $self->api_call(post => 'categories/deleteShopCategory', $data);
595             }
596              
597             sub add_product_to_shop_category {
598             my ($self, $data) = @_;
599             $self->api_call(post => 'products/addProductToShopCategory', $data);
600             }
601              
602             =head1 AUTHOR
603              
604             Marco Pessotto, C<< <melmothx at gmail.com> >>
605              
606             =head1 BUGS
607              
608             Please report any bugs or feature requests to C<bug-marketplace-rakuten at rt.cpan.org>, or through
609             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Marketplace-Rakuten>. I will be notified, and then you'll
610             automatically be notified of progress on your bug as I make changes.
611              
612              
613              
614              
615             =head1 SUPPORT
616              
617             You can find documentation for this module with the perldoc command.
618              
619             perldoc Marketplace::Rakuten
620              
621              
622             You can also look for information at:
623              
624             =over 4
625              
626             =item * RT: CPAN's request tracker (report bugs here)
627              
628             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Marketplace-Rakuten>
629              
630             =item * AnnoCPAN: Annotated CPAN documentation
631              
632             L<http://annocpan.org/dist/Marketplace-Rakuten>
633              
634             =item * CPAN Ratings
635              
636             L<http://cpanratings.perl.org/d/Marketplace-Rakuten>
637              
638             =item * Search CPAN
639              
640             L<http://search.cpan.org/dist/Marketplace-Rakuten/>
641              
642             =back
643              
644              
645             =head1 ACKNOWLEDGEMENTS
646              
647              
648             =head1 LICENSE AND COPYRIGHT
649              
650             Copyright 2015 Marco Pessotto.
651              
652             This program is free software; you can redistribute it and/or modify it
653             under the terms of the the Artistic License (2.0). You may obtain a
654             copy of the full license at:
655              
656             L<http://www.perlfoundation.org/artistic_license_2_0>
657              
658             Any use, modification, and distribution of the Standard or Modified
659             Versions is governed by this Artistic License. By using, modifying or
660             distributing the Package, you accept this license. Do not use, modify,
661             or distribute the Package, if you do not accept this license.
662              
663             If your Modified Version has been derived from a Modified Version made
664             by someone other than you, you are nevertheless required to ensure that
665             your Modified Version complies with the requirements of this license.
666              
667             This license does not grant you the right to use any trademark, service
668             mark, tradename, or logo of the Copyright Holder.
669              
670             This license includes the non-exclusive, worldwide, free-of-charge
671             patent license to make, have made, use, offer to sell, sell, import and
672             otherwise transfer the Package with respect to any patent claims
673             licensable by the Copyright Holder that are necessarily infringed by the
674             Package. If you institute patent litigation (including a cross-claim or
675             counterclaim) against any party alleging that the Package constitutes
676             direct or contributory patent infringement, then this Artistic License
677             to you shall terminate on the date that such litigation is filed.
678              
679             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
680             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
681             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
682             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
683             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
684             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
685             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
686             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
687              
688              
689             =cut
690              
691             1; # End of Marketplace::Rakuten