File Coverage

blib/lib/Slovo/Plugin/Prodan.pm
Criterion Covered Total %
statement 79 79 100.0
branch 1 2 50.0
condition 8 16 50.0
subroutine 11 11 100.0
pod 1 1 100.0
total 100 109 91.7


line stmt bran cond sub pod time code
1             package Slovo::Plugin::Prodan;
2 4     4   7942504 use feature ':5.26';
  4         9  
  4         399  
3 4     4   485 use Mojo::Base 'Mojolicious::Plugin', -signatures;
  4         168190  
  4         27  
4 4     4   5208 use Mojo::JSON qw(true false);
  4         15157  
  4         4877  
5              
6             our $AUTHORITY = 'cpan:BEROV';
7             our $VERSION = '0.04';
8              
9             has app => sub { Slovo->new }, weak => 1;
10              
11 3     3 1 192 sub register ($self, $app, $conf) {
  3         8  
  3         7  
  3         13  
  3         6  
12 3         12 $self->app($app);
13              
14             # Prepend class
15 3         30 unshift @{$app->renderer->classes}, __PACKAGE__;
  3         9  
16 3         31 unshift @{$app->static->classes}, __PACKAGE__;
  3         10  
17 3         33 $app->stylesheets('/css/cart.css');
18 3         2281 $app->javascripts('/js/cart.js');
19             $app->config->{gdpr_consent_url}
20 3   50     2094 = $conf->{gdpr_consent_url} || '/ѿносно/условия.bg.html';
21 3   50     40 $app->config->{phone_url} = $conf->{phone_url} || '+359899999999';
22              
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       30 $self->_migrate($app, $conf) if $conf->{migrate};
28              
29 3         75 my $spec = $app->openapi_spec;
30 3         613 %{$spec->{definitions}} = (%{$spec->{definitions}}, $self->_definitions);
  3         41  
  3         24  
31 3         6 %{$spec->{paths}} = (%{$spec->{paths}}, $self->_paths);
  3         136  
  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         16 push @{$spec->{parameters}{data_type}{enum}}, '_gdpr_consent';
  3         24  
36 3         19 $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         1093898 for my $t ('poruchki', 'products') {
42 6         347 my $T = Mojo::Util::camelize($t);
43 6         106 my $class = "Slovo::Model::$T";
44 6         34 $app->load_class($class);
45 6         16 $app->helper(
46 6     6   8 $t => sub ($c) {
  6         600836  
47 6         39 my $m = $class->new(dbx => $c->dbx, c => $c);
48 6         157 Scalar::Util::weaken $m->{c};
49 6         58 return $m;
50 6         130 });
51             }
52              
53             # configure deliverers
54 3         276 $self->_configure_deliverers($conf);
55 3         14 return $self;
56             }
57              
58             sub _paths {
59             return (
60 3     3   33 '/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             '/gdpr_consent' => {
126             get => {
127             description => 'Page URL for GDPR concent',
128             'x-mojo-to' => 'poruchki#gdpr_consent',
129             responses => {
130             200 => {
131             description => 'The URL to the detailed usage conditions, GDPR, cookies.',
132             schema => {'$ref' => '#/definitions/GdprUrl'}
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   107 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             GdprUrl => {
195             properties => {ihost => {type => 'string'}, url => {type => 'string'}},
196             required => [qw(url ihost)],
197             },
198             );
199             }
200              
201             # Create tables in the database on the very first run if they do not exist.
202 3     3   5 sub _migrate ($self, $app, $conf) {
  3         5  
  3         6  
  3         4  
  3         6  
203 3         25 $app->dbx->migrations->migrate;
204 3         39221 $app->dbx->migrations->name('prodan')
205             ->from_data(__PACKAGE__, 'resources/data/prodan_migrations.sql')->migrate();
206 3         14314 return $self;
207             }
208              
209             # Deliverers are companies which deliver goods to users of our online shop.
210             # Such delivereres in Bulgaria are Econt, Speedy, Bulgarian Posts and others.
211             # Currently we integrate only Econt
212             my sub DELIVERERS {
213 3     3   9 return qw(econt);
214             }
215              
216 3     3   8 sub _configure_deliverers ($self, $conf) {
  3         6  
  3         7  
  3         5  
217 3         13 for my $d (DELIVERERS) {
218 3         10 my $d_sub = '_configure_' . $d;
219 3         18 $self->$d_sub($conf);
220             }
221 3         7 return;
222             }
223              
224             # The keys in the $conf hash reference are named after the examples given at
225             # http://delivery.econt.com/services/
226 3     3   6 sub _configure_econt ($self, $conf) {
  3         7  
  3         6  
  3         5  
227 3         10 my $eco = $conf->{econt};
228              
229             # ID на магазина в "Достави с Еконт"
230 3   50     24 $eco->{shop_id} //= 'demo';
231              
232             # Код за свързване
233 3   50     17 $eco->{private_key} //= 'demo';
234              
235             # валута на магазина (валута на наложения платеж)
236 3   50     16 $eco->{shop_currency} //= 'BGN';
237              
238             # URL визуализиращ форма за доставка
239 3   50     19 $eco->{shippment_calc_url} //= 'https://delivery-demo.econt.com/customer_info.php';
240              
241             # Ендпойнта на услугата за създаване или редактиране на поръчка
242             $eco->{crupdate_order_endpoint}
243 3   50     12 //= 'https://delivery-demo.econt.com/services/OrdersService.updateOrder.json';
244              
245             # Ендпойнта на услугата за създаване или редактиране на товарителница
246             $eco->{create_awb_endpoint}
247 3   50     11 //= 'https://delivery-demo.econt.com/services/OrdersService.createAWB.json';
248              
249             # $self->app->debug($conf);
250 3         14 $self->app->config->{shop} = $eco;
251 3         56 return;
252             }
253              
254             1;
255              
256              
257             =encoding utf8
258              
259             =head1 NAME
260              
261             Slovo::Plugin::Prodan - Make and manage sales in your Slovo-based site
262              
263             =head1 SYNOPSIS
264              
265             # In slovo.conf
266             load_plugins => [
267             #...
268             'Themes::Malka',
269             {
270             Prodan => {
271             migrate => 1,
272             gdpr_consent_url => '/ѿносно/условия.bg.html',
273             econt => {
274             shop_id => $ENV{SLOVO_PRODAN_SHOP_ID},
275             private_key => $ENV{SLOVO_PRODAN_PRIVATE_KEY},
276             shippment_calc_url => 'https://delivery.econt.com/customer_info.php',
277             crupdate_order_endpoint =>
278             'https://delivery.econt.com/services/OrdersService.updateOrder.json',
279             create_awb_endpoint =>
280             'https://delivery.econt.com/services/OrdersService.createAWB.json'
281             }}
282             },
283             #...
284             ],
285              
286             =head1 DESCRIPTION
287              
288             The word про̀дан (прода̀жба) in Bulgarian means sale. Roots are found in Old
289             Common Slavic (Old Bulgarian) I<проданьѥ>. Here is an exerpt from Codex
290             Suprasliensis(331.27) where this word was witnessed: I<сꙑнъ божии. вол҄еѭ
291             на сьпасьнѫѭ страсть съ вами придетъ. и на B<продании> станетъ.
292             искѹпѹѭштааго животворьноѭ кръвьѭ. своеѭ миръ.>
293              
294             L is a L that extends a
295             Slovo-based site and turns it into an online shop.
296              
297             =head1 FEATURES
298              
299             In this edition of L we implement the following features:
300              
301             =head2 A Shopping cart
302              
303             A jQuery and localStorage based shopping cart. Two static files contain
304             the implementation and they can be inflated. The files are
305             C and C. You should inflate these files into
306             your public forlder C for the domain on which you
307             will use it. Even not inflated these will be referred from any page of the
308             site. The site layout C includes automatically these
309             two static files if this plugin is loaded.
310              
311             # Inflate new static files from Slovo::Plugin::Prodan
312             bin/slovo inflate --class Slovo::Plugin::Prodan -p --path domove/xn--b1arjbl.xn--90ae/public
313              
314             To add a product to your cart and make an order, you need a button, containing
315             the product data. For example:
316              
317            
318             title="книжно издание" data-sku="9786199169001"
319             data-title="Житие на света Петка Българска от свети патриарх Евтимий"
320             data-weight="0.5" data-price="7.00">
321             src="/css/malka/book-open-page-variant-outline.svg">
322            
323              
324             See "A template..." below.
325              
326             =head2 Delivery of sold goods
327              
328             A "Pay on delivery" integration with Bulgarian currier L
329             Bulgarian)|https://www.econt.com/developers/43-kakvo-e-dostavi-s-ekont.html>.
330              
331             =head2 Products
332              
333             Products - a products SQL table to populate your pages with products. You
334             create a page with several articles (celini) in it. These celini will be the
335             pages for the products. You prepare a YAML file with products. Each product
336             C property must match exactly the celina C and C
337             on wich this product will be placed. See C
338             and C for examples. See
339             L on how to add and update products.
340              
341             =head2 Products template
342              
343             A template for displaying products within a C. You can modify this
344             template as you wish to display other types of products - not just books as it
345             is now. See C inlined in this file's C<__DATA__>
346             section. It of course can be inflated using
347             L. The template produces the HTML from the
348             products table, including the button mentioned above already.
349              
350             # Add the template form Prodan
351             bin/slovo inflate --class Slovo::Plugin::Prodan \
352             -t --path domove/xn--b1arjbl.xn--90ae/templates/themes/malka
353              
354              
355             =head2 GDPR and Cookies consent
356              
357             A GDPR and cookies consent alert in the footer which upon click leads to the
358             page (celina) where all conditions on using the site can be described. When the
359             user clicks on the link to the I page a flag in C is put
360             so the alert is not shown any more. This flag disappears if the user clears any
361             site data and the alert will appear again if the user vists the site again.
362             The Consent celina is created automatically in the localhost domain as an
363             example. Search for C in the source of this module to see how it
364             is implemented.
365              
366             =head2 TODO some day
367              
368             =over 1
369              
370             =item Invoices - generate an invoice in PDF via headless LibreOffice instance
371             on your server.
372              
373             =item Merchants - a merchants SQL table with Open API to manage and
374             automatically populate invoices.
375              
376             =item Other "Pay on Delivery" providers. Feel free to contibute yours.
377              
378             =item Other types of Payments and/or Online Payment Providers like online POS
379             Terminals etc.
380              
381             =back
382              
383             =head1 METHODS
384              
385             The usual method is implemented.
386              
387             =head2 register
388              
389             Prepends the class to renderer and static classes. Adds some REST API routes,
390             configures the deliverer.
391              
392             =head1 EMBEDDED FILES
393              
394             @@ css/cart.css
395             @@ js/cart.js
396             @@ img/arrow-collapse-all.svg
397             @@ img/cart-arrow-right.svg
398             @@ img/cart.svg
399             @@ img/cart-check.svg
400             @@ img/cart-off.svg
401             @@ img/cart-minus.svg
402             @@ img/cart-plus-white.svg
403             @@ img/cart-plus.svg
404             @@ img/cart-remove.svg
405             @@ img/econt.svg
406             @@ partials/_footer_right.html.ep
407             @@ partials/_gdpr_consent.html.ep
408             @@ partials/_kniga.html.ep
409             @@ resources/data/prodan_migrations.sql
410              
411             =head1 SEE ALSO
412              
413             L,
414             L,
415             L,
416             L,
417             L,
418             L,
419             L
420              
421             =head1 AUTHOR
422              
423             Красимир Беров
424             CPAN ID: BEROV
425             berov на cpan точка org
426             http://слово.бг
427              
428             =head1 CONTRIBUTORS
429              
430             Ordered by time of first commit.
431              
432             =over
433              
434             =item * Your Name
435              
436             =item * Someone Else
437              
438             =item * Another Contributor
439              
440             =back
441              
442             =head1 COPYRIGHT
443              
444             This is free software, licensed under:
445              
446             The Artistic License 2.0 (GPL Compatible)
447              
448             The full text of the license can be found in the
449             LICENSE file included with this module.
450              
451             This distribution contains icons from L and
452             may contain other free software which belongs to their respective authors.
453              
454             =cut
455              
456             __DATA__