# suredonation/1.0.0/inc/api/onboarding-api.php

SureDonation – Donation Forms, Fundraising Campaigns &amp; Donor Management, version 1.0.0. 239 lines.

- Page: https://pluginprobe.com/plugins/suredonation/1.0.0/code/inc/api/onboarding-api.php
- Raw: https://pluginprobe.com/plugins/suredonation/1.0.0/raw/inc/api/onboarding-api.php
- Modified: 2026-06-15T14:19:50+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/suredonation/1.0.0/code/inc/api/onboarding-api.php#L10-L20`.

```php
<?php
/**
 * Onboarding REST endpoints.
 *
 * Routes (under `suredonation/v1`):
 *  - GET    /onboarding/get-status        — return { completed: 'yes'|'no' }
 *  - POST   /onboarding/set-status        — write completion + optional analytics
 *  - POST   /onboarding/create-campaign   — create a draft suredonation_cmpgn
 *  - POST   /onboarding/user-details      — persist lead capture (free-only step)
 *
 * @package SureDonation
 */

namespace SureDonation\Inc\API;

use SureDonation\Inc\Campaigns\Campaign_Cpt;
use SureDonation\Inc\Helper;
use SureDonation\Inc\Onboarding;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Onboarding REST API endpoints.
 *
 * @since 1.0.0
 */
class Onboarding_API {
	/**
	 * Allowed goal types for the create-campaign endpoint.
	 *
	 * @since 1.0.0
	 * @var array<int,string>
	 */
	private const GOAL_TYPES = [ 'raised_amount', 'donation_count' ];

	/**
	 * Return endpoint definitions for Rest_Api to register.
	 *
	 * @return array<string,mixed>
	 * @since 1.0.0
	 */
	public function get_endpoints() {
		return [
			'/onboarding/get-status'      => [
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_status' ],
				'permission_callback' => [ $this, 'check_permissions' ],
			],
			'/onboarding/set-status'      => [
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'set_status' ],
				'permission_callback' => [ $this, 'check_permissions' ],
			],
			'/onboarding/create-campaign' => [
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'create_campaign' ],
				'permission_callback' => [ $this, 'check_permissions' ],
			],
			'/onboarding/user-details'    => [
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'save_user_details' ],
				'permission_callback' => [ $this, 'check_permissions' ],
			],
		];
	}

	/**
	 * Permission gate.
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function check_permissions() {
		return current_user_can( 'manage_options' );
	}

	/**
	 * GET /onboarding/get-status.
	 *
	 * @return WP_REST_Response
	 * @since 1.0.0
	 */
	public function get_status() {
		return new WP_REST_Response(
			[
				'completed' => Onboarding::get_instance()->is_completed() ? 'yes' : 'no',
			]
		);
	}

	/**
	 * POST /onboarding/set-status.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 * @since 1.0.0
	 */
	public function set_status( $request ) {
		$completed = $request->get_param( 'completed' );
		Onboarding::get_instance()->set_completed( 'yes' === $completed ? 'yes' : 'no' );

		return new WP_REST_Response( [ 'success' => true ] );
	}

	/**
	 * POST /onboarding/create-campaign.
	 *
	 * Creates a draft campaign post + writes its meta. Returns the new
	 * campaign id + edit URL so the JS can persist it in onboarding state.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function create_campaign( $request ) {
		$name        = sanitize_text_field( (string) $request->get_param( 'name' ) );
		$goal_type   = (string) $request->get_param( 'goal_type' );
		$description = wp_kses_post( (string) $request->get_param( 'description' ) );

		// Clamp to a non-negative, finite, sane range. The JS already
		// validates this, but the endpoint is callable directly by any
		// manage_options user and shouldn't trust client-side bounds.
		$goal_amount = (float) $request->get_param( 'goal_amount' );
		if ( ! is_finite( $goal_amount ) || $goal_amount < 0 ) {
			$goal_amount = 0.0;
		}
		// Cap at 1e9 so a stray "1e308" can't poison campaign meta.
		$goal_amount = min( $goal_amount, 1000000000.0 );

		if ( '' === trim( $name ) ) {
			return new WP_Error(
				'suredonation_campaign_name_required',
				__( 'Campaign name is required.', 'suredonation' ),
				[ 'status' => 400 ]
			);
		}

		if ( ! in_array( $goal_type, self::GOAL_TYPES, true ) ) {
			$goal_type = 'raised_amount';
		}

		$result = wp_insert_post(
			[
				'post_type'    => Campaign_Cpt::POST_TYPE,
				'post_status'  => 'draft',
				'post_title'   => $name,
				'post_excerpt' => $description,
				'post_author'  => get_current_user_id(),
			],
			true
		);

		if ( is_wp_error( $result ) ) {
			return new WP_Error(
				'suredonation_campaign_create_failed',
				$result->get_error_message(),
				[ 'status' => 500 ]
			);
		}

		$campaign_id = (int) $result;

		if ( $campaign_id <= 0 ) {
			return new WP_Error(
				'suredonation_campaign_create_failed',
				__( 'Could not create the campaign.', 'suredonation' ),
				[ 'status' => 500 ]
			);
		}

		Helper::update_campaign_meta(
			$campaign_id,
			[
				'goal_type'   => $goal_type,
				'goal_amount' => $goal_amount,
			]
		);

		return new WP_REST_Response(
			[
				'success'     => true,
				'campaign_id' => $campaign_id,
				'edit_url'    => admin_url( 'admin.php?page=suredonation#/campaigns/' . $campaign_id ),
			]
		);
	}

	/**
	 * POST /onboarding/user-details.
	 *
	 * Stores the lead-capture payload under suredonation_options so we
	 * don't re-prompt on subsequent setup passes.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 * @since 1.0.0
	 */
	public function save_user_details( $request ) {
		$payload = [
			'first_name' => sanitize_text_field( (string) $request->get_param( 'first_name' ) ),
			'last_name'  => sanitize_text_field( (string) $request->get_param( 'last_name' ) ),
			'email'      => sanitize_email( (string) $request->get_param( 'email' ) ),
			'opted_in'   => (bool) $request->get_param( 'opted_in' ),
		];

		Onboarding::get_instance()->set_user_details( $payload );

		// Persist the usage-tracking opt-in as its own option so other
		// plugin code (analytics, telemetry pings) can check it without
		// loading the consolidated onboarding details. Site option — the
		// BSF Analytics library reads it via get_site_option(), so the
		// write must use the same scope to stay in sync on multisite.
		update_site_option(
			'suredonation_usage_optin',
			$payload['opted_in'] ? 'yes' : 'no'
		);

		/**
		 * Fires after onboarding lead-capture details are persisted.
		 *
		 * Listeners (e.g. Pro analytics) can forward the payload to a
		 * metrics endpoint when `opted_in` is true.
		 *
		 * @since 1.0.0
		 *
		 * @param array<string,mixed> $payload Sanitised payload.
		 */
		do_action( 'suredonation_onboarding_user_details_saved', $payload );

		return new WP_REST_Response( [ 'success' => true ] );
	}
}

```
