File Coverage

blib/lib/Net/API/Stripe.pm
Criterion Covered Total %
statement 110 2335 4.7
branch 0 1272 0.0
condition 0 953 0.0
subroutine 38 307 12.3
pod 92 218 42.2
total 240 5085 4.7


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             ##----------------------------------------------------------------------------
3             ## Stripe API - ~/lib/Net/API/Stripe.pm
4             ## Version v1.0.8
5             ## Copyright(c) 2020 DEGUEST Pte. Ltd.
6             ## Author: Jacques Deguest <@sitael.tokyo.deguest.jp>
7             ## Created 2018/07/19
8             ## Modified 2020/05/28
9             ##
10             ##----------------------------------------------------------------------------
11             package Net::API::Stripe;
12             BEGIN
13             {
14 1     1   105930 use strict;
  1         3  
  1         32  
15 1     1   525 use common::sense;
  1         14  
  1         4  
16 1     1   572 use parent qw( Module::Generic );
  1         293  
  1         6  
17 1     1   15754965 use Encode ();
  1         3  
  1         15  
18 1     1   406 use IO::File;
  1         5354  
  1         102  
19 1     1   394 use Data::UUID;
  1         560  
  1         51  
20 1     1   436 use Net::OAuth;
  1         472  
  1         30  
21 1     1   419 use Crypt::OpenSSL::RSA;
  1         3690  
  1         34  
22 1     1   6 use Digest::MD5 qw( md5_base64 );
  1         2  
  1         47  
23 1     1   454 use Data::Random qw( rand_chars );
  1         8981  
  1         53  
24 1     1   448 use HTTP::Cookies;
  1         6611  
  1         28  
25 1     1   377 use HTTP::Request;
  1         12463  
  1         30  
26 1     1   568 use LWP::UserAgent;
  1         13742  
  1         29  
27 1     1   414 use MIME::QuotedPrint ();
  1         1142  
  1         23  
28 1     1   6 use MIME::Base64 ();
  1         2  
  1         14  
29 1     1   403 use LWP::MediaTypes ();
  1         12898  
  1         27  
30 1     1   5 use JSON;
  1         2  
  1         7  
31 1     1   120 use Scalar::Util ();
  1         1  
  1         17  
32 1     1   4 use Data::Dumper;
  1         2  
  1         39  
33 1     1   424 use URI::Query;
  1         1497  
  1         44  
34 1     1   6 use URI::Escape;
  1         2  
  1         39  
35 1     1   5 use File::Basename;
  1         3  
  1         70  
36 1     1   8 use File::Spec;
  1         1  
  1         21  
37 1     1   3 use Cwd ();
  1         2  
  1         43  
38 1     1   771 use DateTime;
  1         381300  
  1         49  
39 1     1   655 use DateTime::Format::Strptime;
  1         46162  
  1         5  
40 1     1   72 use Nice::Try;
  1         2  
  1         27  
41 1     1   8930737 use Want;
  1         3  
  1         106  
42 1     1   695 use Digest::SHA ();
  1         2086  
  1         26  
43 1     1   613 use Net::IP;
  1         38422  
  1         143  
44 1     1   554 use Devel::Confess;
  1         5473  
  1         4  
45 1     1   58 use constant API_BASE => 'https://api.stripe.com/v1';
  1         1  
  1         104  
46 1     1   7 use constant STRIPE_WEBHOOK_SOURCE_IP => [qw( 54.187.174.169 54.187.205.235 54.187.216.72 54.241.31.99 54.241.31.102 54.241.34.107 )];
  1         2  
  1         72  
47 1     1   10978 our $VERSION = 'v1.0.8';
48             };
49              
50             {
51             our $VERBOSE = 0;
52             our $DEBUG = 0;
53             our $BROWSER = 'Net::API::Stripe/' . $VERSION;
54            
55             our $ERROR_CODE_TO_STRING =
56             {
57             400 => "The request was unacceptable, due to a missing required parameter.",
58             401 => "No valid API key provided.",
59             402 => "The parameters were valid but the request failed.",
60             403 => "The API key doesn't have permissions to perform the request.",
61             404 => "The requested resource doesn't exist.",
62             409 => "The request conflicts with another request.",
63             429 => "Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.",
64             500 => "Something went wrong on Stripe's end.",
65             502 => "Something went wrong on Stripe's end.",
66             503 => "Something went wrong on Stripe's end.",
67             504 => "Something went wrong on Stripe's end.",
68             ## Payout: https://stripe.com/docs/api/payouts/failures
69             account_closed => "The bank account has been closed.",
70             ## Payout
71             account_frozen => "The bank account has been frozen.",
72             amount_too_large => "The specified amount is greater than the maximum amount allowed. Use a lower amount and try again.",
73             amount_too_small => "The specified amount is less than the minimum amount allowed. Use a higher amount and try again.",
74             api_connection_error => "Failure to connect to Stripe's API.",
75             api_error => "Striipe API error",
76             api_key_expired => "The API key provided has expired",
77             authentication_error => "Failure to properly authenticate yourself in the request.",
78             balance_insufficient => "The transfer or payout could not be completed because the associated account does not have a sufficient balance available.",
79             bank_account_exists => "The bank account provided already exists on the specified Customer object. If the bank account should also be attached to a different customer, include the correct customer ID when making the request again.",
80             ## Payout: https://stripe.com/docs/api/payouts/failures
81             bank_account_restricted => "The bank account has restrictions on either the type, or the number, of payouts allowed. This normally indicates that the bank account is a savings or other non-checking account.",
82             bank_account_unusable => "The bank account provided cannot be used for payouts. A different bank account must be used.",
83             bank_account_unverified => "Your Connect platform is attempting to share an unverified bank account with a connected account.",
84             ## Payout
85             bank_ownership_changed => "The destination bank account is no longer valid because its branch has changed ownership.",
86             card_declined => "The card has been declined.",
87             card_error => "Card error",
88             charge_already_captured => "The charge you’re attempting to refund has already been refunded.",
89             ## Payout
90             could_not_process => "The bank could not process this payout.",
91             ## Payout
92             debit_not_authorized => "Debit transactions are not approved on the bank account. (Stripe requires bank accounts to be set up for both credit and debit payouts.)",
93             ## Payout
94             declined => "The bank has declined this transfer. Please contact the bank before retrying.",
95             email_invalid => "The email address is invalid.",
96             expired_card => "The card has expired. Check the expiration date or use a different card.",
97             idempotency_error => "Idempotency error",
98             ## Payout
99             incorrect_account_holder_name => "Your bank notified us that the bank account holder name on file is incorrect.",
100             incorrect_cvc => "The card’s security code is incorrect. Check the card’s security code or use a different card.",
101             incorrect_number => "The card number is incorrect. Check the card’s number or use a different card.",
102             incorrect_zip => "The card’s postal code is incorrect. Check the card’s postal code or use a different card.",
103             instant_payouts_unsupported => "The debit card provided as an external account does not support instant payouts. Provide another debit card or use a bank account instead.",
104             ## Payout
105             insufficient_funds => "Your Stripe account has insufficient funds to cover the payout.",
106             ## Payout
107             invalid_account_number => "The routing number seems correct, but the account number is invalid.",
108             invalid_card_type => "The card provided as an external account is not a debit card. Provide a debit card or use a bank account instead.",
109             invalid_charge_amount => "The specified amount is invalid. The charge amount must be a positive integer in the smallest currency unit, and not exceed the minimum or maximum amount.",
110             ## Payout
111             invalid_currency => "The bank was unable to process this payout because of its currency. This is probably because the bank account cannot accept payments in that currency.",
112             invalid_cvc => "The card’s security code is invalid. Check the card’s security code or use a different card.",
113             invalid_expiry_month => "The card’s expiration month is incorrect. Check the expiration date or use a different card.",
114             invalid_expiry_year => "The card’s expiration year is incorrect. Check the expiration date or use a different card.",
115             invalid_number => "The card number is invalid. Check the card details or use a different card.",
116             invalid_request_error => "Invalid request error. Request has invalid parameters.",
117             livemode_mismatch => "Test and live mode API keys, requests, and objects are only available within the mode they are in.",
118             missing => "Both a customer and source ID have been provided, but the source has not been saved to the customer. ",
119             ## Payout
120             no_account => "The bank account details on file are probably incorrect. No bank account could be located with those details.",
121             parameter_invalid_empty => "One or more required values were not provided. Make sure requests include all required parameters.",
122             parameter_invalid_integer => "One or more of the parameters requires an integer, but the values provided were a different type. Make sure that only supported values are provided for each attribute.",
123             parameter_invalid_string_blank => "One or more values provided only included whitespace. Check the values in your request and update any that contain only whitespace.",
124             parameter_invalid_string_empty => "One or more required string values is empty. Make sure that string values contain at least one character.",
125             parameter_missing => "One or more required values are missing.",
126             parameter_unknown => "The request contains one or more unexpected parameters. Remove these and try again.",
127             payment_method_unactivated => "The charge cannot be created as the payment method used has not been activated.",
128             payouts_not_allowed => "Payouts have been disabled on the connected account.",
129             platform_api_key_expired => "The API key provided by your Connect platform has expired.",
130             postal_code_invalid => "The postal code provided was incorrect.",
131             processing_error => "An error occurred while processing the card. Check the card details are correct or use a different card.",
132             rate_limit => "Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.",
133             rate_limit_error => "Too many requests hit the API too quickly.",
134             testmode_charges_only => "This account has not been activated and can only make test charges.",
135             tls_version_unsupported => "Your integration is using an older version of TLS that is unsupported. You must be using TLS 1.2 or above.",
136             token_already_used => "The token provided has already been used. You must create a new token before you can retry this request.",
137             transfers_not_allowed => "The requested transfer cannot be created. Contact us if you are receiving this error.",
138             ## Payout
139             unsupported_card => "The bank no longer supports payouts to this card.",
140             upstream_order_creation_failed => "The order could not be created. Check the order details and then try again.",
141             url_invalid => "The URL provided is invalid.",
142             validation_error => "Stripe client-side library error: improper field validation",
143             };
144            
145             our $TYPE2CLASS =
146             {
147             account => 'Net::API::Stripe::Connect::Account',
148             ach_credit_transfer => 'Net::API::Stripe::Payment::Source::ACHCreditTransfer',
149             ach_debit => 'Net::API::Stripe::Payment::Source::ACHDebit',
150             account_link => 'Net::API::Stripe::Connect::Account::Link',
151             additional_document => 'Net::API::Stripe::Connect::Account::Document',
152             address => 'Net::API::Stripe::Address',
153             address_kana => 'Net::API::Stripe::Address',
154             address_kanji => 'Net::API::Stripe::Address',
155             application_fee => 'Net::API::Stripe::Connect::ApplicationFee',
156             authorization_controls => 'Net::API::Stripe::Issuing::Card::AuthorizationsControl',
157             balance => 'Net::API::Stripe::Balance',
158             balance_transaction => 'Net::API::Stripe::Balance::Transaction',
159             bank_account => 'Net::API::Stripe::Connect::ExternalAccount::Bank',
160             billing => 'Net::API::Stripe::Billing::Details',
161             billing_address => 'Net::API::Stripe::Address',
162             billing_details => 'Net::API::Stripe::Billing::Details',
163             billing_thresholds => 'Net::API::Stripe::Billing::Thresholds',
164             bitcoin_transaction => 'Net::API::Stripe::Bitcoin::Transaction',
165             branding => 'Net::API::Stripe::Connect::Account::Branding',
166             business_profile => 'Net::API::Stripe::Connect::Business::Profile',
167             capability => 'Net::API::Stripe::Connect::Account::Capability',
168             card => 'Net::API::Stripe::Connect::ExternalAccount::Card',
169             card_payments => 'Net::API::Stripe::Connect::Account::Settings::CardPayments',
170             cardholder => 'Net::API::Stripe::Issuing::Card::Holder',
171             charge => 'Net::API::Stripe::Charge',
172             charges => 'Net::API::Stripe::List',
173             'checkout.session' => 'Net::API::Stripe::Checkout::Session',
174             code_verification => 'Net::API::Stripe::Payment::Source::CodeVerification',
175             company => 'Net::API::Stripe::Connect::Account::Company',
176             country_spec => 'Net::API::Stripe::Connect::CountrySpec',
177             coupon => 'Net::API::Stripe::Billing::Coupon',
178             credit_note => 'Net::API::Stripe::Billing::CreditNote',
179             credit_noteline_item => 'Net::API::Stripe::Billing::CreditNote::LineItem',
180             customer => 'Net::API::Stripe::Customer',
181             customer_address => 'Net::API::Stripe::Address',
182             customer_balance_transaction => 'Net::API::Stripe::Customer::BalanceTransaction',
183             customer_shipping => 'Net::API::Stripe::Shipping',
184             dashboard => 'Net::API::Stripe::Connect::Account::Settings::Dashboard',
185             data => 'Net::API::Stripe::Event::Data',
186             discount => 'Net::API::Stripe::Billing::Discount',
187             dispute => 'Net::API::Stripe::Dispute',
188             dispute_evidence => 'Net::API::Stripe::Dispute::Evidence',
189             document => 'Net::API::Stripe::Connect::Account::Document',
190             error => 'Net::API::Stripe::Error',
191             event => 'Net::API::Stripe::Event',
192             evidence => 'Net::API::Stripe::Issuing::Dispute::Evidence',
193             evidence_details => 'Net::API::Stripe::Dispute::EvidenceDetails',
194             external_accounts => 'Net::API::Stripe::List',
195             fee_refund => 'Net::API::Stripe::Connect::ApplicationFee::Refund',
196             file => 'Net::API::Stripe::File',
197             file_link => 'Net::API::Stripe::File::Link',
198             fraudulent => 'Net::API::Stripe::Issuing::Dispute::Evidence::Fraudulent',
199             generated_from => 'Net::API::Stripe::Payment::GeneratedFrom',
200             individual => 'Net::API::Stripe::Connect::Person',
201             inventory => 'Net::API::Stripe::Order::SKU::Inventory',
202             invoice => 'Net::API::Stripe::Billing::Invoice',
203             invoice_customer_balance_settings => 'Net::API::Stripe::Billing::Invoice::BalanceSettings',
204             invoice_settings => 'Net::API::Stripe::Billing::Invoice::Settings',
205             invoiceitem => 'Net::API::Stripe::Billing::Invoice::Item',
206             ip_address_location => 'Net::API::Stripe::GeoLocation',
207             'issuing.authorization' => 'Net::API::Stripe::Issuing::Authorization',
208             'issuing.card' => 'Net::API::Stripe::Issuing::Card',
209             'issuing.cardholder' => 'Net::API::Stripe::Issuing::Card::Holder',
210             'issuing.dispute' => 'Net::API::Stripe::Issuing::Dispute',
211             'issuing.transaction' => 'Net::API::Stripe::Issuing::Transaction',
212             items => 'Net::API::Stripe::List',
213             last_payment_error => 'Net::API::Stripe::Error',
214             last_setup_error => 'Net::API::Stripe::Error',
215             line_item => 'Net::API::Stripe::Billing::Invoice::LineItem',
216             list => 'Net::API::Stripe::List',
217             list_items => 'Net::API::Stripe::List',
218             lines => 'Net::API::Stripe::List',
219             links => 'Net::API::Stripe::List',
220             login_link => 'Net::API::Stripe::Connect::Account::LoginLink',
221             mandate => 'Net::API::Stripe::Mandate',
222             merchant_data => 'Net::API::Stripe::Issuing::MerchantData',
223             next_action => 'Net::API::Stripe::Payment::Intent::NextAction',
224             order => 'Net::API::Stripe::Order',
225             order_item => 'Net::API::Stripe::Order::Item',
226             order_return => 'Net::API::Stripe::Order::Return',
227             other => 'Net::API::Stripe::Issuing::Dispute::Evidence::Other',
228             outcome => 'Net::API::Stripe::Charge::Outcome',
229             owner => 'Net::API::Stripe::Payment::Source::Owner',
230             package_dimensions => 'Net::API::Stripe::Order::SKU::PackageDimensions',
231             payment_intent => 'Net::API::Stripe::Payment::Intent',
232             payment_method => 'Net::API::Stripe::Payment::Method',
233             payment_method_details => 'Net::API::Stripe::Payment::Method::Details',
234             payments => 'Net::API::Stripe::Connect::Account::Settings::Payments',
235             payout => 'Net::API::Stripe::Payout',
236             payouts => 'Net::API::Stripe::Connect::Account::Settings::Payouts',
237             pending_invoice_item_interval => 'Net::API::Stripe::Billing::Plan',
238             period => 'Net::API::Stripe::Billing::Invoice::Period',
239             person => 'Net::API::Stripe::Connect::Person',
240             plan => 'Net::API::Stripe::Billing::Plan',
241             # plan => 'Net::API::Stripe::Payment::Plan',
242             product => 'Net::API::Stripe::Product',
243             'radar.early_fraud_warning' => 'Net::API::Stripe::Fraud',
244             'radar.value_list' => 'Net::API::Stripe::Fraud::ValueList',
245             'radar.value_list_item' => 'Net::API::Stripe::Fraud::ValueList::Item',
246             receiver => 'Net::API::Stripe::Payment::Source::Receiver',
247             redirect => 'Net::API::Stripe::Payment::Source::Redirect',
248             refund => 'Net::API::Stripe::Refund',
249             refunds => 'Net::API::Stripe::Charge::Refunds',
250             relationship => 'Net::API::Stripe::Connect::Account::Relationship',
251             'reporting.report_run' => 'Net::API::Stripe::Reporting::ReportRun',
252             request => 'Net::API::Stripe::Event::Request',
253             requirements => 'Net::API::Stripe::Connect::Account::Requirements',
254             ## Used in Net::API::Stripe::Reporting::ReportRun
255             result => 'Net::API::Stripe::File',
256             returns => 'Net::API::Stripe::Order::Returns',
257             reversals => 'Net::API::Stripe::Connect::Transfer::Reversals',
258             review => 'Net::API::Stripe::Fraud::Review',
259             review_session => 'Net::API::Stripe::Fraud::Review::Session',
260             scheduled_query_run => 'Net::API::Stripe::Sigma::ScheduledQueryRun',
261             settings => 'Net::API::Stripe::Connect::Account::Settings',
262             setup_intent => 'Net::API::Stripe::Payment::Intent::Setup',
263             shipping => 'Net::API::Stripe::Shipping',
264             shipping_address => 'Net::API::Stripe::Address',
265             sku => 'Net::API::Stripe::Order::SKU',
266             source => 'Net::API::Stripe::Payment::Source',
267             source_order => 'Net::API::Stripe::Order',
268             sources => 'Net::API::Stripe::Customer::Sources',
269             status_transitions => 'Net::API::Stripe::Billing::Invoice::StatusTransition',
270             subscription => 'Net::API::Stripe::Billing::Subscription',
271             subscriptions => 'Net::API::Stripe::List',
272             subscription_item => 'Net::API::Stripe::Billing::Subscription::Item',
273             subscription_schedule => 'Net::API::Stripe::Billing::Subscription::Schedule',
274             support_address => 'Net::API::Stripe::Address',
275             tax_id => 'Net::API::Stripe::Customer::TaxId',
276             tax_ids => 'Net::API::Stripe::Customer::TaxIds',
277             tax_info => 'Net::API::Stripe::Customer::TaxInfo',
278             tax_info_verification => 'Net::API::Stripe::Customer::TaxInfoVerification',
279             tax_rate => 'Net::API::Stripe::Tax::Rate',
280             'terminal.connection_token' => 'Net::API::Stripe::Terminal::ConnectionToken',
281             'terminal.location' => 'Net::API::Stripe::Terminal::Location',
282             'terminal.reader' => 'Net::API::Stripe::Terminal::Reader',
283             token => 'Net::API::Stripe::Token',
284             topup => 'Net::API::Stripe::Connect::TopUp',
285             transactions => 'Net::API::Stripe::List',
286             transfer => 'Net::API::Stripe::Connect::Transfer',
287             transfer_data => 'Net::API::Stripe::Payment::Intent::TransferData',
288             transfer_reversal => 'Net::API::Stripe::Connect::Transfer::Reversal',
289             threshold_reason => 'Net::API::Stripe::Billing::Thresholds',
290             tos_acceptance => 'Net::API::Stripe::Connect::Account::TosAcceptance',
291             transform_usage => 'Net::API::Stripe::Billing::Plan::TransformUsage',
292             usage_record => 'Net::API::Stripe::Billing::UsageRecord',
293             verification => 'Net::API::Stripe::Connect::Account::Verification',
294             verification_data => 'Net::API::Stripe::Issuing::Authorization::VerificationData',
295             verification_fields => 'Net::API::Stripe::Connect::CountrySpec::VerificationFields',
296             verified_address => 'Net::API::Stripe::Address',
297             webhook_endpoint => 'Net::API::Stripe::WebHook::Object',
298             };
299              
300             our $EXPANDABLES_BY_CLASS =
301             {
302             ## Nothing
303             account => {},
304             account_link => {},
305             application_fee =>
306             {
307             account => 'account',
308             application => 'account',
309             balance_transaction => 'balance_transaction',
310             charge => 'charge',
311             ## Actually either a charge or a transfer
312             originating_transaction => 'charge',
313             },
314             ## Nothing
315             balance => {},
316             balance_transaction =>
317             {
318             source => 'source',
319             },
320             bank_account =>
321             {
322             account => 'account',
323             customer => 'customer',
324             },
325             capability =>
326             {
327             account => 'account',
328             },
329             card =>
330             {
331             account => 'account',
332             customer => 'customer',
333             recipient => 'account',
334             },
335             charge =>
336             {
337             application => 'account',
338             balance_transaction => 'balance_transaction',
339             customer => 'customer',
340             dispute => 'dispute',
341             invoice => 'invoice',
342             on_behalf_of => 'acount',
343             order => 'order',
344             review => 'review',
345             source_transfer => 'transfer',
346             transfer => 'transfer',
347             },
348             'checkout.session' =>
349             {
350             customer => 'customer',
351             payment_intent => 'payment_intent',
352             setup_intent => 'setup_intent',
353             subscription => 'subscription',
354             },
355             country_spec => {},
356             coupon => {},
357             credit_note =>
358             {
359             customer => 'customer',
360             customer_balance_transaction => 'customer_balance_transaction',
361             invoice => 'invoice',
362             refund => 'refund',
363             },
364             customer =>
365             {
366             default_source => 'source',
367             'invoice_settings.default_payment_method' => 'payment_method',
368             },
369             customer_balance_transaction =>
370             {
371             credit_note => 'credit_note',
372             customer => 'customer',
373             invoice => 'invoice',
374             },
375             discount =>
376             {
377             customer => 'customer',
378             },
379             dispute =>
380             {
381             charge => 'charge',
382             # "This property cannot be expanded (latest_invoice.charge.dispute.disputed_transaction)"
383             # disputed_transaction => 'balance_transaction',
384             },
385             event => {},
386             fee_refund =>
387             {
388             fee => 'application_fee',
389             balance_transaction => 'balance_transaction',
390             },
391             file => {},
392             file_link =>
393             {
394             file => 'file',
395             },
396             invoice =>
397             {
398             charge => 'charge',
399             customer => 'customer',
400             default_payment_method => 'payment_method',
401             default_source => 'source',
402             payment_intent => 'payment_intent',
403             subscription => 'subscription',
404             },
405             invoiceitem =>
406             {
407             customer => 'customer',
408             invoice => 'invoice',
409             subscription => 'subscription',
410             },
411             'issuing.authorization' =>
412             {
413             cardholder => 'issuing.cardholder',
414             },
415             'issuing.card' =>
416             {
417             replacement_for => 'issuing.card',
418             },
419             'issuing.cardholder' => {},
420             'issuing.dispute' =>
421             {
422             disputed_transaction => 'issuing.transaction',
423             },
424             'issuing.transaction' =>
425             {
426             authorization => 'issuing.authorization',
427             balance_transaction => 'balance_transaction',
428             card => 'issuing.card',
429             cardholder => 'issuing.cardholder',
430             dispute => 'issuing.dispute',
431             },
432             mandate =>
433             {
434             payment_method => 'payment_method',
435             },
436             order =>
437             {
438             charge => 'charge',
439             customer => 'customer',
440             },
441             order_item =>
442             {
443             ## Can be either parent or sku actually
444             parent => 'discount',
445             },
446             order_return =>
447             {
448             order => 'order',
449             refund => 'refund',
450             },
451             payment_intent =>
452             {
453             application => 'account',
454             customer => 'customer',
455             invoice => 'invoice',
456             on_behalf_of => 'account',
457             payment_method => 'payment_method',
458             review => 'review',
459             },
460             payment_method =>
461             {
462             customer => 'customer',
463             },
464             payout =>
465             {
466             balance_transaction => 'balance_transaction',
467             destination => 'account',
468             failure_balance_transaction => 'balance_transaction',
469             },
470             person => {},
471             plan =>
472             {
473             product => 'product',
474             },
475             product => {},
476             'radar.early_fraud_warning' =>
477             {
478             charge => 'charge',
479             },
480             'radar.value_list' => {},
481             'radar.value_list_item' => {},
482             refund =>
483             {
484             balance_transaction => 'balance_transaction',
485             charge => 'charge',
486             failure_balance_transaction => 'balance_transaction',
487             payment_intent => 'payment_intent',
488             source_transfer_reversal => 'transfer_reversal',
489             transfer_reversal => 'transfer_reversal',
490             },
491             'reporting.report_run' => {},
492             'reporting.report_type' => {},
493             review =>
494             {
495             charge => 'charge',
496             payment_intent => 'payment_intent',
497             },
498             schedule =>
499             {
500             customer => 'customer',
501             subscription => 'subscription',
502             },
503             scheduled_query_run => {},
504             setup_intent =>
505             {
506             customer => 'customer',
507             payment_method => 'payment_method',
508             application => 'account',
509             mandate => 'mandate',
510             on_behalf_of => 'account',
511             single_use_mandate => 'mandate',
512             },
513             sku =>
514             {
515             product => 'product',
516             },
517             source => {},
518             subscription =>
519             {
520             customer => 'customer',
521             default_payment_method => 'payment_method',
522             default_source => 'source',
523             latest_invoice => 'invoice',
524             pending_setup_intent => 'setup_intent',
525             schedule => 'schedule',
526             },
527             subscription_item => {},
528             subscription_schedule =>
529             {
530             customer => 'customer',
531             subscription => 'subscription',
532             },
533             tax_id =>
534             {
535             customer => 'customer',
536             },
537             tax_rate => {},
538             'terminal.connection_token' => {},
539             'terminal.location' => {},
540             'terminal.reader' => {},
541             token => {},
542             topup =>
543             {
544             balance_transaction => 'balance_transaction',
545             },
546             transfer =>
547             {
548             destination => 'account',
549             balance_transaction => 'balance_transaction',
550             ## Clueless. It is said to be a payment object (py_GmRo7h8TKguoNX), but cannot find the api documentation for it
551             destination_payment => '',
552             ## charge or payment
553             source_transaction => 'charge',
554             },
555             transfer_reversal =>
556             {
557             balance_transaction => 'balance_transaction',
558             destination_payment_refund => 'refund',
559             source_refund => 'refund',
560             transfer => 'transfer',
561             },
562             usage_record => {},
563             webhook_endpoint => {},
564             };
565            
566             ## As per Stripe documentation: https://stripe.com/docs/api/expanding_objects
567             our $EXPAND_MAX_DEPTH = 4;
568            
569             local $get_expandables = sub
570             {
571             my $class = shift( @_ ) || return;
572             my $pref = shift( @_ );
573             my $depth = shift( @_ ) || 0;
574             ## print( "." x $depth, "Checking class \"$class\" with prefix \"$pref\" and depth $depth\n" );
575             return if( $depth > $EXPAND_MAX_DEPTH );
576             return if( !CORE::exists( $EXPANDABLES_BY_CLASS->{ $class } ) );
577             my $ref = $EXPANDABLES_BY_CLASS->{ $class };
578             my $list = [];
579             CORE::push( @$list, $pref ) if( CORE::length( $pref ) );
580             foreach my $prop ( sort( keys( %$ref ) ) )
581             {
582             my $target_class = $ref->{ $prop };
583             my $new_prefix = CORE::length( $pref ) ? "${pref}.${prop}" : $prop;
584             my $this_path = [split(/\./, $new_prefix)];
585             my $this_depth = scalar( @$this_path );
586             my $res = $get_expandables->( $target_class, $new_prefix, $this_depth );
587             CORE::push( @$list, @$res ) if( ref( $res ) && scalar( @$res ) );
588             }
589             return( $list );
590             };
591            
592             our $EXPANDABLES = {};
593             if( !scalar( keys( %$EXPANDABLES ) ) )
594             {
595             foreach my $prop ( sort( keys( %$EXPANDABLES_BY_CLASS ) ) )
596             {
597             if( !scalar( keys( %{$EXPANDABLES_BY_CLASS->{ $prop }} ) ) )
598             {
599             $EXPANDABLES->{ $prop } = [];
600             next;
601             }
602             my $res = $get_expandables->( $prop, '', 0 );
603             $EXPANDABLES->{ $prop } = $res if( ref( $res ) && scalar( @$res ) );
604             }
605             }
606             ## print( Data::Dumper::Dumper( $EXPANDABLES ), "\n" ); exit;
607             }
608              
609             sub init
610             {
611 0     0 1   my $self = shift( @_ );
612             # $self->{token} = '' unless( length( $self->{token} ) );
613 0 0         $self->{amount} = '' unless( length( $self->{amount} ) );
614 0   0       $self->{currency} ||= 'jpy';
615 0 0         $self->{description} = '' unless( length( $self->{description} ) );
616 0 0         $self->{card} = '' unless( length( $self->{card} ) );
617 0 0         $self->{version} = '' unless( length( $self->{version} ) );
618 0 0         $self->{key} = '' unless( length( $self->{key} ) );
619 0 0         $self->{cookie_file} = '' unless( length( $self->{cookie_file} ) );
620 0 0         $self->{browser} = $BROWSER unless( length( $self->{browser} ) );
621 0 0         $self->{encode_with_json} = 0 unless( length( $self->{encode_with_json} ) );
622 0 0         $self->{api_uri} = URI->new( API_BASE ) unless( length( $self->{api_uri} ) );
623             ## Ask Module::Generic to check if corresponding method exists for each parameter submitted,
624             ## and if so, use it to set the value of the key in hash parameters
625 0           $self->{_init_strict_use_sub} = 1;
626 0 0         $self->{temp_dir} = File::Spec->tmpdir unless( length( $self->{temp_dir} ) );
627             ## Blank on purpose, which means it was not set. If it has a value like 0 or 1, the user has set it and it takes precedence.
628 0           $self->{livemode} = '';
629 0 0         $self->{ignore_unknown_parameters} = '' unless( length( $self->{ignore_unknown_parameters} ) );
630 0 0         $self->{expand} = '' unless( length( $self->{expand} ) );
631             ## Json configuration file
632 0           $self->{conf_file} = '';
633 0           $self->{conf_data} = {};
634 0           $self->SUPER::init( @_ );
635 0           $self->message( 3, "Config file is $self->{conf_file}" );
636 0 0         if( $self->{conf_file} )
637             {
638 0           my $json = $self->{conf_data};
639 0     0     $self->message( 3, "config file parameters are: ", sub{ $self->dumper( $json ) } );
  0            
640 0 0 0       $self->{livemode} = $json->{livemode} if( CORE::length( $json->{livemode} ) && !CORE::length( $self->{livemode} ) );
641 0 0         if( !$self->{key} )
642             {
643 0 0         $self->{key} = $self->{livemode} ? $json->{live_secret_key} : $json->{test_secret_key};
644             }
645 0           for( qw( browser cookie_file temp_dir version ) )
646             {
647 0 0 0       $self->{ $_ } = $json->{ $_ } if( !$self->{ $_ } && length( $json->{ $_ } ) );
648             }
649             }
650 0           $self->{stripe_error} = '';
651 0           $self->{http_response} = '';
652 0           $self->{http_request} = '';
653 0 0         return( $self->error( "No Stripe API private key was provided!" ) ) if( !$self->{key} );
654 0 0         return( $self->error( "No Stripe api version was specified. I was expecting something like ''." ) ) if( !$self->{version} );
655 0           $self->key( $self->{key} );
656 0 0         $self->livemode( $self->{key} =~ /_live/ ? 1 : 0 );
657 0           return( $self );
658             }
659              
660 0     0 1   sub account { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Account', @_ ) ); }
661              
662 0     0 1   sub account_link { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Account::Link', @_ ) ); }
663              
664 0     0 1   sub address { return( shift->_response_to_object( 'Net::API::Stripe::Address', @_ ) ); }
665              
666 0     0 1   sub amount { return( shift->_set_get_number( 'amount', @_ ) ); }
667              
668             sub api_uri
669             {
670 0     0 1   my $self = shift( @_ );
671 0 0         if( @_ )
672             {
673 0           my $url = shift( @_ );
674 0           try
675 0     0     {
676 0           $self->{api_uri} = URI->new( $url );
677             }
678 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
679 0     0     {
680 0           return( $self->error( "Ba URI ($url) provided for base Stripe api: $e" ) );
681 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
682             }
683 0 0 0       return( $self->{api_uri}->clone ) if( Scalar::Util::blessed( $self->{api_uri} ) && $self->{api_uri}->isa( 'URI' ) );
684 0           return( $self->{api_uri} );
685             }
686              
687 0     0 1   sub application_fee { return( shift->_response_to_object( 'Net::API::Stripe::Connect::ApplicationFee', @_ ) ); }
688              
689 0     0 1   sub application_fee_refund { return( shift->_response_to_object( 'Net::API::Stripe::Connect::ApplicationFee::Refund', @_ ) ); }
690              
691 0     0 0   sub auth { return( shift->_set_get_scalar( 'auth', @_ ) ); }
692              
693 0     0 1   sub authorization { return( shift->_response_to_object( 'Net::API::Stripe::Issuing::Authorization', @_ ) ); }
694              
695 0     0 1   sub balance { return( shift->_response_to_object( 'Net::API::Stripe::Balance', @_ ) ); }
696              
697             ## Stripe access points in their order on the api documentation
698             sub balances
699             {
700 0     0 0   my $self = shift( @_ );
701 0           my $allowed = [qw( retrieve )];
702 0           my $action = shift( @_ );
703 0   0       my $meth = $self->_get_method( 'balance', $action, $allowed ) || return;
704 0           return( $self->$meth( @_ ) );
705             }
706              
707             ## Retrieves the current account balance, based on the authentication that was used to make the request.
708             sub balance_retrieve
709             {
710 0     0 0   my $self = shift( @_ );
711             ## No argument
712             #my $hash = $self->_get( 'balance' ) || return;
713 0           my $hash = $self->get( 'balance' );
714 0           $self->message( 3, "Received '$hash' in return, calling _response_to_object()" );
715 0           return( $self->_response_to_object( 'Net::API::Stripe::Balance', $hash ) );
716             }
717              
718 0     0 1   sub balance_transaction { return( shift->_response_to_object( 'Net::API::Stripe::Balance::Transaction', @_ ) ); }
719              
720             sub balance_transactions
721             {
722 0     0 0   my $self = shift( @_ );
723 0           my $allowed = [qw( retrieve list )];
724 0           my $action = shift( @_ );
725 0   0       my $meth = $self->_get_method( 'balance_transaction', $action, $allowed ) || return;
726 0           return( $self->$meth( @_ ) );
727             }
728              
729             ## https://stripe.com/docs/api/balance/balance_history?lang=curl
730             sub balance_transaction_list
731             {
732 0     0 0   my $self = shift( @_ );
733 0           my $args = shift( @_ );
734             my $okParams =
735             {
736 0           expandable => { allowed => $EXPANDABLES->{balance_transaction}, data_prefix_is_ok => 1 },
737             'available_on' => qr/^\d+$/,
738             'available_on.gt' => qr/^\d+$/,
739             'available_on.gte' => qr/^\d+$/,
740             'available_on.lt' => qr/^\d+$/,
741             'available_on.lte' => qr/^\d+$/,
742             'created' => qr/^\d+$/,
743             'created.gt' => qr/^\d+$/,
744             'created.gte' => qr/^\d+$/,
745             'created.lt' => qr/^\d+$/,
746             'created.lte' => qr/^\d+$/,
747             'currency' => qr/^[a-zA-Z]{3}$/,
748             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
749             'ending_before' => qr/^\w+$/,
750             'limit' => qr/^\d+$/,
751             ## "For automatic Stripe payouts only, only returns transactions that were payed out on the specified payout ID."
752             'payout' => qr/^\w+$/,
753             'source' => qr/^\w+$/,
754             'starting_after' => qr/^\w+$/,
755             ## "Only returns transactions of the given type"
756             'type' => qr/^(?:charge|refund|adjustment|application_fee|application_fee_refund|transfer|payment|payout|payout_failure|stripe_fee|network_cost)$/,
757             };
758 0           my $err = $self->_check_parameters( $okParams, $args );
759 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
760 0   0       my $hash = $self->get( 'balance_transactions', $args ) || return;
761 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
762             }
763              
764             sub balance_transaction_retrieve
765             {
766 0     0 0   my $self = shift( @_ );
767 0 0         return( $self->error( "No parameters were provided to retrieve balance transaction information." ) ) if( !scalar( @_ ) );
768 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Balance::Transaction', @_ );
769             my $okParams =
770             {
771 0           expandable => { allowed => $EXPANDABLES->{balance_transaction}, data_prefix_is_ok => 1 },
772             id => { re => qr/^\w+$/, required => 1 }
773             };
774 0           my $err = $self->_check_parameters( $okParams, $args );
775 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
776 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No balance transaction id was provided to retrieve its information." ) );
777 0   0       my $hash = $self->get( "balance/history/${id}" ) || return;
778 0 0   0     return( $self->error( "Cannot find property 'object' in response hash reference: ", sub{ $self->dumper( $hash ) } ) ) if( !CORE::exists( $hash->{object} ) );
  0            
779 0   0       my $class = $self->_object_type_to_class( $hash->{object} ) || return;
780 0           return( $self->_response_to_object( $class, $hash ) );
781             }
782              
783 0     0 1   sub bank_account { return( shift->_response_to_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', @_ ) ); }
784              
785             sub bank_accounts
786             {
787 0     0 0   my $self = shift( @_ );
788 0           my $action = shift( @_ );
789 0           my $allowed = [qw( create retrieve update delete list )];
790 0   0       my $meth = $self->_get_method( 'bank_account', $action, $allowed ) || return;
791 0           return( $self->$meth( @_ ) );
792             }
793              
794             sub bank_account_create
795             {
796 0     0 0   my $self = shift( @_ );
797 0 0         return( $self->error( "No parameters were provided to create a bank account" ) ) if( !scalar( @_ ) );
798 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', @_ );
799             my $okParams =
800             {
801             expandable => { allowed => $EXPANDABLES->{bank_account} },
802 0           external_account => {},
803             account => { re => qr/^\w+$/, required => 1 },
804             metadata => { type => 'hash' },
805             default_for_currency => {},
806             };
807 0 0         if( $self->_is_hash( $args->{external_account} ) )
808             {
809             $okParams->{external_account} =
810             {
811 0           type => 'hash',
812             fields => [qw( object! country! currency! account_holder_name account_holder_type routing_number account_number! )],
813             };
814             }
815             else
816             {
817 0           $okParams->{external_account} = { type => 'scalar', re => qr/^\w+$/ };
818             }
819 0           my $id = $args->{account};
820 0           my $err = $self->_check_parameters( $okParams, $args );
821 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
822 0   0       my $hash = $self->post( "accounts/${id}/external_accounts", $args ) || return;
823 0           return( $self->_response_to_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', $hash ) );
824             }
825              
826             sub bank_account_delete
827             {
828 0     0 0   my $self = shift( @_ );
829 0 0         return( $self->error( "No parameters were provided to delete a bank account information." ) ) if( !scalar( @_ ) );
830 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', @_ );
831             my $okParams =
832             {
833             expandable => { allowed => $EXPANDABLES->{coupon} },
834 0           id => { re => qr/^\w+$/, required => 1 },
835             account => { re => qr/^\w+$/, required => 1 },
836             };
837 0           my $err = $self->_check_parameters( $okParams, $args );
838 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
839 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No bank account id was provided to delete its information." ) );
840 0           my $acct = CORE::delete( $args->{account} );
841 0   0       my $hash = $self->delete( "accounts/${acct}/external_accounts/${id}", $args ) || return;
842 0           return( $self->_response_to_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', $hash ) );
843             }
844              
845             sub bank_account_list
846             {
847 0     0 0   my $self = shift( @_ );
848 0           my $args = shift( @_ );
849             my $okParams =
850             {
851             expandable => { allowed => $EXPANDABLES->{coupon} },
852 0           account => { re => qr/^\w+$/, required => 1 },
853             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
854             'ending_before' => qr/^\w+$/,
855             'limit' => qr/^\d+$/,
856             'starting_after' => qr/^\w+$/,
857             };
858 0           my $err = $self->_check_parameters( $okParams, $args );
859 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
860 0 0         if( $args->{expand} )
861             {
862 0 0         $self->_adjust_list_expandables( $args ) || return;
863             }
864 0   0       my $hash = $self->get( 'coupons', $args ) || return;
865 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
866             }
867              
868             sub bank_account_retrieve
869             {
870 0     0 0   my $self = shift( @_ );
871 0 0         return( $self->error( "No parameters were provided to retrieve a bank account information." ) ) if( !scalar( @_ ) );
872 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', @_ );
873             my $okParams =
874             {
875             expandable => { allowed => $EXPANDABLES->{coupon} },
876 0           id => { re => qr/^\w+$/, required => 1 },
877             account => { re => qr/^\w+$/, required => 1 },
878             };
879 0           my $err = $self->_check_parameters( $okParams, $args );
880 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
881 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No bank account id was provided to retrieve its information." ) );
882 0           my $acct = CORE::delete( $args->{account} );
883 0   0       my $hash = $self->get( "accounts/${acct}/external_accounts/${id}", $args ) || return;
884 0           return( $self->_response_to_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', $hash ) );
885             }
886              
887             sub bank_account_update
888             {
889 0     0 0   my $self = shift( @_ );
890 0 0         return( $self->error( "No parameters were provided to update a bank account" ) ) if( !scalar( @_ ) );
891 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', @_ );
892             my $okParams =
893             {
894             expandable => { allowed => $EXPANDABLES->{coupon} },
895 0           id => { re => qr/^\w+$/, required => 1 },
896             account => { re => qr/^\w+$/, required => 1 },
897             account_holder_name => {},
898             account_holder_type => { re => qr/^(company|individual)$/ },
899             default_for_currency => {},
900             ## Return true only if there is an error
901             metadata => { type => 'hash' },
902             };
903             ## We found some errors
904 0           my $err = $self->_check_parameters( $okParams, $args );
905             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
906 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
907 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No bank account id was provided to update coupon's details" ) );
908 0           my $acct = CORE::delete( $args->{account} );
909 0   0       my $hash = $self->post( "accounts/${acct}/external_accounts/${id}", $args ) || return;
910 0           return( $self->_response_to_object( 'Net::API::Stripe::Connect::ExternalAccount::Bank', $hash ) );
911             }
912              
913 0     0 1   sub browser { return( shift->_set_get_scalar( 'browser', @_ ) ); }
914              
915             # sub billing { return( shift->_instantiate( 'billing', 'Net::API::Stripe::Billing' ) ) }
916              
917 0     0 1   sub capability { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Account::Capability', @_ ) ); }
918              
919 0     0 1   sub card_holder { return( shift->_response_to_object( 'Net::API::Stripe::Issuing::Card::Holder', @_ ) ); }
920              
921 0     0 1   sub card { return( shift->_response_to_object( 'Net::API::Stripe::Connect::ExternalAccount::Card', @_ ) ); }
922              
923             sub cards
924             {
925 0     0 0   my $self = shift( @_ );
926 0           my $action = shift( @_ );
927 0           my $allowed = [qw( create retrieve update delete list )];
928 0   0       my $meth = $self->_get_method( 'card', $action, $allowed ) || return;
929 0           return( $self->$meth( @_ ) );
930             }
931              
932             sub card_create
933             {
934 0     0 0   my $self = shift( @_ );
935 0 0         return( $self->error( "No parameters were provided to create card" ) ) if( !scalar( @_ ) );
936 0           my $args = {};
937 0           my $card_fields = [qw( object number exp_month exp_year cvc currency name metadata default_for_currency address_line1 address_line2 address_city address_state address_zip address_country )];
938             my $okParams =
939             {
940             expandable => { allowed => $EXPANDABLES->{card} },
941 0           id => { re => qr/^\w+$/, required => 1 },
942             ## Token
943             source => { type => 'hash', fields => $card_fields, required => 1 },
944             metadata => { type => 'hash' },
945             };
946            
947 0 0 0       if( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0 0        
948             {
949 0           $args = $_[0]->as_hash({ json => 1 });
950 0           $okParams->{_cleanup} = 1;
951             }
952             elsif( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Payment::Card' ) )
953             {
954 0           $args = $_[0]->as_hash({ json => 1 });
955 0           $args->{id} = CORE::delete( $args->{customer} );
956 0           my $ref = {};
957 0           @$ref{ @$card_fields } = @$args{ @$card_fields };
958 0           $args->{source} = $ref;
959 0           $okParams->{_cleanup} = 1;
960             }
961             else
962             {
963 0           $args = $self->_get_args( @_ );
964             }
965            
966 0           my $err = $self->_check_parameters( $okParams, $args );
967 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
968 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to create a card for the customer" ) );
969 0   0       my $hash = $self->post( "customers/${id}/sources", $args ) || return;
970 0 0   0     return( $self->error( "Cannot find property 'object' in response hash reference: ", sub{ $self->dumper( $hash ) } ) ) if( !CORE::exists( $hash->{object} ) );
  0            
971 0   0       my $class = $self->_object_type_to_class( $hash->{object} ) || return;
972             # return( $self->_response_to_object( 'Net::API::Stripe::Payment::Card', $hash ) );
973 0           return( $self->_response_to_object( $class, $hash ) );
974             }
975              
976             sub card_delete
977             {
978 0     0 0   my $self = shift( @_ );
979 0 0         return( $self->error( "No parameters were provided to delete card" ) ) if( !scalar( @_ ) );
980 0           my $args = {};
981 0 0 0       if( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0 0        
982             {
983 0           my $cust = shift( @_ );
984 0 0         return( $self->error( "No customer id was found in this customer object." ) ) if( !$cust->id );
985 0 0         return( $self->error( "No source is set for the credit card to delete for this customer." ) ) if( !$cust->source );
986 0 0         return( $self->error( "No credit card id found for this customer source to delete." ) ) if( !$cust->source->id );
987 0           $args->{id} = $cust->id;
988 0           $args->{card_id} = $cust->source->id;
989 0           $args->{expand} = 'all';
990             }
991             elsif( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Payment::Card' ) )
992             {
993 0           my $card = shift( @_ );
994 0 0         return( $self->error( "No card id was found in this card object." ) ) if( !$card->id );
995 0 0         return( $self->error( "No customer object is set for this card object." ) ) if( !$card->customer );
996 0 0         return( $self->error( "No customer id found in the customer object in this card object." ) ) if( !$card->customer->id );
997 0           $args->{card_id} = $card->id;
998 0           $args->{id} = $card->customer->id;
999 0           $args->{expand} = 'all';
1000             }
1001             else
1002             {
1003 0           $args = $self->_get_args( @_ );
1004             }
1005             my $okParams =
1006             {
1007             expandable => { allowed => $EXPANDABLES->{card} },
1008 0           id => { re => qr/^\w+$/, required => 1 },
1009             card_id => { re => qr/^\w+$/, required => 1 },
1010             };
1011 0           my $err = $self->_check_parameters( $okParams, $args );
1012 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1013 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to delete his/her card" ) );
1014 0   0       my $cardId = CORE::delete( $args->{card_id} ) || return( $self->error( "No card id was provided to delete customer's card" ) );
1015 0   0       my $hash = $self->delete( "customers/${id}/sources/${cardId}", $args ) || return;
1016 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Card', $hash ) );
1017             }
1018              
1019             sub card_list
1020             {
1021 0     0 0   my $self = shift( @_ );
1022 0 0         return( $self->error( "No parameters were provided to list customer's cards." ) ) if( !scalar( @_ ) );
1023 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
1024             my $okParams =
1025             {
1026 0           expandable => { allowed => $EXPANDABLES->{card}, data_prefix_is_ok => 1 },
1027             ending_before => qr/^\w+$/,
1028             id => { re => /^\w+$/, required => 1 },
1029             limit => qr/^\d+$/,
1030             starting_after => qr/^\w+$/,
1031             };
1032 0           my $err = $self->_check_parameters( $okParams, $args );
1033 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1034 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to list his/her cards" ) );
1035 0 0         if( $args->{expand} )
1036             {
1037 0 0         $self->_adjust_list_expandables( $args ) || return;
1038             }
1039 0   0       my $hash = $self->get( "customers/${id}/sources", $args ) || return;
1040 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Card::List', $hash ) );
1041             }
1042              
1043             sub card_retrieve
1044             {
1045 0     0 0   my $self = shift( @_ );
1046 0 0         return( $self->error( "No parameters were provided to retrieve card information." ) ) if( !scalar( @_ ) );
1047             ## my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Card', @_ );
1048 0           my $args = {};
1049             my $okParams =
1050             {
1051             expandable => { allowed => $EXPANDABLES->{card} },
1052 0           id => { re => qr/^\w+$/, required => 1 },
1053             customer => { re => qr/^\w+$/, required => 1 },
1054             };
1055 0 0 0       if( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0 0        
1056             {
1057 0           my $cust = shift( @_ );
1058 0 0         return( $self->error( "No customer id was found in this customer object." ) ) if( !$cust->id );
1059 0 0         return( $self->error( "No source is set for the credit card to delete for this customer." ) ) if( !$cust->source );
1060 0 0         return( $self->error( "No credit card id found for this customer source to delete." ) ) if( !$cust->source->id );
1061 0           $args->{customer} = $cust->id;
1062 0           $args->{id} = $cust->source->id;
1063 0           $args->{expand} = 'all';
1064 0           $okParams->{_cleanup} = 1;
1065             }
1066             elsif( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Payment::Card' ) )
1067             {
1068 0           my $card = shift( @_ );
1069 0 0         return( $self->error( "No card id was found in this card object." ) ) if( !$card->id );
1070 0 0         return( $self->error( "No customer object is set for this card object." ) ) if( !$card->customer );
1071 0 0         return( $self->error( "No customer id found in the customer object in this card object." ) ) if( !$card->customer->id );
1072 0           $args->{customer} = $card->customer->id;
1073 0           $args->{expand} = 'all';
1074 0           $okParams->{_cleanup} = 1;
1075             }
1076             else
1077             {
1078 0           $args = $self->_get_args( @_ );
1079             }
1080 0           my $err = $self->_check_parameters( $okParams, $args );
1081 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1082 0   0       my $id = CORE::delete( $args->{customer} ) || return( $self->error( "No customer id was provided to retrieve his/her card" ) );
1083 0   0       my $cardId = CORE::delete( $args->{id} ) || return( $self->error( "No card id was provided to retrieve customer's card" ) );
1084 0   0       my $hash = $self->get( "customers/${id}/sources/${cardId}", $args ) || return;
1085 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Card', $hash ) );
1086             }
1087              
1088             sub card_update
1089             {
1090 0     0 0   my $self = shift( @_ );
1091 0 0         return( $self->error( "No parameters were provided to update card." ) ) if( !scalar( @_ ) );
1092 0           my $args = {};
1093             my $okParams =
1094             {
1095             expandable => { allowed => $EXPANDABLES->{card} },
1096             id => { re => qr/^\w+$/, required => 1 },
1097             customer => { re => qr/^\w+$/, required => 1 },
1098             address_city => qr/^.*?$/,
1099             address_country => qr/^[a-zA-Z]{2}$/,
1100             address_line1 => qr/^.*?$/,
1101             address_line2 => qr/^.*?$/,
1102             address_state => qr/^.*?$/,
1103             address_zip => qr/^.*?$/,
1104             exp_month => qr/^\d{1,2}$/,
1105             exp_year => qr/^\d{1,2}$/,
1106 0 0   0     metadata => sub{ return( ref( $_[0] ) eq 'HASH' ? undef() : sprintf( "A hash ref was expected, but instead received '%s'", $_[0] ) ) },
1107 0           name => qr/^.*?$/,
1108             };
1109 0 0 0       if( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0 0        
1110             {
1111 0           my $cust = shift( @_ );
1112 0 0         return( $self->error( "No customer id was found in this customer object." ) ) if( !$cust->id );
1113 0 0         return( $self->error( "No source is set for the credit card to delete for this customer." ) ) if( !$cust->source );
1114 0 0         return( $self->error( "No credit card id found for this customer source to delete." ) ) if( !$cust->source->id );
1115 0           $args = $cust->source->as_hash({ json => 1 });
1116 0           $args->{customer} = $cust->id;
1117 0           $args->{expand} = 'all';
1118 0           $okParams->{_cleanup} = 1;
1119             }
1120             elsif( $self->_is_object( $_[0] ) && $_[0]->isa( 'Net::API::Stripe::Payment::Card' ) )
1121             {
1122 0           my $card = shift( @_ );
1123 0 0         return( $self->error( "No card id was found in this card object." ) ) if( !$card->id );
1124 0 0         return( $self->error( "No customer object is set for this card object." ) ) if( !$card->customer );
1125 0 0         return( $self->error( "No customer id found in the customer object in this card object." ) ) if( !$card->customer->id );
1126 0           $args = $card->as_hash({ json => 1 });
1127 0           $args->{customer} = $card->customer->id;
1128 0           $args->{expand} = 'all';
1129 0           $okParams->{_cleanup} = 1;
1130             }
1131             else
1132             {
1133 0           $args = $self->_get_args( @_ );
1134             }
1135 0           my $err = $self->_check_parameters( $okParams, $args );
1136 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1137 0   0       my $id = CORE::delete( $args->{customer} ) || return( $self->error( "No customer id was provided to update his/her card." ) );
1138 0   0       my $cardId = CORE::delete( $args->{id} ) || return( $self->error( "No card id was provided to update customer's card" ) );
1139 0   0       my $hash = $self->post( "customers/${id}/sources/${cardId}", $args ) || return;
1140 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Card', $hash ) );
1141             }
1142              
1143 0     0 1   sub charge { return( shift->_response_to_object( 'Net::API::Stripe::Charge', @_ ) ); }
1144              
1145             sub charges
1146             {
1147 0     0 0   my $self = shift( @_ );
1148 0           my $allowed = [qw( create retrieve update capture list )];
1149 0           my $action = shift( @_ );
1150 0           my $args = $self->_get_args( @_ );
1151 0   0       my $meth = $self->_get_method( 'charge', $action, $allowed ) || return;
1152 0           return( $self->$meth( $args ) );
1153             }
1154              
1155             sub charge_capture
1156             {
1157 0     0 0   my $self = shift( @_ );
1158 0 0         return( $self->error( "No parameters were provided to update a charge." ) ) if( !scalar( @_ ) );
1159 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Charge', @_ );
1160             my $okParams =
1161             {
1162             id => { re => qr/^\w+$/, required => 1 },
1163             amount => qr/^\d+$/,
1164             application_fee_amount => qr/^\d+$/,
1165             destination => [qw( amount )],
1166             expandable => { allowed => $EXPANDABLES->{charge} },
1167 0           receipt_email => qr/.*?/,
1168             statement_descriptor => qr/^.*?$/,
1169             statement_descriptor_suffix => qr/^.*?$/,
1170             transfer_data => [qw( amount )],
1171             transfer_group => qr/^.*?$/,
1172             };
1173 0           my $err = $self->_check_parameters( $okParams, $args );
1174 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1175 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No charge id was provided to update its charge details." ) );
1176 0 0 0       return( $self->error( "Destination specified, but not account property provided" ) ) if( exists( $args->{destination} ) && !scalar( grep( /^account$/, @{$args->{destination}} ) ) );
  0            
1177 0   0       my $hash = $self->post( "charges/${id}/capture", $args ) || return;
1178 0           return( $self->_response_to_object( 'Net::API::Stripe::Charge', $hash ) );
1179             }
1180              
1181             ## https://stripe.com/docs/api/charges/create
1182             sub charge_create
1183             {
1184 0     0 0   my $self = shift( @_ );
1185 0 0         return( $self->error( "No parameters were provided to create charge." ) ) if( !scalar( @_ ) );
1186 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Charge', @_ );
1187 0 0 0       return( $self->error( "No amount was provided" ) ) if( !exists( $args->{amount} ) || !length( $args->{amount} ) );
1188 0   0       $args->{currency} ||= $self->currency;
1189             my $okParams =
1190             {
1191             expandable => { allowed => $EXPANDABLES->{charge} },
1192 0           amount => { re => qr/^\d+$/, required => 1 },
1193             currency => qr/^[a-zA-Z]{3}$/,
1194             application_fee_amount => qr/^\d+$/,
1195             ## Boolean
1196             capture => { type => 'boolean' },
1197             customer => qr/^\w+$/,
1198             description => qr/^.*?$/,
1199             destination => [qw( account amount )],
1200             metadata => { type => 'hash' },
1201             on_behalf_of => qr/^\w+$/,
1202             ## No way, I am going to indulge in any regex on an e-mail address.
1203             receipt_email => qr/.*?/,
1204             shipping => { fields => [qw( address name carrier phone tracking_number )] },
1205             source => qr/^\w+$/,
1206             statement_descriptor => qr/^.*?$/,
1207             statement_descriptor_suffix => qr/^.*?$/,
1208             transfer_data => { fields => [qw( destination amount )] },
1209             transfer_group => qr/^.*?$/,
1210             idempotency => qr/^.*?$/,
1211             };
1212            
1213             ## We found some errors
1214 0           my $err = $self->_check_parameters( $okParams, $args );
1215 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1216            
1217 0           $args->{currency} = lc( $args->{currency} );
1218 0 0 0       return( $self->error( "Destination specified, but no account property provided" ) ) if( exists( $args->{destination} ) && !scalar( grep( /^account$/, @{$args->{destination}} ) ) );
  0            
1219 0   0       my $hash = $self->post( 'charges', $args ) || return;
1220 0           return( $self->_response_to_object( 'Net::API::Stripe::Charge', $hash ) );
1221             }
1222              
1223             sub charge_list
1224             {
1225 0     0 0   my $self = shift( @_ );
1226 0           my $args = shift( @_ );
1227             my $okParams =
1228             {
1229 0           expandable => { allowed => $EXPANDABLES->{charge}, data_prefix_is_ok => 1 },
1230             'created' => qr/^\d+$/,
1231             'created.gt' => qr/^\d+$/,
1232             'created.gte' => qr/^\d+$/,
1233             'created.lt' => qr/^\d+$/,
1234             'created.lte' => qr/^\d+$/,
1235             'customer' => qr/^\w+$/,
1236             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
1237             'ending_before' => qr/^\w+$/,
1238             'limit' => qr/^\d+$/,
1239             'payment_intent' => qr/^\w+$/,
1240             'source' => [qw( object )],
1241             'starting_after' => qr/^\w+$/,
1242             'transfer_group' => qr/^.*?$/,
1243             };
1244 0           my $err = $self->_check_parameters( $okParams, $args );
1245 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1246 0 0         if( $args->{source} )
1247             {
1248 0 0         return( $self->error( "Invalid source value. It should one of all, alipay_account, bank_account, bitcoin_receiver or card" ) ) if( $args->{source}->{object} !~ /^(?:all|alipay_account|bank_account|bitcoin_receiver|card)$/ );
1249             }
1250 0 0         if( $args->{expand} )
1251             {
1252 0 0         $self->_adjust_list_expandables( $args ) || return;
1253             }
1254 0   0       my $hash = $self->get( 'charges', $args ) || return;
1255 0           return( $self->_response_to_object( 'Net::API::Stripe::Charge::List', $hash ) );
1256             }
1257              
1258             sub charge_retrieve
1259             {
1260 0     0 0   my $self = shift( @_ );
1261 0 0         return( $self->error( "No parameters were provided to retrieve a charge" ) ) if( !scalar( @_ ) );
1262 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Charge', @_ );
1263             my $okParams =
1264             {
1265             expandable => { allowed => $EXPANDABLES->{charge} },
1266 0           id => { re => qr/^\w+$/, required => 1 }
1267             };
1268 0           my $err = $self->_check_parameters( $okParams, $args );
1269 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1270 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No charge id was provided to retrieve its charge details" ) );
1271 0   0       my $hash = $self->get( "charges/${id}", $args ) || return;
1272 0           return( $self->_response_to_object( 'Net::API::Stripe::Charge', $hash ) );
1273             }
1274              
1275             sub charge_update
1276             {
1277 0     0 0   my $self = shift( @_ );
1278 0 0         return( $self->error( "No parameters were provided to update a charge" ) ) if( !scalar( @_ ) );
1279 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Charge', @_ );
1280             my $okParams =
1281             {
1282             id => { re => qr/^\w+$/, required => 1 },
1283             expandable => { allowed => $EXPANDABLES->{charge} },
1284 0           customer => qr/^\w+$/,
1285             description => qr/^.*?$/,
1286             fraud_details => { fields => [qw( user_report )] },
1287             metadata => { type => 'hash' },
1288             receipt_email => qr/.*?/,
1289             shipping => { fields => [qw( address name carrier phone tracking_number )] },
1290             transfer_group => qr/^.*?$/,
1291             };
1292             ## We found some errors
1293 0           my $err = $self->_check_parameters( $okParams, $args );
1294 0 0         if( $args->{fraud_details} )
1295             {
1296 0           my $this = $args->{fraud_details};
1297 0 0         if( $this->{user_report} !~ /^(?:fraudulent|safe)$/ )
1298             {
1299 0           return( $self->error( "Invalid value for fraud_details. It should be either fraudulent or safe" ) );
1300             }
1301             }
1302 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1303 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No charge id was provided to update its charge details" ) );
1304 0   0       my $hash = $self->post( "charges/${id}", $args ) || return;
1305 0           return( $self->_response_to_object( 'Net::API::Stripe::Charge', $hash ) );
1306             }
1307              
1308             sub code2error
1309             {
1310 0     0 1   my $self = shift( @_ );
1311 0   0       my $code = shift( @_ ) || return( $self->error( "No code was provided to get the related error" ) );
1312 0 0         return( $self->error( "No code found for $code" ) ) if( !exists( $ERROR_CODE_TO_STRING->{ $code } ) );
1313 0           return( $ERROR_CODE_TO_STRING->{ $code } );
1314             }
1315              
1316             # sub connect { return( shift->_instantiate( 'connect', 'Net::API::Stripe::Connect' ) ) }
1317              
1318             sub conf_file
1319             {
1320 0     0 1   my $self = shift( @_ );
1321 0 0         if( @_ )
1322             {
1323 0           my $file = shift( @_ );
1324             # $self->message( 3, "Config file provided: $file" );
1325 0 0         if( !-e( $file ) )
    0          
1326             {
1327 0           return( $self->error( "Configuration file $file does not exist." ) );
1328             }
1329             elsif( -z( $file ) )
1330             {
1331 0           return( $self->error( "Configuration file $file is empty." ) );
1332             }
1333 0   0       my $fh = IO::File->new( "<$file" ) || return( $self->error( "Unable to open configuration file $file: $!" ) );
1334 0           $fh->binmode( ':utf8' );
1335 0           my $data = join( '', $fh->getlines );
1336 0           $fh->close;
1337 0           try
1338 0     0     {
1339 0           my $json = JSON->new->relaxed->decode( $data );
1340 0           $self->{conf_data} = $json;
1341 0           $self->{conf_file} = $file;
1342             # $self->message( 3, "Successfully decoded json data: ", sub{ $self->dumper( $json ) } );
1343             }
1344 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
1345 0     0     {
1346 0           return( $self->error( "An error occured while json decoding configuration file $file: $e" ) );
1347 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
1348             }
1349 0           return( $self->{conf_data} );
1350             }
1351              
1352 0     0 1   sub connection_token { return( shift->_response_to_object( 'Net::API::Stripe::Terminal::ConnectionToken', @_ ) ); }
1353              
1354 0     0 1   sub country_spec { return( shift->_response_to_object( 'Net::API::Stripe::Connect::CountrySpec', @_ ) ); }
1355              
1356 0     0 1   sub coupon { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Coupon', @_ ) ); }
1357              
1358             sub coupons
1359             {
1360 0     0 0   my $self = shift( @_ );
1361 0           my $action = shift( @_ );
1362 0           my $allowed = [qw( create retrieve update delete list )];
1363 0   0       my $meth = $self->_get_method( 'coupon', $action, $allowed ) || return;
1364 0           return( $self->$meth( @_ ) );
1365             }
1366              
1367             sub coupon_create
1368             {
1369 0     0 0   my $self = shift( @_ );
1370 0 0         return( $self->error( "No parameters were provided to create a coupon" ) ) if( !scalar( @_ ) );
1371 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Coupon', @_ );
1372             my $okParams =
1373             {
1374             expandable => { allowed => $EXPANDABLES->{coupon} },
1375             duration => { re => qr/^(forever|once|repeating)$/ },
1376             amount_off => { re => qr/^\d+$/ },
1377             currency => { re => qr/^[a-zA-Z]{3}$/ },
1378             duration_in_months => { re => qr/^\d+$/ },
1379             ## The id is the coupon code and can and should be provided by the user
1380             id => {},
1381             max_redemptions => { re => qr/^\d+$/ },
1382             metadata => { type => 'hash' },
1383             name => {},
1384 0 0 0 0     percent_off => sub{ return( $_[0] =~ /^\d+(\.\d+)?$/ && $_[0] > 0 && $_[0] <= 100 ? undef() : "Value provided is not a legitimate percentage off. It should be a float bigger than 0 and smaller of equal to 100." ) },
1385 0           redeem_by => {},
1386             };
1387 0           my $err = $self->_check_parameters( $okParams, $args );
1388 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1389 0   0       my $hash = $self->post( 'coupons', $args ) || return;
1390 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Coupon', $hash ) );
1391             }
1392              
1393             sub coupon_delete
1394             {
1395 0     0 0   my $self = shift( @_ );
1396 0 0         return( $self->error( "No parameters were provided to delete coupon information." ) ) if( !scalar( @_ ) );
1397 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Coupon', @_ );
1398             my $okParams =
1399             {
1400             expandable => { allowed => $EXPANDABLES->{coupon} },
1401 0           id => { re => qr/^\S+$/, required => 1 }
1402             };
1403 0           my $err = $self->_check_parameters( $okParams, $args );
1404 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1405 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No coupon id was provided to delete its information." ) );
1406 0   0       my $hash = $self->delete( "coupons/${id}", $args ) || return;
1407 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Coupon', $hash ) );
1408             }
1409              
1410             sub coupon_list
1411             {
1412 0     0 0   my $self = shift( @_ );
1413 0           my $args = shift( @_ );
1414             my $okParams =
1415             {
1416             expandable => { allowed => $EXPANDABLES->{coupon} },
1417 0           'created' => qr/^\d+$/,
1418             'created.gt' => qr/^\d+$/,
1419             'created.gte' => qr/^\d+$/,
1420             'created.lt' => qr/^\d+$/,
1421             'created.lte' => qr/^\d+$/,
1422             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
1423             'ending_before' => qr/^\w+$/,
1424             'limit' => qr/^\d+$/,
1425             'starting_after' => qr/^\w+$/,
1426             };
1427 0           my $err = $self->_check_parameters( $okParams, $args );
1428 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1429 0 0         if( $args->{expand} )
1430             {
1431 0 0         $self->_adjust_list_expandables( $args ) || return;
1432             }
1433 0   0       my $hash = $self->get( 'coupons', $args ) || return;
1434 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
1435             }
1436              
1437             sub coupon_retrieve
1438             {
1439 0     0 0   my $self = shift( @_ );
1440 0 0         return( $self->error( "No parameters were provided to retrieve coupon information." ) ) if( !scalar( @_ ) );
1441 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Coupon', @_ );
1442             my $okParams =
1443             {
1444             expandable => { allowed => $EXPANDABLES->{coupon} },
1445 0           id => { re => qr/^\S+$/, required => 1 }
1446             };
1447 0           my $err = $self->_check_parameters( $okParams, $args );
1448 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1449 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No coupon id was provided to retrieve its information." ) );
1450 0   0       my $hash = $self->get( "coupons/${id}", $args ) || return;
1451 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Coupon', $hash ) );
1452             }
1453              
1454             sub coupon_update
1455             {
1456 0     0 0   my $self = shift( @_ );
1457 0 0         return( $self->error( "No parameters were provided to update a coupon" ) ) if( !scalar( @_ ) );
1458 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Coupon', @_ );
1459             my $okParams =
1460             {
1461             expandable => { allowed => $EXPANDABLES->{coupon} },
1462 0           id => { re => qr/^\S+$/, required => 1 },
1463             ## Return true only if there is an error
1464             metadata => { type => 'hash' },
1465             name => {},
1466             };
1467             ## We found some errors
1468 0           my $err = $self->_check_parameters( $okParams, $args );
1469             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
1470 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1471 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No coupon id was provided to update coupon's details" ) );
1472 0   0       my $hash = $self->post( "coupons/${id}", $args ) || return;
1473 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Coupon', $hash ) );
1474             }
1475              
1476 0     0 1   sub credit_note { return( shift->_response_to_object( 'Net::API::Stripe::Billing::CreditNote', @_ ) ); }
1477              
1478             sub credit_notes
1479             {
1480 0     0 0   my $self = shift( @_ );
1481 0           my $action = shift( @_ );
1482             ## delete is an alias of void to make it more mnemotechnical to remember
1483 0 0         $action = 'void' if( $action eq 'delete' );
1484 0           my $allowed = [qw( preview create lines lines_preview retrieve update void list )];
1485 0   0       my $meth = $self->_get_method( 'coupons', $action, $allowed ) || return;
1486 0           return( $self->$meth( @_ ) );
1487             }
1488              
1489             sub credit_note_create
1490             {
1491 0     0 0   my $self = shift( @_ );
1492 0 0         return( $self->error( "No parameters were provided to create a credit note" ) ) if( !scalar( @_ ) );
1493 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1494             ## If we are provided with an invoice object, we change our value for only its id
1495 0 0 0       if( $args->{_object} &&
      0        
1496             $self->_is_object( $args->{_object}->{invoice} ) &&
1497             $args->{_object}->invoice->isa( 'Net::API::Stripe::Billing::Invoice' ) )
1498             {
1499 0           my $cred = CORE::delete( $args->{_object} );
1500 0   0       $args->{invoice} = $cred->invoice->id || return( $self->error( "The Invoice object provided for this credit note has no id." ) );
1501             }
1502            
1503             my $okParams =
1504             {
1505             expandable => { allowed => $EXPANDABLES->{credit_note} },
1506 0           invoice => { re => qr/^\w+$/, required => 1 },
1507             amount => { re => qr/^\d+$/ },
1508             credit_amount => { re => qr/^\d+$/ },
1509             lines => { type => 'array', fields => [qw( amount description invoice_line_item quantity tax_rates type unit_amount unit_amount_decimal )] },
1510             memo => {},
1511             metadata => { type => 'hash' },
1512             out_of_band_amount => { re => qr/^\d+$/ },
1513             reason => { re => qr/^(duplicate|fraudulent|order_change|product_unsatisfactory)$/ },
1514             refund => { re => qr/^\w+$/ },
1515             refund_amount => { re => qr/^\d+$/ },
1516             };
1517 0           my $err = $self->_check_parameters( $okParams, $args );
1518 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1519 0   0       my $hash = $self->post( 'credit_notes', $args ) || return;
1520 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::CreditNote', $hash ) );
1521             }
1522              
1523 0     0 0   sub credit_note_line_item { return( shift->_response_to_object( 'Net::API::Stripe::Billing::CreditNote::LineItem', @_ ) ); }
1524              
1525             sub credit_note_lines
1526             {
1527 0     0 0   my $self = shift( @_ );
1528 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1529 0 0         return( $self->error( "No credit note id was provided to retrieve its information." ) ) if( !CORE::length( $args->{id} ) );
1530 0           my $okParams =
1531             {
1532             id => { re => qr/^\w+$/, required => 1 },
1533             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
1534             ending_before => { re => qr/^\w+$/ },
1535             limit => { re => qr/^\d+$/ },
1536             starting_after => { re => qr/^\w+$/ },
1537             };
1538 0           my $err = $self->_check_parameters( $okParams, $args );
1539 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1540 0           my $id = CORE::delete( $args->{id} );
1541 0   0       my $hash = $self->get( "credit_notes/${id}/lines", $args ) || return;
1542 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
1543             }
1544              
1545             sub credit_note_lines_preview
1546             {
1547 0     0 0   my $self = shift( @_ );
1548 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1549             # return( $self->error( "No credit note id was provided to retrieve its information." ) ) if( !CORE::length( $args->{id} ) );
1550 0 0         return( $self->error( "No invoice id or object was provided." ) ) if( !CORE::length( $args->{invoice} ) );
1551 0 0 0       if( $args->{_object} &&
      0        
1552             $self->_is_object( $args->{_object}->{invoice} ) &&
1553             $args->{_object}->invoice->isa( 'Net::API::Stripe::Billing::Invoice' ) )
1554             {
1555 0           my $cred = CORE::delete( $args->{_object} );
1556 0   0       $args->{invoice} = $cred->invoice->id || return( $self->error( "The Invoice object provided for this credit note has no id." ) );
1557             }
1558            
1559             my $okParams =
1560             {
1561             expandable => { allowed => $EXPANDABLES->{credit_note_lines} },
1562             # id => { re => qr/^\w+$/, required => 1 },
1563 0           invoice => { re => qr/^\w+$/, required => 1 },
1564             amount => { re => qr/^\d+$/ },
1565             credit_amount => { re => qr/^\d+$/ },
1566             ending_before => { re => qr/^\w+$/ },
1567             limit => { re => qr/^\d+$/ },
1568             lines => { type => 'array', fields => [qw( amount description invoice_line_item quantity tax_rates type unit_amount unit_amount_decimal )] },
1569             memo => {},
1570             metadata => { type => 'hash' },
1571             out_of_band_amount => { re => qr/^\d+$/ },
1572             reason => { re => qr/^(duplicate|fraudulent|order_change|product_unsatisfactory)$/ },
1573             refund => { re => qr/^\w+$/ },
1574             refund_amount => { re => qr/^\d+$/ },
1575             starting_after => { re => qr/^\w+$/ },
1576             };
1577 0           my $err = $self->_check_parameters( $okParams, $args );
1578 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1579 0           my $id = CORE::delete( $args->{id} );
1580 0   0       my $hash = $self->get( "credit_notes/preview/${id}/lines", $args ) || return;
1581 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
1582             }
1583              
1584             sub credit_note_list
1585             {
1586 0     0 0   my $self = shift( @_ );
1587 0           my $args = shift( @_ );
1588             my $okParams =
1589             {
1590 0           expandable => { allowed => $EXPANDABLES->{credit_note}, data_prefix_is_ok => 1 },
1591             'created' => qr/^\d+$/,
1592             'created.gt' => qr/^\d+$/,
1593             'created.gte' => qr/^\d+$/,
1594             'created.lt' => qr/^\d+$/,
1595             'created.lte' => qr/^\d+$/,
1596             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
1597             'ending_before' => qr/^\w+$/,
1598             'limit' => qr/^\d+$/,
1599             'starting_after' => qr/^\w+$/,
1600             };
1601 0           my $err = $self->_check_parameters( $okParams, $args );
1602 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1603 0 0         if( $args->{expand} )
1604             {
1605 0 0         $self->_adjust_list_expandables( $args ) || return;
1606             }
1607 0   0       my $hash = $self->get( 'coupons', $args ) || return;
1608 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
1609             }
1610              
1611             sub credit_note_preview
1612             {
1613 0     0 0   my $self = shift( @_ );
1614 0 0         return( $self->error( "No parameters were provided to preview a credit note" ) ) if( !scalar( @_ ) );
1615 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1616            
1617 0           my $obj = $args->{_object};
1618             ## If we are provided with an invoice object, we change our value for only its id
1619 0 0 0       if( $obj && $obj->invoice )
1620             {
1621 0   0       $args->{invoice} = $obj->invoice->id || return( $self->error( "The Invoice object provided for this credit note has no id." ) );
1622             }
1623            
1624             my $okParams =
1625             {
1626             expandable => { allowed => $EXPANDABLES->{credit_note} },
1627 0           invoice => { required => 1 },
1628             amount => { re => qr/^\d+$/ },
1629             credit_amount => { re => qr/^\d+$/ },
1630             lines => { type => 'array', fields => [qw( amount description invoice_line_item quantity tax_rates type unit_amount unit_amount_decimal )] },
1631             memo => {},
1632             metadata => { type => 'hash' },
1633             out_of_band_amount => { re => qr/^\d+$/ },
1634             reason => { re => qr/^(duplicate|fraudulent|order_change|product_unsatisfactory)$/ },
1635             refund => { re => qr/^\w+$/ },
1636             refund_amount => { re => qr/^\d+$/ },
1637             };
1638 0           my $err = $self->_check_parameters( $okParams, $args );
1639 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1640 0   0       my $hash = $self->post( 'credit_notes/preview', $args ) || return;
1641 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::CreditNote', $hash ) );
1642             }
1643              
1644             sub credit_note_retrieve
1645             {
1646 0     0 0   my $self = shift( @_ );
1647 0 0         return( $self->error( "No parameters were provided to retrieve credit note information." ) ) if( !scalar( @_ ) );
1648 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1649             my $okParams =
1650             {
1651             expandable => { allowed => $EXPANDABLES->{credit_note} },
1652 0           id => { re => qr/^\w+$/, required => 1 }
1653             };
1654 0           my $err = $self->_check_parameters( $okParams, $args );
1655 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1656 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No credit note id was provided to retrieve its information." ) );
1657 0   0       my $hash = $self->get( "credit_notes/${id}", $args ) || return;
1658 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::CreditNote', $hash ) );
1659             }
1660              
1661             sub credit_note_update
1662             {
1663 0     0 0   my $self = shift( @_ );
1664 0 0         return( $self->error( "No parameters were provided to update a credit note" ) ) if( !scalar( @_ ) );
1665 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1666             my $okParams =
1667             {
1668             expandable => { allowed => $EXPANDABLES->{credit_note} },
1669 0           id => { re => qr/^\w+$/, required => 1 },
1670             memo => {},
1671             ## Return true only if there is an error
1672             metadata => { type => 'hash' },
1673             };
1674             ## We found some errors
1675 0           my $err = $self->_check_parameters( $okParams, $args );
1676             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
1677 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1678 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No credit note id was provided to update credit note's details" ) );
1679 0   0       my $hash = $self->post( "credit_notes/${id}", $args ) || return;
1680 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::CreditNote', $hash ) );
1681             }
1682              
1683             sub credit_note_void
1684             {
1685 0     0 0   my $self = shift( @_ );
1686 0 0         return( $self->error( "No parameters were provided to void credit note information." ) ) if( !scalar( @_ ) );
1687 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::CreditNote', @_ );
1688             my $okParams =
1689             {
1690             expandable => { allowed => $EXPANDABLES->{credit_note} },
1691 0           id => { re => qr/^\w+$/, required => 1 }
1692             };
1693 0           my $err = $self->_check_parameters( $okParams, $args );
1694 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1695 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No credit note id was provided to void it." ) );
1696 0   0       my $hash = $self->post( "credit_notes/${id}/void", $args ) || return;
1697 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::CreditNote', $hash ) );
1698             }
1699              
1700             sub currency
1701             {
1702 0     0 1   my $self = shift( @_ );
1703 0 0         if( @_ )
1704             {
1705 0           $self->_set_get( 'currency', lc( shift( @_ ) ) );
1706             }
1707 0           return( $self->{ 'currency' } );
1708             }
1709              
1710 0     0 1   sub customer { return( shift->_response_to_object( 'Net::API::Stripe::Customer', @_ ) ); }
1711              
1712 0     0 1   sub customer_balance_transaction { return( shift->_response_to_object( 'Net::API::Stripe::Customer::BalanceTransaction', @_ ) ); }
1713              
1714 0     0 1   sub customer_tax_id { return( shift->_response_to_object( 'Net::API::Stripe::Customer::TaxId', @_ ) ); }
1715              
1716             sub customers
1717             {
1718 0     0 0   my $self = shift( @_ );
1719 0           my $action = shift( @_ );
1720 0           my $allowed = [qw( create retrieve update delete delete_discount list )];
1721 0   0       my $meth = $self->_get_method( 'customer', $action, $allowed ) || return;
1722 0           return( $self->$meth( @_ ) );
1723             }
1724              
1725             ## https://stripe.com/docs/api/customers/create?lang=curl
1726             sub customer_create
1727             {
1728 0     0 0   my $self = shift( @_ );
1729 0 0         return( $self->error( "No parameters were provided to create customer" ) ) if( !scalar( @_ ) );
1730 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
1731             my $okParams =
1732             {
1733             expandable => { allowed => $EXPANDABLES->{customer} },
1734 0           account_balance => { re => qr/^\-?\d+$/ },
1735             address => { fields => [qw( line1 city country line2 postal_code state )], package => 'Net::API::Stripe::Address' },
1736             balance => { re => qr/^\-?\d+$/ },
1737             ## Anything goes
1738             coupon => {},
1739             default_source => { re => qr/^\w+$/ },
1740             description => {},
1741             email => {},
1742             ## A possible custom unique identifier
1743             id => {},
1744             ## "The prefix for the customer used to generate unique invoice numbers. Must be 3–12 uppercase letters or numbers."
1745             invoice_prefix => { re => qr/^[A-Z0-9]{3,12}$/ },
1746             invoice_settings => { fields => [qw( custom_fields default_payment_method footer )], package => 'Net::API::Stripe::Billing::Invoice::Settings' },
1747             metadata => { type => 'hash' },
1748             name => {},
1749             payment_method => {},
1750             phone => {},
1751             preferred_locales => { type => 'array' },
1752             shipping => { fields => [qw( address name carrier phone tracking_number )], package => 'Net::API::Stripe::Shipping' },
1753             source => { re => qr/^\w+$/ },
1754             tax_exempt => { re => qr/^(none|exempt|reverse)$/ },
1755             ## array of hash
1756             tax_id_data => { type => 'array', package => 'Net::API::Stripe::Customer::TaxId' },
1757             ## "The customer’s tax ID number. This will be unset if you POST an empty value."
1758             ## "The type of ID number. The only possible value is vat"
1759             tax_info => { fields => [qw( tax_id type )], package => 'Net::API::Stripe::Customer::TaxInfo' },
1760             };
1761 0           my $err = $self->_check_parameters( $okParams, $args );
1762 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1763 0 0 0       return( $self->error( "Invalid tax type value provided. It can only be set to vat" ) ) if( $args->{tax_info} && $args->{tax_info}->{type} ne 'vat' );
1764 0   0       my $hash = $self->post( 'customers', $args ) || return;
1765 0           return( $self->_response_to_object( 'Net::API::Stripe::Customer', $hash ) );
1766             }
1767              
1768             ## https://stripe.com/docs/api/customers/delete?lang=curl
1769             ## "Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer."
1770             sub customer_delete
1771             {
1772 0     0 0   my $self = shift( @_ );
1773 0 0         return( $self->error( "No parameters were provided to delete customer information." ) ) if( !scalar( @_ ) );
1774 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
1775             my $okParams =
1776             {
1777             expandable => { allowed => $EXPANDABLES->{customer} },
1778 0           id => { re => qr/^\w+$/, required => 1 }
1779             };
1780 0           my $err = $self->_check_parameters( $okParams, $args );
1781 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1782 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to delete its information." ) );
1783 0   0       my $hash = $self->delete( "customers/${id}" ) || return;
1784 0           return( $self->_response_to_object( 'Net::API::Stripe::Customer', $hash ) );
1785             }
1786              
1787             sub customer_delete_discount
1788             {
1789 0     0 0   my $self = shift( @_ );
1790 0 0         return( $self->error( "No parameters were provided to delete customer discount." ) ) if( !scalar( @_ ) );
1791 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
1792             my $okParams =
1793             {
1794             expandable => { allowed => $EXPANDABLES->{discount}},
1795 0           id => { re => qr/^\w+$/, required => 1 }
1796             };
1797 0           my $err = $self->_check_parameters( $okParams, $args );
1798 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1799 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to delete its coupon." ) );
1800 0   0       my $hash = $self->delete( "customers/${id}/discount", $args ) || return;
1801 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Discount', $hash ) );
1802             }
1803              
1804             sub customer_list
1805             {
1806 0     0 0   my $self = shift( @_ );
1807 0           my $args = shift( @_ );
1808             my $okParams =
1809             {
1810 0           expandable => { allowed => $EXPANDABLES->{customer}, data_prefix_is_ok => 1 },
1811             'created' => qr/^\d+$/,
1812             'created.gt' => qr/^\d+$/,
1813             'created.gte' => qr/^\d+$/,
1814             'created.lt' => qr/^\d+$/,
1815             'created.lte' => qr/^\d+$/,
1816             'email' => qr/.*?/,
1817             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
1818             'ending_before' => qr/^\w+$/,
1819             'limit' => qr/^\d+$/,
1820             'starting_after' => qr/^\w+$/,
1821             };
1822 0           my $err = $self->_check_parameters( $okParams, $args );
1823 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1824 0 0         if( $args->{source} )
1825             {
1826 0 0         return( $self->error( "Invalid source value. It should one of all, alipay_account, bank_account, bitcoin_receiver or card" ) ) if( $args->{source}->{object} !~ /^(?:all|alipay_account|bank_account|bitcoin_receiver|card)$/ );
1827             }
1828 0 0         if( $args->{expand} )
1829             {
1830 0 0         $self->_adjust_list_expandables( $args ) || return;
1831             }
1832 0   0       my $hash = $self->get( 'customers', $args ) || return;
1833 0           return( $self->_response_to_object( 'Net::API::Stripe::Customer::List', $hash ) );
1834             }
1835              
1836             sub customer_retrieve
1837             {
1838 0     0 0   my $self = shift( @_ );
1839 0 0         return( $self->error( "No parameters were provided to retrieve customer information." ) ) if( !scalar( @_ ) );
1840 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
1841             my $okParams =
1842             {
1843             expandable => { allowed => $EXPANDABLES->{customer} },
1844 0           id => { re => qr/^\w+$/, required => 1 }
1845             };
1846 0           my $err = $self->_check_parameters( $okParams, $args );
1847 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1848 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to retrieve its information." ) );
1849 0   0       my $hash = $self->get( "customers/${id}", $args ) || return;
1850 0           return( $self->_response_to_object( 'Net::API::Stripe::Customer', $hash ) );
1851             }
1852              
1853             ## https://stripe.com/docs/api/customers/update?lang=curl
1854             sub customer_update
1855             {
1856 0     0 0   my $self = shift( @_ );
1857 0 0         return( $self->error( "No parameters were provided to update a customer" ) ) if( !scalar( @_ ) );
1858 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
1859             my $okParams =
1860             {
1861             expandable => { allowed => $EXPANDABLES->{customer} },
1862 0           id => { re => qr/^\w+$/, required => 1 },
1863             account_balance => { re => qr/^\d+$/ },
1864             address => { fields => [qw( line1 line2 city postal_code state country )] },
1865             balance => {},
1866             ## Anything goes
1867             coupon => {},
1868             default_source => { re => qr/^\w+$/ },
1869             description => {},
1870             email => {},
1871             ## "The prefix for the customer used to generate unique invoice numbers. Must be 3–12 uppercase letters or numbers."
1872             invoice_prefix => { re => qr/^[A-Z0-9]{3,12}$/ },
1873             invoice_settings => { fields => [qw( custom_fields default_payment_method footer )] },
1874             ## Return true only if there is an error
1875             metadata => { type => 'hash' },
1876             name => {},
1877             next_invoice_sequence => {},
1878             phone => {},
1879             preferred_locales => { type => 'array' },
1880             shipping => { fields => [qw( address name carrier phone tracking_number )] },
1881             source => { re => qr/^\w+$/ },
1882             tax_exempt => { re => qr/^(none|exempt|reverse)$/ },
1883             ## "The customer’s tax ID number. This will be unset if you POST an empty value."
1884             ## "The type of ID number. The only possible value is vat"
1885             tax_info => { fields => [qw( tax_id type )] },
1886             };
1887             ## We found some errors
1888 0           my $err = $self->_check_parameters( $okParams, $args );
1889 0 0         if( $args->{fraud_details} )
1890             {
1891 0           my $this = $args->{fraud_details};
1892 0 0         if( $this->{user_report} !~ /^(?:fraudulent|safe)$/ )
1893             {
1894 0           return( $self->error( "Invalid value for fraud_details. It should be either fraudulent or safe" ) );
1895             }
1896             }
1897             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
1898 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1899 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to update customer's details" ) );
1900 0   0       my $hash = $self->post( "customers/${id}", $args ) || return;
1901 0           return( $self->_response_to_object( 'Net::API::Stripe::Customer', $hash ) );
1902             }
1903              
1904             sub delete
1905             {
1906 0     0 1   my $self = shift( @_ );
1907 0   0       my $path = shift( @_ ) || return( $self->error( "No api endpoint (path) was provided." ) );
1908 0           my $args = shift( @_ );
1909 0 0 0       return( $self->error( "http query parameters provided were not a hash reference." ) ) if( $args && ref( $args ) ne 'HASH' );
1910 0           my $api = $self->api_uri->clone;
1911 0 0 0       if( $self->_is_object( $path ) && $path->can( 'path' ) )
1912             {
1913 0           $self->message( 3, "$path is a URI object" );
1914 0           $api->path( undef() );
1915 0           $path = $path->path;
1916             }
1917             else
1918             {
1919 0 0         substr( $path, 0, 0 ) = '/' unless( substr( $path, 0, 1 ) eq '/' );
1920             }
1921 0 0 0       $path .= '?' . $self->_encode_params( $args ) if( $args && %$args );
1922 0           my $req = HTTP::Request->new( 'DELETE', $api . $path );
1923 0           return( $self->_make_request( $req ) );
1924             }
1925              
1926 0     0 1   sub discount { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Discount', @_ ) ); }
1927              
1928             sub discounts
1929             {
1930 0     0 0   my $self = shift( @_ );
1931 0           my $action = shift( @_ );
1932 0           my $allowed = [qw( delete_customer delete_subscription )];
1933 0 0         return( $self->error( "Unknown action \"$action\" for discounts." ) ) if( !scalar( grep( /^$action$/, @$allowed ) ) );
1934 0 0         if( $action eq 'delete_customer' )
    0          
1935             {
1936 0           return( $self->customers( delete_discount => @_ ) );
1937             }
1938             elsif( $action eq 'delete_subscription' )
1939             {
1940 0           return( $self->subscriptions( delete_discount => @_ ) );
1941             }
1942             ## Should not reach here
1943             else
1944             {
1945 0           return( $self->error( "Unknown and untrapped action \"$action\" for discount." ) );
1946             }
1947             }
1948              
1949 0     0 1   sub dispute { return( shift->_response_to_object( 'Net::API::Stripe::Dispute', @_ ) ); }
1950              
1951             sub disputes
1952             {
1953 0     0 0   my $self = shift( @_ );
1954 0           my $action = shift( @_ );
1955 0           my $allowed = [qw( close retrieve update list )];
1956 0   0       my $meth = $self->_get_method( 'dispute', $action, $allowed ) || return;
1957 0           return( $self->$meth( @_ ) );
1958             }
1959              
1960             sub dispute_close
1961             {
1962 0     0 0   my $self = shift( @_ );
1963 0 0         return( $self->error( "No parameters were provided to close dispute." ) ) if( !scalar( @_ ) );
1964 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Dispute', @_ );
1965             my $okParams =
1966             {
1967             expandable => { allowed => $EXPANDABLES->{dispute} },
1968 0           id => { re => qr/^\w+$/, required => 1 }
1969             };
1970 0           my $err = $self->_check_parameters( $okParams, $args );
1971 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
1972 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No dispute id was provided to close." ) );
1973 0   0       my $hash = $self->delete( "disputes/${id}/close", $args ) || return;
1974 0           return( $self->_response_to_object( 'Net::API::Stripe::Dispute', $hash ) );
1975             }
1976              
1977 0     0 1   sub dispute_evidence { return( shift->_response_to_object( 'Net::API::Stripe::Dispute', @_ ) ); }
1978              
1979             sub dispute_list
1980             {
1981 0     0 0   my $self = shift( @_ );
1982 0           my $args = shift( @_ );
1983             my $okParams =
1984             {
1985 0           expandable => { allowed => $EXPANDABLES->{dispute}, data_prefix_is_ok => 1 },
1986             'created' => { re => qr/^\d+$/ },
1987             'created.gt' => { re => qr/^\d+$/ },
1988             'created.gte' => { re => qr/^\d+$/ },
1989             'created.lt' => { re => qr/^\d+$/ },
1990             'created.lte' => { re => qr/^\d+$/ },
1991             'charge' => { re => qr/.*?/ },
1992             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
1993             'ending_before' => { re => qr/^\w+$/ },
1994             'limit' => { re => qr/^\d+$/ },
1995             'payment_intent' => { re => qr/^\w+$/ },
1996             'starting_after' => { re => qr/^\w+$/ },
1997             };
1998 0           my $err = $self->_check_parameters( $okParams, $args );
1999 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2000 0 0         if( $args->{expand} )
2001             {
2002 0 0         $self->_adjust_list_expandables( $args ) || return;
2003             }
2004 0   0       my $hash = $self->get( 'disputes', $args ) || return;
2005 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
2006             }
2007              
2008             sub dispute_retrieve
2009             {
2010 0     0 0   my $self = shift( @_ );
2011 0 0         return( $self->error( "No parameters were provided to retrieve dispute information." ) ) if( !scalar( @_ ) );
2012 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Dispute', @_ );
2013             my $okParams =
2014             {
2015             expandable => { allowed => $EXPANDABLES->{dispute} },
2016 0           id => { re => qr/^\w+$/, required => 1 }
2017             };
2018 0           my $err = $self->_check_parameters( $okParams, $args );
2019 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2020 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No dispute id was provided to retrieve its information." ) );
2021 0   0       my $hash = $self->get( "disputes/${id}", $args ) || return;
2022 0           return( $self->_response_to_object( 'Net::API::Stripe::Dispute', $hash ) );
2023             }
2024              
2025             sub dispute_update
2026             {
2027 0     0 0   my $self = shift( @_ );
2028 0 0         return( $self->error( "No parameters were provided to update a dispute" ) ) if( !scalar( @_ ) );
2029 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Dispute', @_ );
2030             my $okParams =
2031             {
2032             expandable => { allowed => $EXPANDABLES->{dispute} },
2033 0           id => { re => qr/^\w+$/, required => 1 },
2034             evidence => { fields => [qw( access_activity_log billing_address cancellation_policy cancellation_policy_disclosure cancellation_rebuttal customer_communication customer_email_address customer_name customer_purchase_ip customer_signature duplicate_charge_documentation duplicate_charge_explanation duplicate_charge_id product_description receipt refund_policy refund_policy_disclosure refund_refusal_explanation service_date service_documentation shipping_address shipping_carrier shipping_date shipping_documentation shipping_tracking_number uncategorized_file uncategorized_text )] },
2035             metadata => { type => 'hash' },
2036             submit => {},
2037             };
2038             ## We found some errors
2039 0           my $err = $self->_check_parameters( $okParams, $args );
2040             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
2041 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2042 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No dispute id was provided to update dispute's details" ) );
2043 0   0       my $hash = $self->post( "disputes/${id}", $args ) || return;
2044 0           return( $self->_response_to_object( 'Net::API::Stripe::Dispute', $hash ) );
2045             }
2046              
2047 0     0 1   sub encode_with_json { return( shift->_set_get( 'encode_with_json', @_ ) ) };
2048              
2049 0     0 1   sub event { return( shift->_response_to_object( 'Net::API::Stripe::Event', @_ ) ); }
2050              
2051             ## Can be 'all' or an integer representing a depth
2052 0     0 1   sub expand { return( shift->_set_get_scalar( 'expand', @_ ) ); }
2053              
2054             sub fields
2055             {
2056 0     0 1   my $self = shift( @_ );
2057 0   0       my $type = shift( @_ ) || return( $self->error( "No object type was provided to get its list of methods." ) );
2058 0           my $class;
2059 0 0         if( $class = $self->_is_object( $type ) )
2060             {
2061 0           $self->message( 3, "Was provided an object with class name \"$class\"." );
2062             }
2063             else
2064             {
2065 0           $self->message( 3, "Getting object class for type '$type'." );
2066 0           $class = $self->_object_type_to_class( $type );
2067             }
2068 0           $self->message( 3, "Class found is '$class'." );
2069 1     1   9 no strict 'refs';
  1         2  
  1         18694  
2070 0 0         if( !$self->_is_class_loaded( $class ) )
2071             {
2072 0           $self->message( 3, "Loading class '$class'." );
2073 0           $self->_load_class( $class );
2074             }
2075 0           my @methods = grep{ defined &{"${class}::$_"} } keys( %{"${class}::"} );
  0            
  0            
  0            
2076 0           return( \@methods );
2077             }
2078              
2079 0     0 1   sub file { return( shift->_response_to_object( 'Net::API::Stripe::File', @_ ) ); }
2080              
2081 0     0 1   sub file_link { return( shift->_response_to_object( 'Net::API::Stripe::File::Link', @_ ) ); }
2082              
2083             # sub fraud { return( shift->_instantiate( 'fraud', 'Net::API::Stripe::Fraud' ) ) }
2084              
2085             sub files
2086             {
2087 0     0 0   my $self = shift( @_ );
2088 0           my $action = shift( @_ );
2089 0           my $allowed = [qw( create retrieve list )];
2090 0   0       my $meth = $self->_get_method( 'files', $action, $allowed ) || return;
2091 0           return( $self->$meth( @_ ) );
2092             }
2093              
2094             sub file_create
2095             {
2096 0     0 0   my $self = shift( @_ );
2097 0 0         return( $self->error( "No parameters were provided to create a file" ) ) if( !scalar( @_ ) );
2098 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::File', @_ );
2099             my $okParams =
2100             {
2101             expand => { allowed => $EXPANDABLES->{file} },
2102 0           file => {},
2103             purpose => { re => qr/^(business_icon|business_logo|customer_signature|dispute_evidence|identity_document|pci_document|tax_document_user_upload)$/ },
2104             file_link_data => { type => 'hash', field => [qw( create expires_at metadata )] },
2105             };
2106 0           my $err = $self->_check_parameters( $okParams, $args );
2107 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2108 0 0         if( !CORE::length( $args->{file} ) )
2109             {
2110 0           return( $self->error( "No file was provided to upload." ) );
2111             }
2112 0           my $file = Cwd::abs_path( $args->{file} );
2113 0 0         if( !-e( $file ) )
    0          
    0          
2114             {
2115 0           return( $self->error( "File \"$file\" does not exist." ) );
2116             }
2117             elsif( -z( $file ) )
2118             {
2119 0           return( $self->error( "File \"$file\" is empty." ) );
2120             }
2121             elsif( !-r( $file ) )
2122             {
2123 0           return( $self->error( "File \"$file\" does not have read permission for us (uid = $>)." ) );
2124             }
2125 0           $args->{file} = { _filepath => $file };
2126 0   0       my $hash = $self->post_multipart( 'files', $args ) || return;
2127 0           return( $self->_response_to_object( 'Net::API::Stripe::File', $hash ) );
2128             }
2129              
2130             sub file_list
2131             {
2132 0     0 0   my $self = shift( @_ );
2133 0           my $args = shift( @_ );
2134             my $okParams =
2135             {
2136             expand => { allowed => $EXPANDABLES->{file} },
2137 0           'created' => qr/^\d+$/,
2138             'created.gt' => qr/^\d+$/,
2139             'created.gte' => qr/^\d+$/,
2140             'created.lt' => qr/^\d+$/,
2141             'created.lte' => qr/^\d+$/,
2142             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
2143             ending_before => qr/^\w+$/,
2144             limit => qr/^\d+$/,
2145             purpose => {},
2146             starting_after => qr/^\w+$/,
2147             };
2148 0           my $err = $self->_check_parameters( $okParams, $args );
2149 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2150 0 0         if( $args->{expand} )
2151             {
2152 0 0         $self->_adjust_list_expandables( $args ) || return;
2153             }
2154 0   0       my $hash = $self->get( 'files', $args ) || return;
2155 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
2156             }
2157              
2158             sub file_retrieve
2159             {
2160 0     0 0   my $self = shift( @_ );
2161 0 0         return( $self->error( "No parameters were provided to retrieve file information." ) ) if( !scalar( @_ ) );
2162 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::File', @_ );
2163             my $okParams =
2164             {
2165             expand => { allowed => $EXPANDABLES->{file} },
2166 0           id => { re => qr/^\w+$/, required => 1 }
2167             };
2168 0           my $err = $self->_check_parameters( $okParams, $args );
2169 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2170 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No file id was provided to retrieve its information." ) );
2171 0   0       my $hash = $self->get( "files/${id}", $args ) || return;
2172 0           return( $self->_response_to_object( 'Net::API::Stripe::File', $hash ) );
2173             }
2174              
2175 0     0 1   sub fraud { return( shift->_response_to_object( 'Net::API::Stripe::Fraud', @_ ) ); }
2176              
2177             sub generate_uuid
2178             {
2179 0     0 1   return( Data::UUID->new->create_str );
2180             }
2181              
2182             sub get
2183             {
2184 0     0 1   my $self = shift( @_ );
2185 0   0       my $path = shift( @_ ) || return( $self->error( "No api endpoint (path) was provided." ) );
2186 0           my $args = shift( @_ );
2187 0 0 0       return( $self->error( "http query parameters provided were not a hash reference." ) ) if( $args && ref( $args ) ne 'HASH' );
2188 0           my $api = $self->api_uri->clone;
2189 0 0 0       if( $self->_is_object( $path ) && $path->can( 'path' ) )
2190             {
2191 0           $self->message( 3, "$path is a URI object" );
2192 0           $api->path( undef() );
2193 0           $path = $path->path;
2194             }
2195             else
2196             {
2197 0 0         substr( $path, 0, 0 ) = '/' unless( substr( $path, 0, 1 ) eq '/' );
2198             }
2199 0 0 0       $path .= '?' . $self->_encode_params( $args ) if( $args && %$args );
2200 0           $self->message( 3, "Preparing get request to ${api}${path}" );
2201 0           my $req = HTTP::Request->new( 'GET', $api . $path );
2202 0           return( $self->_make_request( $req ) );
2203             }
2204              
2205             sub http_client
2206             {
2207 0     0 1   my $self = shift( @_ );
2208 0 0         return( $self->{ua} ) if( $self->{ua} );
2209 0           my $cookie_file = $self->cookie_file;
2210 0           my $browser = $self->browser;
2211 0           my $ua = LWP::UserAgent->new;
2212 0           $ua->timeout( 5 );
2213 0           $ua->agent( $browser );
2214 0           $ua->cookie_jar({ file => $cookie_file });
2215 0           $self->{ua} = $ua;
2216 0           return( $ua );
2217             }
2218              
2219 0     0 1   sub http_request { return( shift->_set_get_object( 'http_request', 'HTTP::Request', @_ ) ); }
2220              
2221 0     0 1   sub http_response { return( shift->_set_get_object( 'http_response', 'HTTP::Response', @_ ) ); }
2222              
2223 0     0 1   sub ignore_unknown_parameters { return( shift->_set_get_boolean( 'ignore_unknown_parameters', @_ ) ); }
2224              
2225 0     0 1   sub invoice { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Invoice', @_ ) ); }
2226              
2227             sub invoices
2228             {
2229 0     0 0   my $self = shift( @_ );
2230 0           my $action = shift( @_ );
2231             ## Stripe use this api end point uncollectible, but this is prone to mispelling and not easy to remember
2232             ## So we use write off and convert one into another transparently
2233 0 0         $action = 'invoice_write_off' if( $action eq 'invoice_uncollectible' );
2234 0           my $allowed = [qw( create delete finalise lines lines_upcoming invoice_write_off upcoming pay retrieve send update void list )];
2235 0   0       my $meth = $self->_get_method( 'coupons', $action, $allowed ) || return;
2236 0           return( $self->$meth( @_ ) );
2237             }
2238              
2239             sub invoice_create
2240             {
2241 0     0 0   my $self = shift( @_ );
2242 0 0         return( $self->error( "No parameters were provided to create an invoice" ) ) if( !scalar( @_ ) );
2243 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2244 0           my $obj = $args->{_object};
2245             ## If we are provided with an invoice object, we change our value for only its id
2246 0 0 0       if( ( $obj && $obj->customer ) ||
      0        
      0        
2247             ( $self->_is_object( $args->{customer} ) && $args->{customer}->isa( 'Net::API::Stripe::Customer' ) ) )
2248             {
2249 0 0         my $cust = $obj ? $obj->customer : $args->{customer};
2250 0   0       $args->{customer} = $cust->id || return( $self->error( "The Customer object provided for this invoice has no id." ) );
2251             }
2252            
2253 0 0 0       if( ( $obj && $obj->subscription ) ||
      0        
      0        
      0        
2254             ( $args->{subscription} && $self->_is_object( $args->{subscription} ) && $args->{subscription}->isa( 'Net::API::Stripe::Billing::Subscription' ) ) )
2255             {
2256 0 0         my $sub = $obj ? $obj->subscription : $args->{subscription};
2257 0   0       $args->{subscription} = $sub->id || return( $self->error( "The Subscription object provided for this invoice has no id." ) );
2258             }
2259            
2260             my $okParams =
2261             {
2262             expandable => { allowed => $EXPANDABLES->{invoice} },
2263 0           customer => { required => 1 },
2264             application_fee_amount => { re => qr/^\d+$/ },
2265             auto_advance => {},
2266             collection_method => { re => qr/^(charge_automatically|send_invoice)$/ },
2267             custom_fields => { fields => [qw( name value )], type => 'array' },
2268             days_until_due => { re => qr/^\d+$/ },
2269             default_payment_method => { re => qr/^\w+$/ },
2270             default_source => { re => qr/^\w+$/ },
2271             default_tax_rates => { re => qr/^\d+(?:\.\d+)?$/ },
2272             description => {},
2273             due_date => {},
2274             footer => {},
2275             metadata => { type => 'hash' },
2276             statement_descriptor => {},
2277             subscription => { re => qr/^\w+$/ },
2278             tax_percent => { re => qr/^\d+(?:\.\d+)?$/ },
2279             };
2280 0           my $err = $self->_check_parameters( $okParams, $args );
2281 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2282 0   0       my $hash = $self->post( 'invoices', $args ) || return;
2283 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2284             }
2285              
2286             ## Delete a draft invoice
2287             sub invoice_delete
2288             {
2289 0     0 0   my $self = shift( @_ );
2290 0 0         return( $self->error( "No parameters were provided to delete a draft invoice." ) ) if( !scalar( @_ ) );
2291 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2292             my $okParams =
2293             {
2294             expandable => { allowed => $EXPANDABLES->{invoice} },
2295 0           id => { re => qr/^\w+$/, required => 1 }
2296             };
2297 0           my $err = $self->_check_parameters( $okParams, $args );
2298 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2299 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No draft invoice id was provided to delete its information." ) );
2300 0   0       my $hash = $self->delete( "invoices/${id}", $args ) || return;
2301 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2302             }
2303              
2304             sub invoice_finalise
2305             {
2306 0     0 0   my $self = shift( @_ );
2307 0 0         return( $self->error( "No parameters were provided to pay invoice." ) ) if( !scalar( @_ ) );
2308 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2309             my $okParams =
2310             {
2311             expandable => { allowed => $EXPANDABLES->{invoice} },
2312 0           id => { re => qr/^\w+$/, required => 1 },
2313             auto_advance => {},
2314             };
2315 0           my $err = $self->_check_parameters( $okParams, $args );
2316 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2317 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to pay it." ) );
2318 0   0       my $hash = $self->post( "invoices/${id}/finalize", $args ) || return;
2319 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2320             }
2321              
2322             ## Make everyone happy, British English and American English
2323             *invoice_finalize = \&invoice_finalise;
2324              
2325             sub invoice_lines
2326             {
2327 0     0 0   my $self = shift( @_ );
2328 0 0         return( $self->error( "No parameters were provided to get the invoice line items." ) ) if( !scalar( @_ ) );
2329 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2330             ## There are no expandable properties as of 2020-02-14
2331 0           my $okParams =
2332             {
2333             id => { re => qr/^\w+$/, required => 1 },
2334             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
2335             ending_before => { re => qr/^\w+$/ },
2336             limit => { re => qr/^\d+$/ },
2337             starting_after => { re => qr/^\w+$/ },
2338             };
2339 0           my $err = $self->_check_parameters( $okParams, $args );
2340 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2341 0           my $id = CORE::delete( $args->{id} );
2342 0 0         if( $args->{expand} )
2343             {
2344 0 0         $self->_adjust_list_expandables( $args ) || return;
2345             }
2346 0   0       my $hash = $self->get( "invoices/${id}/lines", $args ) || return;
2347 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
2348             }
2349              
2350             sub invoice_lines_upcoming
2351             {
2352 0     0 0   my $self = shift( @_ );
2353 0 0         return( $self->error( "No parameters were provided to get the incoming invoice line items." ) ) if( !scalar( @_ ) );
2354 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2355             ## If any
2356 0           my $obj = $args->{_object};
2357 0 0 0       if( ( $obj && $obj->customer ) ||
      0        
      0        
2358             ( $self->_is_object( $args->{customer} ) && $args->{customer}->isa( 'Net::API::Stripe::Customer' ) ) )
2359             {
2360 0 0         my $cust = $obj ? $obj-customer : $args->{customer};
2361 0   0       $args->{customer} = $cust->id || return( $self->error( "No customer id could be found in this customer object." ) );
2362             }
2363            
2364 0 0 0       if( ( $obj && $obj->schedule && $obj->schedule->id ) ||
      0        
      0        
      0        
      0        
2365             ( $args->{schedule} && $self->_is_object( $args->{schedule} ) && $args->{schedule}->isa( 'Net::API::Stripe::Billing::Subscription::Schedule' ) ) )
2366             {
2367 0 0         my $sched = $obj ? $obj-schedule : $args->{schedule};
2368 0   0       $args->{schedule} = $sched->id || return( $self->error( "No subscription schedule id could be found in this subscription schedule object." ) );
2369             }
2370            
2371 0 0 0       if( ( $obj && $obj->subscription && $obj->subscription->id ) ||
      0        
      0        
      0        
      0        
2372             ( $args->{subscription} && $self->_is_object( $args->{subscription} ) && $args->{subscription}->isa( 'Net::API::Stripe::Billing::Subscription' ) ) )
2373             {
2374 0 0         my $sub = $obj ? $obj-subscription : $args->{subscription};
2375 0   0       $args->{subscription} = $sub->id || return( $self->error( "No subscription id could be found in this subscription object." ) );
2376             }
2377            
2378 0           my $okParams =
2379             {
2380             customer => { re => qr/^\w+$/ },
2381             coupon => {},
2382             ending_before => { re => qr/^\w+$/ },
2383             invoice_items => { type => 'array', fields => [qw( amount currency description discountable invoiceitem metadata period.end period.start quantity tax_rates unit_amount unit_amount_decimal )] },
2384             limit => { re => qr/^\d+$/ },
2385             schedule => { re => qr/^\w+$/ },
2386             starting_after => { re => qr/^\w+$/ },
2387             subscription => { re => qr/^\w+$/ },
2388             ## A timestamp
2389             subscription_billing_cycle_anchor => {},
2390             ## A timestamp
2391             subscription_cancel_at => {},
2392             ## Boolean
2393             subscription_cancel_at_period_end => {},
2394             ## "This simulates the subscription being canceled or expired immediately."
2395             subscription_cancel_now => {},
2396             subscription_default_tax_rates => { type => 'array' },
2397             subscription_items => {},
2398             subscription_prorate => { re => qr/^(subscription_items|subscription|subscription_items|subscription_trial_end)$/ },
2399             subscription_proration_behavior => { re => qr/^(create_prorations|none|always_invoice)$/ },
2400             ## Timestamp
2401             subscription_proration_date => {},
2402             ## Timestamp
2403             subscription_start_date => {},
2404             subscription_tax_percent=> { re => qr/^\d+(\.\d+)?$/ },
2405             subscription_trial_end => {},
2406             subscription_trial_from_plan => {},
2407             };
2408 0           my $err = $self->_check_parameters( $okParams, $args );
2409 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2410 0 0         if( $args->{expand} )
2411             {
2412 0 0         $self->_adjust_list_expandables( $args ) || return;
2413             }
2414 0   0       my $hash = $self->get( 'invoices/upcoming/lines', $args ) || return;
2415 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
2416             }
2417              
2418             sub invoice_list
2419             {
2420 0     0 0   my $self = shift( @_ );
2421 0           my $args = $self->_get_args( @_ );
2422 0 0 0       if( $self->_is_object( $args->{customer} ) && $args->{customer}->isa( 'Net::API::Stripe::Customer' ) )
2423             {
2424 0   0       $args->{customer} = $args->{customer}->id || return( $self->error( "No customer id could be found in this customer object." ) );
2425             }
2426            
2427 0 0 0       if( $args->{subscription} && $self->_is_object( $args->{subscription} ) && $args->{subscription}->isa( 'Net::API::Stripe::Billing::Subscription' ) )
      0        
2428             {
2429 0   0       $args->{subscription} = $args->{subscription}->id || return( $self->error( "No subscription id could be found in this subscription object." ) );
2430             }
2431            
2432             my $okParams =
2433             {
2434 0           expandable => { allowed => $EXPANDABLES->{invoice}, data_prefix_is_ok => 1 },
2435             collection_method => { re => qr/^(charge_automatically|send_invoice)$/ },
2436             created => { re => qr/^\d+$/ },
2437             'created.gt' => { re => qr/^\d+$/ },
2438             'created.gte' => { re => qr/^\d+$/ },
2439             'created.lt' => { re => qr/^\d+$/ },
2440             'created.lte' => { re => qr/^\d+$/ },
2441             customer => { re => qr/^\w+$/ },
2442             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
2443             'due_date.gt' => { re => qr/^\d+$/ },
2444             'due_date.gte' => { re => qr/^\d+$/ },
2445             'due_date.lt' => { re => qr/^\d+$/ },
2446             'due_date.lte' => { re => qr/^\d+$/ },
2447             ending_before => { re => qr/^\w+$/ },
2448             limit => { re => qr/^\d+$/ },
2449             starting_after => { re => qr/^\w+$/ },
2450             status => { re => qr/^(draft|open|paid|uncollectible|void)$/ },
2451             subscription => { re => qr/^\w+$/ },
2452             };
2453 0           my $err = $self->_check_parameters( $okParams, $args );
2454 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2455 0 0         if( $args->{expand} )
2456             {
2457 0 0         $self->_adjust_list_expandables( $args ) || return;
2458             }
2459 0   0       my $hash = $self->get( 'invoices', $args ) || return;
2460 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
2461             }
2462              
2463             sub invoice_pay
2464             {
2465 0     0 0   my $self = shift( @_ );
2466 0 0         return( $self->error( "No parameters were provided to pay invoice." ) ) if( !scalar( @_ ) );
2467 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2468 0           my $obj = $args->{_object};
2469 0 0 0       if( ( $obj && $obj->payment_method ) ||
      0        
      0        
      0        
2470             ( $args->{payment_method} && $self->_is_object( $args->{payment_method} ) && $args->{payment_method}->isa( 'Net::API::Stripe::Payment::Method' ) ) )
2471             {
2472 0 0         my $pm = $obj ? $obj->payment_method : $args->{payment_method};
2473 0   0       $args->{payment_method} = $pm->id || return( $self->error( "No payment method id could be found in this payment method object." ) );
2474             }
2475            
2476 0 0 0       if( ( $obj && $obj->source ) ||
      0        
      0        
      0        
2477             ( $args->{source} && $self->_is_object( $args->{source} ) && $args->{source}->isa( 'Net::API::Stripe::Payment::Source' ) ) )
2478             {
2479 0 0         my $src = $obj ? $obj->source : $args->{source};
2480 0   0       $args->{source} = $src->id || return( $self->error( "No payment source id could be found in this payment source object." ) );
2481             }
2482             my $okParams =
2483             {
2484             expandable => { allowed => $EXPANDABLES->{invoice} },
2485 0           id => { re => qr/^\w+$/, required => 1 },
2486             ## Boolean for the case where the amount received is not the exact one claimed and to basically give it up
2487             forgive => {},
2488             ## Boolean
2489             off_session => {},
2490             ## Boolean: paid outside of Stripe
2491             paid_out_of_band => {},
2492             payment_method => { re => qr/^\w+$/ },
2493             source => { re => qr/^\w+$/ },
2494             };
2495 0           my $err = $self->_check_parameters( $okParams, $args );
2496 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2497 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to pay it." ) );
2498 0   0       my $hash = $self->post( "invoices/${id}/pay", $args ) || return;
2499 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2500             }
2501              
2502             sub invoice_retrieve
2503             {
2504 0     0 0   my $self = shift( @_ );
2505 0 0         return( $self->error( "No parameters were provided to retrieve invoice information." ) ) if( !scalar( @_ ) );
2506 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2507             my $okParams =
2508             {
2509             expandable => { allowed => $EXPANDABLES->{invoice} },
2510 0           id => { re => qr/^\w+$/, required => 1 }
2511             };
2512 0           my $err = $self->_check_parameters( $okParams, $args );
2513 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2514 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to retrieve its information." ) );
2515 0   0       my $hash = $self->get( "invoices/${id}", $args ) || return;
2516 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2517             }
2518              
2519             sub invoice_send
2520             {
2521 0     0 0   my $self = shift( @_ );
2522 0 0         return( $self->error( "No parameters were provided to send invoice." ) ) if( !scalar( @_ ) );
2523 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2524             my $okParams =
2525             {
2526             expandable => { allowed => $EXPANDABLES->{invoice} },
2527 0           id => { re => qr/^\w+$/, required => 1 },
2528             };
2529 0           my $err = $self->_check_parameters( $okParams, $args );
2530 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2531 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to send it." ) );
2532 0   0       my $hash = $self->post( "invoices/${id}/send", $args ) || return;
2533 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2534             }
2535              
2536             sub invoice_upcoming
2537             {
2538 0     0 0   my $self = shift( @_ );
2539 0 0         return( $self->error( "No parameters were provided to retrieve an upcoming invoice." ) ) if( !scalar( @_ ) );
2540 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2541            
2542 0           my $obj = $args->{_object};
2543 0 0 0       if( ( $obj && $obj->customer ) ||
      0        
      0        
2544             ( $self->_is_object( $args->{customer} ) && $args->{customer}->isa( 'Net::API::Stripe::Customer' ) ) )
2545             {
2546 0 0         my $cust = $obj ? $obj->customer : $args->{customer};
2547 0   0       $args->{customer} = $cust->id || return( $self->error( "No customer id could be found in this customer object." ) );
2548             }
2549            
2550 0 0 0       if( ( $obj && $obj->schedule ) ||
      0        
      0        
      0        
2551             ( $args->{schedule} && $self->_is_object( $args->{schedule} ) && $args->{schedule}->isa( 'Net::API::Stripe::Billing::Subscription::Schedule' ) ) )
2552             {
2553 0 0         my $sched = $obj ? $obj->schedule : $args->{schedule};
2554 0   0       $args->{schedule} = $sched->id || return( $self->error( "No subscription schedule id could be found in this subscription schedule object." ) );
2555             }
2556            
2557 0 0 0       if( ( $obj && $obj->subscription ) ||
      0        
      0        
      0        
2558             ( $args->{subscription} && $self->_is_object( $args->{subscription} ) && $args->{subscription}->isa( 'Net::API::Stripe::Billing::Subscription' ) ) )
2559             {
2560 0 0         my $sub = $obj ? $obj->subscription : $args->{subscription};
2561 0   0       $args->{subscription} = $sub->id || return( $self->error( "No subscription id could be found in this subscription object." ) );
2562             }
2563            
2564             my $okParams =
2565             {
2566             expandable => { allowed => $EXPANDABLES->{invoice} },
2567 0           customer => { re => qr/^\w+$/ },
2568             coupon => {},
2569             invoice_items => { type => 'array', fields => [qw( amount currency description discountable invoiceitem metadata period.end period.start quantity tax_rates unit_amount unit_amount_decimal )] },
2570             schedule => { re => qr/^\w+$/ },
2571             subscription => { re => qr/^\w+$/ },
2572             ## A timestamp
2573             subscription_billing_cycle_anchor => {},
2574             ## A timestamp
2575             subscription_cancel_at => {},
2576             ## Boolean
2577             subscription_cancel_at_period_end => {},
2578             ## "This simulates the subscription being canceled or expired immediately."
2579             subscription_cancel_now => {},
2580             subscription_default_tax_rates => { type => 'array' },
2581             subscription_items => {},
2582             subscription_prorate => { re => qr/^(subscription_items|subscription|subscription_items|subscription_trial_end)$/ },
2583             subscription_proration_behavior => { re => qr/^(create_prorations|none|always_invoice)$/ },
2584             ## Timestamp
2585             subscription_proration_date => {},
2586             ## Timestamp
2587             subscription_start_date => {},
2588             subscription_tax_percent => { re => qr/^\d+(\.\d+)?$/ },
2589             subscription_trial_end => {},
2590             subscription_trial_from_plan => {},
2591             };
2592 0           my $err = $self->_check_parameters( $okParams, $args );
2593 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2594 0   0       my $hash = $self->post( 'invoices/upcoming', $args ) || return;
2595 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2596             }
2597              
2598             sub invoice_update
2599             {
2600 0     0 0   my $self = shift( @_ );
2601 0 0         return( $self->error( "No parameters were provided to update an invoice" ) ) if( !scalar( @_ ) );
2602 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2603            
2604             my $okParams =
2605             {
2606             expandable => { allowed => $EXPANDABLES->{invoice} },
2607 0           id => { re => qr/^\w+$/, required => 1 },
2608             application_fee_amount => { re => qr/^\d+$/ },
2609             auto_advance => {},
2610             collection_method => { re => qr/^(charge_automatically|send_invoice)$/ },
2611             custom_fields => { fields => [qw( name value )], type => 'array' },
2612             days_until_due => { re => qr/^\d+$/ },
2613             default_payment_method => { re => qr/^\w+$/ },
2614             default_source => { re => qr/^\w+$/ },
2615             default_tax_rates => { re => qr/^\d+(?:\.\d+)?$/ },
2616             description => {},
2617             due_date => {},
2618             footer => {},
2619             metadata => { type => 'hash' },
2620             statement_descriptor => {},
2621             tax_percent => { re => qr/^\d+(?:\.\d+)?$/ },
2622             };
2623             ## We found some errors
2624 0           my $err = $self->_check_parameters( $okParams, $args );
2625             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
2626 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2627 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to update invoice's details" ) );
2628 0   0       my $hash = $self->post( "invoices/${id}", $args ) || return;
2629 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2630             }
2631              
2632             sub invoice_void
2633             {
2634 0     0 0   my $self = shift( @_ );
2635 0 0         return( $self->error( "No parameters were provided to void invoice information." ) ) if( !scalar( @_ ) );
2636 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2637             my $okParams =
2638             {
2639             expandable => { allowed => $EXPANDABLES->{invoice} },
2640 0           id => { re => qr/^\w+$/, required => 1 }
2641             };
2642 0           my $err = $self->_check_parameters( $okParams, $args );
2643 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2644 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to void it." ) );
2645 0   0       my $hash = $self->post( "invoices/${id}/void", $args ) || return;
2646 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2647             }
2648              
2649             sub invoice_write_off
2650             {
2651 0     0 1   my $self = shift( @_ );
2652 0 0         return( $self->error( "No parameters were provided to make invoice uncollectible." ) ) if( !scalar( @_ ) );
2653 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Invoice', @_ );
2654             my $okParams =
2655             {
2656             expandable => { allowed => $EXPANDABLES->{invoice} },
2657 0           id => { re => qr/^\w+$/, required => 1 }
2658             };
2659 0           my $err = $self->_check_parameters( $okParams, $args );
2660 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2661 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No invoice id was provided to make it uncollectible." ) );
2662 0   0       my $hash = $self->post( "invoices/${id}/mark_uncollectible", $args ) || return;
2663 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Invoice', $hash ) );
2664             }
2665              
2666 0     0 1   sub invoice_item { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Invoice::Item', @_ ) ); }
2667              
2668 0     0 1   sub invoice_line_item { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Invoice::LineItem', @_ ) ); }
2669              
2670 0     0 1   sub invoice_settings { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Invoice::Settings', @_ ) ); }
2671              
2672             # sub issuing { return( shift->_instantiate( 'issuing', 'Net::API::Stripe::Issuing' ) ) }
2673              
2674 0     0 1   sub issuing_card { return( shift->_response_to_object( 'Net::API::Stripe::Issuing::Card', @_ ) ); }
2675              
2676 0     0 1   sub issuing_dispute { return( shift->_response_to_object( 'Net::API::Stripe::Issuing::Dispute', @_ ) ); }
2677              
2678 0     0 1   sub issuing_transaction { return( shift->_response_to_object( 'Net::API::Stripe::Issuing::Transaction', @_ ) ); }
2679              
2680 0     0 1   sub json { return( JSON->new->allow_nonref ); }
2681              
2682             sub key
2683             {
2684 0     0 1   my $self = shift( @_ );
2685 0 0         if( @_ )
2686             {
2687 0           my $key = $self->{key} = shift( @_ );
2688 0           my $auth = 'Basic ' . MIME::Base64::encode_base64( $key . ':' );
2689 0           $self->auth( $auth );
2690             }
2691 0           return( $self->{key} );
2692             }
2693              
2694 0     0 1   sub livemode { return( shift->_set_get_boolean( 'livemode', @_ ) ); }
2695              
2696 0     0 1   sub location { return( shift->_response_to_object( 'Net::API::Stripe::Terminal::Location', @_ ) ); }
2697              
2698 0     0 1   sub login_link { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Account::LoginLink', @_ ) ); }
2699              
2700 0     0 1   sub order { return( shift->_response_to_object( 'Net::API::Stripe::Order' ) ) }
2701              
2702 0     0 1   sub order_item { return( shift->_response_to_object( 'Net::API::Stripe::Order::Item' ) ) }
2703              
2704             ## subs to access child packages
2705 0     0 1   sub payment_intent { return( shift->_response_to_object( 'Net::API::Stripe::Payment::Intent', @_ ) ); }
2706              
2707 0     0 1   sub payment_method { return( shift->_response_to_object( 'Net::API::Stripe::Payment::Method', @_ ) ); }
2708              
2709             sub payment_methods
2710             {
2711 0     0 0   my $self = shift( @_ );
2712 0           my $action = shift( @_ );
2713 0           my $allowed = [qw( create retrieve update list attach detach )];
2714 0   0       my $meth = $self->_get_method( 'payment_method', $action, $allowed ) || return;
2715 0           return( $self->$meth( @_ ) );
2716             }
2717              
2718             sub payment_method_attach
2719             {
2720 0     0 0   my $self = shift( @_ );
2721 0 0         return( $self->error( "No parameters were provided to attach a payment method" ) ) if( !scalar( @_ ) );
2722 0           my $args;
2723 0 0         if( $self->_is_object( $_[0] ) )
2724             {
2725 0 0         if( $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0          
2726             {
2727 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
2728 0           my $obj = $args->{_object};
2729 0           $args->{customer} = $obj->id;
2730 0 0         $args->{id} = $obj->payment_method->id if( $obj->payment_method );
2731             }
2732             elsif( $_[0]->isa( 'Net::API::Stripe::Payment::Method' ) )
2733             {
2734 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Method', @_ );
2735             }
2736             }
2737             else
2738             {
2739 0           $args = $self->_get_args( @_ );
2740             }
2741             my $okParams =
2742             {
2743             expandable => { allowed => $EXPANDABLES->{payment_method} },
2744 0           id => { re => qr/^\w+$/, required => 1 },
2745             customer => { re => qr/^\w+$/, required => 1 },
2746             };
2747 0           my $err = $self->_check_parameters( $okParams, $args );
2748 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2749 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No payment method id was provided to attach to attach it to the customer with id \"$args->{customer}\"." ) );
2750 0   0       my $hash = $self->post( "payment_methods/${id}/attach", $args ) || return;
2751 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Method', $hash ) );
2752             }
2753              
2754             sub payment_method_create
2755             {
2756 0     0 0   my $self = shift( @_ );
2757 0 0         return( $self->error( "No parameters were provided to create a payment_method" ) ) if( !scalar( @_ ) );
2758 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Method', @_ );
2759             my $okParams =
2760             {
2761             expandable => { allowed => $EXPANDABLES->{payment_method} },
2762 0           type => { re => qr/^(?:card|fpx|ideal|sepa_debit)$/, required => 1 },
2763             billing_details => { fields => [qw( address.city address.country address.line1 address.line2 address.postal_code address.state email name phone )] },
2764             metadata => { type => 'hash' },
2765             card => { fields => [qw( exp_month exp_year number cvc )] },
2766             fpx => { fields => [qw( bank )] },
2767             ideal => { fields => [qw( bank )] },
2768             sepa_debit => { fields => [qw( iban )] },
2769             };
2770 0           my $err = $self->_check_parameters( $okParams, $args );
2771 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2772 0   0       my $hash = $self->post( 'payment_methods', $args ) || return;
2773 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Method', $hash ) );
2774             }
2775              
2776             ## https://stripe.com/docs/api/payment_methods/detach
2777             sub payment_method_detach
2778             {
2779 0     0 0   my $self = shift( @_ );
2780 0 0         return( $self->error( "No parameters were provided to detach a payment method." ) ) if( !scalar( @_ ) );
2781 0           my $args;
2782 0 0         if( $self->_is_object( $_[0] ) )
2783             {
2784 0 0         if( $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0          
2785             {
2786 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
2787 0           my $obj = $args->{_object};
2788 0           $args->{customer} = $obj->id;
2789 0 0         if( $obj->payment_method )
    0          
2790             {
2791 0           $args->{id} = $obj->payment_method->id;
2792             }
2793             elsif( $obj->invoice_settings->default_payment_method )
2794             {
2795 0           $args->{id} = $obj->invoice_settings->default_payment_method->id;
2796             }
2797 0 0         return( $self->error( "No payent method id could be found in this customer object." ) ) if( !$args->{id} );
2798             }
2799             elsif( $_[0]->isa( 'Net::API::Stripe::Payment::Method' ) )
2800             {
2801 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Method', @_ );
2802             }
2803             }
2804             else
2805             {
2806 0           $args = $self->_get_args( @_ );
2807             }
2808             my $okParams =
2809             {
2810             expandable => { allowed => $EXPANDABLES->{payment_method} },
2811 0           id => { re => qr/^\w+$/, required => 1 },
2812             };
2813 0           my $err = $self->_check_parameters( $okParams, $args );
2814 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2815 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No payment method id was provided to attach to attach it to the customer with id \"$args->{customer}\"." ) );
2816 0   0       my $hash = $self->post( "payment_methods/${id}/detach", $args ) || return;
2817 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Method', $hash ) );
2818             }
2819              
2820             sub payment_method_list
2821             {
2822 0     0 0   my $self = shift( @_ );
2823 0           my $args = $self->_get_args( @_ );
2824             my $okParams =
2825             {
2826 0           expandable => { allowed => $EXPANDABLES->{payment_method}, data_prefix_is_ok => 1 },
2827             customer => { required => },
2828             type => { re => qr/^(?:card|fpx|ideal|sepa_debit)$/, required => 1 },
2829             ending_before => {},
2830             limit => { re => qr/^\d+$/ },
2831             starting_after => {},
2832             };
2833 0           my $err = $self->_check_parameters( $okParams, $args );
2834 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2835 0 0         if( $args->{expand} )
2836             {
2837 0 0         $self->_adjust_list_expandables( $args ) || return;
2838             }
2839 0   0       my $hash = $self->get( 'payment_methods', $args ) || return;
2840 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
2841             }
2842              
2843             sub payment_method_retrieve
2844             {
2845 0     0 0   my $self = shift( @_ );
2846 0 0         return( $self->error( "No parameters were provided to retrieve payment method information." ) ) if( !scalar( @_ ) );
2847 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Method', @_ );
2848             my $okParams =
2849             {
2850             expandable => { allowed => $EXPANDABLES->{payment_method} },
2851 0           id => { re => qr/^\w+$/, required => 1 }
2852             };
2853 0           my $err = $self->_check_parameters( $okParams, $args );
2854 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2855 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No payment method id was provided to retrieve its information." ) );
2856 0   0       my $hash = $self->get( "payment_methods/${id}", $args ) || return;
2857 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Method', $hash ) );
2858             }
2859              
2860             ## https://stripe.com/docs/api/payment_methods/update
2861             sub payment_method_update
2862             {
2863 0     0 0   my $self = shift( @_ );
2864 0 0         return( $self->error( "No parameters were provided to update a payment method" ) ) if( !scalar( @_ ) );
2865 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Method', @_ );
2866             my $okParams =
2867             {
2868             expandable => { allowed => $EXPANDABLES->{payment_method} },
2869 0           id => { re => qr/^\w+$/, required => 1 },
2870             billing_details => { fields => [qw( address.city address.country address.line1 address.line2 address.postal_code address.state email name phone )] },
2871             metadata => { type => 'hash' },
2872             card => { fields => [qw( exp_month exp_year )] },
2873             sepa_debit => { fields => [qw( iban )] },
2874             };
2875             ## We found some errors
2876 0           my $err = $self->_check_parameters( $okParams, $args );
2877             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
2878 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2879 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No payment method id was provided to update payment method's details" ) );
2880 0   0       my $hash = $self->post( "payment_methods/${id}", $args ) || return;
2881 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Method', $hash ) );
2882             }
2883              
2884 0     0 1   sub payout { return( shift->_response_to_object( 'Net::API::Stripe::Payout', @_ ) ); }
2885              
2886 0     0 1   sub person { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Person', @_ ) ); }
2887              
2888 0     0 1   sub plan { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Plan', @_ ) ); }
2889              
2890             sub plans
2891             {
2892 0     0 0   my $self = shift( @_ );
2893 0           my $action = shift( @_ );
2894 0           my $allowed = [qw( create retrieve update list delete )];
2895 0   0       my $meth = $self->_get_method( 'plan', $action, $allowed ) || return;
2896 0           return( $self->$meth( @_ ) );
2897             }
2898              
2899             ## Find plan by product id or nickname
2900             sub plan_by_product
2901             {
2902 0     0 0   my $self = shift( @_ );
2903 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Product', @_ );
2904 0           my $id = CORE::delete( $args->{id} );
2905 0           my $nickname = CORE::delete( $args->{nickname} );
2906 0 0 0       return( $self->error( "No product id or plan name was provided to find its related product." ) ) if( !$id && !$nickname );
2907 0           $self->message( 3, "Finding the plans associated with a product using product id '$id' and product nickname '$nickname'." );
2908 0 0         $args->{product} = $id if( $id );
2909 0           my $check_both_active_and_inactive = 0;
2910 0 0         if( !CORE::length( $args->{active} ) )
2911             {
2912 0           $check_both_active_and_inactive++;
2913 0           $args->{active} = $self->true;
2914             }
2915 0   0       my $list = $self->plans( list => $args ) || return;
2916 0           $self->message( 3, "http request issued is: ", $self->http_request->as_string );
2917 0           my $objects = [];
2918 0           while( my $this = $list->next )
2919             {
2920             ## If this was specified, this is a restrictive query
2921 0 0 0       if( $nickname && $this->nickname eq $nickname )
    0          
2922             {
2923 0           CORE::push( @$objects, $this );
2924             }
2925             ## or at least we have this
2926             elsif( $id )
2927             {
2928 0           CORE::push( @$objects, $this );
2929             }
2930             }
2931             ## Now, we also have to check for inactive plans, because Stripe requires the active parameter to be provided or else it defaults to inactive
2932             ## How inefficient...
2933 0 0         if( $check_both_active_and_inactive )
2934             {
2935 0           $args->{active} = $self->false;
2936 0   0       my $list = $self->plans( list => $args ) || return;
2937 0           my $objects = [];
2938 0           while( my $this = $list->next )
2939             {
2940 0 0 0       if( $nickname && $this->nickname eq $nickname )
    0          
2941             {
2942 0           CORE::push( @$objects, $this );
2943             }
2944             elsif( $id )
2945             {
2946 0           CORE::push( @$objects, $this );
2947             }
2948             }
2949             }
2950 0           return( $objects );
2951             }
2952              
2953             sub plan_create
2954             {
2955 0     0 0   my $self = shift( @_ );
2956 0 0         return( $self->error( "No parameters were provided to create a plan" ) ) if( !scalar( @_ ) );
2957 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Plan', @_ );
2958 0           my $obj = $args->{_object};
2959 0 0 0       if( $self->_is_object( $args->{product} ) && $args->{product}->isa( 'Net::API::Stripe::Product' ) )
2960             {
2961 0           my $prod_hash = $args->{product}->as_hash({ json => 1 });
2962 0           $args->{product} = $prod_hash;
2963             }
2964             #$self->message( 3, "Data to be submitted to create a plan is: ", sub{ $self->dumper( $args ) });
2965             #exit;
2966             my $okParams =
2967             {
2968             expandable => { allowed => $EXPANDABLES->{plan} },
2969 0           id => {},
2970             active => {},
2971             aggregate_usage => {},
2972             amount => { required => 1 },
2973             amount_decimal => {},
2974             billing_scheme => {},
2975             currency => { required => 1 },
2976             interval => { requried => 1, re => qr/^(?:day|week|month|year)$/ },
2977             interval_count => {},
2978             metadata => { type => 'hash' },
2979             nickname => {},
2980             product => { required => 1 },
2981             tiers => { fields => [qw( up_to flat_amount flat_amount_decimal unit_amount unit_amount_decimal )] },
2982             tiers_mode => { re => qr/^(graduated|volume)$/ },
2983             transform_usage => { fields => [qw( divide_by round )] },
2984             trial_period_days => {},
2985             usage_type => { re => qr/^(?:metered|licensed)$/ },
2986             };
2987 0           my $err = $self->_check_parameters( $okParams, $args );
2988 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
2989 0   0       my $hash = $self->post( 'plans', $args ) || return;
2990 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Plan', $hash ) );
2991             }
2992              
2993             ## https://stripe.com/docs/api/customers/delete?lang=curl
2994             ## "Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer."
2995             sub plan_delete
2996             {
2997 0     0 0   my $self = shift( @_ );
2998 0 0         return( $self->error( "No parameters were provided to delete plan information." ) ) if( !scalar( @_ ) );
2999 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Plan', @_ );
3000             my $okParams =
3001             {
3002             expandable => { allowed => $EXPANDABLES->{plan} },
3003 0           id => { re => qr/^\w+$/, required => 1 }
3004             };
3005 0           my $err = $self->_check_parameters( $okParams, $args );
3006 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3007 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No plan id was provided to delete its information." ) );
3008 0   0       my $hash = $self->delete( "plans/${id}", $args ) || return;
3009 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Plan', $hash ) );
3010             }
3011              
3012             sub plan_list
3013             {
3014 0     0 0   my $self = shift( @_ );
3015 0           my $args = $self->_get_args( @_ );
3016 0 0 0       if( $self->_is_object( $args->{product} ) && $args->{product}->isa( 'Net::API::Stripe::Product' ) )
3017             {
3018 0           my $prod_hash = $args->{product}->as_hash({ json => 1 });
3019 0 0         $args->{product} = $prod_hash->{id} ? $prod_hash->{id} : undef();
3020             }
3021             my $okParams =
3022             {
3023 0           expandable => { allowed => $EXPANDABLES->{plan}, data_prefix_is_ok => 1 },
3024             # boolean
3025             'active' => {},
3026             'created' => { re => qr/^\d+$/ },
3027             'created.gt' => { re => qr/^\d+$/ },
3028             'created.gte' => { re => qr/^\d+$/ },
3029             'created.lt' => { re => qr/^\d+$/ },
3030             'created.lte' => { re => qr/^\d+$/ },
3031             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
3032             'ending_before' => {},
3033             'limit' => { re => qr/^\d+$/ },
3034             'product' => { re => qr/^\w+$/ },
3035             'starting_after' => {},
3036             };
3037 0           foreach my $bool ( qw( active ) )
3038             {
3039 0 0         next if( !CORE::length( $args->{ $bool } ) );
3040 0 0 0       $args->{ $bool } = ( $args->{ $bool } eq 'true' || ( $args->{ $bool } ne 'false' && $args->{ $bool } ) ) ? 'true' : 'false';
3041             }
3042 0           my $err = $self->_check_parameters( $okParams, $args );
3043 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3044 0 0         if( $args->{expand} )
3045             {
3046 0 0         $self->_adjust_list_expandables( $args ) || return;
3047             }
3048 0   0       my $hash = $self->get( 'plans', $args ) || return;
3049 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
3050             }
3051              
3052             sub plan_retrieve
3053             {
3054 0     0 0   my $self = shift( @_ );
3055 0 0         return( $self->error( "No parameters were provided to retrieve plan information." ) ) if( !scalar( @_ ) );
3056 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Plan', @_ );
3057             my $okParams =
3058             {
3059             expandable => { allowed => $EXPANDABLES->{plan} },
3060 0           id => { re => qr/^\w+$/, required => 1 }
3061             };
3062 0           my $err = $self->_check_parameters( $okParams, $args );
3063 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3064 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No plan id was provided to retrieve its information." ) );
3065 0   0       my $hash = $self->get( "plans/${id}", $args ) || return;
3066 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Plan', $hash ) );
3067             }
3068              
3069             ## https://stripe.com/docs/api/customers/update?lang=curl
3070             sub plan_update
3071             {
3072 0     0 0   my $self = shift( @_ );
3073 0 0         return( $self->error( "No parameters were provided to update a plan" ) ) if( !scalar( @_ ) );
3074 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Plan', @_ );
3075 0 0 0       if( $self->_is_object( $args->{product} ) && $args->{product}->isa( 'Net::API::Stripe::Product' ) )
3076             {
3077 0           $args->{product} = $args->{product}->id;
3078             }
3079             my $okParams =
3080             {
3081             expandable => { allowed => $EXPANDABLES->{plan} },
3082 0           id => { required => 1 },
3083             active => { re => qr/^(?:true|False)$/ },
3084             metadata => { type => 'hash' },
3085             nickname => {},
3086             product => { re => qr/^\w+$/ },
3087             trial_period_days => {},
3088             };
3089             ## We found some errors
3090 0           my $err = $self->_check_parameters( $okParams, $args );
3091             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
3092 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3093 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No plan id was provided to update plan's details" ) );
3094 0   0       my $hash = $self->post( "plans/${id}", $args ) || return;
3095 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Plan', $hash ) );
3096             }
3097              
3098             sub post
3099             {
3100 0     0 1   my $self = shift( @_ );
3101 0   0       my $path = shift( @_ ) || return( $self->error( "No api endpoint (path) was provided." ) );
3102 0           my $args = shift( @_ );
3103 0 0 0       return( $self->error( "http query parameters provided were not a hash reference." ) ) if( $args && ref( $args ) ne 'HASH' );
3104 0           my $ua = $self->http_client;
3105 0           my $api = $self->api_uri->clone;
3106 0 0 0       if( $self->_is_object( $path ) && $path->can( 'path' ) )
3107             {
3108 0           $self->message( 3, "$path is a URI object" );
3109 0           $api->path( undef() );
3110 0           $path = $path->path;
3111             }
3112             else
3113             {
3114 0 0         substr( $path, 0, 0 ) = '/' unless( substr( $path, 0, 1 ) eq '/' );
3115             }
3116             # my $ref = $self->_encode_params( $args );
3117             # $self->message( 3, "Redeem by ref is '", ref( $args->{redeem_by} ), "'." );
3118             # $self->message( 3, $self->dump( $ref ) ); exit;
3119 0           my $h = [];
3120 0 0         if( exists( $args->{idempotency} ) )
3121             {
3122 0 0         $args->{idempotency} = $self->generate_uuid if( !length( $args->{idempotency} ) );
3123 0           $self->messagef( 3, "Using idempotency key %s", $args->{idempotency} );
3124 0           push( @$h, 'Idempotency-Key', CORE::delete( $args->{idempotency} ) );
3125             }
3126 0 0         my $req = HTTP::Request->new(
3127             'POST', $api . $path,
3128             $h,
3129             ( $args ? $self->_encode_params( $args ) : undef() )
3130             );
3131 0           $self->message( 3, "Post request is: ", $req->as_string );
3132 0           return( $self->_make_request( $req ) );
3133             }
3134              
3135             ## Using rfc2388 rules
3136             ## https://tools.ietf.org/html/rfc2388
3137             sub post_multipart
3138             {
3139 0     0 1   my $self = shift( @_ );
3140 0   0       my $path = shift( @_ ) || return( $self->error( "No api endpoint (path) was provided." ) );
3141 0           my $args = shift( @_ );
3142 0 0 0       return( $self->error( "http query parameters provided were not a hash reference." ) ) if( $args && ref( $args ) ne 'HASH' );
3143 0           my $ua = $self->http_client;
3144 0           my $api = $self->api_uri->clone;
3145 0 0 0       if( $self->_is_object( $path ) && $path->can( 'path' ) )
3146             {
3147 0           $self->message( 3, "$path is a URI object" );
3148 0           $api->path( undef() );
3149 0           $path = $path->path;
3150             }
3151             else
3152             {
3153 0 0         substr( $path, 0, 0 ) = '/' unless( substr( $path, 0, 1 ) eq '/' );
3154             }
3155 0           my $h = HTTP::Headers->new(
3156             Content_Type => 'multipart/form-data',
3157             );
3158 0 0         if( exists( $args->{idempotency} ) )
3159             {
3160 0 0         $args->{idempotency} = $self->generate_uuid if( !length( $args->{idempotency} ) );
3161 0           $self->messagef( 3, "Using idempotency key %s", $args->{idempotency} );
3162 0           $h->header( 'Idempotency-Key' => CORE::delete( $args->{idempotency} ) );
3163             }
3164 0           my $req = HTTP::Request->new( POST => $api . $path, $h );
3165 0           my $data = $self->_encode_params_multipart( $args, { encoding => 'quoted-printable' } );
3166 0           foreach my $f ( keys( %$data ) )
3167             {
3168 0           foreach my $ref ( @{$data->{ $f }} )
  0            
3169             {
3170 0 0         if( $ref->{filename} )
3171             {
3172 0           my $fname = $ref->{filename};
3173             $req->add_part( HTTP::Message->new(
3174             HTTP::Headers->new(
3175             Content_Disposition => "form-data; name=\"${f}\"; filename=\"${fname}\"",
3176             Content_Type => ( $ref->{type} ? $ref->{type} : 'application/octet-stream' ),
3177             ( $ref->{encoding} ? ( Content_Transfer_Encoding => $ref->{encoding} ) : undef() ),
3178             Content_Length => CORE::length( $ref->{value} ),
3179             ),
3180             $ref->{value}
3181 0 0         ));
    0          
3182             }
3183             else
3184             {
3185 0   0       $ref->{type} ||= 'text/plain';
3186             $req->add_part( HTTP::Message->new(
3187             HTTP::Headers->new(
3188             Content_Disposition => "form-data; name=\"${f}\"",
3189             Content_Type => ( $ref->{type} eq 'text/plain' ? 'text/plain;charset="utf-8"' : $ref->{type} ),
3190             Content_Length => CORE::length( $ref->{value} ),
3191             Content_Transfer_Encoding => ( $ref->{encoding} ? $ref->{encoding} : '8bit' ),
3192             ),
3193             $ref->{value}
3194 0 0         ));
    0          
3195             }
3196             }
3197             }
3198 0           $self->message( 3, "Post request is: ", $req->as_string );
3199 0           return( $self->_make_request( $req ) );
3200             }
3201              
3202             sub prices
3203             {
3204 0     0 0   my $self = shift( @_ );
3205 0           my $action = shift( @_ );
3206 0           my $allowed = [qw( create retrieve update list )];
3207 0   0       my $meth = $self->_get_method( 'price', $action, $allowed ) || return;
3208 0           return( $self->$meth( @_ ) );
3209             }
3210              
3211             sub price_create
3212             {
3213 0     0 0   my $self = shift( @_ );
3214 0 0         return( $self->error( "No parameters were provided to create a price" ) ) if( !scalar( @_ ) );
3215 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Price', @_ );
3216 0           my $obj = $args->{_object};
3217 0 0 0       if( $self->_is_object( $args->{product} ) && $args->{product}->isa( 'Net::API::Stripe::Product' ) )
3218             {
3219 0           my $prod_hash = $args->{product}->as_hash({ json => 1 });
3220 0           $args->{product} = $prod_hash;
3221             }
3222             #$self->message( 3, "Data to be submitted to create a plan is: ", sub{ $self->dumper( $args ) });
3223             #exit;
3224             my $okParams =
3225             {
3226             expandable => { allowed => $EXPANDABLES->{price} },
3227 0           id => {},
3228             active => {},
3229             billing_scheme => {},
3230             currency => { required => 1 },
3231             lookup_key => {},
3232             metadata => { type => 'hash' },
3233             nickname => {},
3234             product => { required => 1 },
3235             product_data => { fields => [qw( id name active metadata statement_descriptor unit_label )] },
3236             recurring => { fields => [qw( interval aggregate_usage interval_count trial_period_days usage_type )] },
3237             transfer_lookup_key => {},
3238             transform_quantity => { fields => [qw( divide_by round )] },
3239             tiers => { fields => [qw( up_to flat_amount flat_amount_decimal unit_amount unit_amount_decimal )] },
3240             tiers_mode => { re => qr/^(graduated|volume)$/ },
3241             unit_amount => { required => 1 },
3242             unit_amount_decimal => {},
3243             };
3244 0           my $err = $self->_check_parameters( $okParams, $args );
3245 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3246 0   0       my $hash = $self->post( 'prices', $args ) || return;
3247 0           return( $self->_response_to_object( 'Net::API::Stripe::Price', $hash ) );
3248             }
3249              
3250             sub price_list
3251             {
3252 0     0 0   my $self = shift( @_ );
3253 0           my $args = $self->_get_args( @_ );
3254 0 0 0       if( $self->_is_object( $args->{product} ) && $args->{product}->isa( 'Net::API::Stripe::Product' ) )
3255             {
3256 0           my $prod_hash = $args->{product}->as_hash({ json => 1 });
3257 0 0         $args->{product} = $prod_hash->{id} ? $prod_hash->{id} : undef();
3258             }
3259             my $okParams =
3260             {
3261 0           expandable => { allowed => $EXPANDABLES->{price}, data_prefix_is_ok => 1 },
3262             # boolean
3263             active => {},
3264             created => { re => qr/^\d+$/ },
3265             'created.gt' => { re => qr/^\d+$/ },
3266             'created.gte' => { re => qr/^\d+$/ },
3267             'created.lt' => { re => qr/^\d+$/ },
3268             'created.lte' => { re => qr/^\d+$/ },
3269             currency => {},
3270             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
3271             ending_before => {},
3272             limit => { re => qr/^\d+$/ },
3273             lookup_keys => {},
3274             product => { re => qr/^\w+$/ },
3275             recurring => { fields => [qw( interval usage_type )] },
3276             starting_after => {},
3277             type => {},
3278             };
3279 0           foreach my $bool ( qw( active ) )
3280             {
3281 0 0         next if( !CORE::length( $args->{ $bool } ) );
3282 0 0 0       $args->{ $bool } = ( $args->{ $bool } eq 'true' || ( $args->{ $bool } ne 'false' && $args->{ $bool } ) ) ? 'true' : 'false';
3283             }
3284 0           my $err = $self->_check_parameters( $okParams, $args );
3285 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3286 0 0         if( $args->{expand} )
3287             {
3288 0 0         $self->_adjust_list_expandables( $args ) || return;
3289             }
3290 0   0       my $hash = $self->get( 'prices', $args ) || return;
3291 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
3292             }
3293              
3294             sub price_retrieve
3295             {
3296 0     0 0   my $self = shift( @_ );
3297 0 0         return( $self->error( "No parameters were provided to retrieve price information." ) ) if( !scalar( @_ ) );
3298 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Price', @_ );
3299             my $okParams =
3300             {
3301             expandable => { allowed => $EXPANDABLES->{price} },
3302 0           id => { re => qr/^\w+$/, required => 1 }
3303             };
3304 0           my $err = $self->_check_parameters( $okParams, $args );
3305 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3306 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No price id was provided to retrieve its information." ) );
3307 0   0       my $hash = $self->get( "prices/${id}", $args ) || return;
3308 0           return( $self->_response_to_object( 'Net::API::Stripe::Price', $hash ) );
3309             }
3310              
3311             ## https://stripe.com/docs/api/customers/update?lang=curl
3312             sub price_update
3313             {
3314 0     0 0   my $self = shift( @_ );
3315 0 0         return( $self->error( "No parameters were provided to update a price object" ) ) if( !scalar( @_ ) );
3316 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Price', @_ );
3317 0 0 0       if( $self->_is_object( $args->{product} ) && $args->{product}->isa( 'Net::API::Stripe::Product' ) )
3318             {
3319 0           $args->{product} = $args->{product}->id;
3320             }
3321             my $okParams =
3322             {
3323             expandable => { allowed => $EXPANDABLES->{price} },
3324 0           id => { required => 1 },
3325             active => { re => qr/^(?:true|False)$/ },
3326             lookup_key => {},
3327             metadata => { type => 'hash' },
3328             nickname => {},
3329             recurring => { fields => [qw( interval aggregate_usage interval_count trial_period_days usage_type )] },
3330             transfer_lookup_key => {},
3331             };
3332             ## We found some errors
3333 0           my $err = $self->_check_parameters( $okParams, $args );
3334 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3335 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No price id was provided to update price's details" ) );
3336 0   0       my $hash = $self->post( "prices/${id}", $args ) || return;
3337 0           return( $self->_response_to_object( 'Net::API::Stripe::Price', $hash ) );
3338             }
3339              
3340 0     0 1   sub product { return( shift->_response_to_object( 'Net::API::Stripe::Product', @_ ) ); }
3341              
3342             sub products
3343             {
3344 0     0 0   my $self = shift( @_ );
3345 0           my $action = shift( @_ );
3346 0           my $allowed = [qw( create retrieve update list delete )];
3347 0   0       my $meth = $self->_get_method( 'product', $action, $allowed ) || return;
3348 0           return( $self->$meth( @_ ) );
3349             }
3350              
3351             sub product_by_name
3352             {
3353 0     0 0   my $self = shift( @_ );
3354 0           my $args = $self->_get_args( @_ );
3355 0           my $name = CORE::delete( $args->{name} );
3356 0           my $nicname = CORE::delete( $args->{nickname} );
3357 0   0       my $list = $self->products( list => $args ) || return;
3358 0           my $objects = [];
3359 0           while( my $this = $list->next )
3360             {
3361 0 0 0       if( ( $name && $this->name eq $name ) ||
      0        
      0        
3362             ( $nickname && $this->nickname eq $nickname ) )
3363             {
3364 0           CORE::push( @$objects, $this );
3365             }
3366             }
3367 0           return( $objects );
3368             }
3369              
3370             sub product_create
3371             {
3372 0     0 0   my $self = shift( @_ );
3373 0 0         return( $self->error( "No parameters were provided to create a product" ) ) if( !scalar( @_ ) );
3374 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Product', @_ );
3375             my $okParams =
3376             {
3377             expandable => { allowed => $EXPANDABLES->{product} },
3378             ## Yes, an id may be provided
3379             id => {},
3380             name => { required => 1 },
3381             type => { re => qr/^(good|service)$/, required => 1 },
3382             active => {},
3383             ## Used to exist, but then disappeaared from the api
3384 0 0 0 0     attributes => sub{ return( ref( $_[0] ) eq 'ARRAY' && scalar( @{$_[0]} ) <= 5 ? undef() : "An array reference of up to 5 items was expected." ) },
3385             caption => {},
3386             deactivate_on => { type => 'array' },
3387             description => {},
3388 0 0 0 0     images => sub{ return( ref( $_[0] ) eq 'ARRAY' && scalar( @{$_[0]} ) <= 8 ? undef() : "An array reference of up to 8 images was expected." ) },
3389 0           metadata => { type => 'hash' },
3390             package_dimensions => {},
3391             shippable => {},
3392             statement_descriptor => {},
3393             unit_label => {},
3394             url => {},
3395             };
3396 0           my $err = $self->_check_parameters( $okParams, $args );
3397 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3398 0   0       my $hash = $self->post( 'products', $args ) || return;
3399 0           return( $self->_response_to_object( 'Net::API::Stripe::Product', $hash ) );
3400             }
3401              
3402             ## https://stripe.com/docs/api/customers/delete?lang=curl
3403             ## "Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer."
3404             sub product_delete
3405             {
3406 0     0 0   my $self = shift( @_ );
3407 0 0         return( $self->error( "No parameters were provided to delete product information." ) ) if( !scalar( @_ ) );
3408 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Product', @_ );
3409             my $okParams =
3410             {
3411             expandable => { allowed => $EXPANDABLES->{product} },
3412 0           id => { re => qr/^\w+$/, required => 1 }
3413             };
3414 0           my $err = $self->_check_parameters( $okParams, $args );
3415 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3416 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No product id was provided to delete its information." ) );
3417 0   0       my $hash = $self->delete( "products/${id}", $args ) || return;
3418 0           return( $self->_response_to_object( 'Net::API::Stripe::Product', $hash ) );
3419             }
3420              
3421             sub product_list
3422             {
3423 0     0 0   my $self = shift( @_ );
3424 0           my $args = $self->_get_args( @_ );
3425             my $okParams =
3426             {
3427             expandable => { allowed => $EXPANDABLES->{product} },
3428 0           'created' => { re => qr/^\d+$/ },
3429             'created.gt' => { re => qr/^\d+$/ },
3430             'created.gte' => { re => qr/^\d+$/ },
3431             'created.lt' => { re => qr/^\d+$/ },
3432             'created.lte' => { re => qr/^\d+$/ },
3433             # boolean
3434             'active' => { type => 'boolean' },
3435             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
3436             'ending_before' => {},
3437             'ids' => { type => 'array' },
3438             'limit' => { re => qr/^\d+$/ },
3439             # boolean
3440             'shippable' => { type => 'boolean' },
3441             'starting_after' => {},
3442             'type' => {},
3443             'url' => {},
3444             };
3445 0           my $err = $self->_check_parameters( $okParams, $args );
3446 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3447 0 0         if( $args->{expand} )
3448             {
3449 0 0         $self->_adjust_list_expandables( $args ) || return;
3450             }
3451 0   0       my $hash = $self->get( 'products', $args ) || return;
3452 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
3453             }
3454              
3455             sub product_retrieve
3456             {
3457 0     0 0   my $self = shift( @_ );
3458 0 0         return( $self->error( "No parameters were provided to retrieve product information." ) ) if( !scalar( @_ ) );
3459 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Product', @_ );
3460             my $okParams =
3461             {
3462             expandable => { allowed => $EXPANDABLES->{product} },
3463 0           id => { re => qr/^\w+$/, required => 1 }
3464             };
3465 0           my $err = $self->_check_parameters( $okParams, $args );
3466 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3467 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No product id was provided to retrieve its information." ) );
3468 0   0       my $hash = $self->get( "products/${id}", $args ) || return;
3469 0           return( $self->_response_to_object( 'Net::API::Stripe::Product', $hash ) );
3470             }
3471              
3472             ## https://stripe.com/docs/api/customers/update?lang=curl
3473             sub product_update
3474             {
3475 0     0 0   my $self = shift( @_ );
3476 0 0         return( $self->error( "No parameters were provided to update a product" ) ) if( !scalar( @_ ) );
3477 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Product', @_ );
3478             my $okParams =
3479             {
3480             expandable => { allowed => $EXPANDABLES->{product} },
3481             id => { re => qr/^\w+$/, required => 1 },
3482             active => {},
3483 0 0 0 0     attributes => sub{ return( ref( $_[0] ) eq 'ARRAY' && scalar( @{$_[0]} ) <= 5 ? undef() : "An array reference of up to 5 items was expected." ) },
3484             caption => {},
3485             deactivate_on => { type => 'array' },
3486             description => {},
3487 0 0 0 0     images => sub{ return( ref( $_[0] ) eq 'ARRAY' && scalar( @{$_[0]} ) <= 8 ? undef() : "An array reference of up to 8 images was expected." ) },
3488 0           metadata => { type => 'hash' },
3489             name => { required => 1 }.
3490             package_dimensions => {},
3491             shippable => {},
3492             statement_descriptor => {},
3493             type => { re => qr/^(good|service)$/, required => 1 },
3494             unit_label => {},
3495             url => {},
3496             };
3497             ## We found some errors
3498 0           my $err = $self->_check_parameters( $okParams, $args );
3499             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
3500 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3501 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No product id was provided to update product's details" ) );
3502 0   0       my $hash = $self->post( "products/${id}", $args ) || return;
3503 0           return( $self->_response_to_object( 'Net::API::Stripe::Product', $hash ) );
3504             }
3505              
3506 0     0 1   sub reader { return( shift->_response_to_object( 'Net::API::Stripe::Terminal::Reader' ) ) }
3507              
3508 0     0 1   sub refund { return( shift->_response_to_object( 'Net::API::Stripe::Refund', @_ ) ); }
3509              
3510 0     0 1   sub return { return( shift->_response_to_object( 'Net::API::Stripe::Order::Return' ) ) }
3511              
3512 0     0 1   sub review { return( shift->_response_to_object( 'Net::API::Stripe::Fraud::Review', @_ ) ); }
3513              
3514 0     0 1   sub schedule { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Schedule', @_ ) ); }
3515              
3516             sub schedules
3517             {
3518 0     0 0   my $self = shift( @_ );
3519 0           my $action = shift( @_ );
3520 0           my $allowed = [qw( create retrieve update list cancel release )];
3521 0   0       my $meth = $self->_get_method( 'schedule', $action, $allowed ) || return;
3522 0           return( $self->$meth( @_ ) );
3523             }
3524              
3525             ## https://stripe.com/docs/api/subscription_schedules/cancel?lang=curl
3526             ## "Cancels a subscription schedule and its associated subscription immediately (if the subscription schedule has an active subscription). A subscription schedule can only be canceled if its status is not_started or active."
3527             sub schedule_cancel
3528             {
3529 0     0 0   my $self = shift( @_ );
3530 0 0         return( $self->error( "No parameters were provided to cancel subscription schedule information." ) ) if( !scalar( @_ ) );
3531 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription::Schedule', @_ );
3532             my $okParams =
3533             {
3534             expandable => { allowed => $EXPANDABLES->{schedule} },
3535 0           id => { re => qr/^\w+$/, required => 1 },
3536             ## "If the subscription schedule is active, indicates whether or not to generate a final invoice that contains any un-invoiced metered usage and new/pending proration invoice items. Defaults to true."
3537             invoice_now => { type => 'boolean' },
3538             ## "If the subscription schedule is active, indicates if the cancellation should be prorated. Defaults to true."
3539             prorate => { type => 'boolean' },
3540             };
3541 0           my $err = $self->_check_parameters( $okParams, $args );
3542 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3543 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription schedule id was provided to cancel." ) );
3544 0   0       my $hash = $self->post( "subscription_schedules/${id}/cancel", $args ) || return;
3545 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Schedule', $hash ) );
3546             }
3547              
3548             sub schedule_create
3549             {
3550 0     0 0   my $self = shift( @_ );
3551 0 0         return( $self->error( "No parameters were provided to create a subscription schedule" ) ) if( !scalar( @_ ) );
3552 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription::Schedule', @_ );
3553             my $okParams =
3554             {
3555             expandable => { allowed => $EXPANDABLES->{schedule} },
3556 0           customer => {},
3557             default_settings => { fields => [qw( billing_thresholds.amount_gte billing_thresholds.reset_billing_cycle_anchor collection_method default_payment_method invoice_settings.days_until_due )] },
3558             end_behavior => { re => qr/^(release|cancel)$/ },
3559             from_subscription => {},
3560             metadata => {},
3561             phases => { type => 'array', fields => [qw( plans.plan plans.billing_thresholds.usage_gte plans.quantity plans.tax_rates application_fee_percent billing_thresholds.amount_gte billing_thresholds.reset_billing_cycle_anchor collection_method coupon default_payment_method default_tax_rates end_date invoice_settings.days_until_due iterations tax_percent trial trial_end )]},
3562             start_date => { type => 'datetime' },
3563             };
3564            
3565 0           my $obj = $args->{_object};
3566 0 0         if( $obj )
3567             {
3568 0           $args->{start_date} = $obj->current_phase->start_date->epoch;
3569             }
3570 0           my $err = $self->_check_parameters( $okParams, $args );
3571 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3572 0   0       my $hash = $self->post( 'subscription_schedules', $args ) || return;
3573 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Schedule', $hash ) );
3574             }
3575              
3576             sub schedule_list
3577             {
3578 0     0 0   my $self = shift( @_ );
3579 0           my $args = shift( @_ );
3580             my $okParams =
3581             {
3582 0           expandable => { allowed => $EXPANDABLES->{schedule}, data_prefix_is_ok => 1 },
3583             'canceled_at' => { re => qr/^\d+$/ },
3584             'canceled_at.gt' => { re => qr/^\d+$/ },
3585             'canceled_at.gte' => { re => qr/^\d+$/ },
3586             'canceled_at.lt' => { re => qr/^\d+$/ },
3587             'canceled_at.lte' => { re => qr/^\d+$/ },
3588             'completed_at' => { re => qr/^\d+$/ },
3589             'completed_at.gt' => { re => qr/^\d+$/ },
3590             'completed_at.gte' => { re => qr/^\d+$/ },
3591             'completed_at.lt' => { re => qr/^\d+$/ },
3592             'completed_at.lte' => { re => qr/^\d+$/ },
3593             'created' => { re => qr/^\d+$/ },
3594             'created.gt' => { re => qr/^\d+$/ },
3595             'created.gte' => { re => qr/^\d+$/ },
3596             'created.lt' => { re => qr/^\d+$/ },
3597             'created.lte' => { re => qr/^\d+$/ },
3598             'customer' => {},
3599             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
3600             'ending_before' => {},
3601             'limit' => { re => qr/^\d+$/ },
3602             'released_at' => { re => qr/^\d+$/ },
3603             'released_at.gt' => { re => qr/^\d+$/ },
3604             'released_at.gte' => { re => qr/^\d+$/ },
3605             'released_at.lt' => { re => qr/^\d+$/ },
3606             'released_at.lte' => { re => qr/^\d+$/ },
3607             'scheduled' => { type => 'boolean' },
3608             'starting_after' => {},
3609             };
3610 0           my $err = $self->_check_parameters( $okParams, $args );
3611 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3612 0 0         if( $args->{expand} )
3613             {
3614 0 0         $self->_adjust_list_expandables( $args ) || return;
3615             }
3616 0   0       my $hash = $self->get( 'subscription_schedules', $args ) || return;
3617 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
3618             }
3619              
3620             ## "Releases the subscription schedule immediately, which will stop scheduling of its phases, but leave any existing subscription in place. A schedule can only be released if its status is not_started or active. If the subscription schedule is currently associated with a subscription, releasing it will remove its subscription property and set the subscription’s ID to the released_subscription property."
3621             ## https://stripe.com/docs/api/subscription_schedules/release
3622             sub schedule_release
3623             {
3624 0     0 0   my $self = shift( @_ );
3625 0 0         return( $self->error( "No parameters were provided to retrieve subscription schedule information." ) ) if( !scalar( @_ ) );
3626 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription::Schedule', @_ );
3627             my $okParams =
3628             {
3629             expandable => { allowed => $EXPANDABLES->{schedule} },
3630 0           id => { re => qr/^\w+$/, required => 1 },
3631             preserve_cancel_date => { type => 'boolean' },
3632             };
3633 0           my $err = $self->_check_parameters( $okParams, $args );
3634 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3635 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription schedule id was provided to retrieve its information." ) );
3636 0   0       my $hash = $self->post( "subscription_schedules/${id}/release", $args ) || return;
3637 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Schedule', $hash ) );
3638             }
3639              
3640             sub schedule_retrieve
3641             {
3642 0     0 0   my $self = shift( @_ );
3643 0 0         return( $self->error( "No parameters were provided to retrieve subscription schedule information." ) ) if( !scalar( @_ ) );
3644 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription::Schedule', @_ );
3645             my $okParams =
3646             {
3647             expandable => { allowed => $EXPANDABLES->{schedule} },
3648 0           id => { re => qr/^\w+$/, required => 1 }
3649             };
3650 0           my $err = $self->_check_parameters( $okParams, $args );
3651 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3652 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription schedule id was provided to retrieve its information." ) );
3653 0   0       my $hash = $self->get( "subscription_schedules/${id}", $args ) || return;
3654 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Schedule', $hash ) );
3655             }
3656              
3657             ## https://stripe.com/docs/api/customers/update?lang=curl
3658             sub schedule_update
3659             {
3660 0     0 0   my $self = shift( @_ );
3661 0 0         return( $self->error( "No parameters were provided to update a subscription schedule" ) ) if( !scalar( @_ ) );
3662 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription::Schedule', @_ );
3663             my $okParams =
3664             {
3665             expandable => { allowed => $EXPANDABLES->{schedule} },
3666 0           id => { re => qr/^\w+$/, required => 1 },
3667             default_settings => { fields => [qw( billing_thresholds.amount_gte billing_thresholds.reset_billing_cycle_anchor collection_method default_payment_method invoice_settings.days_until_due )] },
3668             end_behavior => { re => qr/^(release|cancel)$/ },
3669             from_subscription => {},
3670             metadata => { type => 'hash' },
3671             phases => { type => 'array', fields => [qw( plans.plan plans.billing_thresholds.usage_gte plans.quantity plans.tax_rates application_fee_percent billing_thresholds.amount_gte billing_thresholds.reset_billing_cycle_anchor collection_method coupon default_payment_method default_tax_rates end_date invoice_settings.days_until_due iterations tax_percent trial trial_end )]},
3672             prorate => { type => 'boolean' },
3673             };
3674             ## We found some errors
3675 0           my $err = $self->_check_parameters( $okParams, $args );
3676             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
3677 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3678 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription schedule id was provided to update subscription schedule's details" ) );
3679 0   0       my $hash = $self->post( "subscription_schedules/${id}", $args ) || return;
3680 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Schedule', $hash ) );
3681             }
3682              
3683             # sub session { return( shift->_response_to_object( 'Net::API::Stripe::Session', @_ ) ); }
3684              
3685 0     0 1   sub schedule_query { return( shift->_response_to_object( 'Net::API::Stripe::Sigma::ScheduledQueryRun' ) ) }
3686              
3687 0     0 1   sub session { return( shift->_response_to_object( 'Net::API::Stripe::Checkout::Session', @_ ) ); }
3688              
3689             sub sessions
3690             {
3691 0     0 0   my $self = shift( @_ );
3692 0           my $action = shift( @_ );
3693 0           my $allowed = [qw( create retrieve list )];
3694 0   0       my $meth = $self->_get_method( 'subscription', $action, $allowed ) || return;
3695 0           return( $self->$meth( @_ ) );
3696             }
3697              
3698             ## https://stripe.com/docs/api/checkout/sessions/create
3699             ## https://stripe.com/docs/payments/checkout/fulfillment#webhooks
3700             ## See webhook event checkout.session.completed
3701             sub session_create
3702             {
3703 0     0 0   my $self = shift( @_ );
3704 0 0         return( $self->error( "No parameters were provided to create a session" ) ) if( !scalar( @_ ) );
3705 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Checkout::Session', @_ );
3706             my $okParams =
3707             {
3708             expandable => { allowed => $EXPANDABLES->{session} },
3709 0           cancel_url => { required => 1 },
3710             payment_method_types => { required => 1, re => qr/^(card|ideal)$/ },
3711             success_url => { required => 1 },
3712             billing_address_collection => { re => qr/^(auto|required)$/ },
3713             client_reference_id => {},
3714             ## ID of an existing customer, if one exists.
3715             customer => {},
3716             customer_email => {},
3717             ## array of hash reference
3718             line_items => { type => 'array', fields => [qw( amount currency name quantity description images )] },
3719             locale => { re => qr/^(local|[a-z]{2})$/ },
3720             mode => { re => qr/^(setup|subscription)$/ },
3721             payment_intent_data => { fields => [qw( application_fee_amount capture_method description metadata on_behalf_of receipt_email setup_future_usage shipping.address.line1 shipping.address.line2 shipping.address.city shipping.address.country shipping.address.postal_code shipping.address.state shipping.name shipping.carrier shipping.phone shipping.tracking_number statement_descriptor transfer_data.destination )] },
3722             setup_intent_data => { fields => [qw( description metadata on_behalf_of )] },
3723             submit_type => { re => qr/^(auto|book|donate|pay)$/ },
3724             subscription_data => { fields => [qw( items.plan items.quantity application_fee_percent metadata trial_end trial_from_plan trial_period_days )] },
3725             };
3726 0           my $err = $self->_check_parameters( $okParams, $args );
3727 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3728 0   0       my $hash = $self->post( 'checkout/sessions', $args ) || return;
3729 0           return( $self->_response_to_object( 'Net::API::Stripe::Checkout::Session', $hash ) );
3730             }
3731              
3732             sub session_list
3733             {
3734 0     0 0   my $self = shift( @_ );
3735 0           my $args = shift( @_ );
3736             my $okParams =
3737             {
3738 0           expandable => { allowed => $EXPANDABLES->{schedule}, data_prefix_is_ok => 1 },
3739             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
3740             'ending_before' => {},
3741             'limit' => { re => qr/^\d+$/ },
3742             'payment_intent' => { type => 'scalar' },
3743             'subscription' => { re => qr/^\w+$/ },
3744             'starting_after' => {},
3745             };
3746 0           my $err = $self->_check_parameters( $okParams, $args );
3747 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3748 0 0         if( $args->{expand} )
3749             {
3750 0 0         $self->_adjust_list_expandables( $args ) || return;
3751             }
3752 0   0       my $hash = $self->get( 'checkout/sessions', $args ) || return;
3753 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
3754             }
3755              
3756             sub session_retrieve
3757             {
3758 0     0 0   my $self = shift( @_ );
3759 0   0       my $args = shift( @_ ) || return( $self->error( "No parameters were provided to retrieve a tax id" ) );
3760             my $okParams =
3761             {
3762             expandable => { allowed => $EXPANDABLES->{session} },
3763 0           id => { re => qr/^\w+$/, required => 1 },
3764             };
3765 0           my $err = $self->_check_parameters( $okParams, $args );
3766 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3767 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No tax id was provided to retrieve its details" ) );
3768 0   0       my $hash = $self->get( "checkout/sessions/${id}", $args ) || return;
3769 0           return( $self->_response_to_object( 'Net::API::Stripe::Checkout::Session', $hash ) );
3770             }
3771              
3772 0     0 1   sub setup_intent { return( shift->_response_to_object( 'Net::API::Stripe::Payment::Intent::Setup', @_ ) ); }
3773              
3774             # sub sigma { return( shift->_instantiate( 'sigma', 'Net::API::Stripe::Sigma' ) ) }
3775              
3776 0     0 1   sub shipping { return( shift->_response_to_object( 'Net::API::Stripe::Shipping', @_ ) ); }
3777              
3778 0     0 1   sub sku { return( shift->_response_to_object( 'Net::API::Stripe::Order::SKU' ) ) }
3779              
3780 0     0 1   sub source { return( shift->_response_to_object( 'Net::API::Stripe::Payment::Source', @_ ) ); }
3781              
3782             sub sources
3783             {
3784 0     0 0   my $self = shift( @_ );
3785 0           my $action = shift( @_ );
3786 0           my $allowed = [qw( create retrieve update detach attach )];
3787 0   0       my $meth = $self->_get_method( 'source', $action, $allowed ) || return;
3788 0           return( $self->$meth( @_ ) );
3789             }
3790              
3791             sub source_attach
3792             {
3793 0     0 0   my $self = shift( @_ );
3794 0 0         return( $self->error( "No parameters were provided to attach a source." ) ) if( !scalar( @_ ) );
3795 0           my $args;
3796 0 0         if( $self->_is_object( $_[0] ) )
3797             {
3798 0 0         if( $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0          
3799             {
3800 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
3801             }
3802             elsif( $_[0]->isa( 'Net::API::Stripe::Payment::Source' ) )
3803             {
3804 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Source', @_ );
3805 0           my $obj = $args->{_object};
3806 0           $args->{source} = $obj->id;
3807 0 0         $args->{id} = $obj->customer->id if( $obj->customer );
3808             }
3809             }
3810             else
3811             {
3812 0           $args = $self->_get_args( @_ );
3813             }
3814             my $okParams =
3815             {
3816             expandable => { allowed => $EXPANDABLES->{session} },
3817 0           id => { re => qr/^\w+$/, required => 1 },
3818             source => { re => qr/^\w+$/, required => 1 },
3819             };
3820 0           my $err = $self->_check_parameters( $okParams, $args );
3821 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3822 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to attach the source to." ) );
3823 0   0       my $hash = $self->post( "customers/${id}/sources", $args ) || return;
3824 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Source', $hash ) );
3825             }
3826              
3827             sub source_create
3828             {
3829 0     0 0   my $self = shift( @_ );
3830 0 0         return( $self->error( "No parameters were provided to create a source" ) ) if( !scalar( @_ ) );
3831 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Source', @_ );
3832             my $okParams =
3833             {
3834             expandable => { allowed => $EXPANDABLES->{session} },
3835 0           type => { required => 1 },
3836             amount => {},
3837             currency => {},
3838             flow => {},
3839             mandate => { fields => [qw( acceptance acceptance.status acceptance.date acceptance.ip acceptance.offline.contact_email acceptance.online acceptance.type acceptance.user_agent amount currency interval notification_method )] },
3840             metadata => { type => 'hash' },
3841             owner => { fields => [qw( address.city address.country address.line1 address.line2 address.postal_code address.state email name phone )] },
3842             receiver => { fields => [qw( refund_attributes_method )] },
3843             redirect => { fields => [qw( return_url )] },
3844             source_order => { fields => [qw( items.amount items.currency items.description items.parent items.quantity items.type shipping.address.city shipping.address.country shipping.address.line1 shipping.address.line2 shipping.address.postal_code shipping.address.state shipping.carrier shipping.name shipping.phone shipping.tracking_number )] },
3845             statement_descriptor => {},
3846             token => {},
3847             usage => {},
3848             };
3849 0           my $err = $self->_check_parameters( $okParams, $args );
3850 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3851 0   0       my $hash = $self->post( 'sources', $args ) || return;
3852 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Source', $hash ) );
3853             }
3854              
3855             ## https://stripe.com/docs/api/customers/delete?lang=curl
3856             ## "Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer."
3857             sub source_detach
3858             {
3859 0     0 0   my $self = shift( @_ );
3860 0 0         return( $self->error( "No parameters were provided to detach a source." ) ) if( !scalar( @_ ) );
3861 0           my $args;
3862 0 0         if( $self->_is_object( $_[0] ) )
3863             {
3864 0 0         if( $_[0]->isa( 'Net::API::Stripe::Customer' ) )
    0          
3865             {
3866 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
3867             }
3868             elsif( $_[0]->isa( 'Net::API::Stripe::Payment::Source' ) )
3869             {
3870 0           $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Source', @_ );
3871 0           my $obj = $args->{_object};
3872 0           $args->{source} = $obj->id;
3873 0 0         $args->{id} = $obj->customer->id if( $obj->customer );
3874             }
3875             }
3876             else
3877             {
3878 0           $args = $self->_get_args( @_ );
3879             }
3880             my $okParams =
3881             {
3882             expandable => { allowed => $EXPANDABLES->{session} },
3883 0           id => { re => qr/^\w+$/, required => 1 },
3884             source => { re => qr/^\w+$/, required => 1 },
3885             };
3886 0           my $err = $self->_check_parameters( $okParams, $args );
3887 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3888 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to detach the source from it." ) );
3889 0   0       my $src_id = CORE::delete( $args->{source} ) || return( $self->error( "No source id was provided to detach." ) );
3890 0   0       my $hash = $self->delete( "customers/${id}/sources/${src_id}", $args ) || return;
3891 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Source', $hash ) );
3892             }
3893              
3894             sub source_retrieve
3895             {
3896 0     0 0   my $self = shift( @_ );
3897 0 0         return( $self->error( "No parameters were provided to retrieve source information." ) ) if( !scalar( @_ ) );
3898 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Source', @_ );
3899             my $okParams =
3900             {
3901             expandable => { allowed => $EXPANDABLES->{session} },
3902 0           id => { re => qr/^\w+$/, required => 1 }
3903             };
3904 0           my $err = $self->_check_parameters( $okParams, $args );
3905 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3906 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No source id was provided to retrieve its information." ) );
3907 0   0       my $hash = $self->get( "sources/${id}", $args ) || return;
3908 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Source', $hash ) );
3909             }
3910              
3911             ## https://stripe.com/docs/api/sources/update?lang=curl
3912             sub source_update
3913             {
3914 0     0 0   my $self = shift( @_ );
3915 0 0         return( $self->error( "No parameters were provided to update a source" ) ) if( !scalar( @_ ) );
3916 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Payment::Source', @_ );
3917             my $okParams =
3918             {
3919             expandable => { allowed => $EXPANDABLES->{session} },
3920 0           id => { re => qr/^\w+$/, required => 1 },
3921             amount => {},
3922             mandate => { fields => [qw( acceptance acceptance.status acceptance.date acceptance.ip acceptance.offline.contact_email acceptance.online acceptance.type acceptance.user_agent amount currency interval notification_method )] },
3923             metadata => { type => 'hash' },
3924             owner => { fields => [qw( address.city address.country address.line1 address.line2 address.postal_code address.state email name phone )] },
3925             source_order => { fields => [qw( items.amount items.currency items.description items.parent items.quantity items.type shipping.address.city shipping.address.country shipping.address.line1 shipping.address.line2 shipping.address.postal_code shipping.address.state shipping.carrier shipping.name shipping.phone shipping.tracking_number )] },
3926             };
3927             ## We found some errors
3928 0           my $err = $self->_check_parameters( $okParams, $args );
3929 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3930 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No source id was provided to update source's details" ) );
3931 0   0       my $hash = $self->post( "sources/${id}", $args ) || return;
3932 0           return( $self->_response_to_object( 'Net::API::Stripe::Payment::Source', $hash ) );
3933             }
3934              
3935 0     0 1   sub subscription { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Subscription', @_ ) ); }
3936              
3937 0     0 1   sub subscription_item { return( shift->_response_to_object( 'Net::API::Stripe::Billing::Subscription::Item', @_ ) ); }
3938              
3939             sub subscriptions
3940             {
3941 0     0 0   my $self = shift( @_ );
3942 0           my $action = shift( @_ );
3943 0           my $allowed = [qw( create delete_discount retrieve update list cancel )];
3944 0   0       my $meth = $self->_get_method( 'subscription', $action, $allowed ) || return;
3945 0           return( $self->$meth( @_ ) );
3946             }
3947              
3948             ## https://stripe.com/docs/api/customers/delete?lang=curl
3949             ## "Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer."
3950             sub subscription_cancel
3951             {
3952 0     0 0   my $self = shift( @_ );
3953 0 0         return( $self->error( "No parameters were provided to cancel subscription information." ) ) if( !scalar( @_ ) );
3954 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription', @_ );
3955             my $okParams =
3956             {
3957             expandable => { allowed => $EXPANDABLES->{subscription} },
3958 0           id => { re => qr/^\w+$/, required => 1 }
3959             };
3960 0           my $err = $self->_check_parameters( $okParams, $args );
3961 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
3962 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription id was provided to cancel." ) );
3963 0   0       my $hash = $self->delete( "subscriptions/${id}", $args ) || return;
3964 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription', $hash ) );
3965             }
3966              
3967             sub subscription_create
3968             {
3969 0     0 0   my $self = shift( @_ );
3970 0 0         return( $self->error( "No parameters were provided to create a subscription" ) ) if( !scalar( @_ ) );
3971 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription', @_ );
3972             my $okParams =
3973             {
3974             expandable => { allowed => $EXPANDABLES->{subscription} },
3975 0           customer => { required => 1 },
3976             application_fee_percent => { re => qr/^[0-100]$/ },
3977             backdate_start_date => { type => 'datetime' },
3978             # billing_cycle_anchor => { re => qr/^\d+$/ },
3979             billing_cycle_anchor => { type => 'datetime' },
3980             billing_thresholds => { fields => [qw( amount_gte reset_billing_cycle_anchor )] },
3981             cancel_at => { type => 'datetime' },
3982             cancel_at_period_end => {},
3983             collection_method => { re => qr/^(?:charge_automatically|send_invoice)$/ },
3984             coupon => {},
3985             days_until_due => {},
3986             default_payment_method => {},
3987             default_source => {},
3988             default_tax_rates => { type => 'array' },
3989             items => { type => 'array', fields => [qw( plan billing_thresholds.usage_gte metadata quantity tax_rates )], required => 1 },
3990             metadata => { type => 'hash' },
3991             off_session => {},
3992             payment_behavior => { re => qr/^(?:allow_incomplete|error_if_incomplete)$/ },
3993             pending_invoice_item_interval => { fields => [qw( interval interval_count )] },
3994             prorate => {},
3995             proration_behavior => { type => 'string', re => qr/^(billing_cycle_anchor|create_prorations|none)$/ },
3996             tax_percent => { re => qr/^[0-100]$/ },
3997             trial_end => { re => qr/^(?:\d+|now)$/ },
3998             trial_from_plan => {},
3999             trial_period_days => {},
4000             };
4001 0           my $err = $self->_check_parameters( $okParams, $args );
4002 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4003 0     0     $self->message( 3, "Posting the following data: ", sub{ $self->dumper( $args ) } );
  0            
4004 0   0       my $hash = $self->post( 'subscriptions', $args ) || return;
4005 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription', $hash ) );
4006             }
4007              
4008             sub subscription_delete_discount
4009             {
4010 0     0 0   my $self = shift( @_ );
4011 0 0         return( $self->error( "No parameters were provided to delete subscription discount." ) ) if( !scalar( @_ ) );
4012 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription', @_ );
4013             my $okParams =
4014             {
4015             expandable => { allowed => $EXPANDABLES->{discount} },
4016 0           id => { re => qr/^\w+$/, required => 1 }
4017             };
4018 0           my $err = $self->_check_parameters( $okParams, $args );
4019 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4020 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription id was provided to delete its coupon." ) );
4021 0   0       my $hash = $self->delete( "subscriptions/${id}/discount", $args ) || return;
4022 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Discount', $hash ) );
4023             }
4024              
4025             sub subscription_list
4026             {
4027 0     0 0   my $self = shift( @_ );
4028 0           my $args = $self->_get_args( @_ );
4029             my $okParams =
4030             {
4031 0           expandable => { allowed => $EXPANDABLES->{subscription}, data_prefix_is_ok => 1 },
4032             # boolean
4033             active => { type => 'boolean' },
4034             'created' => { re => qr/^\d+$/ },
4035             'created.gt' => { re => qr/^\d+$/ },
4036             'created.gte' => { re => qr/^\d+$/ },
4037             'created.lt' => { re => qr/^\d+$/ },
4038             'created.lte' => { re => qr/^\d+$/ },
4039             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
4040             'ending_before' => {},
4041             'ids' => { type => 'array' },
4042             'limit' => { re => qr/^\d+$/ },
4043             # boolean
4044             'shippable' => { type => 'boolean' },
4045             'starting_after' => {},
4046             # 'type' => {},
4047             # 'url' => {},
4048             };
4049 0           my $err = $self->_check_parameters( $okParams, $args );
4050 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4051 0 0         if( $args->{expand} )
4052             {
4053 0 0         $self->_adjust_list_expandables( $args ) || return;
4054             }
4055 0   0       my $hash = $self->get( 'subscriptions', $args ) || return;
4056 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
4057             }
4058              
4059             sub subscription_retrieve
4060             {
4061 0     0 0   my $self = shift( @_ );
4062 0 0         return( $self->error( "No parameters were provided to retrieve subscription information." ) ) if( !scalar( @_ ) );
4063 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription', @_ );
4064             my $okParams =
4065             {
4066             expandable => { allowed => $EXPANDABLES->{subscription} },
4067 0           id => { re => qr/^\w+$/, required => 1 }
4068             };
4069 0           my $err = $self->_check_parameters( $okParams, $args );
4070 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4071 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription id was provided to retrieve its information." ) );
4072 0   0       my $hash = $self->get( "subscriptions/${id}", $args ) || return;
4073 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription', $hash ) );
4074             }
4075              
4076             ## https://stripe.com/docs/api/customers/update?lang=curl
4077             sub subscription_update
4078             {
4079 0     0 0   my $self = shift( @_ );
4080 0 0         return( $self->error( "No parameters were provided to update a subscription" ) ) if( !scalar( @_ ) );
4081 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::Subscription', @_ );
4082             my $okParams =
4083             {
4084             expandable => { allowed => $EXPANDABLES->{subscription} },
4085 0           id => { req => qr/^\w+$/, required => 1 },
4086             application_fee_percent => { re => qr/^[0-100]$/ },
4087             billing_cycle_anchor => { re => qr/^\d+$/ },
4088             billing_thresholds => { fields => [qw( amount_gte reset_billing_cycle_anchor )] },
4089             cancel_at => {},
4090             cancel_at_period_end => {},
4091             collection_method => { re => qr/^(?:charge_automatically|send_invoice)$/ },
4092             coupon => {},
4093             days_until_due => {},
4094             default_payment_method => { type => 'string', re => qr/^[\w\_]+$/ },
4095             default_source => {},
4096             default_tax_rates => {},
4097             items => { type => 'array', fields => [qw( id plan billing_thresholds.usage_gte clear_usage deleted metadata quantity tax_rates )] },
4098             metadata => { type => 'hash' },
4099             off_session => {},
4100             pause_collection => { type => 'string', fields => [qw(behavior resumes_at)] },
4101             payment_behavior => { re => qr/^(?:allow_incomplete|error_if_incomplete)$/ },
4102             pending_invoice_item_interval => { fields => [qw( interval interval_count )] },
4103             prorate => {},
4104             proration_date => { type => 'datetime' },
4105             tax_percent => { re => qr/^[0-100]$/ },
4106             trial_end => { re => qr/^(?:\d+|now)$/ },
4107             trial_from_plan => {},
4108             };
4109             ## We found some errors
4110 0           my $err = $self->_check_parameters( $okParams, $args );
4111             # $self->message( 3, "Data to be posted: ", $self->dumper( $args ) ); exit;
4112 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4113 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No subscription id was provided to update subscription's details" ) );
4114 0   0       my $hash = $self->post( "subscriptions/${id}", $args ) || return;
4115 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::Subscription', $hash ) );
4116             }
4117              
4118 0     0 1   sub tax_id { return( shift->_response_to_object( 'Net::API::Stripe::Billing::TaxID', @_ ) ); }
4119              
4120             sub tax_ids
4121             {
4122 0     0 1   my $self = shift( @_ );
4123 0           my $action = shift( @_ );
4124 0           my $allowed = [qw( create retrieve delete list )];
4125 0   0       my $meth = $self->_get_method( 'tax_id', $action, $allowed ) || return;
4126 0           return( $self->$meth( @_ ) );
4127             }
4128              
4129             sub tax_id_create
4130             {
4131 0     0 0   my $self = shift( @_ );
4132 0 0         return( $self->error( "No parameters were provided to create a tax_id" ) ) if( !scalar( @_ ) );
4133 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::TaxID', @_ );
4134             my $okParams =
4135             {
4136             expandable => { allowed => $EXPANDABLES->{tax_id} },
4137 0           customer => { re => qr/^\w+$/, required => 1 },
4138             ## au_abn, ch_vat, eu_vat, in_gst, mx_rfc, no_vat, nz_gst, or za_vat
4139             type => { re => qr/^[a-z]{2}_[a-z]+$/ },
4140             value => {},
4141             };
4142 0           my $err = $self->_check_parameters( $okParams, $args );
4143 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4144 0   0       my $id = CORE::delete( $args->{customer} ) || return( $self->error( "No customer id was provided to create a tax_id for the customer" ) );
4145 0   0       my $hash = $self->post( "customers/$id/tax_ids", $args ) || return;
4146 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::TaxID', $hash ) );
4147             }
4148              
4149             sub tax_id_delete
4150             {
4151 0     0 0   my $self = shift( @_ );
4152 0 0         return( $self->error( "No parameters were provided to delete a tax_id" ) ) if( !scalar( @_ ) );
4153 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::TaxID', @_ );
4154             my $okParams =
4155             {
4156             expandable => { allowed => $EXPANDABLES->{tax_id} },
4157 0           id => { re => qr/^\w+$/, required => 1 },
4158             customer => { re => qr/^\w+$/, required => 1 },
4159             };
4160 0           my $err = $self->_check_parameters( $okParams, $args );
4161 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4162 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No tax id was provided to delete." ) );
4163 0   0       my $cust_id = CORE::delete( $args->{customer} ) || return( $self->error( "No customer id was provided to delete his/her tax_id" ) );
4164 0   0       my $hash = $self->delete( "customers/${cust_id}/tax_ids/${id}", $args ) || return;
4165 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::TaxID', $hash ) );
4166             }
4167              
4168             sub tax_id_list
4169             {
4170 0     0 0   my $self = shift( @_ );
4171 0 0         return( $self->error( "No parameters were provided to list customer's tax ids" ) ) if( !scalar( @_ ) );
4172 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Customer', @_ );
4173             my $okParams =
4174             {
4175 0           expandable => { allowed => $EXPANDABLES->{tax_id}, data_prefix_is_ok => 1 },
4176             id => { re => qr/^\w+$/, required => 1 },
4177             ## "A cursor for use in pagination. ending_before is an object ID that defines your place in the list."
4178             ending_before => qr/^\w+$/,
4179             limit => qr/^\d+$/,
4180             starting_after => qr/^\w+$/,
4181             };
4182 0           my $err = $self->_check_parameters( $okParams, $args );
4183 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4184 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No customer id was provided to list his/her tax ids" ) );
4185 0 0         if( $args->{expand} )
4186             {
4187 0 0         $self->_adjust_list_expandables( $args ) || return;
4188             }
4189 0   0       my $hash = $self->get( "customers/${id}/tax_ids", $args ) || return;
4190 0           return( $self->_response_to_object( 'Net::API::Stripe::List', $hash ) );
4191             }
4192              
4193             sub tax_id_retrieve
4194             {
4195 0     0 0   my $self = shift( @_ );
4196 0 0         return( $self->error( "No parameters were provided to retrieve tax_id" ) ) if( !scalar( @_ ) );
4197 0           my $args = $self->_get_args_from_object( 'Net::API::Stripe::Billing::TaxID', @_ );
4198             my $okParams =
4199             {
4200             expandable => { allowed => $EXPANDABLES->{tax_id} },
4201 0           id => { re => qr/^\w+$/, required => 1 },
4202             customer => { re => qr/^\w+$/, required => 1 },
4203             };
4204 0           my $err = $self->_check_parameters( $okParams, $args );
4205 0 0         return( $self->error( join( ' ', @$err ) ) ) if( scalar( @$err ) );
4206 0   0       my $id = CORE::delete( $args->{id} ) || return( $self->error( "No tax id was provided to retrieve customer's tax_id" ) );
4207 0   0       my $cust_id = CORE::delete( $args->{customer} ) || return( $self->error( "No customer id was provided to retrieve his/her tax_id" ) );
4208 0   0       my $hash = $self->get( "customers/${cust_id}/tax_ids/${id}", $args ) || return;
4209 0           return( $self->_response_to_object( 'Net::API::Stripe::Billing::TaxID', $hash ) );
4210             }
4211              
4212 0     0 1   sub tax_rate { return( shift->_response_to_object( 'Net::API::Stripe::Tax::Rate', @_ ) ); }
4213              
4214             # sub terminal { return( shift->_instantiate( 'terminal', 'Net::API::Stripe::Terminal' ) ) }
4215              
4216 0     0 1   sub token { return( shift->_response_to_object( 'Net::API::Stripe::Token', @_ ) ); }
4217              
4218 0     0 1   sub topup { return( shift->_response_to_object( 'Net::API::Stripe::Connect::TopUp', @_ ) ); }
4219              
4220 0     0 1   sub transfer { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Transfer', @_ ) ); }
4221              
4222 0     0 1   sub transfer_reversal { return( shift->_response_to_object( 'Net::API::Stripe::Connect::Transfer::Reversal', @_ ) ); }
4223              
4224 0     0 1   sub usage_record { return( shift->_response_to_object( 'Net::API::Stripe::Billing::UsageRecord', @_ ) ); }
4225              
4226 0     0 1   sub value_list { return( shift->_response_to_object( 'Net::API::Stripe::Fraud::ValueList', @_ ) ); }
4227              
4228 0     0 1   sub value_list_item { return( shift->_response_to_object( 'Net::API::Stripe::Fraud::ValueList::Item', @_ ) ); }
4229              
4230 0     0 1   sub version { return( shift->_set_get_scalar( 'version', @_ ) ); }
4231              
4232 0     0 1   sub webhook { return( shift->_response_to_object( 'Net::API::Stripe::WebHook::Object' ) ) }
4233              
4234             sub webhook_validate_signature
4235             {
4236 0     0 0   my $self = shift( @_ );
4237 0           my $opts = {};
4238 0 0 0       $opts = shift( @_ ) if( @_ && ref( $_[0] ) eq 'HASH' );
4239 0 0         return( $self->error( "No webhook secret was provided." ) ) if( !$opts->{secret} );
4240 0 0         return( $self->error( "No Stripe signature was provided." ) ) if( !$opts->{signature} );
4241 0 0         return( $self->error( "No payload was provided." ) ) if( !CORE::length( $opts->{payload} ) );
4242             ## 5 minutes
4243 0   0       $opts->{time_tolerance} ||= ( 5 * 60 );
4244 0           my $sig = $opts->{signature};
4245 0           my $max_time_spread = $opts->{time_tolerance};
4246 0           my $signing_secret = $opts->{secret};
4247 0           my $payload = $opts->{payload};
4248 0 0         $payload = Encode::decode_utf8( $payload ) if( !Encode::is_utf8( $payload ) );
4249            
4250             ## Example:
4251             # Stripe-Signature: t=1492774577,
4252             # v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,
4253             # v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
4254 0 0         return( $self->error({ code => 400, message => "Event data received from Stripe is empty" }) ) if( !CORE::length( $sig ) );
4255 0           my @parts = split( /\,[[:blank:]]*/, $sig );
4256 0           $self->message( 3, "Signature parts are: '", join( "', '", @parts ), "'." );
4257 0           my $q = {};
4258 0           for( @parts )
4259             {
4260 0           my( $n, $v ) = split( /[[:blank:]]*\=[[:blank:]]*/, $_, 2 );
4261 0           $q->{ $n } = $v;
4262             }
4263 0     0     $self->message( 3, "Hash parameters are: ", sub{ $self->dumper( $q ) } );
  0            
4264 0 0         return( $self->error({ code => 400, message => "No timestamp found in Stripe event data" }) ) if( !CORE::exists( $q->{t} ) );
4265 0 0         return( $self->error({ code => 400, message => "Timestamp is empty in Stripe event data received." }) ) if( !CORE::length( $q->{t} ) );
4266 0 0         return( $self->error({ code => 400, message => "No signature found in Stripe event data" }) ) if( !CORE::exists( $q->{v1} ) );
4267 0 0         return( $self->error({ code => 400, message => "Signature is empty in Stripe event data received." }) ) if( !CORE::length( $q->{v1} ) );
4268             ## Must be a unix timestamp
4269 0 0         return( $self->error({ code => 400, message => "Invalid timestamp received in Stripe event data" }) ) if( $q->{t} !~ /^\d+$/ );
4270             ## Must be a hash hmac with sha256, e.g. 5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
4271 0 0         return( $self->error({ code => 400, message => "Invalid signature received in Stripe event data" }) ) if( $q->{v1} !~ /^[a-z0-9]{64}$/ );
4272 0           my $dt;
4273 0           try
4274 0     0     {
4275 0           $dt = DateTime->from_epoch( epoch => $q->{t}, time_zone => 'local' );
4276             }
4277 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
4278 0     0     {
4279 0           return( $self->error({ code => 400, message => "Invalid timestamp ($q->{t}): $e" }) );
4280 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
4281            
4282             ## This needs to be in real utf8, ie NOT perl internal utf8
4283 0           my $signed_payload = Encode::encode_utf8( join( '.', $q->{t}, $payload ) );
4284 0           my $expect_sign = Digest::SHA::hmac_sha256_hex( $signed_payload, $signing_secret );
4285 0           $self->message( 3, "Expected signature is: $expect_sign" );
4286 0 0         $self->message( 3, "Signature ", ( $expect_sign ne $q->{v1} ? 'does not match' : 'matches' ) );
4287 0 0         return( $self->error({ code => 401, message => "Invalid signature." }) ) if( $expect_sign ne $q->{v1} );
4288 0           my $time_diff = time() - $q->{t};
4289 0 0         return( $self->error({ code => 400, message => "Bad timestamp ($q->{t}). It is set in the future: $dt" }) ) if( $time_diff < 0 );
4290 0 0         return( $self->error({ code => 406, message => "Timestamp is too old." }) ) if( $time_diff >= $max_time_spread );
4291 0           return( 1 );
4292             }
4293              
4294             ## https://stripe.com/docs/ips
4295             sub webhook_validate_caller_ip
4296             {
4297 0     0 0   my $self = shift( @_ );
4298 0           my $opts = {};
4299 0 0 0       $opts = shift( @_ ) if( @_ && ref( $_[0] ) eq 'HASH' );
4300 0 0         return( $self->error({ code => 500, message => "No ip address was provided to check." }) ) if( !$opts->{ip} );
4301 0           my $err = [];
4302 0           my $ips = STRIPE_WEBHOOK_SOURCE_IP;
4303             my $ip = Net::IP->new( $opts->{ip} ) || do
4304 0   0       {
4305             warn( "Warning only: IP '$raw' is not valid: ", Net::IP->Error, "\n" );
4306             push( @$err, sprintf( "IP '$raw' is not valid: %s", Net::IP->Error ) );
4307             return( '' );
4308             };
4309 0           $self->messagef( 3, "IP block provided has %d IP addresses, starts with %s and ends with %s", $ip->size, $ip->ip, $ip->last_ip );
4310 0           foreach my $stripe_ip ( @$ips )
4311             {
4312 0           my $stripe_ip_object = Net::IP->new( $stripe_ip );
4313             ## We found an existing ip same as the one we are adding, so we skip
4314             ## If we are given a block that has some overlapping elements, we go ahead and add it
4315             ## because it would become complicated and risky to only take the ips that do not overalp in the given block
4316 0 0         if( !( $ip->overlaps( $stripe_ip_object ) == $Net::IP::IP_NO_OVERLAP ) )
4317             {
4318 0           return( $ip );
4319             }
4320             }
4321 0 0         if( $opts->{ignore_ip} )
4322             {
4323 0           $self->message( 3, "This ip \"$ip\" does not match any of Stripe source ip and normally, this would return an error." );
4324 0           return( $ip );
4325             }
4326             else
4327             {
4328 0           return( $self->error({ code => Apache2::Const::HTTP_FORBIDDEN, message => "IP address $opts->{ip} is not a valid Stripe ip and is not authorised to access this resource." }) );
4329             }
4330             }
4331              
4332             ## This is to be called for methods used to make api calls to Stripe to get list of objects
4333             ## And for which the user wants to expand some object's embedded objects
4334             ## See: https://stripe.com/docs/api/expanding_objects
4335             ## This allows the user to do simply default_source for customers' list when in reality
4336             ## the api requires data.default_source
4337             sub _adjust_list_expandables
4338             {
4339 0     0     my $self = shift( @_ );
4340 0           my $args = shift( @_ );
4341 0 0         return( $self->error( "User parameters list provided is '$args' and I was expecting a hash reference." ) ) if( ref( $args ) ne 'HASH' );
4342 0 0         if( ref( $args->{expand} ) eq 'ARRAY' )
4343             {
4344 0           my $new = [];
4345 0           for( my $i = 0; $i < scalar( @{$args->{expand}} ); $i++ )
  0            
4346             {
4347 0 0         substr( $args->{expand}->[$i], 0, 0 ) = 'data.' if( substr( $args->{expand}->[$i], 0, 5 ) ne 'data.' );
4348 0           my $path = [split( /\./, $args->{expand}->[$i] )];
4349             ## Make sure that with the new 'data' prefix, this does not exceed 4 level of depth
4350 0 0         push( @$new, $args->{expand}->[$i] ) if( scalar( @$path ) <= $EXPAND_MAX_DEPTH );
4351             }
4352 0           $args->{expand} = $new;
4353             }
4354 0           return( $self );
4355             }
4356              
4357             sub _as_hash
4358             {
4359 0     0     my $self = shift( @_ );
4360 0           my $this = shift( @_ );
4361 0           my $opts = {};
4362 0 0 0       $opts = shift( @_ ) if( @_ && ref( $_[0] ) eq 'HASH' );
4363 0 0         $opts->{seen} = {} if( !$opts->{seen} );
4364 0           my $ref = {};
4365 0 0         if( $self->_is_object( $this ) )
    0          
4366             {
4367 0 0         $ref = $this->as_hash if( $this->can( 'as_hash' ) );
4368             }
4369             ## Recursively transform into hash
4370             elsif( ref( $this ) eq 'HASH' )
4371             {
4372             ## Prevent recursion
4373 0           my $ref_addr = Scalar::Util::refaddr( $this );
4374 0 0         $self->message( 3, "Skipping this hash with address $ref_addr that is looping." ) if( $opts->{seen}->{ $ref_addr } );
4375 0 0         return( $opts->{seen}->{ $ref_addr } ) if( $opts->{seen}->{ $ref_addr } );
4376 0           $opts->{seen}->{ $ref_addr } = $this;
4377             # $ref = $hash;
4378 0           foreach my $k ( keys( %$this ) )
4379             {
4380 0 0 0       if( ref( $this->{ $k } ) eq 'HASH' || $self->_is_object( $this->{ $k } ) )
    0          
    0          
4381             {
4382 0           $self->message( 3, "Calling _as_hash for item $this->{$k}" );
4383 0           my $rv = $self->_as_hash( $this->{ $k }, $opts );
4384 0 0         $ref->{ $k } = $rv if( scalar( keys( %$rv ) ) );
4385             }
4386             elsif( ref( $this->{ $k } ) eq 'ARRAY' )
4387             {
4388 0           my $new = [];
4389 0           foreach my $that ( @{$this->{ $k }} )
  0            
4390             {
4391 0 0 0       if( ref( $that ) eq 'HASH' || $self->_is_object( $that ) )
4392             {
4393 0           my $rv = $self->_as_hash( $that, $opts );
4394 0 0         push( @$new, $rv ) if( scalar( keys( %$rv ) ) );
4395             }
4396             }
4397 0 0         $ref->{ $k } = $new if( scalar( @$new ) );
4398             }
4399             ## For stringification
4400             elsif( CORE::length( "$this->{$k}" ) )
4401             {
4402 0           $ref->{ $k } = $this->{ $k };
4403             }
4404             }
4405             }
4406             else
4407             {
4408 0           return( $self->error( "Unknown data type $this to be converted into hash for api call." ) );
4409             }
4410 0           return( $ref );
4411             }
4412              
4413             sub _check_parameters
4414             {
4415 0     0     my $self = shift( @_ );
4416 0           my $okParams = shift( @_ );
4417 0           my $args = shift( @_ );
4418 0           my $err = [];
4419            
4420 0           my $seen = {};
4421             local $check_fields_recursive = sub
4422             {
4423 0     0     my( $hash, $mirror, $field, $required ) = @_;
4424 0           my $errors = [];
4425             # push( @$err, "Unknown property $v for key $k." ) if( !scalar( grep( /^$v$/, @$this ) ) );
4426 0           foreach my $k ( sort( keys( %$hash ) ) )
4427             {
4428 0 0         if( !CORE::exists( $mirror->{ $k } ) )
4429             {
4430 0           push( @$errors, "Unknown property \"$k\" for key \"$field\"." );
4431 0           next;
4432             }
4433 0           my $addr;
4434 0 0         $addr = Scalar::Util::refaddr( $hash->{ $k } ) if( ref( $hash->{ $k } ) eq 'HASH' );
4435             ## Found a hash, check recursively and avoid looping endlessly
4436 0 0 0       if( ref( $hash->{ $k } ) eq 'HASH' &&
      0        
4437             ref( $mirror->{ $k } ) eq 'HASH' &&
4438             # ++$hash->{ $k }->{__check_fields_recursive_looping} == 1 )
4439             ++$seen->{ $addr } == 1 )
4440             {
4441 0 0         my $deep_errors = $check_fields_recursive->( $hash->{ $k }, $mirror->{ $k }, $k, CORE::exists( $required->{ $k } ) ? $required->{ $k } : {} );
4442 0           CORE::push( @$errors, @$deep_errors );
4443             }
4444             }
4445            
4446             ## Check required fields
4447 0           foreach my $k ( sort( keys( %$required ) ) )
4448             {
4449 0 0 0       if( !CORE::exists( $hash->{ $k } ) ||
4450             !CORE::length( $hash->{ $k } ) )
4451             {
4452 0           CORE::push( @$errors, "Field \"$k\" is required but missing in hash provided." );
4453             }
4454             }
4455 0           return( $errors );
4456 0           };
4457            
4458 0           foreach my $k ( keys( %$args ) )
4459             {
4460             ## Special case for expand and for private parameters starting with '_'
4461 0 0 0       next if( $k eq 'expand' || $k eq 'expandable' || substr( $k, 0, 1 ) eq '_' );
      0        
4462 0 0         if( !CORE::exists( $okParams->{ $k } ) )
4463             {
4464             ## This is handy when an object was passed to one of the api method and
4465             ## the object contains a bunch of data not all relevant to the api call
4466             ## It makes it easy to pass the object and let this interface take only what is relevant
4467 0 0 0       if( $okParams->{_cleanup} || $args->{_cleanup} || $self->ignore_unknown_parameters )
      0        
4468             {
4469 0           CORE::delete( $args->{ $k } );
4470             }
4471             else
4472             {
4473 0           push( @$err, "Unknown parameter \"$k\"." );
4474             }
4475 0           next;
4476             }
4477             ## $dict is either a hash dictionary or a sub
4478 0           my $dict = $okParams->{ $k };
4479 0 0         if( ref( $dict ) eq 'HASH' )
    0          
4480             {
4481 0           my $pkg;
4482 0 0 0       if( $dict->{fields} && ref( $dict->{fields} ) eq 'ARRAY' )
4483             {
4484 0           my $this = $dict->{fields};
4485 0 0 0       if( ref( $args->{ $k } ) eq 'ARRAY' && $dict->{type} eq 'array' )
    0          
4486             {
4487             ## Just saying it's ok
4488             }
4489             elsif( ref( $args->{ $k } ) ne 'HASH' )
4490             {
4491 0           push( @$err, sprintf( "Parameter \"$k\" must be a dictionary definition with following possible hash keys: \"%s\". Did you forget type => 'array' ?", join( ', ', @$this ) ) );
4492 0           next;
4493             }
4494            
4495             ## We build a test mirror hash structure against which we will check if actual data fields exist or not
4496 0           my $mirror = {};
4497 0           my $required = {};
4498 0           foreach my $f ( @$this )
4499             {
4500 0           my @path = CORE::split( /\./, $f );
4501 0           my $parent_hash = $mirror;
4502 0           my $parent_req = $required;
4503 0           for( my $i = 0; $i < scalar( @path ); $i++ )
4504             {
4505 0           my $p = $path[$i];
4506 0 0         if( substr( $p, -1, 1 ) eq '!' )
4507             {
4508 0           $p = substr( $p, 0, CORE::length( $p ) - 1 );
4509 0           $parent_req->{ $p } = 1;
4510             }
4511            
4512 0 0         if( $i == $#path )
4513             {
4514 0           $parent_hash->{ $p } = 1;
4515             }
4516             else
4517             {
4518 0 0 0       $parent_hash->{ $p } = {} unless( CORE::exists( $parent_hash->{ $p } ) && ref( $parent_hash->{ $p } ) eq 'HASH' );
4519 0 0         if( CORE::exists( $parent_req->{ $p } ) )
4520             {
4521 0 0         $parent_req->{ $p } = {} unless( ref( $parent_req->{ $p } ) eq 'HASH' );
4522 0           $parent_req = $parent_req->{ $p };
4523             }
4524 0           $parent_hash = $parent_hash->{ $p };
4525             }
4526             }
4527             }
4528            
4529             ## Do we have dots in field names? If so, this is a multi dimensional hash we are potentially looking at
4530 0 0 0       if( ref( $args->{ $k } ) eq 'HASH' )
    0          
4531             {
4532 0           my $res = $check_fields_recursive->( $args->{ $k }, $mirror, $k );
4533 0 0         push( @$err, @$res ) if( scalar( @$res ) );
4534             }
4535             elsif( ref( $args->{ $k } ) eq 'ARRAY' && $dict->{type} eq 'array' )
4536             {
4537 0           my $arr = $args->{ $k };
4538 0           for( my $i = 0; $i < scalar( @$arr ); $i++ )
4539             {
4540 0 0         if( ref( $arr->[ $i ] ) ne 'HASH' )
4541             {
4542 0           push( @$err, sprintf( "Invalid data type at offset $i. Parameter \"$k\" must be a dictionary definition with following possible hash keys: \"%s\"", join( ', ', @$this ) ) );
4543 0           next;
4544             }
4545 0           my $res = $check_fields_recursive->( $arr->[ $i ], $mirror, $k );
4546 0 0         push( @$err, @$res ) if( scalar( @$res ) );
4547             }
4548             }
4549             # $clean_up_check_fields_recursive->( $args->{ $k } );
4550             }
4551 0 0 0       if( $dict->{required} && !CORE::exists( $args->{ $k } ) )
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
4552             {
4553 0           push( @$err, "Parameter \"$k\" is required, but missing" );
4554             }
4555             ## _is_object is inherited from Module::Object
4556             elsif( ( $pkg = $self->_is_object( $args->{ $k } ) ) && $dict->{package} && $dict->{package} ne $pkg )
4557             {
4558 0           push( @$err, "Parameter \"$k\" value is a package \"$pkg\", but I was expecting \"$dict->{package}\"" );
4559             }
4560             elsif( $dict->{re} && ref( $dict->{re} ) eq 'Regexp' && $args->{ $k } !~ /$dict->{re}/ )
4561             {
4562 0           push( @$err, "Parameter \"$k\" with value \"$args->{$k}\" does not have a legitimate value." );
4563             }
4564             elsif( $dict->{type} &&
4565             (
4566             ( $dict->{type} eq 'scalar' && ref( $args->{ $k } ) ) ||
4567             ( $dict->{type} ne 'scalar' && ref( $args->{ $k } ) && lc( ref( $args->{ $k } ) ) ne $dict->{type} )
4568             )
4569             )
4570             {
4571 0           push( @$err, "I was expecting a data of type $dict->{type}, but got " . lc( ref( $args->{ $k } ) ) );
4572             }
4573             elsif( $dict->{type} eq 'boolean' && CORE::length( $args->{ $k } ) )
4574             {
4575 0 0 0       $args->{ $k } = ( $args->{ $k } eq 'true' || ( $args->{ $k } ne 'false' && $args-->{ $k } ) ) ? 'true' : 'false';
4576             }
4577             elsif( $dict->{type} eq 'date' || $dict->{type} eq 'datetime' )
4578             {
4579 0 0 0       unless( $self->_is_object( $args->{ $k } ) && $args->{ $k }->isa( 'DateTime' ) )
4580             {
4581 0 0         my $tz = $dict->{time_zone} ? $dict->{time_zone} : 'GMT';
4582 0           my $dt;
4583 0 0 0       if( $dict->{type} eq 'date' &&
    0 0        
    0          
4584             $args->{ $k } =~ /^(?<year>\d{4})[\.|\-](?<month>\d{1,2})[\.|\-](?<day>\d{1,2})$/ )
4585             {
4586 0           try
4587 0     0     {
4588             $dt = DateTime(
4589             year => int( $+{year} ),
4590             month => int( $+{month} ),
4591 0           day => int( $+{day} ),
4592             hour => 0,
4593             minute => 0,
4594             second => 0,
4595             time_zone => $tz
4596             );
4597 0           $args->{ $k } = $dt;
4598             }
4599 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
4600 0     0     {
4601 0           push( @$err, "Invalid date (" . $args->{ $k } . ") provided for parameter \"$k\": $e" );
4602 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
4603             }
4604             elsif( $dict->{type} eq 'datetime' &&
4605             $args->{ $k } =~ /^(?<year>\d{4})[\.|\-](?<month>\d{1,2})[\.|\-](?<day>\d{1,2})[T|[:blank:]]+(?<hour>\d{1,2}):(?<minute>\d{1,2}):(?<second>\d{1,2})$/ )
4606             {
4607 0           try
4608 0     0     {
4609             $dt = DateTime(
4610             year => int( $+{year} ),
4611             month => int( $+{month} ),
4612             day => int( $+{day} ),
4613             hour => int( $+{hour} ),
4614             minute => int( $+{minute} ),
4615 0           second => int( $+{second} ),
4616             time_zone => $tz
4617             );
4618 0           $args->{ $k } = $dt;
4619             }
4620 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
4621 0     0     {
4622 0           push( @$err, "Invalid datetime (" . $args->{ $k } . ") provided for parameter \"$k\": $e" );
4623 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
4624             }
4625             elsif( $args->{ $k } =~ /^\d+$/ )
4626             {
4627 0           try
4628 0     0     {
4629             $dt = DateTime->from_epoch(
4630 0           epoch => $args->{ $k },
4631             time_zone => $tz,
4632             );
4633             }
4634 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
4635 0     0     {
4636 0           push( @$err, "Invalid timestamp (" . $args->{ $k } . ") provided for parameter \"$k\": $e" );
4637 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
4638             }
4639 0 0         if( $dt )
4640             {
4641 0 0         my $pattern = $dict->{pattern} ? $dict->{pattern} : '%s';
4642 0           my $strp = DateTime::Format::Strptime->new(
4643             pattern => $pattern,
4644             locale => 'en_GB',
4645             time_zone => $tz,
4646             );
4647 0           $dt->set_formatter( $strp );
4648 0           $args->{ $k } = $dt;
4649             }
4650             }
4651             }
4652             }
4653             elsif( ref( $this ) eq 'CODE' )
4654             {
4655 0           my $res = $this->( $args->{ $k } );
4656 0 0         push( @$err, "Invalid parameter \"$k\" with value \"$args->{$k}\": $res" ) if( $res );
4657             }
4658             }
4659            
4660 0 0         $args->{expand} = $self->expand if( !CORE::length( $args->{expand} ) );
4661 0 0         if( exists( $args->{expand} ) )
4662             {
4663 0           my $depth;
4664 0           my $no_need_to_check = 0;
4665 0 0 0       if( $args->{expand} eq 'all' || $args->{expand} =~ /^\d+$/ )
4666             {
4667 0           $no_need_to_check++;
4668 0 0         if( $args->{expand} =~ /^\d+$/ )
4669             {
4670 0           $depth = int( $args->{expand} );
4671             }
4672 0           $self->message( 3, "Requested to expand all possible properties." );
4673 0 0 0       if( exists( $okParams->{expandable} ) && exists( $okParams->{expandable}->{allowed} ) && ref( $okParams->{expandable}->{allowed} ) eq 'ARRAY' )
      0        
4674             {
4675 0           $args->{expand} = $okParams->{expandable}->{allowed};
4676 0     0     $self->message( 3, "epxand now contains: ", sub{ $self->dump( $args->{expand} ) } );
  0            
4677             }
4678             ## There is no allowed expandable properties, but it was called anyway, so we do this to avoid an error below
4679             else
4680             {
4681 0           $self->message( 3, "No possible properties to expand were found." );
4682 0           $args->{expand} = [];
4683             }
4684             }
4685 0 0         push( @$err, sprintf( "expand property should be an array, but instead '%s' was provided", $args->{expand} ) ) if( ref( $args->{expand} ) ne 'ARRAY' );
4686 0 0 0       if( scalar( @{$args->{expand}} ) && exists( $okParams->{expandable} ) )
  0 0          
4687             {
4688 0 0         return( $self->error( "expandable parameter is not a hash (", ref( $okParams->{expandable} ), ")." ) ) if( ref( $okParams->{expandable} ) ne 'HASH' );
4689 0 0         return( $self->error( "No \"allowed\" attribute in the expandable parameter hash." ) ) if( !CORE::exists( $okParams->{expandable}->{allowed} ) );
4690 0           my $expandable = $okParams->{expandable}->{allowed};
4691 0           my $errExpandables = [];
4692 0 0         if( !$no_need_to_check )
    0          
4693             {
4694 0 0         if( scalar( @$expandable ) )
4695             {
4696 0           $self->message( 3, "Checking expanded properties '", join( "', '", @{$args->{expand}} ), "' against expandable properties: '", join( "', '", @$expandable ), "'." );
  0            
4697 0 0         return( $self->error( "List of expandable attributes needs to be an array reference, but found instead a ", ref( $expandable ) ) ) if( ref( $expandable ) ne 'ARRAY' );
4698             ## Return a list with the dot prefixed with backslash
4699 0           my $list = join( '|', map( quotemeta( $_ ), @$expandable ) );
4700 0 0         my $re = $okParams->{expandable}->{data_prefix_is_ok} ? qr/^(?:data\.)?($list)$/ : qr/^($list)$/;
4701 0           foreach my $k ( @{$args->{expand}} )
  0            
4702             {
4703 0 0         if( $k !~ /$re/ )
4704             {
4705 0           push( @$errExpandables, $k );
4706             }
4707             }
4708             }
4709             else
4710             {
4711 0           push( @$errExpandables, @{$args->{expand}} );
  0            
4712             }
4713             }
4714             elsif( $depth )
4715             {
4716 0 0         my $max_depth = CORE::length( $depth ) ? $depth : $EXPAND_MAX_DEPTH;
4717 0           for( my $i = 0; $i < scalar( @{$args->{expand}} ); $i++ )
  0            
4718             {
4719             ## Count the number of dots. Make sure this does not exceed the $EXPAND_MAX_DEPTH which is 4 as of today (2020-02-23)
4720             # my $this_depth = scalar( () = $args->{expand}->[$i] =~ /\./g );
4721 0           my $path_parts = [split( /\./, $args->{expand}->[$i] )];
4722 0 0         if( scalar( @$path_parts ) > $max_depth )
4723             {
4724 0           my $old = [CORE::splice( @$path_parts, $max_depth - 1 )];
4725 0           $args->{expand}->[$i] = $path_parts;
4726             }
4727             }
4728             }
4729 0 0         push( @$err, sprintf( "The following properties are not allowed to expand: %s", join( ', ', @$err ) ) ) if( scalar( @$errExpandables ) );
4730             }
4731             elsif( !exists( $okParams->{expandable} ) )
4732             {
4733 0 0         push( @$err, sprintf( "Following elements were provided to be expanded, but no expansion is supported: '%s'.", CORE::join( "', '", @{$args->{expand}} ) ) ) if( scalar( @{$args->{expand}} ) );
  0            
  0            
4734             }
4735             }
4736             else
4737             {
4738 0           $self->message( 3, "No expansion requested." );
4739             }
4740 0           my @private_params = grep( /^_/, keys( %$args ) );
4741 0           CORE::delete( @$args{ @private_params } );
4742 0           return( $err );
4743             }
4744              
4745             sub _check_required
4746             {
4747 0     0     my $self = shift( @_ );
4748 0           my $required = shift( @_ );
4749 0 0         return( $self->error( "I was expecting an array reference of required field." ) ) if( ref( $required ) ne 'ARRAY' );
4750 0           my $args = shift( @_ );
4751 0 0         return( $self->error( "I was expecting an hash reference of parameters." ) ) if( ref( $args ) ne 'HASH' );
4752 0           my $err = [];
4753 0           foreach my $f ( @$required )
4754             {
4755 0 0 0       push( @$err, "Parameter $f is missing, and is required." ) if( !CORE::exists( $args->{ $f } ) || !CORE::length( $args->{ $f } ) );
4756             }
4757 0           return( $args );
4758             }
4759              
4760             sub _convert_boolean_for_json
4761             {
4762 0     0     my $self = shift( @_ );
4763 0   0       my $hash = shift( @_ ) || return;
4764 0           my $seen = {};
4765             local $crawl = sub
4766             {
4767 0     0     my $this = shift( @_ );
4768 0           foreach my $k ( keys( %$this ) )
4769             {
4770 0           $self->message( 3, "Checking field '$k'." );
4771 0 0 0       if( ref( $this->{ $k } ) eq 'HASH' )
    0          
4772             {
4773 0           my $addr = Scalar::Util::refaddr( $this->{ $k } );
4774 0 0         next if( ++$seen->{ $addr } > 1 );
4775 0           $crawl->( $this->{ $k } );
4776             }
4777             elsif( $self->_is_object( $this->{ $k } ) && $this->{ $k }->isa( 'Module::Generic::Boolean' ) )
4778             {
4779 0           $self->message( 3, "Field is a Boolean object. COnverting to true or false" );
4780 0 0         $this->{ $k } = $this->{ $k } ? 'true' : 'false';
4781             }
4782             }
4783 0           };
4784 0           $crawl->( $hash );
4785             }
4786              
4787             sub _encode_params
4788             {
4789 0     0     my $self = shift( @_ );
4790 0           my $args = shift( @_ );
4791 0 0         if( $self->{ '_encode_with_json' } )
4792             {
4793 0           return( $self->json->utf8->allow_blessed->encode( $args ) );
4794             }
4795             local $encode = sub
4796             {
4797 0     0     my( $pref, $data ) = @_;
4798 0           my $type = lc( ref( $data ) );
4799 0           $self->message( 3, "prefix is '$pref' and data type is '$type' (value = '$data')." );
4800 0           my $comp = [];
4801 0 0 0       if( $type eq 'hash' )
    0 0        
    0 0        
    0          
    0          
    0          
4802             {
4803 0           foreach my $k ( sort( keys( %$data ) ) )
4804             {
4805 0           my $ke = URI::Escape::uri_escape( $k );
4806 0           my $pkg = Scalar::Util::blessed( $data->{ $k } );
4807 0 0 0       if( $pkg && $pkg =~ /^Net::API::Stripe/ &&
      0        
      0        
4808             $data->{ $k }->can( 'id' ) &&
4809             $data->{ $k }->id )
4810             {
4811 0           push( @$comp, "${pref}${ke}" . '=' . $data->{ $k }->id );
4812 0           next;
4813             }
4814 0 0         my $res = $encode->( ( $pref ? sprintf( '%s[%s]', $pref, $ke ) : $ke ), $data->{ $k } );
4815 0           push( @$comp, @$res );
4816             }
4817             }
4818             elsif( $type eq 'array' )
4819             {
4820             ## According to Stripe's response to my mail inquiry of 2019-11-04 on how to structure array of hash in url encoded form data
4821 0           for( my $i = 0; $i < scalar( @$data ); $i++ )
4822             {
4823 0 0         my $res = $encode->( ( $pref ? sprintf( '%s[%d]', $pref, $i ) : sprintf( '[%d]', $i ) ), $data->[$i] );
4824 0           push( @$comp, @$res );
4825             }
4826             }
4827             elsif( ref( $data ) eq 'JSON::PP::Boolean' || ref( $data ) eq 'Module::Generic::Boolean' )
4828             {
4829 0 0         push( @$comp, sprintf( '%s=%s', $pref, $data ? 'true' : 'false' ) );
4830             }
4831             elsif( ref( $data ) eq 'SCALAR' && ( $$data == 1 || $$daata == 0 ) )
4832             {
4833 0 0         push( @$comp, sprintf( '%s=%s', $pref, $$data ? 'true' : 'false' ) );
4834             }
4835             elsif( $type eq 'datetime' )
4836             {
4837 0           push( @$comp, sprintf( '%s=%s', $pref, $data->epoch ) );
4838             }
4839             elsif( $type )
4840             {
4841 0           die( "Don't know what to do with data type $type\n" );
4842             }
4843             else
4844             {
4845 0           push( @$comp, sprintf( '%s=%s', $pref, URI::Escape::uri_escape_utf8( $data ) ) );
4846             }
4847 0           return( $comp );
4848 0           };
4849 0           my $res = $encode->( '', $args );
4850 0           return( join( '&', @$res ) );
4851             }
4852              
4853             sub _encode_params_multipart
4854             {
4855 0     0     my $self = shift( @_ );
4856 0           my $args = shift( @_ );
4857 0           my $opts = {};
4858 0 0 0       $opts = pop( @_ ) if( scalar( @_ ) && ref( $_[-1] ) eq 'HASH' );
4859             local $set_value = sub
4860             {
4861 0     0     my( $key, $val, $ref, $param ) = @_;
4862 0 0         $param = {} if( !CORE::length( $param ) );
4863 0 0         $param->{encoding} = $opts->{encoding} if( !CORE::length( $param->{encoding} ) );
4864 0 0         if( !CORE::exists( $ref->{ $key } ) )
4865             {
4866 0           $ref->{ $key } = [];
4867             }
4868 0           my $this = {};
4869 0 0         $this->{filename} = $param->{filename} if( CORE::length( $param->{filename} ) );
4870 0 0         $this->{type} = $param->{type} if( CORE::length( $param->{type} ) );
4871 0 0         $val = Encode::encode_utf8( $val ) if( substr( $this->{type}, 0, 4 ) eq 'text' );
4872 0 0         if( $param->{encoding} )
4873             {
4874 0 0 0       if( $param->{encoding} eq 'qp' || $param->{encoding} eq 'quoted-printable' )
    0          
4875             {
4876 0           $this->{value} = MIME::QuotedPrint::encode_qp( $val );
4877 0           $this->{encoding} = 'Quoted-Printable';
4878             }
4879             elsif( $param->{encoding} eq 'base64' )
4880             {
4881 0           $this->{value} = MIME::Base64::encode_base64( $val );
4882 0           $this->{encoding} = 'Base64';
4883             }
4884             else
4885             {
4886 0           die( "Unknown encoding method \"", $param->{encoding}, "\"\n" );
4887             }
4888             }
4889             else
4890             {
4891 0           $this->{value} = $val;
4892             }
4893 0           CORE::push( @{$ref->{ $key }}, $this );
  0            
4894 0           };
4895            
4896             local $encode = sub
4897             {
4898 0     0     my( $pref, $data, $hash ) = @_;
4899 0           my $type = lc( ref( $data ) );
4900 0 0 0       if( $type eq 'hash' )
    0 0        
    0 0        
    0          
    0          
4901             {
4902 0           foreach my $k ( sort( keys( %$data ) ) )
4903             {
4904             # my $ke = URI::Escape::uri_escape( $k );
4905 0           my $ke = $k;
4906 0           $ke =~ s/([\\\"])/\\$1/g;
4907 0           my $pkg = Scalar::Util::blessed( $data->{ $k } );
4908 0 0 0       if( $pkg && $pkg =~ /^Net::API::Stripe/ &&
    0 0        
      0        
      0        
4909             $data->{ $k }->can( 'id' ) &&
4910             $data->{ $k }->id )
4911             {
4912 0           $set_value->( "${pref}${ke}", $data->{ $k }->id, $hash, { type => 'text/plain' } );
4913 0           next;
4914             }
4915             ## This is a file
4916             elsif( ref( $data->{ $k } ) eq 'HASH' &&
4917             CORE::exists( $data->{ $k }->{_filepath} ) )
4918             {
4919 0 0         return( $self->error( "File path argument is actually empty" ) ) if( !CORE::length( $data->{ $k }->{_filepath} ) );
4920 0           my $this_file = Cwd::abs_path( $data->{ $k }->{_filepath} );
4921 0           my $fname = File::Basename::fileparse( $this_file );
4922 0           $self->message( 3, "File path ", $data->{ $k }->{_filepath}, " becomes '$this_file' with flie name '$fname'." );
4923 0 0         if( !-e( $this_file ) )
    0          
4924             {
4925 0           $self->error( "File \"$this_file\" does not exist." );
4926 0           next;
4927             }
4928             elsif( !-r( $this_file ) )
4929             {
4930 0           $self->error( "File \"$this_file\" is not reaable." );
4931 0           next;
4932             }
4933             my $io = IO::File->new( "<$this_file" ) || do
4934 0   0       {
4935             $self->error( "Cannot open file \"$this_file\": $!" );
4936             next;
4937             };
4938             # $io->binmode;
4939             ## my $binary = join( '', $io->getlines );
4940 0           my $binary = '';
4941 0           1 while( $io->read( $binary, 1024, CORE::length( $binary ) ) );
4942 0           $io->close;
4943 0 0         if( !CORE::length( $binary ) )
4944             {
4945 0           $self->error( "File data after reading file \"$this_file\" is empty!" );
4946 0           next;
4947             }
4948 0           my $mime_type = LWP::MediaTypes::guess_media_type( $this_file );
4949 0           $fname =~ s/([\\\"])/\\$1/g;
4950 0           $self->messagef( 3, "%d bytes of data found in this file '$fname' with mime type '$mime_type'." );
4951 0           $set_value->( "${pref}${ke}", $binary, $hash, { encoding => base64, filename => $fname, type => $mime_type } );
4952 0           next;
4953             }
4954 0 0         $encode->( ( $pref ? sprintf( '%s[%s]', $pref, $ke ) : $ke ), $data->{ $k }, $hash );
4955             }
4956             }
4957             elsif( $type eq 'array' )
4958             {
4959             ## According to Stripe's response to my mail inquiry of 2019-11-04 on how to structure array of hash in url encoded form data
4960 0           for( my $i = 0; $i < scalar( @$data ); $i++ )
4961             {
4962 0 0         $encode->( ( $pref ? sprintf( '%s[%d]', $pref, $i ) : sprintf( '[%d]', $i ) ), $data->[$i], $hash );
4963             }
4964             }
4965             elsif( ref( $data ) eq 'JSON::PP::Boolean' || ref( $data ) eq 'Module::Generic::Boolean' )
4966             {
4967 0 0         $set_value->( $pref, $data ? 'true' : 'false', $hash, { type => 'text/plain' } );
4968             }
4969             elsif( ref( $data ) eq 'SCALAR' && ( $$data == 1 || $$daata == 0 ) )
4970             {
4971 0 0         $set_value->( $pref, $$data ? 'true' : 'false', $hash, { type => 'text/plain' } );
4972             }
4973             elsif( $type )
4974             {
4975 0           die( "Don't know what to do with data type $type\n" );
4976             }
4977             else
4978             {
4979 0           $set_value->( $pref, $data, $hash, { type => 'text/plain' } );
4980             }
4981 0           };
4982 0           my $result = {};
4983 0           $encode->( '', $args, $result );
4984 0           return( $result );
4985             }
4986              
4987             sub _get_args
4988             {
4989 0     0     my $self = shift( @_ );
4990 0 0 0       return( {} ) if( !scalar( @_ ) || ( scalar( @_ ) == 1 && !defined( $_[0] ) ) );
      0        
4991             ## Arg is one unique object
4992 0 0         return( $_[0] ) if( $self->_is_object( $_[0] ) );
4993 0 0         my $args = ref( $_[0] ) eq 'HASH' ? $_[0] : { @_ == 1 ? ( id => $_[0] ) : @_ };
    0          
4994 0           return( $args );
4995             }
4996              
4997             sub _get_args_from_object
4998             {
4999 0     0     my $self = shift( @_ );
5000 0   0       my $class = shift( @_ ) || return( $self->error( "No class was provided to get its information as parameters." ) );
5001 0           my $args = {};
5002 0 0 0       if( $self->_is_object( $_[0] ) && $_[0]->isa( $class ) )
5003             {
5004 0           my $obj = shift( @_ );
5005 0           $args = $obj->as_hash({ json => 1 });
5006 0           $args->{expand} = 'all';
5007 0           $args->{_cleanup} = 1;
5008 0           $args->{_object} = $obj;
5009             }
5010             else
5011             {
5012 0           $args = $self->_get_args( @_ );
5013             }
5014 0           return( $args );
5015             }
5016              
5017             sub _get_method
5018             {
5019 0     0     my $self = shift( @_ );
5020 0           my( $type, $action, $allowed ) = @_;
5021 0 0         return( $self->error( "No action was provided to get the associated method." ) ) if( !CORE::length( $action ) );
5022 0 0         return( $self->error( "Allowed method list provided is not an array reference." ) ) if( ref( $allowed ) ne 'ARRAY' );
5023 0 0         return( $self->error( "Allowed method list provided is empty." ) ) if( !scalar( @$allowed ) );
5024 0 0         if( $action eq 'remove' )
    0          
5025             {
5026 0           $action = 'delete';
5027             }
5028             elsif( $action eq 'add' )
5029             {
5030 0           $action = 'create';
5031             }
5032 0 0         if( !scalar( grep( /^$action$/, @$allowed ) ) )
5033             {
5034 0           return( $self->error( "Method $action is not authorised for $type" ) );
5035             }
5036 0           my $meth = $self->can( "${type}_${action}" );
5037 0 0         return( $self->error( "Method ${type}_${action} is not implemented in class '", ref( $self ), "'" ) ) if( !$meth );
5038 0           return( $meth );
5039             }
5040              
5041             sub _instantiate
5042             {
5043 0     0     my $self = shift( @_ );
5044 0           my $name = shift( @_ );
5045 0 0 0       return( $self->{ $name } ) if( exists( $self->{ $name } ) && Scalar::Util::blessed( $self->{ $name } ) );
5046 0           my $class = shift( @_ );
5047 0           my $this;
5048 0           try
5049 0     0     {
5050             ## https://stackoverflow.com/questions/32608504/how-to-check-if-perl-module-is-available#comment53081298_32608860
5051             # require $class unless( defined( *{"${class}::"} ) );
5052 0           my $rc = eval{ $self->_load_class( $class ) };
  0            
5053 0 0         return( $self->error( "Unable to load class $class: $@" ) ) if( $@ );
5054 0   0       $this = $class->new(
5055             'debug' => $self->debug,
5056             'verbose' => $self->verbose,
5057             ) || return( $self->pass_error( $class->error ) );
5058 0           $this->{parent} = $self;
5059             }
5060 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
5061 0     0     {
5062 0           return( $self->error({ code => 500, message => $e }) );
5063 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
5064 0           return( $this );
5065             }
5066              
5067             sub _make_error
5068             {
5069 0     0     my $self = shift( @_ );
5070 0           my $args = shift( @_ );
5071 0           return( $self->error( $args ) );
5072             }
5073              
5074             sub _make_request
5075             {
5076 0     0     my $self = shift( @_ );
5077 0           my $req = shift( @_ );
5078 0           my( $e, $resp, $ret, $is_error );
5079             $ret = eval
5080 0           {
5081 0           $req->header( 'Authorization' => $self->{auth} );
5082 0           $req->header( 'Stripe_Version' => $self->{version} );
5083 0           $req->header( 'Content-Type' => 'application/x-www-form-urlencoded' );
5084 0 0         $req->header( 'Content-Type' => 'application/json' ) if( $self->encode_with_json );
5085 0           $req->header( 'Accept' => 'application/json' );
5086              
5087 0           $resp = $self->http_client->request( $req );
5088 0           $self->{http_request} = $req;
5089 0           $self->{http_response} = $resp;
5090             ## if( $resp->code == 200 )
5091 0 0 0       if( $resp->is_success || $resp->is_redirect )
5092             {
5093 0           $self->message( 3, "Request successful, decoding its content" );
5094 0           my $hash = $self->json->utf8->decode( $resp->decoded_content );
5095 0           $self->message( 3, "Returning $hash" );
5096             ## $ret = data_object( $hash );
5097 0           return( $hash );
5098             }
5099             else
5100             {
5101 0           $self->messagef( 3, "Request failed with error %s", $resp->message );
5102 0 0         if( $resp->header( 'Content_Type' ) =~ m{text/html} )
5103             {
5104 0           return( $self->_make_error({
5105             code => $resp->code,
5106             type => $resp->message,
5107             message => $resp->message
5108             }) );
5109             }
5110             else
5111             {
5112 0           my $hash = $self->json->utf8->decode( $resp->decoded_content );
5113 0     0     $self->message( 3, "Error returned by Stripe is: ", sub{ $self->dumper( $hash ) } );
  0            
5114 0           $self->message( 3, "Creating error from Stripe error $hash->{error}" );
5115 0   0       return( $self->_make_error( $hash->{error} // $hash ) );
5116             }
5117             }
5118             };
5119 0 0         if( $@ )
5120             {
5121 0           $self->message( 3, "Returning error $@" );
5122 0 0         return( $self->_make_error({
5123             'type' => "Could not decode HTTP response: $@",
5124             $resp
5125             ? ( 'message' => $resp->status_line . ' - ' . $resp->content )
5126             : (),
5127             }) );
5128             }
5129             ## return( $ret ) if( $ret );
5130 0           $self->message( 3, "Returning the result value '$ret'" );
5131 0           return( $ret );
5132             }
5133              
5134             sub _object_class_to_type
5135             {
5136 0     0     my $self = shift( @_ );
5137 0   0       my $class = shift( @_ ) || return( $self->error( "No class was provided to find its associated type." ) );
5138 0 0         $class = ref( $class ) if( $self->_is_object( $class ) );
5139 0           my $ref = $Net::API::Stripe::TYPE2CLASS;
5140 0           foreach my $c ( keys( %$ref ) )
5141             {
5142 0 0         return( $c ) if( $ref->{ $c } eq $class );
5143             }
5144 0           return;
5145             }
5146              
5147             sub _object_type_to_class
5148             {
5149 0     0     my $self = shift( @_ );
5150 0   0       my $type = shift( @_ ) || return( $self->error( "No object type was provided" ) );
5151 0           my $ref = $Net::API::Stripe::TYPE2CLASS;
5152             # $self->messagef( 3, "\$TYPE2CLASS has %d elements", scalar( keys( %$ref ) ) );
5153 0 0         return( $self->error( "No object type '$type' known to get its related class for field $self->{_field}" ) ) if( !exists( $ref->{ $type } ) );
5154 0           return( $ref->{ $type } );
5155             }
5156              
5157             sub _process_array_objects
5158             {
5159 0     0     my $self = shift( @_ );
5160 0           my $class = shift( @_ );
5161 0   0       my $ref = shift( @_ ) || return;
5162 0 0 0       return if( !ref( $ref ) || ref( $ref ) ne 'ARRAY' );
5163 0           for( my $i = 0; $i < scalar( @$ref ); $i++ )
5164             {
5165 0           my $hash = $ref->[$i];
5166 0 0         next if( ref( $hash ) ne 'HASH' );
5167 0           my $o = $class->new( %$hash );
5168 0           $ref->[$i] = $o;
5169             }
5170 0           return( $ref );
5171             }
5172              
5173             sub _response_to_object
5174             {
5175 0     0     my $self = shift( @_ );
5176 0           my $class = shift( @_ );
5177 0 0         return( $self->error( "No hash was provided" ) ) if( !scalar( @_ ) );
5178 0           my $hash = $self->_get_args( @_ );
5179             ## my $callbacks = $CALLBACKS->{ $class };
5180             ## $self->message( "Found callbacks for $class: ", sub{ Dumper( $CALLBACKS ) } );
5181             ## $self->messagef( "%d callbacks found for class $class", scalar( keys( %$callbacks ) ) );
5182             # $self->message( 3, "Called for class $class with hash $hash" );
5183 0           my $o;
5184 0           try
5185 0     0     {
5186             ## https://stackoverflow.com/questions/32608504/how-to-check-if-perl-module-is-available#comment53081298_32608860
5187             # eval( "require $class;" ) unless( defined( *{"${class}::"} ) );
5188 0           my $rc = eval{ $self->_load_class( $class ) };
  0            
5189 0 0         return( $self->error( "An error occured while trying to load the module $class: $@" ) ) if( $@ );
5190             # $self->messagef( 3, "Creating an object of claass $class with %d elements inside.", scalar( keys( %$hash ) ) );
5191             $o = $class->new({
5192             '_parent' => $self,
5193             '_debug' => $self->{debug},
5194             '_dbh' => $self->{_dbh},
5195 0           }, $hash );
5196             }
5197 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
5198 0     0     {
5199 0           return( $self->error( $e ) );
5200 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
5201 0 0         return( $self->pass_error( $class->error ) ) if( !defined( $o ) );
5202             # $self->message( 3, "Returning object $o for class $class" );
5203             # $self->message( 3, "Object $o structure is: ", $self->dumper( $o ) );
5204 0           return( $o );
5205             }
5206              
5207             # https://stripe.com/docs/api#errors
5208             package Net::API::Stripe::Error;
5209             BEGIN
5210             {
5211 1     1   10 use strict;
  1         2  
  1         29  
5212 1     1   5 use parent -norequire, qw( Module::Generic::Exception );
  1         2  
  1         9  
5213 1     1   144 our( $VERSION ) = '0.1';
5214             };
5215              
5216             1;
5217              
5218             __END__