PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.5.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.5.1
1.6.1 1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / payments / offline / offline-frontend.php

offline-frontend.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.5.1, at inc/payments/offline/offline-frontend.php

208 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Offline Frontend - AJAX handler for offline donations
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc\Payments\Offline;
9
10 use SureDonation\Inc\Database\Tables\Donations;
11 use SureDonation\Inc\Database\Tables\Donors;
12 use SureDonation\Inc\Emails\Email_Handler;
13 use SureDonation\Inc\Helper;
14 use SureDonation\Inc\Payments\Payment_Helper;
15 use SureDonation\Inc\Traits\Get_Instance;
16
17 // Exit if accessed directly.
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 /**
23 * Offline_Frontend class
24 * Handles frontend offline donation processing
25 *
26 * @since 1.0.0
27 */
28 class Offline_Frontend {
29 use Get_Instance;
30
31 /**
32 * Constructor
33 *
34 * @since 1.0.0
35 */
36 public function __construct() {
37 // AJAX handlers for both logged in and non-logged in users.
38 add_action( 'wp_ajax_suredonation_create_offline_donation', [ $this, 'create_offline_donation' ] );
39 add_action( 'wp_ajax_nopriv_suredonation_create_offline_donation', [ $this, 'create_offline_donation' ] );
40 }
41
42 /**
43 * Create offline donation via AJAX.
44 *
45 * @return void
46 * @since 1.0.0
47 */
48 public function create_offline_donation() {
49 // Throttle abuse on this public endpoint before doing any work.
50 if ( ! Helper::check_rate_limit( 'offline_create_donation' ) ) {
51 wp_send_json_error( [ 'message' => __( 'Too many requests. Please wait a moment and try again.', 'suredonation' ) ], 429 );
52 }
53
54 check_ajax_referer( 'suredonation_donation_form', 'nonce' );
55
56 // Reject bot submissions caught by the honeypot before processing.
57 if ( Helper::is_honeypot_spam() ) {
58 wp_send_json_error( [ 'message' => __( 'Your submission was flagged as spam. Please try again.', 'suredonation' ) ] );
59 }
60
61 // Verify offline donations are enabled.
62 if ( ! Offline_Helper::is_offline_enabled() ) {
63 wp_send_json_error( [ 'message' => __( 'Offline donations are not enabled.', 'suredonation' ) ] );
64 }
65
66 // Extract and sanitize form data.
67 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified above.
68 $is_standalone = isset( $_POST['is_standalone'] ) && '1' === $_POST['is_standalone'];
69 $campaign_id = isset( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : 0;
70 $amount = isset( $_POST['amount'] ) ? floatval( wp_unslash( $_POST['amount'] ) ) : 0;
71 $donor_email = isset( $_POST['donor_email'] ) ? sanitize_email( wp_unslash( $_POST['donor_email'] ) ) : '';
72 $donor_name = isset( $_POST['donor_name'] ) ? sanitize_text_field( wp_unslash( $_POST['donor_name'] ) ) : '';
73 $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
74 // Derive the donor phone from the validated mapped field, not a separate
75 // unvalidated $_POST['donor_phone'] (see Payment_Helper::get_mapped_donor_phone).
76 $donor_phone = Payment_Helper::get_mapped_donor_phone( $form_id );
77 $block_id = isset( $_POST['block_id'] ) ? sanitize_text_field( wp_unslash( $_POST['block_id'] ) ) : '';
78 // Display-only flag: the donor's real name/email/phone are still stored
79 // below and only public surfaces mask them.
80 $is_anonymous = Payment_Helper::get_submitted_is_anonymous( $form_id );
81 // phpcs:enable WordPress.Security.NonceVerification.Missing
82
83 // Standalone forms must not have a campaign.
84 if ( $is_standalone ) {
85 $campaign_id = 0;
86 }
87
88 // Validate required fields — campaign only required for non-standalone forms.
89 if ( ! $is_standalone && empty( $campaign_id ) ) {
90 wp_send_json_error( [ 'message' => __( 'Invalid campaign.', 'suredonation' ) ] );
91 }
92
93 if ( $amount <= 0 ) {
94 wp_send_json_error( [ 'message' => __( 'Invalid donation amount.', 'suredonation' ) ] );
95 }
96
97 if ( empty( $donor_email ) ) {
98 wp_send_json_error( [ 'message' => __( 'Email address is required.', 'suredonation' ) ] );
99 }
100
101 // Validate campaign only if not standalone.
102 if ( ! $is_standalone ) {
103 $campaign = get_post( $campaign_id );
104 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
105 wp_send_json_error( [ 'message' => __( 'Invalid campaign.', 'suredonation' ) ] );
106 }
107
108 if ( 'publish' !== $campaign->post_status ) {
109 wp_send_json_error( [ 'message' => __( 'This campaign is not available for donations.', 'suredonation' ) ] );
110 }
111
112 $campaign_status = Helper::get_campaign_meta_value( $campaign_id, 'campaign_status', 'active' );
113 if ( 'paused' === $campaign_status || 'completed' === $campaign_status ) {
114 wp_send_json_error( [ 'message' => __( 'This campaign is not currently accepting donations.', 'suredonation' ) ] );
115 }
116 }
117
118 // Validate form_id and block_id are present for amount validation.
119 if ( empty( $form_id ) || empty( $block_id ) ) {
120 wp_send_json_error( [ 'message' => __( 'Invalid form configuration.', 'suredonation' ) ] );
121 }
122
123 // Validate field values + amount against block config (skip Stripe minimum for offline).
124 $currency = Payment_Helper::get_currency();
125 $validation_result = Payment_Helper::validate_submission( Payment_Helper::get_submitted_fields(), $amount, $currency, $form_id, $block_id, 'offline', 'one-time' );
126 if ( ! $validation_result['valid'] ) {
127 wp_send_json_error(
128 [
129 'message' => esc_html( $validation_result['message'] ),
130 'fieldErrors' => $validation_result['field_errors'],
131 ]
132 );
133 }
134
135 // Get or create donor.
136 $donor_id = Donors::get_or_create( $donor_email, $donor_name, $donor_phone );
137
138 // Create donation record.
139 $donation_id = Donations::add(
140 [
141 'campaign_id' => $campaign_id,
142 'donor_id' => $donor_id ? $donor_id : 0,
143 'amount' => $amount,
144 'fees_covered' => 0,
145 'currency' => $currency,
146 'gateway' => 'offline',
147 'payment_status' => 'pending',
148 'payment_mode' => Payment_Helper::get_payment_mode(),
149 'donor_name' => $donor_name,
150 'donor_email' => $donor_email,
151 'donor_phone' => $donor_phone,
152 'is_anonymous' => $is_anonymous ? 1 : 0,
153 // Always one-time, whatever the block is configured for: an offline
154 // pledge has no instrument to charge on a schedule. The payment-type
155 // guard the Stripe and PayPal extractors run is deliberately NOT
156 // applied here -- it would reject every offline donation on a
157 // recurring form, which is the failure it exists to prevent, not
158 // cause. Whether such a form should offer offline at all is a
159 // separate product question.
160 'donation_type' => 'one-time',
161 'form_id' => $form_id,
162 'ip_address' => Helper::get_client_ip(),
163 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
164 'referer_url' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
165 ]
166 );
167
168 if ( ! $donation_id ) {
169 wp_send_json_error( [ 'message' => __( 'Failed to create donation record.', 'suredonation' ) ] );
170 }
171
172 // Persist the submitted field values for the entry record.
173 Donations::set_submitted_fields( $donation_id, Payment_Helper::get_submitted_field_data() );
174
175 // Add log entry.
176 Donations::add_log(
177 $donation_id,
178 'created',
179 __( 'Offline donation created — pending payment', 'suredonation' ),
180 [
181 'gateway' => 'offline',
182 ]
183 );
184
185 // Send donation processing email (offline donations are pending, not completed).
186 Email_Handler::send_donation_processing(
187 $donation_id,
188 $campaign_id,
189 [
190 'donor_name' => $donor_name,
191 'donor_email' => $donor_email,
192 'amount' => $amount,
193 'currency' => $currency,
194 'gateway' => 'offline',
195 'donation_type' => 'one-time',
196 ],
197 $form_id
198 );
199
200 wp_send_json_success(
201 [
202 'donationId' => $donation_id,
203 'message' => Helper::render_confirmation_message( $donation_id ),
204 ]
205 );
206 }
207 }
208