File Coverage

blib/lib/Slovo/Plugin/Prodan.pm
Criterion Covered Total %
statement 80 80 100.0
branch 1 2 50.0
condition 9 18 50.0
subroutine 11 11 100.0
pod 1 1 100.0
total 102 112 91.0


line stmt bran cond sub pod time code
1             package Slovo::Plugin::Prodan;
2 4     4   6171746 use feature ':5.26';
  4         9  
  4         374  
3 4     4   385 use Mojo::Base 'Mojolicious::Plugin', -signatures;
  4         137407  
  4         25  
4 4     4   4346 use Mojo::JSON qw(true false);
  4         12926  
  4         4280  
5              
6             our $AUTHORITY = 'cpan:BEROV';
7             our $VERSION = '0.05';
8              
9             has app => sub { Slovo->new }, weak => 1;
10              
11 3     3 1 203 sub register ($self, $app, $conf) {
  3         7  
  3         6  
  3         6  
  3         4  
12 3         10 $self->app($app);
13              
14             # Prepend class
15 3         25 unshift @{$app->renderer->classes}, __PACKAGE__;
  3         12  
16 3         28 unshift @{$app->static->classes}, __PACKAGE__;
  3         11  
17 3         35 $app->stylesheets('/css/cart.css');
18 3         2244 $app->javascripts('/js/cart.js');
19             $app->config->{consents}{gdpr_url}
20 3   50     1904 = $conf->{consents}{gdpr_url} || '/ѿносно/условия.bg.html';
21 3   50     42 $app->config->{consents}{phone_url} = $conf->{consents}{phone_url} || '+359899999999';
22 3   50     22 $app->config->{consents}{delivery_prices_url} ||= '/ѿносно/цени-доставка.bg.html';
23              
24             # $app->log->debug(join $/, sort keys %INC);
25             # $app->debug('Prodan $config', $conf);
26             # Set this flag, when we have changes to the tables to be applied.
27 3 50       36 $self->_migrate($app, $conf) if $conf->{migrate};
28              
29 3         101 my $spec = $app->openapi_spec;
30 3         724 %{$spec->{definitions}} = (%{$spec->{definitions}}, $self->_definitions);
  3         28  
  3         25  
31 3         8 %{$spec->{paths}} = (%{$spec->{paths}}, $self->_paths);
  3         151  
  3         15  
32              
33             # Add new data_type for celina. Now a corresponding partial template can be
34             # used to render the new data_type.
35 3         6 push @{$spec->{parameters}{data_type}{enum}}, '_consents';
  3         14  
36 3         20 $app->plugin(OpenAPI => {spec => $spec});
37              
38             # $app->debug($spec);
39             # Generate helpers for instantiating Slovo::Model classes just like
40             # Slovo::PLugin::MojoDBx
41 3         968533 for my $t ('poruchki', 'products') {
42 6         355 my $T = Mojo::Util::camelize($t);
43 6         98 my $class = "Slovo::Model::$T";
44 6         34 $app->load_class($class);
45 6         13 $app->helper(
46 6     6   12 $t => sub ($c) {
  6         575800  
47 6         34 my $m = $class->new(dbx => $c->dbx, c => $c);
48 6         169 Scalar::Util::weaken $m->{c};
49 6         16 return $m;
50 6         133 });
51             }
52              
53             # configure deliverers
54 3         278 $self->_configure_deliverers($conf);
55 3         16 return $self;
56             }
57              
58             sub _paths {
59             return (
60 3     3   21 '/poruchki' => {
61             post => {
62             description => 'Create a new order',
63             'x-mojo-to' => 'poruchki#store',
64             parameters => [{
65             required => true,
66             in => 'body',
67             name => 'Poruchka',
68             schema => {'$ref' => '#/definitions/Poruchka'}
69             },
70             ],
71             responses => {
72             201 => {
73             description => 'Order created successfully!',
74             schema => {'$ref' => '#/definitions/Poruchka'}
75              
76             },
77             default => {'$ref' => '#/definitions/ErrorResponse'}}}
78             },
79              
80             '/poruchka/:deliverer/:deliverer_id' => {
81             put => {
82             description => 'show an order by given :deliverer and :id with that deliverer. ',
83             'x-mojo-to' => 'poruchki#show',
84             parameters => [{
85             name => 'deliverer_id',
86             in => 'path',
87             description => 'Id of the order in deliverer\'s system',
88             type => 'integer',
89             required => true,
90              
91             },
92             {
93             name => 'deliverer',
94             in => 'path',
95             description => 'A string which denotes the deliverer. Example: "email". ',
96             type => 'string',
97             enum => [qw(email econt)],
98             required => true,
99             },
100             {
101             name => 'id',
102             in => 'formData',
103             description =>
104             '"id" property, found in the order structure. Generated by the Econt delivery confirmation form',
105             type => 'string',
106             required => true,
107              
108             },
109             ],
110             responses => {
111             200 => {
112             description =>
113             'Show eventually updated order from :deliverer with :deliverer_id',
114             schema => {'$ref' => '#/definitions/Poruchka'}
115              
116             },
117             default => {'$ref' => '#/definitions/ErrorResponse'}}}
118             },
119             '/shop' => {
120             get => {
121             description => 'Provides data for the shop',
122             'x-mojo-to' => 'poruchki#shop',
123             responses => {default => {'$ref' => '#/definitions/ErrorResponse'}}}
124             },
125             '/consents' => {
126             get => {
127             description => 'Page URL for GDPR concent',
128             'x-mojo-to' => 'poruchki#consents',
129             responses => {
130             200 => {
131             description => 'The URL to the detailed usage conditions, GDPR, cookies.',
132             schema => {'$ref' => '#/definitions/Consents'}
133             },
134             default => {'$ref' => '#/definitions/ErrorResponse'}}}
135             },
136             );
137             }
138              
139             # Returns description as a perl structure of objects defined for the json API
140             # to be added to the /definitions of our OpenAPI
141             sub _definitions {
142             return (
143 3     3   123 ListOfPoruchki => {
144             description => 'An array of Poruchka items.',
145             items => {'$ref' => '#/definitions/Poruchka', type => 'array',}
146             },
147             Poruchka => {
148             properties => {
149             deliverer_id => {
150             description =>
151             'Id of the order as given by Econt, returned with the response to the user-agent(browser)',
152             type => 'integer',
153             },
154             name => {maxLength => 100, type => 'string',},
155             email => {maxLength => 100, minLength => 0, type => 'string',},
156             phone => {maxLength => 20, type => 'string',},
157             deliverer => {maxLength => 100, type => 'string',},
158             city_name => {maxLength => 55, type => 'string'},
159             address => {maxLength => 155, type => 'string'},
160             notes => {maxLength => 255, type => 'string'},
161             items => {'$ref' => '#/definitions/OrderProducts', type => 'array',},
162             way_bill_id => {
163             description =>
164             'Id at the deliverer site, returned by their system after we created the way-bill at their site.',
165             maxLength => 40,
166             type => 'string'
167             },
168             executed => {
169             type => 'integer',
170             minimum => 0,
171             maximum => 9,
172             description => 'Level of execution of the order.0:registered',
173             }
174             },
175             },
176             OrderProducts => {
177             description => 'An array of OrderProduct items in an order.',
178             items => {
179             '$ref' => '#/definitions/OrderProduct',
180             type => 'array',
181              
182             }
183             },
184             OrderProduct => {
185             description => 'An item in an order (cart): sku, title, quantity, price',
186             properties => {
187             sku => {maxLength => 40, type => 'string'},
188             title => {maxLength => 155, type => 'string'},
189             quantity => {type => 'integer'},
190             weight => {type => 'number'},
191             price => {type => 'number'},
192             }
193             },
194             Consents => {
195             properties => {
196             ihost => {type => 'string'},
197             gdpr_url => {type => 'string'},
198             delivery_prices_url => {type => 'string'}
199             },
200             required => [qw(gdpr_url ihost delivery_prices_url)],
201             },
202             );
203             }
204              
205             # Create tables in the database on the very first run if they do not exist.
206 3     3   5 sub _migrate ($self, $app, $conf) {
  3         7  
  3         3  
  3         7  
  3         5  
207 3         28 $app->dbx->migrations->migrate;
208 3         34802 $app->dbx->migrations->name('prodan')
209             ->from_data(__PACKAGE__, 'resources/data/prodan_migrations.sql')->migrate();
210 3         13826 return $self;
211             }
212              
213             # Deliverers are companies which deliver goods to users of our online shop.
214             # Such delivereres in Bulgaria are Econt, Speedy, Bulgarian Posts and others.
215             # Currently we integrate only Econt
216             my sub DELIVERERS {
217 3     3   10 return qw(econt);
218             }
219              
220 3     3   7 sub _configure_deliverers ($self, $conf) {
  3         6  
  3         7  
  3         5  
221 3         12 for my $d (DELIVERERS) {
222 3         10 my $d_sub = '_configure_' . $d;
223 3         17 $self->$d_sub($conf);
224             }
225 3         5 return;
226             }
227              
228             # The keys in the $conf hash reference are named after the examples given at
229             # http://delivery.econt.com/services/
230 3     3   7 sub _configure_econt ($self, $conf) {
  3         6  
  3         6  
  3         5  
231 3         10 my $eco = $conf->{econt};
232              
233             # ID на магазина в "Достави с Еконт"
234 3   50     25 $eco->{shop_id} //= 'demo';
235              
236             # Код за свързване
237 3   50     18 $eco->{private_key} //= 'demo';
238              
239             # валута на магазина (валута на наложения платеж)
240 3   50     24 $eco->{shop_currency} //= 'BGN';
241              
242             # URL визуализиращ форма за доставка
243 3   50     12 $eco->{shippment_calc_url} //= 'https://delivery-demo.econt.com/customer_info.php';
244              
245             # Ендпойнта на услугата за създаване или редактиране на поръчка
246             $eco->{crupdate_order_endpoint}
247 3   50     11 //= 'https://delivery-demo.econt.com/services/OrdersService.updateOrder.json';
248              
249             # Ендпойнта на услугата за създаване или редактиране на товарителница
250             $eco->{create_awb_endpoint}
251 3   50     12 //= 'https://delivery-demo.econt.com/services/OrdersService.createAWB.json';
252              
253             # $self->app->debug($conf);
254 3         16 $self->app->config->{shop} = $eco;
255 3         62 return;
256             }
257              
258             1;
259              
260              
261             =encoding utf8
262              
263             =head1 NAME
264              
265             Slovo::Plugin::Prodan - Make and manage sales in your Slovo-based site
266              
267             =head1 SYNOPSIS
268              
269             # In slovo.conf
270             load_plugins => [
271             #...
272             'Themes::Malka',
273             {
274             Prodan => {
275             migrate => 1,
276             consents => {
277             gdpr_url => '/ѿносно/условия.bg.html',
278             phone_url => $ENV{SLOVO_PRODAN_PHONE_URL},
279             delivery_prices_url => '/ѿносно/цени-доставки.bg.html',
280             },
281             econt => {
282             shop_id => $ENV{SLOVO_PRODAN_SHOP_ID},
283             private_key => $ENV{SLOVO_PRODAN_PRIVATE_KEY},
284             shippment_calc_url => 'https://delivery.econt.com/customer_info.php',
285             crupdate_order_endpoint =>
286             'https://delivery.econt.com/services/OrdersService.updateOrder.json',
287             create_awb_endpoint =>
288             'https://delivery.econt.com/services/OrdersService.createAWB.json'
289             }}
290             },
291             #...
292             ],
293              
294             =head1 DESCRIPTION
295              
296             The word про̀дан (прода̀жба) in Bulgarian means sale. Roots are found in Old
297             Common Slavic (Old Bulgarian) I<проданьѥ>. Here is an exerpt from Codex
298             Suprasliensis(331.27) where this word was witnessed: I<сꙑнъ божии. вол҄еѭ
299             на сьпасьнѫѭ страсть съ вами придетъ. и на B<продании> станетъ.
300             искѹпѹѭштааго животворьноѭ кръвьѭ. своеѭ миръ.>
301              
302             L is a L that extends a
303             Slovo-based site and turns it into an online shop.
304              
305             =head1 FEATURES
306              
307             In this edition of L we implement the following features:
308              
309             =head2 A Shopping cart
310              
311             A jQuery and localStorage based shopping cart. Two static files contain
312             the implementation and they can be inflated. The files are
313             C and C. You should inflate these files into
314             your public forlder C for the domain on which you
315             will use it. Even not inflated these will be referred from any page of the
316             site. The site layout C includes automatically these
317             two static files if this plugin is loaded.
318              
319             # Inflate new static files from Slovo::Plugin::Prodan
320             bin/slovo inflate --class Slovo::Plugin::Prodan -p --path domove/xn--b1arjbl.xn--90ae/public
321              
322             To add a product to your cart and make an order, you need a button, containing
323             the product data. For example:
324              
325            
326             title="книжно издание" data-sku="9786199169001"
327             data-title="Житие на света Петка Българска от свети патриарх Евтимий"
328             data-weight="0.5" data-price="7.00">
329             src="/css/malka/book-open-page-variant-outline.svg">
330            
331              
332             See "A template..." below.
333              
334             =head2 Delivery of sold goods
335              
336             A "Pay on delivery" integration with Bulgarian currier L
337             Bulgarian)|https://www.econt.com/developers/43-kakvo-e-dostavi-s-ekont.html>.
338              
339             =head2 Products
340              
341             Products - a products SQL table to populate your pages with products. You
342             create a page with several articles (celini) in it. These celini will be the
343             web-pages for the products. You prepare a YAML file with products. Each product
344             C property must match exactly the celina C and C on
345             wich this product will be placed. See C and
346             C for examples. See L
347             on how to add and update products.
348              
349             =head2 Products template for books
350              
351             A template for displaying products within a C. You can modify this
352             template as you wish to display other types of products - not just books as it
353             is now. See C inlined in this file's C<__DATA__>
354             section. It of course can be inflated using
355             L. The template produces the HTML from the
356             products table, including the button mentioned above already.
357              
358             # Add the template form Prodan
359             bin/slovo inflate --class Slovo::Plugin::Prodan \
360             -t --path domove/xn--b1arjbl.xn--90ae/templates/themes/malka
361              
362              
363             =head2 Consents
364              
365             A section in the Prodan configuration for different settings - only urls for
366             now. C<$app-Econfig('consents')> may contain any settings needed for the
367             client side of the plugin not related dierctly to integration with deliverers
368             or payment providers.
369              
370             =head3 GDPR and Cookies consent
371              
372             A GDPR and cookies consent alert in the footer which upon click leads to the
373             page (celina) where all conditions on using the site can be described. When the
374             user clicks on the link to the I page a flag in C is put
375             so the alert is not shown any more. This flag disappears if the user clears any
376             site data and the alert will appear again if the user vists the site again.
377             The Consent celina is created automatically in the localhost domain as an
378             example. Search for C in the source of this module to see how it
379             is implemented.
380              
381             Settings:
382              
383             Keys Default Values
384             --------------------------------------------
385             gdpr_url '/ѿносно/условия.bg.html'
386             ihost punycode_decode(ed) current host
387              
388             =head3 Delivery prices URL
389              
390             This is just a setting for this plugin - C. Defaults to
391             '/ѿносно/цени-доставка.bg.html'. This is a place where the prices for delivery
392             are described. The link is displayed at the bottom of the shopping cart widget.
393             It is created automatically for localhost as the C
394              
395             =head3 phone_url
396              
397             Currently displayed as a link in the _footer_right.html.ep template.
398              
399              
400             =head2 TODO some day
401              
402             =over 1
403              
404             =item Invoices - generate an invoice in PDF via headless LibreOffice instance
405             on your server.
406              
407             =item Merchants - a merchants SQL table with Open API to manage and
408             automatically populate invoices.
409              
410             =item Other "Pay on Delivery" providers. Feel free to contibute yours.
411              
412             =item Other types of Payments and/or Online Payment Providers like online POS
413             Terminals etc.
414              
415             =back
416              
417             =head1 METHODS
418              
419             The usual method is implemented.
420              
421             =head2 register
422              
423             Prepends the class to renderer and static classes. Adds some REST API routes,
424             configures the deliverer.
425              
426             =head1 EMBEDDED FILES
427              
428             @@ css/cart.css
429             @@ js/cart.js
430             @@ img/arrow-collapse-all.svg
431             @@ img/cart-arrow-right.svg
432             @@ img/cart.svg
433             @@ img/cart-check.svg
434             @@ img/cart-off.svg
435             @@ img/cart-minus.svg
436             @@ img/cart-plus-white.svg
437             @@ img/cart-plus.svg
438             @@ img/cart-remove.svg
439             @@ img/econt.svg
440             @@ partials/_footer_right.html.ep
441             @@ partials/_consents.html.ep
442             @@ partials/_kniga.html.ep
443             @@ resources/data/prodan_migrations.sql
444              
445             =head1 SEE ALSO
446              
447             L,
448             L,
449             L,
450             L,
451             L,
452             L,
453             L
454              
455             =head1 AUTHOR
456              
457             Красимир Беров
458             CPAN ID: BEROV
459             berov на cpan точка org
460             http://слово.бг
461              
462             =head1 CONTRIBUTORS
463              
464             Ordered by time of first commit.
465              
466             =over
467              
468             =item * Your Name
469              
470             =item * Someone Else
471              
472             =item * Another Contributor
473              
474             =back
475              
476             =head1 COPYRIGHT
477              
478             This is free software, licensed under:
479              
480             The Artistic License 2.0 (GPL Compatible)
481              
482             The full text of the license can be found in the
483             LICENSE file included with this module.
484              
485             This distribution contains icons from L and
486             may contain other free software which belongs to their respective authors.
487              
488             =cut
489              
490             __DATA__