File Coverage

blib/lib/Slovo/Plugin/Prodan.pm
Criterion Covered Total %
statement 78 78 100.0
branch 1 2 50.0
condition 7 14 50.0
subroutine 11 11 100.0
pod 1 1 100.0
total 98 106 92.4


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