PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.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.6.1, at inc/payments/offline/offline-frontend.php

213 lines 8.0 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 // Likewise for the optional public message, read from the form's Donor
78 // Comment field (see Payment_Helper::get_mapped_donor_comment).
79 $donor_comment = Payment_Helper::get_mapped_donor_comment( $form_id );
80 $block_id = isset( $_POST['block_id'] ) ? sanitize_text_field( wp_unslash( $_POST['block_id'] ) ) : '';
81 // Display-only flag: the donor's real name/email/phone are still stored
82 // below and only public surfaces mask them.
83 $is_anonymous = Payment_Helper::get_submitted_is_anonymous( $form_id );
84 // phpcs:enable WordPress.Security.NonceVerification.Missing
85
86 // Standalone forms must not have a campaign.
87 if ( $is_standalone ) {
88 $campaign_id = 0;
89 }
90
91 // Validate required fields — campaign only required for non-standalone forms.
92 if ( ! $is_standalone && empty( $campaign_id ) ) {
93 wp_send_json_error( [ 'message' => __( 'Invalid campaign.', 'suredonation' ) ] );
94 }
95
96 if ( $amount <= 0 ) {
97 wp_send_json_error( [ 'message' => __( 'Invalid donation amount.', 'suredonation' ) ] );
98 }
99
100 if ( empty( $donor_email ) ) {
101 wp_send_json_error( [ 'message' => __( 'Email address is required.', 'suredonation' ) ] );
102 }
103
104 // Validate campaign only if not standalone.
105 if ( ! $is_standalone ) {
106 $campaign = get_post( $campaign_id );
107 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
108 wp_send_json_error( [ 'message' => __( 'Invalid campaign.', 'suredonation' ) ] );
109 }
110
111 if ( 'publish' !== $campaign->post_status ) {
112 wp_send_json_error( [ 'message' => __( 'This campaign is not available for donations.', 'suredonation' ) ] );
113 }
114
115 $campaign_status = Helper::get_campaign_meta_value( $campaign_id, 'campaign_status', 'active' );
116 if ( 'paused' === $campaign_status || 'completed' === $campaign_status ) {
117 wp_send_json_error( [ 'message' => __( 'This campaign is not currently accepting donations.', 'suredonation' ) ] );
118 }
119 }
120
121 // Validate form_id and block_id are present for amount validation.
122 if ( empty( $form_id ) || empty( $block_id ) ) {
123 wp_send_json_error( [ 'message' => __( 'Invalid form configuration.', 'suredonation' ) ] );
124 }
125
126 // Validate field values + amount against block config (skip Stripe minimum for offline).
127 $currency = Payment_Helper::get_currency();
128 $validation_result = Payment_Helper::validate_submission( Payment_Helper::get_submitted_fields(), $amount, $currency, $form_id, $block_id, 'offline', 'one-time' );
129 if ( ! $validation_result['valid'] ) {
130 wp_send_json_error(
131 [
132 'message' => esc_html( $validation_result['message'] ),
133 'fieldErrors' => $validation_result['field_errors'],
134 ]
135 );
136 }
137
138 // Get or create donor.
139 $donor_id = Donors::get_or_create( $donor_email, $donor_name, $donor_phone );
140
141 // Create donation record.
142 $donation_id = Donations::add(
143 [
144 'campaign_id' => $campaign_id,
145 'donor_id' => $donor_id ? $donor_id : 0,
146 'amount' => $amount,
147 'fees_covered' => 0,
148 'currency' => $currency,
149 'gateway' => 'offline',
150 'payment_status' => 'pending',
151 'payment_mode' => Payment_Helper::get_payment_mode(),
152 'donor_name' => $donor_name,
153 'donor_email' => $donor_email,
154 'donor_phone' => $donor_phone,
155 'is_anonymous' => $is_anonymous ? 1 : 0,
156 // Always one-time, whatever the block is configured for: an offline
157 // pledge has no instrument to charge on a schedule. The payment-type
158 // guard the Stripe and PayPal extractors run is deliberately NOT
159 // applied here -- it would reject every offline donation on a
160 // recurring form, which is the failure it exists to prevent, not
161 // cause. Whether such a form should offer offline at all is a
162 // separate product question.
163 'donation_type' => 'one-time',
164 'donor_comment' => $donor_comment,
165 'donor_comment_status' => Donations::initial_comment_status( $donor_comment ),
166 'form_id' => $form_id,
167 'ip_address' => Helper::get_client_ip(),
168 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
169 'referer_url' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
170 ]
171 );
172
173 if ( ! $donation_id ) {
174 wp_send_json_error( [ 'message' => __( 'Failed to create donation record.', 'suredonation' ) ] );
175 }
176
177 // Persist the submitted field values for the entry record.
178 Donations::set_submitted_fields( $donation_id, Payment_Helper::get_submitted_field_data() );
179
180 // Add log entry.
181 Donations::add_log(
182 $donation_id,
183 'created',
184 __( 'Offline donation created — pending payment', 'suredonation' ),
185 [
186 'gateway' => 'offline',
187 ]
188 );
189
190 // Send donation processing email (offline donations are pending, not completed).
191 Email_Handler::send_donation_processing(
192 $donation_id,
193 $campaign_id,
194 [
195 'donor_name' => $donor_name,
196 'donor_email' => $donor_email,
197 'amount' => $amount,
198 'currency' => $currency,
199 'gateway' => 'offline',
200 'donation_type' => 'one-time',
201 ],
202 $form_id
203 );
204
205 wp_send_json_success(
206 [
207 'donationId' => $donation_id,
208 'message' => Helper::render_confirmation_message( $donation_id ),
209 ]
210 );
211 }
212 }
213