Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Phoenix Nest — Booking → E‑Signature Bridge

WordPress PHP License Status Tests

A single‑file WordPress plugin that turns a MotoPress vehicle booking into a pre‑filled, legally e‑signed Motor Vehicle Rental Agreement via WP E‑Signature — with the renter's data captured once and baked into the signed PDF so the executed copy is tamper‑correct, not a blank template.

Live in production: phoenixnest.properties — Phoenix Nest Properties LLC (Destin, FL vehicle rentals). A customer books a vehicle, is redirected into a pre‑filled agreement, signs once, and receives a completed PDF; the owner counter‑signature is applied automatically.


Why this exists

Off‑the‑shelf, MotoPress and WP E‑Signature don't talk to each other. A rental business needs the booking a customer just made to flow straight into a specific, pre‑filled, legally‑binding agreement — no re‑typing, no blank fields, no manual document creation per booking. This plugin is the integration layer that makes that a single, hands‑off flow.

It solves three problems that a naïve "just add a shortcode" approach gets wrong:

  1. Timing — MotoPress writes customer data after the initial save, and submits over admin-ajax, so you can't read the data or steer the browser at submit time.
  2. Prefill transport — WP E‑Signature's [esigget] merge fields read the original request via filter_input(), so values must ride the signing URL as real query parameters.
  3. Persistence — merge‑field values render on screen but do not persist into the signed copy by default; the multi‑step signing flow re‑renders the document without them, leaving blanks in the executed PDF.

Architecture

sequenceDiagram
    actor R as Renter
    participant MP as MotoPress Booking
    participant BR as Bridge Plugin
    participant ES as WP E-Signature

    R->>MP: Submit booking (dates, address, DL, insurance)
    MP->>BR: save_post (pri 20) — flag booking "pending e-sign" (15-min transient)
    MP-->>R: Redirect to booking-confirmation view
    R->>BR: template_redirect intercepts the confirmation
    Note over BR: Gather booking meta →<br/>build the pn_* prefill map
    BR-->>R: 302 → signing page with ?pn_*=… prefill
    R->>ES: Review pre-filled agreement, draw/type signature
    ES->>BR: esig_document_clone_render_content (per-signer clone created)
    Note over BR: Bake pn_* values into the clone<br/>as static text — BEFORE signature capture
    ES->>ES: Capture signature on the baked clone (tamper-integrity holds)
    ES-->>R: Store signed PDF + auto owner counter-sign → /booking-confirmed/
Loading

The three hooks

Hook Function Responsibility
save_post (pri 20) pn_esign_mark_pending Flags a front‑end booking as pending e‑sign. Correctly distinguishes a real booking (admin-ajax) from a genuine wp‑admin edit.
template_redirect pn_esign_confirmation_redirect Intercepts MotoPress's confirmation view, gathers all booking meta into the pn_* prefill map, and redirects the renter to the signing page. The pending‑flag gate prevents booking‑ID enumeration from leaking renter PII.
esig_document_clone_render_content pn_esign_store_doc_id v1.1.0 — records which signed document belongs to which booking (_pn_esign_doc_id), the link nothing stored before. Only writes when the request carries a matching HMAC over the booking id, because that id can arrive via an attacker‑controlled referer.
esig_document_clone_render_content pn_esign_bake_clone The persistence fix: when WP E‑Signature clones the master document for a signer, replaces every [esigget pn_*] merge field with the real value as static text, before the signature is captured — so the signed content is exactly what was signed.

v1.1.0 — linking a booking to its signed document

Nothing recorded which signed document belonged to which booking, so anything downstream could only guess an agreement from name + date. v1.1.0 closes that:

  • pn_bid + pn_sig ride the signing URL alongside the existing prefill, so they survive the same referer fallback.
  • At clone time, pn_esign_store_doc_id() validates and writes _pn_esign_doc_id.
  • Only the document id is stored, never the checksum. The did= hash in the PDF URL is sha1( $doc_id . $bakedContent ) and changes if the document is ever re-saved, so it is resolved at render time via document_checksum_by_id().

Two decisions worth knowing before touching it:

  1. The guard is an HMAC this plugin mints itself. A pn_bid arriving through HTTP_REFERER is attacker-controlled, so without a signature a forged referer could stamp _pn_esign_doc_id onto any post on the site. wp_salt() means one cannot be forged.
  2. First clone wins. copyDocument() runs once per signer, so a counter-signature or a re-send would otherwise overwrite the renter's document id.

Installation

# From wp-content/plugins/
git clone https://github.com/codebyshoaib/phoenix-esign-bridge.git

Or download the ZIP and upload via Plugins → Add New → Upload Plugin, then Activate.

Requirements: WordPress with MotoPress Booking Calendar, WP E‑Signature (core + Signer Input Fields / Stand Alone Documents add‑ons), PHP 7.4+.


Configuration

The plugin is convention‑driven and needs no settings page. To wire it to your document:

  1. In WP E‑Signature, build a Stand Alone document and drop [esigget pn_key] merge fields into the body (e.g. [esigget pn_renter_name], [esigget pn_total_due]).
  2. Set the document's post‑sign redirect to your confirmation page (e.g. /booking-confirmed/).
  3. Point the signing page path via the pn_esign_page_path filter if it differs from the default:
add_filter( 'pn_esign_page_path', fn() => '/motor-vehicle-rental-agreement/' );

Any pn_* merge key you add to the document is covered automatically by the bake step. The prefill map (name, address, driver's licence, pickup/return, total due, additional drivers, …) is assembled in pn_esign_build_prefill().


Engineering notes

  • Bake at clone time, never after signing. Rewriting content after esig_signature_saved would break WP E‑Signature's tamper checksum. The clone filter fires on the raw decrypted content before checksum + encrypt + store — the only correct place to inject values.
  • PII stays off persistent storage. Prefill rides a single, one‑time confirmation→signing redirect gated by a short‑lived transient; there's no booking‑ID endpoint that echoes renter data.
  • Packaged as its own plugin, not appended to another. Isolation means a host or theme update can't silently wipe the integration, and a fatal in this code disables only the feature — it never white‑screens the site.
  • Watch the PHP function‑hoisting trap. A top‑level if ( function_exists('…') ) return; "safety guard" is a footgun here: PHP hoists top‑level function definitions at compile time, so such a guard is always true and the plugin returns before registering any hooks — functions exist, but nothing is wired. Verify integrations by confirming the hook is actually attached (or with an end‑to‑end signed‑PDF check), not merely "no fatal error."

Tests

php tests/selftest.php     # → OK (31 assertions)

No framework, no WordPress: it stubs the WP functions the plugin touches and asserts the two things that fail silently — that the hooks are actually attached (see the hoisting note above, which cost this plugin a production bug), and that the doc-id guard rejects an unsigned id, a wrong signature, a correctly-signed id that is not a booking, and a post that does not exist. Nothing local replaces the end-to-end signing check.


Part of a four-plugin system

Each plugin owns one job and one set of meta keys, so a bug in a reporting screen can never take down the booking flow.

Plugin Job
1 Custom booking fields (site-specific, not published) Injects and saves renter fields on the booking form → _phoenix_*
2 phoenix-esign-bridge (this repo) Booking → pre-filled agreement → signed PDF, and records which document belongs to which booking
3 phoenix-vehicle-condition Pickup + return condition photos at handover → _pn_vc_*
4 phoenix-customer-records Read-only: groups every booking into customers and shows all of the above in one place
flowchart LR
    F["1 · booking fields<br/><code>_phoenix_*</code>"] --> E["2 · e-sign bridge<br/><code>_pn_esign_doc_id</code>"]
    E --> V["3 · condition photos<br/><code>_pn_vc_*</code> + attachments"]
    F --> R["4 · customer records<br/>reads only"]
    E --> R
    V --> R
Loading

_pn_esign_doc_id is the seam between 2 and 4: plugin 4's agreement link only works for documents signed after v1.1.0 went live, because a link that was never recorded cannot be backfilled.


Compatibility

Validated against WP E‑Signature 2.1.x and MotoPress Booking Calendar. The bake step depends on the esig_document_clone_render_content filter; after any major WP E‑Signature update, run one test signing and confirm the resulting PDF contains real field values.


Author

Shoaib Ud Din — Full‑stack engineer LinkedIn · GitHub

License

GPL‑2.0‑or‑later — consistent with the WordPress plugin ecosystem.

About

WordPress plugin bridging MotoPress bookings to pre-filled, e-signed rental agreements (WP E-Signature): auto-prefill, confirmation redirect, and tamper-safe value baking. Live on phoenixnest.properties.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages