Ignition PayPal Gateway — Developer Reference
The Ignition PayPal Gateway add-on registers a PayPal payment gateway through core’s payment-gateway framework and drives core payment-attempt and refund transitions from PayPal’s REST API and webhooks. From v0.2.0 it also supports Platform mode — splitting each pledge between the campaign’s creator and the platform through PayPal for Marketplaces — gated on the Ignition Platform Mode add-on. It exposes the action and filter hooks below and owns the routes that follow.
Capability
Section titled “Capability”The gateway advertises the platform_mode capability through core’s gateway capability seam: Igcf_Payment_Gateway::supports( 'platform_mode' ) returns true. Platform-fee and payout add-ons detect split-capable checkout by querying Igcf_Payment_Gateway_Service::gateway_supports( 'paypal', 'platform_mode' ) rather than depending on this add-on’s slug or version. The capability is advertised statically — whether platform mode is actually operational (Platform Mode active, credentials saved, the model selected) is a separate runtime state.
The platform’s cut is read from core’s application-fee seam, the igcf_payment_attempt_application_fee filter. With no platform-fee add-on supplying a value the fee is 0, so the creator receives the full amount and the platform takes nothing; the split order is still created with the creator as payee. Refund failure codes are registered through core’s igcf_refund_failure_codes filter.
From v0.3.0 the add-on also answers core’s igcf_payment_attempt_settles_application_fee predicate for its own attempts. It returns true only where the attempt is a PayPal attempt whose campaign has a viable split — evaluated with the same split_context() viability the checkout split branch uses, against the environment the pledge’s money actually lives in, so the record gate and the collect decision cannot disagree. It passes the value through untouched when another answerer has already claimed the attempt or the attempt is not PayPal’s, so a future split-capable gateway can coexist on the same filter. A fee add-on should read Igcf_Payment_Attempt_Service::settles_application_fee() rather than inspecting the gateway.
Settlement figures
Section titled “Settlement figures”When a capture settles — on the backer’s return, or through either webhook backstop — the add-on reads that capture’s seller_receivable_breakdown from PayPal and records what PayPal reported settling: the processing fee PayPal kept, the gross it worked from, the platform fee PayPal reported taking on a split, and the currency it reported in.
The read happens after the attempt is already succeeded, and is best-effort by contract. Any failure — network, authentication, or a capture that carries no breakdown at all (a plain merchant capture reports no platform_fees key) — logs once and records nothing. A settled payment is never turned into a failed one by a settlement read.
Presence is a marker, not a value. A payment whose breakdown has been read answers the seams below even when the fee is a genuine 0; a payment whose breakdown has never been read answers nothing, leaving core’s own defaults in place. Consumers must preserve that distinction — an absent figure means “never reported”, which is not the same as “the processor kept nothing”. A breakdown whose currency does not match the attempt’s currency is recorded for audit but never marked as read, so no downstream figure is reported in the wrong currency.
Core seams the add-on answers
Section titled “Core seams the add-on answers”| Filter | What the add-on answers |
|---|---|
igcf_payment_attempt_processing_fee |
The fee PayPal kept on that attempt, in minor units, once its breakdown has been read. |
igcf_pledge_processing_fees |
The same figure per pledge, batched in one query. Only pledges with a settled PayPal capture appear as keys, so another gateway’s answer is never disturbed. |
igcf_pledge_net_amounts |
Subtracts that fee from each pledge’s net, at priority 5. It iterates the incoming map’s own keys, so a pledge absent from the map stays absent. |
igcf_pledge_settlement_terms |
Declares one processing term with destination gateway at core’s reserved order 200, so the wp-admin pledge detail page renders a Processing (gateway) line. Registered on is_admin() only. |
igcf_payment_attempt_operator_custody |
false only where the PayPal attempt’s campaign has a viable platform split — the same split-viability check the checkout split path runs, against the environment the pledge’s money lives in. Merchant mode and a non-viable split are both custodial. |
igcf_projected_charge_context |
Sets settles_application_fee at priority 10, so a fee add-on reading the seam at a later priority sees the settlement answer already resolved. It never clears a true another gateway has claimed. |
Every one of these scopes itself to its own payments and returns the incoming value untouched otherwise, so a second split-capable gateway can coexist on all six. Most do it by checking the payment method; Igcf_Paypal_Platform::subtract_processing_fees() and ::report_processing_fees() instead join against the PayPal order table, so a pledge with no PayPal order is left alone.
Platform-fee reconciliation
Section titled “Platform-fee reconciliation”Once a split capture’s breakdown is recorded, the add-on compares the platform fee PayPal reported settling against the fee the order asked for, and writes a diagnostic to the PHP error log when the two differ. Nothing is corrected and no user-facing surface changes — money PayPal has already settled cannot be moved from here. The comparison is between the two recorded figures and nothing recomputed: both already include every add-on’s contribution to the application fee (a commission from Platform Fees, a tip from Contributions), so substituting a freshly computed commission would flag every tipped pledge.
Why PayPal is unavailable
Section titled “Why PayPal is unavailable”Igcf_Paypal_Gateway::get_unavailable_reasons( int $campaign_id, ?string $environment = null ) returns every reason PayPal can’t be offered on a campaign, in check order, or an empty array when it can. Each entry is array{ code: string, message: string, fix_url: string }. is_available_for_campaign() is derived from it — PayPal is offered exactly when there are no reasons — so checkout and the admin explanation never disagree. Pass $environment (live or sandbox) to check against a specific environment instead of the campaign’s own. On Ignition Crowdfunding 0.23.0 and later, core passes this list through igcf_gateway_unavailable_reasons with gateway id paypal.
Messages are plain text, not escaped; escape them on output. No message ever contains a credential value. fix_url points at the PayPal settings screen for reasons fixed there, and is empty otherwise.
| Code | Meaning | fix_url |
|---|---|---|
funding_not_flexible |
The campaign uses fixed funding. | Empty |
credentials_missing |
Merchant mode: the campaign’s environment has no complete credential pair. | Settings |
platform_mode_inactive |
Creators receive pledges, but neither environment has a complete Platform credential pair. | Settings |
platform_credentials_missing |
The campaign’s environment has no complete Platform credential pair. | Settings |
partner_account_id_missing |
The campaign’s environment has no Platform Account ID. | Settings |
campaign_owner_missing |
The campaign has no creator. | Empty |
creator_not_connected |
The creator has not connected PayPal for this environment. | Empty |
creator_not_payable |
The creator connected, but PayPal can’t send them payments yet. | Empty |
creator_merchant_id_missing |
The creator connected, but PayPal sent no account ID. | Empty |
The checks don’t stop at the first failure. A fixed-funding campaign with missing credentials reports both, and in platform mode the platform-side and creator-side checks both run. Igcf_Paypal_Platform::split_context() likewise returns a reasons list of codes; its reason is the first entry.
Blocking a cancel after payment
Section titled “Blocking a cancel after payment”The add-on answers core’s igcf_pledge_before_{$new_status} filter for canceled. If any still-pending payment attempt on the pledge has a PayPal order that recorded a capture or reads COMPLETED, it returns a WP_Error with code igcf_pledge_payment_already_captured and the message “This pledge may already have been paid at PayPal. Refund it instead of cancelling.” The cancel is refused. A WP_Error from an earlier listener is passed through untouched.
As a second guard, the capture path checks the pledge as well as the attempt. A webhook or return that arrives after the pledge left pending captures nothing.
Scheduled events
Section titled “Scheduled events”| Hook | Schedule | Description |
|---|---|---|
igcf_paypal_settlement_sweep |
twicedaily |
Recovery sweep for captures whose settlement read failed. Reads at most 25 rows per run, newest first, limited to rows created within the last 30 days — it recovers transient failures, it is not a historical backfill. An authentication failure stops the run rather than repeating it, and the next tick starts again from the top. Scheduled on load when missing (so a database restore re-arms it), and cleared on deactivation and on uninstall. |
Actions
Section titled “Actions”The add-on fires these actions on PayPal payment and onboarding events. Each fires only on a genuine state change — a repeated webhook, or a reconciling backstop that arrives after the state already changed, does not fire a duplicate.
| Hook | Parameters | Description |
|---|---|---|
igcf_paypal_order_created |
$attempt_id, $order_id, $environment |
Fires after a PayPal order is created for a payment attempt, just before the backer is redirected to approval. $order_id is the PayPal order id; $environment is sandbox or live. Fires for every order, direct or split. |
igcf_paypal_split_order_created |
$attempt_id, $order_id, $environment, $payee_merchant_id, $platform_fee |
Fires in addition to igcf_paypal_order_created (never instead of it) after a multiparty split order is recorded. $payee_merchant_id is the creator’s PayPal merchant id who receives the funds; $platform_fee is the platform cut in minor units, 0 when none. The reconciliation point for a platform-fee add-on. |
igcf_paypal_payment_retried |
$pledge_id, $attempt_id, $order_id, $environment |
Fires when a backer retries an interrupted payment — a fresh attempt and PayPal order were created and the backer is about to be redirected to approval again. |
igcf_paypal_payment_captured |
$attempt_id, $capture_id, $payer |
Fires when a PayPal capture succeeds and the payment attempt is driven to succeeded. $capture_id is the PayPal capture id; $payer is an array with payer_id and payer_email. |
igcf_paypal_payment_failed |
$attempt_id, $failure_code |
Fires when a PayPal capture is declined or fails, whether detected on the backer’s return or via a webhook. $failure_code is the mapped failure code recorded on the attempt. |
igcf_paypal_payment_refunded |
$refund_id, $paypal_refund_id, $attempt_id |
Fires once a PayPal refund settles, whether initiated by an admin refund or reconciled from a webhook. $refund_id is core’s refund id; $paypal_refund_id is PayPal’s, stored as the refund’s gateway reference. |
igcf_paypal_creator_onboarding_started |
$creator_user_id, $environment, $tracking_id |
Fires when a creator begins connecting their PayPal account — a Partner Referrals link is minted and a pending connection recorded — just before the creator is redirected to PayPal. |
igcf_paypal_creator_connected |
$creator_user_id, $environment, $merchant_id |
Fires once when a creator’s PayPal account becomes payable (funds receivable and permissions granted), on the not-payable → payable transition only — whether detected on the onboarding return, a status re-check, or the onboarding webhook. |
igcf_paypal_creator_connection_revoked |
$creator_user_id, $environment, $merchant_id |
Fires once when a creator revokes the platform’s permission and the connection transitions to revoked. |
Filters
Section titled “Filters”| Hook | Parameters | Description |
|---|---|---|
igcf_paypal_platform_is_active |
$active |
Override whether the add-on treats Ignition Platform Mode as active, without toggling real plugin activation. Primarily a testing and specialized-setup seam. |
Payment amount and claim decisions still flow through core’s own filters, such as igcf_pledge_amount_to_charge and igcf_checkout_payment_method.
Routes
Section titled “Routes”The add-on handles these inbound requests. The two frontend markers are detected from query-string markers on template_redirect; they are not registered WordPress query vars.
| Route | Type | Description |
|---|---|---|
POST igcf-paypal/v1/webhook |
REST | Receives PayPal’s server-to-server payment notifications. Public by design — PayPal is an unauthenticated caller — so every request is signature-verified inside the handler before it is trusted. In platform mode this listener also carries a Platform-app subscription, verified against the platform credential set for a split order. |
POST igcf-paypal/v1/onboarding-webhook |
REST | Receives PayPal’s marketplace notifications for creator onboarding and consent — the onboarding-completed (MERCHANT.ONBOARDING.COMPLETED) and partner-consent-revoked events. Public and signature-verified with the same path as the payment webhook. |
?igcf_paypal_return |
Query-string marker | Where PayPal returns the backer after they approve or cancel a payment; the handler captures the order and settles the attempt. |
?igcf_paypal_retry=<pledge_id> |
Query-string marker | The “Complete payment” retry entry point, nonce-protected and gated on pledge ownership and eligibility; it starts a fresh PayPal order for a pending pledge. In platform mode the retry reuses the original order’s payee and environment so a split follows its first routing. |
Related
Section titled “Related”- Ignition PayPal Gateway user guide
- Ignition Platform Mode — the add-on that gates PayPal Platform mode.
- Registering a Payment Gateway — the core gateway framework this add-on builds on.
- Service Layer → Payment gateways — the gateway base class, the capability seam, and services.