Made a huge amount of progress over the last two weekends getting ready for Seamless Donations 4.0. The big focus was in adding internationalization features and a reveal family system designed for form management. We’re also just about at the point where it’s time to see if the new system can talk to PayPal.
Once again, I should point out that these lab notes are my way of recording what’s being done inside the code. If you just want to use Seamless Donations, there’s no need to understand all of this information. But if you want to follow along, it’s all here for you.
Internationalizing Seamless Donations
Changed ‘Taiwan, Province of China’ to just “Taiwan’. That way those who want to consider it a province can, and those for whom that’s not acceptable don’t need to think of it that way.
Also added a filter seamless_donations_geography_country_list that provides the entire countries array, so you can go in and diddle with any country name you want on the fly. Just try not to change the keys, because other elements of the code are dependent.
While I was at it, also added the filter seamless_donations_geography_state_list for state names and seamless_donations_geography_province_list. Why would you want to change your state name? I have no idea, but if Texans really want to have their state show up as “The Great State of Texas” or Californians want “The Sovereign State of California,” now they can. It’s all about flexibility. One note: do be aware that if you change any of these strings, you should test with PayPal. It’s entirely possible a string change could break something.
Making the plugin translation-ready
Went through all the main strings and made sure they all used the “seamless-donations” domain. This is an important setup for making the plugin translation-ready. Also updated the plugin’s header so that the translation domain details are accessible. All that remains is generation of the POT file just before uploading the plugin to the repository.
Added Indian Rupee as currency
Added the Indian Rupee as a currency to Seamless Donations. This is now in the currency selector but is as-yet untested with PayPal. We’ll need to confirm it properly works in a PayPal transaction.
The new donation form
The new donation form is called by the shortcode [[seamless-donations]]. The original form (before you run the upgrade migrator) will still be available via the [[dgx-donate]] shortcode.
While much effort has been made to keep the new donation form similar to the pre-4.0 version, there have been some improvements made. In the donation amount section, when the Other radio box is selected, the Other amount field drops down, otherwise it remains hidden. In the tribute section, the email vs. physical address radio buttons now determine whether email contact information or physical contact information is displayed on the form.
Understanding the reveal family system
The cloak/reveal/conceal system is the series of components in the forms framework that manages how the action of one button effects the visibility of another button.
The reveal family
Each reveal family entry in the array contains a key (either cloak, reveal, or conceal) and a value. The value is an arbitrary name that puts the key’s parent element into a reveal family that works together. For example, say a checkbox has a reveal key with a value of “myset”. Later a text field has a cloak key, also with the value of “myset”. The “myset” value defines the reveal family and connects the two objects so when the checkbox is clicked, it triggers the visibility of the text field.
Family names are really just CSS classes. So while they are used as values in the form array, they can actually be assigned as a class to any HTML tag and (with the addition of the class dot prefix) work as CSS selector. It is also possible to put multiple family names in one value (as in “myset yourset”). This allows for complex conceals and reveals to be coded into the form.
The cloak key
Any element that has a key named “cloak” will be given a “display:none” style and will initially not be displayed on the page. When an element with a reveal tag that has a matching family is triggered, the cloaked item is made visible on the page. If the cloaked item is already visible, a triggered reveal will hide the element with the matching reveal family.
The reveal key
The reveal key is used to specify what elements are revealed once the parent element is triggered. Specifies that this element can be used to reveal another element. The value of the key/value pair is the reveal family, which determines which elements will be revealed when the reveal element is triggered. When reveal is used with a select list, reveal is no longer a key with a scalar string value. Instead it is an array containing option values as keys and space-separated lists of reveal families as values. When a specified option value is selected, the reveal family elements are displayed.
The conceal key
Conceal is the opposite of reveal. The conceal key specifies the elements to be hidden once the element is triggered. For radio buttons and check boxes, conceal works as you’d expect: if this button is triggered, conceal elements of the specified family. However, when it comes to selection lists, conceal elements are not represented in an array. Rather, they become part of the parent div of the select and specify the families to be concealed. In practice the way this works is that when an option is selected, first all the matching conceal families are concealed and then the matching reveal families attached to the given select options are revealed.
Check and uncheck keys
Although not directly part of the reveal system, sometimes when you show a hidden radio or checkbox control, you’ll need to set the value of that control. The check and uncheck keys allow you to do that. You put the key into the control that does the reveal (a radio button or checkbox). When that control is activated (changed to a checked or selected value), it will also check or uncheck the control listed as the value of the check or uncheck key. It uses the id (the name of the element’s array) as the value of the item to check or uncheck.
So, for example, if you want to set radioA to selected when buttonB is checked, you would put check:radioA into buttonB. Note that it is not necessary that either control be hidden or have a reveal, but this feature was designed to work in that context primarily.
Example: the Seamless Donations Tribute form section
To understand how these keys work together, let’s look at the Seamless Donations Tribute form section. This is a complex section where various parts need to reveal and conceal based on live user input. Here are the actions:
The main “Check here to donate in honor or memory of someone” checkbox
This starts off visible, but unchecked. When it’s checked, we want to make the honoree’s name field show up as well as the two “send acknowledgement” radio buttons. Because “send acknowledgement via email” will be checked as the default, we also want the email name and address fields to be revealed when the checkbox is triggered. These fields are cloaked using the in-honor family, and a reveal:in-honor key is set to turn them on when the user checks the boxes.
We also want the other fields in this section to be initially hidden, but we don’t use the “in-honor” family because we don’t want them to turn on when the main checkbox is checked. We want them to turn on only if a later condition is met based on an action triggered with one of the initially-hidden controls. We’ll use conceal to hide families postal-acknowledgement, conceal-postcode, conceal-state, and conceal-province.
Honoree’s name text field
Initially hidden using cloak:in-honor-of.
“Send acknowledgement via email” radio button
Initially hidden using cloak:in-honor. When first made visible, the email destination name and email address text fields are made visible with it. But if selected later, after the postal mail radio button was triggered, the selection of this button must first hide the postal destination name and street address text fields, the country field, and state, province, and postal code fields.
When triggered by the user, this button uses reveal:email-acknowledgement to tell all divs in the email-acknowledgement family to appear. It uses conceal to hide families postal-acknowledgement, conceal-postcode, conceal-state, and conceal-province.
“Send acknowledgement via postal mail” radio button
Initially hidden using cloak:in-honor. When this button is selected, the email destination name and email address text fields need to disappear and the postal destination name and street address text fields need to appear. In addition, the country field needs to appear and as it appears (based on the country option selected), determine whether to show state, province, and postal code fields.
When triggered by the user, this button uses reveal:postal-acknowledgement to tell all tags in the reveal:postal-acknowledgement family to appear. It uses conceal:email-acknowledgement to hide the email-related fields.
Email destination name and email address text fields
Initially hidden using cloak:in-honor and cloak:email-acknowledgement. Because the cloak key is used, the control is hidden. These fields can then be managed through both the in-honor and email-acknowledgement families.
Postal destination name and street address text fields
Initially hidden using cloak:postal-acknowledgement.
Country field selection list
Initially hidden using cloak:postal-acknowledgement. While coding, I’ve set the country field to reveal fields based only on few country options (US, Canada, and UK). Here’s the reveal array for country:
- UK: conceal-postcode
- US: conceal-postcode, conceal-state
- CA: conceal-postcode, conceal-province
The only reason I used “conceal-” as a prefix was because state and province may be too common as CSS classes and I didn’t want them triggered by other rogue classes or selectors.
This element also uses a conceal key to conceal the postcode, state, and province. When a new selection is made, a change is triggered, and first all three families are concealed and then, if the option is appropriate certain families are then revealed. But we don’t just want the conceal/reveal sequence to trigger only when an option is chosen. We also want it to trigger when the country field is displayed so the right supporting fields are shown when the country is made visible. Further, we want it to trigger when the country field is hidden. In the case of hiding the country, all three fields need to be hidden.
This means we need three trigger functions in JavaScript/jQuery. One that runs when a change event is noticed (which conceals, then reveals fields). Another function is called on element show, and it, too, conceals then reveals fields. Finally, a function is called on element hide, and it only needs to conceal fields.
State text field
This can’t be hidden using cloak:postal-acknowledgment. If it was, then the field would show up when the radio button was selected, and we don’t yet know if this field is meant to show. So it’s initially hidden using cloak:conceal-state.
Province text field
This can’t be hidden using cloak:postal-acknowledgment. If it was, then the field would show up when the radio button was selected, and we don’t yet know if this field is meant to show. So it’s initially hidden using cloak:conceal-province.
Postal code text field
This can’t be hidden using cloak:postal-acknowledgment. If it was, then the field would show up when the radio button was selected, and we don’t yet know if this field is meant to show. So it’s initially hidden using cloak:conceal-postcode.
More hooks
Added the following hooks that allow users to safely modify every aspect of the front-end form via filters. Each of these is passed the form array, which can be modified, and then should return it properly to the form generator.
- seamless_donations_form_donation_section: modifies the form array for the donation section
- seamless_donations_form_tribute_section: modifies the form array for the tribute section
- seamless_donations_form_donor_section: modifies the form array for the donor information section
- seamless_donations_form_billing_section: modifies the form for the donor billing section
- seamless_donations_form_submit_section: modifies the submit button for PayPal donations
Form thoughts
Some of how the form is constructed seems less useful that it could potentially be. For example, there’s an employer match field, but it really does’t do more than get the name of the employer and ask if the company matches donations. Employer can be displayed whether or not employer match is enabled. There’s also an occupation field.
The right way to do this would be to set up a separate employer section that only appears if employer match is selected. When it does show up, it should ask for appropriate employer match submission information (so the charity can then submit the match request). In the interests of getting the baseline 4.0 out, I’m not coding that now. It will be quite easy to update that more comprehensively in an update or extension.
Understanding the PayPal mechanism
When a form is submitted, it sends all its data (including hidden field data) to the JavaScript function SeamlessDonationsCheckout. This function then executes dbx_donate_paypalstd_ajax_checkout.
The dbx_donate_paypalstd_ajax_checkout function, located in dbx_donate_paypalstd.php, packs up all the form data for submission via WordPress’ Ajax library to PayPal. One of the key things it does is create a WordPress transient based on the session ID that contains an array of all the relevant donation data. It then drops the first half of the log message, telling us the transaction has begun.
PayPal returns its data via the IPN handler class Dgx_Donate_IPN_Handler, located in dbx_donate_paypalstd_ipn.php. First, the class verifies that the PayPal response is valid and, if not, generates an error. But if the response is valid, there are possible actions Dgx_Donate_IPN_Handler can take, depending what happens from PayPal
- If the transaction ID has already been seen, ignore the transaction for this run
- If the session ID has not yet been seen
- If there’s no data from the transaction, log a no-data error
- If there is data from the transaction, execute dgx_donate_create_donation_from_transient_data to create a donation record
- If there is a session ID, but no transient data, execute dgx_donate_create_donation_from_paypal_data to generate donation record data, but from PayPal’s info
- If the session ID has been seen
- This is assumed to be a recurring donation generated by PayPal
- Create donation data using dgx_donate_create_donation_from_donation
Dgx_Donate_IPN_Handler then calls dgx_donate_send_donation_notification to send an admin notification and dgx_donate_send_thank_you_email to send a thank-you email.
I don’t want to build two IPN response mechanisms, because the IPN handler is already in use and defined on a lot of systems. I don’t want to make site operators have to go in and revise their PayPal settings. So I’ll need to modify Dgx_Donate_IPN_Handler to detect if we’re running the 4.0-converted code and if so, run donation-record creator functions for the 4.0 architecture.