This month I started a new job at Alert Logic, a cybersecurity provider with Perl (among many other things) at its beating heart. I’ve been learning a lot, and part of the process has been understanding the APIs in the code base. To that end, I’ve been writing small test scripts to tease apart data structures, using Perl array-processing, list-processing, and hash- (i.e., associative array)-processing functions.
I’ve covered map, grep, and friends a coupletimes before. Most recently, I described using List::Util’s any function to check if a condition is true for any item in a list. In the simplest case, you can use it to check to see if a given value is in the list at all:
use feature 'say';
use List::Util 'any';
my @colors =
qw(red orange yellow green blue indigo violet);
say 'matched' if any { /^red$/ } @colors;
However, if you’re going to be doing this a lot with arbitrary strings, Perl FAQ section 4 advises turning the array into the keys of a hash and then checking for membership there. For example, here’s a simple script to check if the colors input (either from the keyboard or from files passed as arguments) are in the rainbow:
#!/usr/bin/env perl
use v5.22; # introduced <<>> for safe opening of arguments
use warnings;
my %in_colors = map {$_ => 1}
qw(red orange yellow green blue indigo violet);
while (<<>>) {
chomp;
say "$_ is in the rainbow" if $in_colors{$_};
}
List::Util has a bunch of functions for processing lists of pairs that I’ve found useful when pawing through hashes. pairgrep, for example, acts just like grep but instead assigns $a and $b to each key and value passed in and returns the resulting pairs that match. I’ve used it as a quick way to search for hash entries matching certain value conditions:
use List::Util 'pairgrep';
my %numbers = (zero => 0, one => 1, two => 2, three => 3);
my %odds = pairgrep {$b % 2} %numbers;
Sure, you could do this by invoking a mix of plain grep, keys, and a hash slice, but it’s noisier and more repetitive:
use v5.20; # for key/value hash slice
my %odds = %numbers{grep {$numbers{$_} % 2} keys %numbers};
pairgrep’s compiled C‑based XS code can also be faster, as evidenced by this Benchmark script that works through a hash made of the Unix words file (479,828 entries on my machine):
#!/usr/bin/env perl
use v5.20;
use warnings;
use List::Util 'pairgrep';
use Benchmark 'cmpthese';
my (%words, $count);
open my $fh, '<', '/usr/share/dict/words'
or die "can't open words: $!";
while (<$fh>) {
chomp;
$words{$_} = $count++;
}
close $fh;
cmpthese(100, {
grep => sub {
my %odds = %words{grep {$words{$_} % 2} keys %words};
},
pairgrep => sub {
my %odds = pairgrep {$b % 2} %words;
},
} );
The Java world had an… interesting weekend when security researchers revealed on December 9 a vulnerability in the popular Apache Log4j 2 software library for recording and debugging events. Systems as diverse as Amazon Web Services, Apple iCloud, and the Minecraft video game could be exploited to run arbitrary code on a server merely by sending a specially-crafted string of text. Information technology professionals have been scrambling ever since the initial disclosure to patch, upgrade, reconfigure, or otherwise protect affected servers. It’s bad, and past unpatched vulnerabilities like this have been responsible for the exposure of millions of people’s sensitive data.
Many Perl applications use the similarly-named and ‑designed Log::Log4perl library, and the good news is that as far as I can tell the latter doesn’t suffer from the type of vulnerability described above. This doesn’t mean poorly-written or ‑configured Perl-based systems are immune to all exploits, just this particular one. You should be safe to continue using Log4perl unless someone has deliberately configured it otherwise, and in fact, my work uses it extensively.
But in this case, you can and should have the best of both worlds — logging at different levels to appropriate destinations while still dropping into the interactive debugger when you need to do something trickier like examine program state or tweak a data structure on the fly. I use both techniques and only emphasize the advocacy of step debugging because it’s understood less.
Inspired by my parents coming to visit at the end of the week, I thought I’d write about how Perl classes can have “parents” as well, from which they inherit methods. Although it might seem on the surface as though there’s more than one way to do it, these techniques all share the same underlying mechanism.
A couple of years ago my Newfold Digital colleague David Oswald created a fork of parent called parent::versioned that supports specifying the lowest version for superclasses. You call it like this:
use parent::versioned ['Local::MyParentClass' => 1.23];
Within an OO system
There are dozens of object-oriented programming systems on CPAN that provide syntactic sugar and extra features to Perl’s minimal but flexible basics. Two of the more popular ones, Moose and Moo, offer an extends keyword that you should use instead of use parent so that your subclasses may take advantage of their features:
package Local::MyChildClass;
use Moo;
extends 'Local::MyParentClass';
Moose can also specify a required superclass version:
package Local::MyChildClass;
use Moose;
extends 'Local::MyParentClass' => {-version => 1.23};
Also, use the MooseX::NonMoose module when extending non-Moose classes, again so you get Moose features even though your methods are coming from somewhere else:
package Local::MyMooseClass;
use Moose;
use MooseX::NonMoose;
extends 'Local::MyPlainParentClass';
The experimental Object::Pad module specifies a single superclass while defining the class name with an optional version. Per the author’s suggested file layout, including a required minimum version, it would look like:
use Object::Pad 0.41;
package Local::MyChildClass;
class Local::MyChildClass isa Local::MyParentClass 1.23;
Object::Pad and Corinna, its inspiration, are works in progress so this syntax isn’t set in stone. The latter’s designer Curtis “Ovid” Poe blogged earlier this week about considering a more self-consistent syntax.
To quote the Perl documentation, “multiple inheritance often indicates a design problem, but Perl always gives you enough rope to hang yourself with if you ask for it.” All the techniques described above except for Object::Pad support multiple inheritance by specifying a list of superclasses. For example:
package Local::MyChildClass;
use parent qw(Local::MyParentClass1 Local::MyParentClass2);
If you’re using roles instead of or on top of superclasses (I’ve seen both situations) and your OO system doesn’t support them on its own, you can use the Role::Tiny module, first by describing your role in one package and then consuming it in another:
package Local::DoesSomething;
use Role::Tiny;
...
1;
package Local::MyConsumer;
use Role::Tiny::With;
with 'Local::DoesSomething';
...
1;
Moo::Role uses Role::Tiny under the hood and Moo can compose roles from either. The syntax for both Moo and Moose is similar:
package Local::DoesSomething;
use Moo::Role; # or "use Moose::Role;"
...
1;
package Local::MyConsumer;
use Moo; # or "use Moose;"
with 'Local::DoesSomething';
...
1;
Object::Pad specifies roles with the role keyword, and both classes and roles use does to consume them:
use Object::Pad 0.56;
package Local::DoesSomething;
role Local::DoesSomething does Local::DoesSomethingElse;
...
1;
use Object::Pad 0.56;
package Local::MyConsumer;
class Local::MyConsumer does Local::DoesSomething;
...
1;
The previous caveat about possible changes to this syntax applies.
Like parent, (sort of) like child
Of course, the whole point of inheritance or role consumption is so your child or consumer class can reuse functions and methods. Each of the techniques above has its ways of overriding that code, from the Perl built-in SUPER pseudo-class to Moose’s override and super keywords, to Moose’s and Moo’s method modifiers. (You can use the latter outside of Moo since it’s provided by Class::Method::Modifiers.)
I’ve written about choosing between overriding and modifying methods before, and when it comes to Moose and Moo code I’m now on the side of using the around method modifier if a method needs to call an inherited or consumed method of the same name. Object::Pad doesn’t have method modifiers (yet), so classes that use it will have to satisfy themselves with SUPER in their methods with an :override attribute that will throw an error if a parent doesn’t also provide the same method.
The Parent Wrap
In the end, your choice of Perl OO system will determine how (or whether) you handle inheritance and may even be a deciding factor. Which would you choose? And more importantly, have I made my parents proud with this post?
At my work, we extensively use the Moose object system to take care of what would ordinarily be very tedious boilerplate object-oriented Perl code. In one part of the codebase, we have a family of classes that, among other things, map Perl methods to the names of various calls in a third-party API within our larger organization. Those private Perl methods are in turn called from public methods provided by rolesconsumed by these classes so that other areas aren’t concerned with said API’s details.
Without going into too many specifics, I had a bunch of classes all with sections that looked like this:
sub _create_method { return 'api_add' }
sub _retrieve_method { return 'api_info' }
sub _search_method { return 'api_list' }
sub _update_method { return 'api_update' }
sub _cancel_method { return 'api_remove' }
sub _suspend_method { return 'api_disable' }
sub _unsuspend_method { return 'api_restore' }
... # etc.
The values returned by these very simple methods might differ from class to class depending on the API call needed, and different classes might have a different mix of these methods depending on what roles they consume.
These methods had built up over time as developers had expanded the classes’ functionality, and this week it was my turn. I decided to apply the DRY (don’t repeat yourself) principle and create them from a simple hash table like so:
At first, I thought to myself, “These look like private read-only attributes!” So I wrote:
use Moose;
...
has $_ => (
is => 'ro',
init_arg => undef,
default => $METHOD_MAP{$_},
) for keys %METHOD_MAP;
Of course, I’d have to move the classes’ with statements after these definitions so the roles they consume could “see” these runtime-defined attributes. But some of the methods used to read these are class methods (e.g., called as ClassName->foo() rather than $object->foo()), and Moose attributes are only set after the construction of a class instance.
Then I thought, “Hey, Moose has a MOP (meta-object protocol)! I’ll use that to generate these methods at runtime!”
my $meta = __PACKAGE__->meta;
while (my ($method, $api_call) = each %METHOD_MAP) {
$meta->add_method( $method => sub {$api_call} );
}
The add_method documentation “strongly encourage[s]” you to pass a metamethod object rather than a code reference, though, so that would look like:
use Moose::Meta::Method;
my $meta = __PACKAGE__->meta;
while (my ($method, $api_call) = each %METHOD_MAP) {
$meta->add_method( $method = Moose::Meta::Method->wrap(
sub {$api_call}, __PACKAGE__, $meta,
);
}
This was getting ugly. There had to be a better way, and fortunately there was in the form of Dave Rolsky’s MooseX::ClassAttribute module. It simplifies the above to:
use MooseX::ClassAttribute;
class_has $_ => (
is => 'ro',
default => $METHOD_MAP{$_},
) for keys %METHOD_MAP;
Note there’s no need for init_arg => undef to prevent setting the attribute in the constructor. Although they’re still Moose attributes, they act like class methods so long as the class consumes the roles that require them after the attribute definitions.
The lesson as always is to check CPAN (or the appropriate mix of your language’s software repository, forums like Stack Overflow, etc.) for anything that could conceivably have application outside of your particular circumstances. Twenty-five years into my career and I’m still leaping into code without first considering that someone smarter than me has already done the work.
Template processors and engines are one of those pieces of software where it seems every developer wants to reinvent the wheel. Goodness knows I’ve done it earlier in my career. Tell me if this sounds familiar:
You need to mix data into a document so you start with Perl’s string interpolation in "double quotes" or sprintf formats. (Or maybe you investigate formats, but the less said about them the better.)
You realize your documents need to display things based on certain conditions, or you want to loop over a list or some other structure.
You add these features via keyword parsing and escape characters, thinking it’s OK since this is just a small bespoke project.
Before you know it you’ve invented another domain-specific language (DSL) and have to support it on top of the application you were trying to deliver in the first place.
Stop. Just stop. Decades of others who have walked this same path have already done this for you. Especially if you’re using a web framework like Dancer, Mojolicious, or Catalyst, where the template processor is either built-in or pluggable from CPAN. Even if you’re not developing a web application there are several general-purpose options of various capabilities like Template Toolkit and Template::Mustache. Investigate the alternatives and determine if they have the features, performance, and support you need. If you’re sure none of them truly meet your unique requirements, then maybe, maybe consider rolling your own.
Whatever you decide, realize that as your application or website grows your investment in that selection will only deepen. Porting to a new template processor can be as challenging as porting any source code to a new programming language.
Unfortunately, there are about as many opinions on how to choose a template processor as there are template processors. For example, in 2013 Roland Koehler wrote a good Python-oriented article on several considerations and the different approaches available. Although he ended up developing his own (quelle surprise), he makes a good case that a template processor ought to at least provide various logic constructs as well as embedded expressions, if not a full programming language. Koehler specifically warns against the latter, though, as a template developer might change an application’s data model, to say nothing of the possibility of executing arbitrary destructive code.
But using those modules’ DSLs means more sophisticated tasks need extra time and effort finding the correct logic and expressions. This also assumes that their designer(s) have anticipated my needs either through built-in features or extensions. I’m already writing Perl; why should I switch to another, more limited language and environment provided I can remain disciplined enough to avoid issues like those described above by Koehler?
So for my personal projects, I favor template processors that use the full power of the Perl language like Mojolicious’ embedded Perl renderer or the venerable Text::Template for non-web applications. It saves me time and I’ll likely want more than any DSL can provide. This may not apply to your situation, though, and I’m open to counter-arguments.
What’s your favorite template processor and why? Let me know in the comments.
Look, I get it. You don’t like the Perl programming language or have otherwise disregarded it as “dead.” (Or perhaps you haven’t, in which case please check out my other blog posts!) It has weird noisy syntax, mixing regular expressions, sigils on variable names, various braces and brackets for data structures, and a menagerie of cryptic special variables. It’s old: 34 years in December, with a history of (sometimes amateur) developers that have used and abused that syntax to ship code of questionable quality. Maybe you grudgingly accept its utility but think it should die gracefully, maintained only to run legacy applications.
asynchronous libraries (e.g., IO::Async and the aforementioned Mojolicious’ Mojo::IOLoop)
All of this is available through a mature installation toolchain that doesn’t break from month to month.
Finally and most importantly, there’s the global Perl community. The COVID-19 pandemic has put a damper on the hundreds of global Perl Mongers groups’ meetups, but that hasn’t stopped the yearly Perl and Raku Conference from meeting virtually. (In the past there have also been yearly European and Asian conferences, occasional forays into South America and Russia, as well as hackathons and workshops worldwide.) There are IRCservers and channels for chat, mailing lists galore, blogs (yes, apart from this one), and a quirky social network that predates Facebook and Twitter.
So no, Perl isn’t dead or even dying, but if you don’t like it and favor something newer, that’s OK! Technologies can coexist on their own merits and advocates of one don’t have to beat down their contemporaries to be successful. Perl happens to be battle-tested (to borrow a term from my friend Curtis “Ovid” Poe), it runs large parts of the Web (speaking from direct and ongoing experience in the hosting business here), and it’s still evolving to meet the needs of its users.
If you can see this, then the site’s upgrade from HostGator shared hosting to VPS and the subsequent Cloudflare DNS change is complete. Thank you for flying with us.
We have a huge codebase of over 700,000 lines of Perl spread across a couple dozen Git repositories at work. Sometimes refactoring is easy if the classes and methods involved are confined to one of those repos, but last week we wanted to rename a method that was potentially used across many of them without having to QA and launch so many changes. After getting some help from Dan Book and Ryan Voots on the #perl libera.chat IRC channel, I arrived at the following solution.
First, if all you want to do is alias the new method call to the old while making the least amount of changes, you can just do this:
*new_method = \&old_method;
This takes advantage of Perl’s typeglobs by assigning to the new method’s name in the symbol table a reference (indicated by the \ character) to the old method. Methods are just subroutines in Perl, and although you don’t need the & character when calling one, you do need it if you’re passing a subroutine as an argument or creating a reference, as we’re doing above.
I wanted to do a bit more, though. First, I wanted to log the calls to the old method name so that I could track just how widely it’s used and have a head start on renaming it elsewhere in our codebase. Also, I didn’t want to fill our logs with those calls — we have enough noise in there already. And lastly, I wanted future calls to go directly to the new method name without adding another stack frame when using caller or Carp.
With all that in mind, here’s the result:
sub old_method {
warn 'old_method is deprecated';
no warnings 'redefine';
*old_method = \&new_method;
goto &new_method;
}
sub new_method {
# code from old_method goes here
}
Old (and not-so-old) hands at programming are probably leaping out of their seats right now yelling, “YOU’RE USING GOTO! GOTO IS CONSIDERED HARMFUL!” And they’re right, but this isn’t Dijkstra’s goto. From the Perl manual:
The goto &NAME form is quite different from the other forms of goto. In fact, it isn’t a goto in the normal sense at all, and doesn’t have the stigma associated with other gotos. Instead, it exits the current subroutine (losing any changes set by local) and immediately calls in its place the named subroutine using the current value of @_. […] After the goto, not even caller will be able to tell that this routine was called first.
Computer scientists call this tail call elimination. The bottom line is that this achieves our third goal above: immediately jumping to the new method as if it were originally called.
The other tricky bit is in the line before, when we’re redefining old_method to point to new_method while we’re still inside old_method. (Yes, you can do this.) If you’re running under use warnings (and we are, and you should), you first need to disable that warning. Later calls to old_method will go straight to new_method without logging anything.
And that’s it. The next step after launching this change is to add a story to our backlog to monitor our logs for calls to the old method, and gradually refactor our other repositories. Then we can finally remove the old method wrapper.
{"id":"1","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":"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":"Leave a tip!","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}