Skip to content

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.

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.

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.

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.

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.

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.

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.

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.

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.
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.

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.