Connects your web site to social media. Likes, retweets, mentions, cross-posting, and more... - GitHub - snarfed/bridgy: 📣 Connects your web site to social media. Likes, retweets, mentions, cross...
Been wiring up this blog for IndieWeb and Bridgy is the speÂcial sauce that conÂnects it to more siloed social networks
I menÂtioned at the Ephemeral Miniconf last month that as soon as I write about one Perl modÂule (or five), someÂone inevitably brings up anothÂer (or sevÂen) I’ve missed. And of course, it hapÂpened again last week: no soonÂer had I writÂten in passÂing that I was using Exception::Class than the denizens of the Libera Chat IRC#perl chanÂnel insistÂed I should use Throwable instead for definÂing my excepÂtions. (I’ve already blogged about varÂiÂous ways of catchÂing excepÂtions.)
Why Throwable? Aside from Exception::Class’s author recÂomÂmendÂing it over his own work due to a ​“nicer, more modÂern interÂface,” Throwable is a Moo role, so it’s comÂposÂable into classÂes along with othÂer roles instead of muckÂing about with mulÂtiÂple inherÂiÂtance. This means that if your excepÂtions need to do someÂthing reusable in your appliÂcaÂtion like logÂging, you can also conÂsume a role that does that and not have so much dupliÂcate code. (No, I’m not going to pick a favorite logÂging modÂule; I’ll probÂaÂbly get that wrong too.)
However, since Throwable is a role instead of a class, I would have to define sevÂerÂal addiÂtionÂal packages in my tiny modÂuliÂno script from last week, one for each excepÂtion class I want. The beauÂty of Exception::Class is its simÂple declarÂaÂtive nature: just use it and pass a list of desired class names along with options for attribÂutÂes and whatÂnot. What’s needÂed for simÂple use casÂes like mine is a declarÂaÂtive synÂtax for definÂing sevÂerÂal excepÂtion classÂes withÂout the noise of mulÂtiÂple packages.
Enter Throwable::SugarFactory, a modÂule that enables you to do just that by adding an exception funcÂtion for declarÂing excepÂtion classÂes. (There’s also the similarly-​named Throwable::Factory; see the above disÂcusÂsion about nevÂer being able to covÂer everybody’s favorites.) The exception funcÂtion takes three arguÂments: the name of the desired excepÂtion class as a string, a descripÂtion, and an optionÂal list of instrucÂtions Moo uses to build the class. It might look someÂthing like this:
package Local::My::Exceptions;
use Throwable::SugarFactory;
exception GenericError => 'something bad happened';
exception DetailedError => 'something specific happened' =>
( has => [ message => ( is => 'ro' ) ] );
1;
Throwable::SugarFactory takes care of creÂatÂing conÂstrucÂtor funcÂtions in Perl-​style snake_case as well as funcÂtions for detectÂing what kind of excepÂtion is being caught, so you can use your new excepÂtion library like this:
#!/usr/bin/env perl
use experimental qw(isa);
use Feature::Compat::Try;
use JSON::MaybeXS;
use Local::My::Exceptions;
try {
die generic_error();
}
catch ($e) {
warn 'whoops!';
}
try {
die detailed_error( message => 'you got me' );
}
catch ($e) {
die encode_json( $e->to_hash )
if $e isa DetailedError and defined $e->message;
$e->throw if $e->does('Throwable');
die $e;
}
The above also demonÂstrates a couÂple of othÂer Throwable::SugarFactory feaÂtures. First, you get a to_hash method that returns a hash refÂerÂence of all excepÂtion data, suitÂable for seriÂalÂizÂing to JSON. Second, you get all of Throwable’s methÂods, includÂing throw for re-​throwing exceptions.
So where does this leave last week’s FOAAS.com modÂuliÂno client demonÂstraÂtion of object mockÂing tests? With a litÂtle bit of rewritÂing to define and then use our sweetÂer excepÂtion library, it looks like this. You can review for a descripÂtion of the rest of its workings.
#!/usr/bin/env perl
package Local::CallFOAAS::Exceptions;
use Throwable::SugarFactory;
BEGIN {
exception NoMethodError =>
'no matching WebService::FOAAS method' =>
( has => [ method => ( is => 'ro' ) ] );
exception ServiceError =>
'error from WebService::FOAAS' =>
( has => [ message => ( is => 'ro' ) ] );
}
package Local::CallFOAAS; # this is a modulino
use Test2::V0; # enables strict, warnings, utf8
# declare all the new stuff we're using
use feature qw(say state);
use experimental qw(isa postderef signatures);
use Feature::Compat::Try;
use Syntax::Construct qw(non-destructive-substitution);
use WebService::FOAAS ();
use Package::Stash;
BEGIN { Local::CallFOAAS::Exceptions->import() }
my $foaas = Package::Stash->new('WebService::FOAAS');
my $run_as =
!!$ENV{CPANTEST} ? 'test'
: !defined scalar caller ? 'run'
: undef;
__PACKAGE__->$run_as(@ARGV) if defined $run_as;
sub run ( $class, @args ) {
try { say $class->call_method(@args) }
catch ($e) {
die 'No method ', $e->method, "\n"
if $e isa NoMethodError;
die 'Service error: ', $e->message, "\n"
if $e isa ServiceError;
die "$e\n";
}
return;
}
# Utilities
sub methods ($) {
state @methods = sort map s/^foaas_(.+)/$1/r,
grep /^foaas_/, $foaas->list_all_symbols('CODE');
return @methods;
}
sub call_method ( $class, $method = '', @args ) {
state %methods = map { $_ => 1 } $class->methods();
die no_method_error( method => $method )
unless $methods{$method};
return do {
try { $foaas->get_symbol("&$method")->(@args) }
catch ($e) { die service_error( message => $e ) }
};
}
# Testing
sub test ( $class, @ ) {
state $stash = Package::Stash->new($class);
state @tests = sort grep /^_test_/,
$stash->list_all_symbols('CODE');
for my $test (@tests) {
subtest $test => sub {
try { $class->$test() }
catch ($e) { diag $e }
};
}
done_testing();
return;
}
sub _test_can ($class) {
state @subs = qw(run call_method methods test);
can_ok $class, \@subs, "can do: @subs";
return;
}
sub _test_methods ($class) {
my $mock = mock 'WebService::FOAAS' => ( track => 1 );
for my $method ( $class->methods() ) {
$mock->override( $method => 1 );
ok lives { $class->call_method($method) },
"$method lives";
ok scalar $mock->sub_tracking->{$method}->@*,
"$method called";
}
return;
}
sub _test_service_failure ($class) {
my $mock = mock 'WebService::FOAAS';
for my $method ( $class->methods() ) {
$mock->override( $method => sub { die 'mocked' } );
my $exception =
dies { $class->call_method($method) };
isa_ok $exception, [ServiceError],
"$method throws ServiceError on failure";
like $exception->message, qr/^mocked/,
"correct error in $method exception";
}
return;
}
1;
[Updated, thanks to Dan Book, Karen Etheridge, and Bob Kleemann] The only goofy bit above is the need to put the exception calls in a BEGIN block and then explicÂitÂly call BEGIN { Local::CallFOAAS::Exceptions->import() }. Since the two packÂages are in the same file, I can’t do a use stateÂment since the implied require would look for a corÂreÂspondÂing file or entry in %INC. (You can get around this by messÂing with %INC directÂly or through a modÂule like me::inlined that does that messÂing for you, but for a single-​purpose modÂuliÂno like this it’s fine.)
Over the past two years, I’ve gotÂten back into playÂing Dungeons & Dragons, the famous tableÂtop fanÂtaÂsy role-​playing game. As a softÂware develÂopÂer and musiÂcian, one of my favorite charÂacÂter classÂes to play is the bard, a magÂiÂcal and inspirÂing perÂformer or wordÂsmith. The list of basic bardic spells includes Vicious Mockery, enchantÂiÂng verÂbal barbs that have the powÂer to psyÂchiÂcalÂly damÂage and disÂadÂvanÂtage an oppoÂnent even if they don’t underÂstand the words. (Can you see why this is so appealÂing to a coder?)
Mocking has a role to play in softÂware testÂing as well, in the form of mock objects that simÂuÂlate parts of a sysÂtem that are too britÂtle, too slow, too comÂpliÂcatÂed, or othÂerÂwise too finicky to use in realÂiÂty. They enable disÂcrete unit testÂing withÂout relyÂing on depenÂdenÂcies exterÂnal to the code being testÂed. Mocks are great for dataÂbasÂes, web serÂvices, or othÂer netÂwork resources where the goal is to test what you wrote, not what’s out in ​“the cloud” somewhere.
Speaking of web serÂvices and mockÂing, one of my favorites is the long-​running FOAAS (link has lanÂguage not safe for work), a surÂprisÂingÂly expanÂsive RESTful insult serÂvice. There’s a corÂreÂspondÂing Perl client API, of course, but what I was missÂing was a handy Perl script to call that API from the terÂmiÂnal comÂmand line. So I wrote the folÂlowÂing over Thanksgiving break, tryÂing to keep it simÂple while also showÂing the basics of mockÂing such an API. It also demonÂstrates some newÂer Perl synÂtax and testÂing techÂniques as well as briÂan d foy​’s modÂuliÂno conÂcept from Mastering Perl (secÂond ediÂtion, 2014) that marÂries script and modÂule into a self-​contained exeÂcutable library.
#!/usr/bin/env perl
package Local::CallFOAAS; # this is a modulino
use Test2::V0; # enables strict, warnings, utf8
# declare all the new stuff we're using
use feature qw(say state);
use experimental qw(isa postderef signatures);
use Feature::Compat::Try;
use Syntax::Construct qw(non-destructive-substitution);
use WebService::FOAAS ();
use Package::Stash;
use Exception::Class (
NoMethodException => {
alias => 'throw_no_method',
fields => 'method',
},
ServiceException => { alias => 'throw_service' },
);
my $foaas = Package::Stash->new('WebService::FOAAS');
my $run_as =
!!$ENV{CPANTEST} ? 'test'
: !defined scalar caller ? 'run'
: undef;
__PACKAGE__->$run_as(@ARGV) if defined $run_as;
sub run ( $class, @args ) {
try { say $class->call_method(@args) }
catch ($e) {
die 'No method ', $e->method, "\n"
if $e isa NoMethodException;
die 'Service error: ', $e->error, "\n"
if $e isa ServiceException;
die "$e\n";
}
return;
}
# Utilities
sub methods ($) {
state @methods = sort map s/^foaas_(.+)/$1/r,
grep /^foaas_/, $foaas->list_all_symbols('CODE');
return @methods;
}
sub call_method ( $class, $method = '', @args ) {
state %methods = map { $_ => 1 } $class->methods();
throw_no_method( method => $method )
unless $methods{$method};
return do {
try { $foaas->get_symbol("&$method")->(@args) }
catch ($e) { throw_service( error => $e ) }
};
}
# Testing
sub test ( $class, @ ) {
state $stash = Package::Stash->new($class);
state @tests = sort grep /^_test_/,
$stash->list_all_symbols('CODE');
for my $test (@tests) {
subtest $test => sub {
try { $class->$test() }
catch ($e) { diag $e }
};
}
done_testing();
return;
}
sub _test_can ($class) {
state @subs = qw(run call_method methods test);
can_ok( $class, \@subs, "can do: @subs" );
return;
}
sub _test_methods ($class) {
my $mock = mock 'WebService::FOAAS' => ( track => 1 );
for my $method ( $class->methods() ) {
$mock->override( $method => 1 );
ok lives { $class->call_method($method) },
"$method lives";
ok scalar $mock->sub_tracking->{$method}->@*,
"$method called";
}
return;
}
sub _test_service_failure ($class) {
my $mock = mock 'WebService::FOAAS';
for my $method ( $class->methods() ) {
$mock->override( $method => sub { die 'mocked' } );
my $exception =
dies { $class->call_method($method) };
isa_ok $exception, ['ServiceException'],
"$method throws ServiceException on failure";
like $exception->error, qr/^mocked/,
"correct error in $method exception";
}
return;
}
1;
Let’s walk through the code above.
Preliminaries
First, there’s a generÂic sheÂbang line to indiÂcate that Unix and Linux sysÂtems should use the perl exeÂcutable found in the user’s PATH via the env comÂmand. I declare a packÂage name (in the Local:: nameÂspace) so as not to polÂlute the default main packÂage of othÂer scripts that might want to require this as a modÂule. Then I use the Test2::V0 bunÂdle from Test2::Suite since the embedÂded testÂing code uses many of its funcÂtions. This also has the side effect of enabling the strict, warnÂings, and utf8 pragÂmas, so there’s no need to explicÂitÂly use them here.
(Why Test2 instead of Test::More and its derivÂaÂtives and add-​ons? Both are mainÂtained by the same author, who recÂomÂmends the forÂmer. I’m seeÂing more and more modÂules using it, so I thought this would be a great opporÂtuÂniÂty to learn.)
Next, I bring in the aforeÂmenÂtioned FOAAS Perl API withÂout importÂing any of its funcÂtions, Package::Stash to make metaproÂgramÂming easÂiÂer, and a couÂple of excepÂtion classÂes so that the comÂmand line funcÂtion and othÂer conÂsumers might betÂter tell what caused a failÂure. In prepaÂraÂtion for the methÂods below dynamÂiÂcalÂly disÂcovÂerÂing what funcÂtions are proÂvidÂed by WebService::FOAAS, I gathÂer up its symÂbol table (or stash) into the $foaas variable.
The next block deterÂmines how, if at all, I’m going to run the code as a script. If the CPANTEST enviÂronÂment variÂable is set, I’ll call the test class method sub, but if there’s no subÂrouÂtine callÂing me I’ll exeÂcute the run class method. Either will receive the comÂmand line arguÂments from @ARGV. If neiÂther of these conÂdiÂtions is true, do nothÂing; the rest of the code is method declarations.
Modulino methods, metaprogramming, and exceptions
The first of these is the run method. It’s a thin wrapÂper around the call_method class method detailed below, either outÂputting its result or dieing with an approÂpriÂate error dependÂing on the class of excepÂtion thrown. Although I chose not to write tests for this outÂput, future tests might call this method and catch these rethrown excepÂtions to match against them. The mesÂsages end with a \n newÂline charÂacÂter so die knows not to append the curÂrent script line number.
Next is a utilÂiÂty method called methods that uses Package::Stash’s list_all_symbols to retrieve the names of all named CODE blocks (i.e., subs) from WebService::FOAAS’s symÂbol table. Reading from right to left, these are then filÂtered with grep to only find those beginÂning in foaas_ and then transÂformed with map to remove that preÂfix. The list is then sorted and stored in a state variÂable and returned so it need not be iniÂtialÂized again.
(As an aside, although perlcritic sternÂly warns against it I’ve choÂsen the expresÂsion forms of grep and map here over their blockforms for simÂplicÂiÂty’s sake. It’s OK to bend the rules if you have a good reason.)
sub call_method is where the real action takes place. Its paraÂmeÂters are the class that called it, the name of a FOAAS$method (defaultÂed to the empÂty string), and an array of optionÂal arguÂments in @args. I build a hash or assoÂciaÂtive array from the earÂliÂer methods method which I then use to see if the passed method name is one I know about. If not, I throw a NoMethodException using the throw_no_method alias funcÂtion creÂatÂed when I used Exception::Class at the beginÂning. Using a funcÂtion instead of NoMethodException->throw() means that it’s checked at comÂpile time rather than runÂtime, catchÂing typos.
I get the subÂrouÂtine (denotÂed by a & sigÂil) named by $method from the $foaas stash and pass it any furÂther received arguÂments from @args. If that WebService::FOAAS subÂrouÂtine throws an excepÂtion it’ll be caught and re-​thrown as a ServiceException; othÂerÂwise call_method returns the result. It’s up to the caller to deterÂmine what, if anyÂthing, to do with that result or any thrown exceptions.
Testing the modulino with mocks
This is where I start using those Test2::Suite tools I menÂtioned at the beginÂning. The test class method starts by buildÂing a filÂtered list of all subs beginÂning with _test_ in the curÂrent class, much like methods did above with WebService::FOAAS. I then loop through that list of subs, runÂning each as a subtest conÂtainÂing a class method with any excepÂtions reportÂed as diagÂnosÂtics.
The rest of the modÂuliÂno is subÂtest methÂods, startÂing with a simÂple _test_can sanÂiÂty check for the pubÂlic methÂods in the class. Following that is _test_methods, which starts by mocking the WebService::FOAAS packÂage and telling Test2::Mock I want to track any added, overÂridÂden, or set subs. I then loop through all the method names returned by the methods class method, overrideing each one to return a simÂple true valÂue. I then test passÂing those names to call_method and use the hash refÂerÂence returned by sub_tracking to check that the overÂridÂden sub was called. This seems a lot simÂpler than the Test::Builder-based mockÂing libraries I’ve tried like Test::MockModule and Test::MockObject.
_test_service_failure acts in much the same way, checkÂing that call_method corÂrectÂly throws ServiceExceptions if the wrapped WebService::FOAAS funcÂtion dies. The main difÂferÂence is that the mocked WebService::FOAASsubs are now overÂridÂden with a code refÂerÂence (sub { die 'mocked' }), which call_method uses to popÂuÂlate the rethrown ServiceException​’s error field.
Wrapping up
With luck, this artiÂcle has givÂen you some ideas, whether it’s in makÂing scripts (perÂhaps legaÂcy code) testable to improve them, or writÂing betÂter unit tests that mock depenÂdenÂcies, or delvÂing a litÂtle into metaproÂgramÂming so you can dynamÂiÂcalÂly supÂport and test new feaÂtures of said depenÂdenÂcies. I hope you haven’t come away too offendÂed, at least. Let me know in the comÂments what you think.
{"id":"3","mode":"text_link","open_style":"in_place","currency_code":"USD","currency_symbol":"$","currency_type":"decimal","blank_flag_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":500,"top_media_type":"featured_image","featured_image_url":"https:\/\/phoenixtrap.com\/wp-content\/uploads\/2021\/02\/image-200x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Do you like what you see? Leave a tip!","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"The Phoenix Trap","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"The Phoenix Trap","form_subtitle":"Do you like what you see? Leave a one-time or recurring tip!","currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for being a supporter!","payment_confirmation_title":"The Phoenix Trap","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
{"id":"11","mode":"button","open_style":"in_modal","currency_code":"USD","currency_symbol":"$","currency_type":"decimal","blank_flag_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":500,"top_media_type":"featured_image","featured_image_url":"https:\/\/phoenixtrap.com\/wp-content\/uploads\/2021\/02\/image-200x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Leave a tip!","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"The Phoenix Trap","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"The Phoenix Trap","form_subtitle":"Do you like what you see? Leave a one-time or recurring tip!","currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for being a supporter!","payment_confirmation_title":"The Phoenix Trap","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"What is the name on your credit card?"},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"What is your email address?"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}