PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / includes / gateways / class-charitable-gateway-stripe-am.php

class-charitable-gateway-stripe-am.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/gateways/class-charitable-gateway-stripe-am.php

1,731 lines 57.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Stripe Payment Gateway class.
4 *
5 * @package Charitable/Classes/Charitable_Gateway_Stripe_AM
6 * @author David Bisset
7 * @copyright Copyright (c) 2023, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.6.55
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Gateway_Stripe_AM' ) ) :
19
20 /**
21 * Stripe Gateway.
22 *
23 * @since 1.0.0
24 */
25 class Charitable_Gateway_Stripe_AM extends Charitable_Gateway {
26
27 /** The gateway ID. */
28 const ID = 'stripe';
29
30 /** The Stripe API version we are using. */
31 const STRIPE_API_VERSION = '2019-03-14';
32
33 /**
34 * Gateway badge.
35 *
36 * @var string
37 */
38 protected $badge = '';
39
40 /**
41 * Instantiate the gateway class, defining its key values.
42 *
43 * @since 1.0.0
44 */
45 public function __construct() {
46
47 /**
48 * Filter the gateway name
49 *
50 * @since 1.0.0
51 *
52 * @param string $name Gateway name.
53 */
54 $this->name = apply_filters( 'charitable_gateway_stripe_name', __( 'Stripe', 'charitable' ) );
55
56 $this->defaults = [
57 'label' => __( 'Stripe', 'charitable' ),
58 ];
59
60 $this->badge = __( 'Recommended', 'charitable' );
61
62 $this->recommended = true;
63
64 $this->supports = [
65 '1.3.0',
66 'credit-card',
67 'recurring',
68 'refunds',
69 ];
70
71 /* Needed for backwards compatibility with Charitable < 1.3 */
72 $this->credit_card_form = true;
73
74 add_filter( 'charitable_option_enable_stripe_checkout', array( $this, 'maybe_return_stripe_connect_setting' ), 10, 4 );
75 add_filter( 'charitable_option_enable_checkout_billing_address_collection', array( $this, 'maybe_return_stripe_connect_setting' ), 10, 4 );
76 add_filter( 'charitable_option_statement_descriptor', array( $this, 'maybe_return_stripe_connect_setting' ), 10, 4 );
77 add_filter( 'charitable_option_statement_descriptor_custom', array( $this, 'maybe_return_stripe_connect_setting' ), 10, 4 );
78
79 add_action( 'charitable_stripe_ipn_event', array( $this, 'maybe_process_webhook_with_stripe_connect' ), 10, 2 );
80
81 add_filter( 'charitable_minimum_donation_amount', array( $this, 'set_minimum_donation_amount' ), 10 );
82 }
83
84 /**
85 * Register the Stripe payment gateway class.
86 *
87 * @param string[] $gateways The list of registered gateways.
88 * @return string[]
89 * @since 1.7.0
90 */
91 public static function register_gateway( $gateways ) {
92 $gateways['stripe'] = 'Charitable_Gateway_Stripe';
93 return $gateways;
94 }
95
96 /**
97 * Register gateway settings.
98 *
99 * @param array $settings The existing settings to display for the Stripe settings page.
100 * @return array
101 * @since 1.7.0
102 */
103 public function gateway_settings( $settings ) {
104
105 $settings = $settings + array(
106 'cc_fields_format' => [
107 'title' => __( 'Credit Card Fields Format <span class="badge beta">Beta</span>', 'charitable' ),
108 'type' => 'select',
109 'priority' => 4,
110 'class' => 'general-settings',
111 'default' => 'below_amount_selection',
112 'options' => array(
113 '' => __( 'Single field', 'charitable' ),
114 'multiple' => __( 'Multiple fields', 'charitable' ),
115 ),
116 'help' => __( 'Single field (default) provides the card, expiration date, zip code, and CVC fields in one field. Multiple fields (beta) are seperated.', 'charitable' ),
117 ],
118 );
119
120 if ( ( charitable_is_debug() ) || charitable_is_admin_debug() ) {
121
122 if ( charitable()->is_stripe_connect_addon() ) {
123
124 $settings = $settings + array(
125 'enable_stripe_checkout' => [
126 'type' => 'checkbox',
127 'title' => __( 'Use Stripe Checkout', 'charitable' ),
128 'priority' => 4,
129 'help' => __( 'When you enable Stripe Checkout, donors enter their credit card details into a secure hosted payment page managed by Stripe. <a href="https://stripe.com/docs/checkout" target="_blank">https://stripe.com/docs/checkout</a>', 'charitable' ),
130 ],
131 'section_statement_descriptor' => [
132 'type' => 'heading',
133 'title' => __( 'Statement Descriptor', 'charitable' ),
134 'priority' => 25,
135 ],
136 'statement_descriptor' => [
137 'type' => 'select',
138 'title' => __( 'Format', 'charitable' ),
139 'priority' => 26,
140 'help' => __( 'Customize the statement descriptor that will appear on donors\' bank statements.', 'charitable' ),
141 'default' => 'auto',
142 'options' => [
143 'auto' => __( 'Use campaign title', 'charitable' ),
144 'custom' => __( 'Set a custom descriptor', 'charitable' ),
145 ],
146 ],
147 'statement_descriptor_custom' => [
148 'type' => 'text',
149 'title' => __( 'Custom Statement Descriptor', 'charitable' ),
150 'priority' => 26,
151 'help' => __( 'A custom statement descriptor up to 22 characters long.', 'charitable' ),
152 'attrs' => [
153 'data-trigger-key' => '#charitable_settings_gateways_stripe_statement_descriptor',
154 'data-trigger-value' => 'custom',
155 'maxlength' => 22,
156 ],
157 ],
158 );
159
160 }
161
162 return array_merge(
163 $settings,
164 array(
165 'section_live_mode' => [
166 'title' => __( 'Live Settings', 'charitable' ),
167 'type' => 'heading',
168 'priority' => 5,
169 ],
170 'live_secret_key' => [
171 'type' => 'text',
172 'title' => __( 'Live Secret Key', 'charitable' ),
173 'priority' => 6,
174 'class' => 'wide',
175 ],
176 'live_public_key' => [
177 'type' => 'text',
178 'title' => __( 'Live Publishable Key', 'charitable' ),
179 'priority' => 7,
180 'class' => 'wide',
181 ],
182 'section_test_mode' => [
183 'title' => __( 'Test Settings', 'charitable' ),
184 'type' => 'heading',
185 'priority' => 10,
186 ],
187 'test_secret_key' => [
188 'type' => 'text',
189 'title' => __( 'Test Secret Key', 'charitable' ),
190 'priority' => 11,
191 'class' => 'wide',
192 ],
193 'test_public_key' => [
194 'type' => 'text',
195 'title' => __( 'Test Publishable Key', 'charitable' ),
196 'priority' => 12,
197 'class' => 'wide',
198 ],
199 'api_description' => array(
200 'type' => 'content',
201 'title' => __( 'Connection Status', 'charitable' ),
202 'content' => $this->get_connection_status_content(),
203 'priority' => 21,
204 ),
205 )
206 );
207
208 } else {
209
210 $stripe_settings = array(
211 'api_description' => array(
212 'type' => 'content',
213 'title' => __( 'Connection Status', 'charitable' ),
214 'content' => $this->get_connection_status_content(),
215 'priority' => 5,
216 ),
217 );
218
219 if ( charitable_get_option( 'test_mode' ) || ( isset( $force_test_mode ) && $force_test_mode ) ) {
220 $mode = 'test';
221 } else {
222 $mode = 'live';
223 }
224
225 if ( $this->check_keys_exist( $mode ) && false === charitable_using_stripe_connect() && charitable()->is_stripe_connect_addon() ) {
226 // if there ARE keys and the Stripe addon is active... BUT WE ARE NOT USING 1.70 Stripe Connect... so this is most likely previous stripe addon keys.
227 $stripe_settings = $stripe_settings + array(
228 'stripe_modify_keys' => [
229 'title' => '',
230 'type' => 'content',
231 'class' => 'manually-modify',
232 'content' => '<a href="#" data-charitable-args="mode=' . $mode . '" data-charitable-action="show-stripe-keys" class="manually-modify-keys" id="charitable-modify-keys">Manually Modify Keys</a>',
233 'priority' => 15,
234 ],
235 );
236 }
237
238 if ( charitable()->is_stripe_connect_addon() ) {
239
240 $stripe_settings = $stripe_settings + array(
241 'enable_stripe_checkout' => [
242 'type' => 'checkbox',
243 'title' => __( 'Use Stripe Checkout', 'charitable' ),
244 'priority' => 16,
245 'help' => __( 'When you enable Stripe Checkout, donors enter their credit card details into a secure hosted payment page managed by Stripe. <a href="https://stripe.com/docs/checkout" target="_blank">https://stripe.com/docs/checkout</a>', 'charitable' ),
246 ],
247 'enable_checkout_billing_address_collection' => [
248 'type' => 'checkbox',
249 'title' => __( 'Require Billing Address in Checkout', 'charitable' ),
250 'priority' => 17,
251 'help' => __( 'If enabled, donors will always be asked to enter their billing address in Checkout.', 'charitable' ),
252 'attrs' => [
253 'data-trigger-key' => '#charitable_settings_gateways_stripe_enable_stripe_checkout',
254 'data-trigger-value' => 'checked',
255 ],
256 ],
257 'section_statement_descriptor' => [
258 'type' => 'heading',
259 'title' => __( 'Statement Descriptor', 'charitable' ),
260 'priority' => 20,
261 ],
262 'statement_descriptor' => [
263 'type' => 'select',
264 'title' => __( 'Format', 'charitable' ),
265 'priority' => 21,
266 'help' => __( 'Customize the statement descriptor that will appear on donors\' bank statements.', 'charitable' ),
267 'default' => 'auto',
268 'options' => [
269 'auto' => __( 'Use campaign title', 'charitable' ),
270 'custom' => __( 'Set a custom descriptor', 'charitable' ),
271 ],
272 ],
273 'statement_descriptor_custom' => [
274 'type' => 'text',
275 'title' => __( 'Custom Statement Descriptor', 'charitable' ),
276 'priority' => 22,
277 'help' => __( 'A custom statement descriptor up to 22 characters long.', 'charitable' ),
278 'attrs' => [
279 'data-trigger-key' => '#charitable_settings_gateways_stripe_statement_descriptor',
280 'data-trigger-value' => 'custom',
281 'maxlength' => 22,
282 ],
283 ],
284 );
285 }
286
287 if ( charitable_using_stripe_connect() ) {
288
289 unset( $stripe_settings['section_live_mode'] );
290 unset( $stripe_settings['live_secret_key'] );
291 unset( $stripe_settings['live_public_key'] );
292 unset( $stripe_settings['section_test_mode'] );
293 unset( $stripe_settings['test_secret_key'] );
294 unset( $stripe_settings['test_public_key'] );
295
296 }
297
298 if ( ( charitable_is_debug() ) || charitable_is_admin_debug() ) {
299 $stripe_settings = $stripe_settings + array(
300 'section_stripe_checkout' => [
301 'title' => __( 'Stripe Checkout INTERNAL', 'charitable' ),
302 'type' => 'heading',
303 'priority' => 4,
304 ],
305 'section_live_mode' => [
306 'title' => __( 'Live Settings', 'charitable' ),
307 'type' => 'heading',
308 'priority' => 5,
309 ],
310 'live_secret_key' => [
311 'type' => 'text',
312 'title' => __( 'Live Secret Key', 'charitable' ),
313 'priority' => 6,
314 'class' => 'wide',
315 ],
316 'live_public_key' => [
317 'type' => 'text',
318 'title' => __( 'Live Publishable Key', 'charitable' ),
319 'priority' => 7,
320 'class' => 'wide',
321 ],
322 'section_test_mode' => [
323 'title' => __( 'Test Settings', 'charitable' ),
324 'type' => 'heading',
325 'priority' => 10,
326 ],
327 'test_secret_key' => [
328 'type' => 'text',
329 'title' => __( 'Test Secret Key', 'charitable' ),
330 'priority' => 11,
331 'class' => 'wide',
332 ],
333 'test_public_key' => [
334 'type' => 'text',
335 'title' => __( 'Test Publishable Key', 'charitable' ),
336 'priority' => 12,
337 'class' => 'wide',
338 ],
339 );
340 }
341
342 return array_merge( $settings, $stripe_settings );
343
344 }
345 }
346
347 /**
348 * Get the Stripe logo.
349 *
350 * @since 1.8.7
351 *
352 * @return string
353 */
354 public function get_logo() {
355 return '<svg width="49" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.4718 10.3338c0-3.41791-1.6696-6.11484-4.8607-6.11484-3.2045 0-5.1434 2.69693-5.1434 6.08814 0 4.0187 2.289 6.048 5.5743 6.048 1.6023 0 2.8141-.3604 3.7296-.8678v-2.6702c-.9155.4539-1.9658.7343-3.2987.7343-1.3061 0-2.464-.4539-2.6121-2.0294h6.5841c0-.1735.0269-.8678.0269-1.1882Zm-6.6514-1.26838c0-1.50868.929-2.13618 1.7773-2.13618.8213 0 1.6965.6275 1.6965 2.13618h-3.4738Zm-8.5499-4.84646c-1.3195 0-2.1678.61415-2.639 1.04139l-.1751-.82777h-2.9621V20l3.3661-.7076.0134-3.7784c.4847.3471 1.1984.8411 2.3832.8411 2.4102 0 4.6048-1.9225 4.6048-6.1548-.0134-3.87186-2.235-5.98134-4.5913-5.98134Zm-.8079 9.19894c-.7944 0-1.2656-.2804-1.5888-.6275l-.0134-4.95328c.35-.38719.8348-.65421 1.6022-.65421 1.2253 0 2.0735 1.36182 2.0735 3.11079 0 1.7891-.8347 3.1242-2.0735 3.1242Zm-9.6001-9.98666 3.3796-.72096V0l-3.3796.70761v2.72363Zm0 1.01469h3.3796V16.1282h-3.3796V4.44593Zm-3.6219.98798-.2154-.98798h-2.9083V16.1282h3.3661V8.21095c.7944-1.02804 2.1408-.84112 2.5582-.69426V4.44593c-.4309-.16022-2.0062-.45394-2.8006.98798Zm-6.7322-3.88518-3.2853.69426-.01346 10.69421c0 1.976 1.49456 3.4313 3.48726 3.4313 1.1041 0 1.912-.2003 2.3563-.4406v-2.7103c-.4309.1736-2.5583.7877-2.5583-1.1882V7.28972h2.5583V4.44593h-2.5583l.0135-2.8972ZM3.40649 7.83712c0-.5207.43086-.72096 1.14447-.72096 1.0233 0 2.31588.30707 3.33917.85447V4.83311c-1.11755-.44059-2.22162-.61415-3.33917-.61415C1.81769 4.21896 0 5.63418 0 7.99733c0 3.68487 5.11647 3.09747 5.11647 4.68627 0 .6141-.53858.8144-1.29258.8144-1.11755 0-2.54477-.4539-3.675782-1.0681v3.1776c1.252192.534 2.517842.761 3.675782.761 2.80059 0 4.72599-1.3752 4.72599-3.765-.01346-3.97867-5.14339-3.27106-5.14339-4.76638Z" fill="#635BFF"/></svg>';
356 }
357
358 /**
359 * Determine if Stripe is connected.
360 *
361 * @since 1.8.1.12
362 *
363 * @return int
364 */
365 public function maybe_stripe_connected() {
366 return $this->check_keys_exist( 'live' ) || $this->check_keys_exist( 'test' );
367 }
368
369 /**
370 * Get connection status content.
371 *
372 * @since 1.8.1.12
373 *
374 * @param bool $force_test_mode Whether to force test mode.
375 *
376 * @return bool
377 */
378 public function get_connection_status_content( $force_test_mode = false ) {
379
380 if ( charitable_get_option( 'test_mode' ) || $force_test_mode ) {
381 $mode = 'test';
382 } else {
383 $mode = 'live';
384 }
385
386 $html = '';
387
388 if ( ! $this->check_keys_exist( $mode ) || ! charitable_using_stripe_connect() ) {
389 $html .= $this->get_stripe_connect_button();
390 } else {
391
392 if ( ! charitable_is_pro() ) {
393 $html .= '<div class="charitable-inline-notice info">
394 <p>
395 <strong>' . esc_html__( 'Pay as you go pricing:', 'charitable' ) . '</strong> ' .
396 sprintf(
397 /* translators: %1$s: opening link tag, %2$s: closing link tag */
398 esc_html__( '3%% per transaction + Stripe fees. %1$sUpgrade to Pro%2$s for no added fees and priority support.', 'charitable' ),
399 '<a target="_blank" href="' . esc_url( charitable_pro_upgrade_url( 'gateway-stripe' ) ) . '">',
400 '</a>'
401 ) . '</p>
402 </div>';
403 }
404
405 $html .= '<p id="wpcharitable-stripe-auth-error-account-actions" style="display: block;">' . sprintf(
406 /* translators: %1$s Stripe payment mode. %2$s Opening anchor tag for reconnecting to Stripe, do not translate. %3$s Opening anchor tag for disconnecting Stripe, do not translate. %4$s Closing anchor tag, do not translate. */
407 __( '%1$sDisconnect this account%2$s.', 'charitable' ),
408 '<strong>Connected in ' . $mode . ' mode</strong>. <a href="' . esc_url( $this->get_stripe_disconnect_url() ) . '" class="wpcharitable-disconnect-link">',
409 '</a>'
410 ) . '</p>';
411
412 $html .= '<p id="wpcharitable-stripe-activated-account-actions" style="display: none;">' . sprintf(
413 /* translators: %1$s Stripe payment mode. %2$s Opening anchor tag for reconnecting to Stripe, do not translate. %3$s Opening anchor tag for disconnecting Stripe, do not translate. %4$s Closing anchor tag, do not translate. */
414 __( 'Your Stripe account is connected in %1$s mode. %2$sDisconnect this account%3$s.', 'charitable' ),
415 '<strong>' . $mode . '</strong>',
416 '<a href="' . esc_url( $this->get_stripe_disconnect_url() ) . '" class="wpcharitable-disconnect-link">',
417 '</a>'
418 ) . '</p>';
419
420 $html .= '<p id="wpcharitable-stripe-unactivated-account-actions" style="display: none;"><a href="' . esc_url( $this->get_stripe_disconnect_url() ) . '">' .
421 __( 'Disconnect temporary account', 'charitable' ) .
422 '</a></p>';
423 }
424
425 return $html;
426 }
427
428 /**
429 * Generate the Stripe Connect button.
430 *
431 * @since 1.8.2
432 *
433 * @param string $redirect_url The URL to redirect to after connecting.
434 * @return string
435 */
436 public function get_stripe_connect_button( $redirect_url = '' ) {
437
438 $url = $this->get_stripe_connect_url( $redirect_url );
439
440 ob_start();
441 ?>
442
443 <a href="<?php echo esc_url( $url ); ?>" aria-label="<?php echo esc_attr__( 'Connect with Stripe', 'charitable' ); ?>" class="wpcharitable-stripe-connect">
444 <span>
445 <?php
446 /* translators: Text before Stripe logo for "Connect with Stripe" button. */
447 esc_html_e( 'Connect with', 'charitable' );
448 ?>
449 </span>
450
451 <svg width="49" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.4718 10.3338c0-3.41791-1.6696-6.11484-4.8607-6.11484-3.2045 0-5.1434 2.69693-5.1434 6.08814 0 4.0187 2.289 6.048 5.5743 6.048 1.6023 0 2.8141-.3604 3.7296-.8678v-2.6702c-.9155.4539-1.9658.7343-3.2987.7343-1.3061 0-2.464-.4539-2.6121-2.0294h6.5841c0-.1735.0269-.8678.0269-1.1882Zm-6.6514-1.26838c0-1.50868.929-2.13618 1.7773-2.13618.8213 0 1.6965.6275 1.6965 2.13618h-3.4738Zm-8.5499-4.84646c-1.3195 0-2.1678.61415-2.639 1.04139l-.1751-.82777h-2.9621V20l3.3661-.7076.0134-3.7784c.4847.3471 1.1984.8411 2.3832.8411 2.4102 0 4.6048-1.9225 4.6048-6.1548-.0134-3.87186-2.235-5.98134-4.5913-5.98134Zm-.8079 9.19894c-.7944 0-1.2656-.2804-1.5888-.6275l-.0134-4.95328c.35-.38719.8348-.65421 1.6022-.65421 1.2253 0 2.0735 1.36182 2.0735 3.11079 0 1.7891-.8347 3.1242-2.0735 3.1242Zm-9.6001-9.98666 3.3796-.72096V0l-3.3796.70761v2.72363Zm0 1.01469h3.3796V16.1282h-3.3796V4.44593Zm-3.6219.98798-.2154-.98798h-2.9083V16.1282h3.3661V8.21095c.7944-1.02804 2.1408-.84112 2.5582-.69426V4.44593c-.4309-.16022-2.0062-.45394-2.8006.98798Zm-6.7322-3.88518-3.2853.69426-.01346 10.69421c0 1.976 1.49456 3.4313 3.48726 3.4313 1.1041 0 1.912-.2003 2.3563-.4406v-2.7103c-.4309.1736-2.5583.7877-2.5583-1.1882V7.28972h2.5583V4.44593h-2.5583l.0135-2.8972ZM3.40649 7.83712c0-.5207.43086-.72096 1.14447-.72096 1.0233 0 2.31588.30707 3.33917.85447V4.83311c-1.11755-.44059-2.22162-.61415-3.33917-.61415C1.81769 4.21896 0 5.63418 0 7.99733c0 3.68487 5.11647 3.09747 5.11647 4.68627 0 .6141-.53858.8144-1.29258.8144-1.11755 0-2.54477-.4539-3.675782-1.0681v3.1776c1.252192.534 2.517842.761 3.675782.761 2.80059 0 4.72599-1.3752 4.72599-3.765-.01346-3.97867-5.14339-3.27106-5.14339-4.76638Z" fill="#fff"/></svg>
452 </a>
453
454 <style>
455 .wpcharitable-stripe-connect {
456 color: #fff;
457 font-size: 15px;
458 font-weight: bold;
459 text-decoration: none;
460 line-height: 1;
461 background-color: #635bff;
462 border-radius: 3px;
463 padding: 10px 20px;
464 display: inline-flex;
465 align-items: center;
466 }
467
468 .wpcharitable-stripe-connect:focus,
469 .wpcharitable-stripe-connect:hover {
470 color: #fff;
471 background-color: #0a2540;
472 }
473
474 .wpcharitable-stripe-connect:focus {
475 outline: 0;
476 box-shadow: inset 0 0 0 1px #fff, 0 0 0 1.5px #0a2540;
477 }
478
479 .wpcharitable-stripe-connect svg {
480 margin-left: 5px;
481 }
482 </style>
483
484 <?php
485 return ob_get_clean();
486 }
487
488 /**
489 * Get the URL to connect to Stripe.
490 *
491 * @since 1.8.0
492 *
493 * @param string $redirect_url The URL to redirect to after connecting.
494 * @return string
495 */
496 public function get_stripe_connect_url( $redirect_url = '' ) {
497 if ( empty( $redirect_url ) ) {
498
499 $redirect_url = add_query_arg(
500 array(
501 'tab' => 'gateways',
502 'page' => 'charitable-settings',
503 'group' => 'gateways_stripe',
504 ),
505 admin_url( 'admin.php' )
506 );
507
508 }
509
510 return add_query_arg(
511 array(
512 'live_mode' => (int) ! charitable_get_option( 'test_mode' ), // (int) ! $this->is_test_mode(),
513 'state' => str_pad( wp_rand( wp_rand(), PHP_INT_MAX ), 100, wp_rand(), STR_PAD_BOTH ),
514 'customer_site_url' => urlencode( $redirect_url ),
515 ),
516 'https://wpcharitable.com/stripe-connect/?wpcharitable_gateway_connect_init=stripe_connect' // todo: filter market site url.
517 );
518 }
519
520 /**
521 * Get the URL to disconnect from Stripe.
522 *
523 * @since 1.8.0
524 *
525 * @return string
526 */
527 public function get_stripe_disconnect_url() {
528 return add_query_arg(
529 array(
530 'wpcharitable-stripe-disconnect' => true,
531 '_wpnonce' => wp_create_nonce(
532 'wpcharitable-stripe-connect-disconnect'
533 ),
534 'tab' => 'gateways',
535 'page' => 'charitable-settings',
536 'group' => 'gateways_stripe',
537 ),
538 admin_url( 'admin.php' )
539 );
540 }
541
542 /**
543 * Returns the current gateway's ID.
544 *
545 * @return string
546 * @since 1.0.3
547 */
548 public static function get_gateway_id() {
549 return self::ID;
550 }
551
552 /**
553 * Return the keys to use.
554 *
555 * This will return the test keys if test mode is enabled. Otherwise, returns
556 * the production keys.
557 *
558 * @since 1.7.0
559 *
560 * @param boolean $force_test_mode Forces the test API keys to be used.
561 * @return string[]
562 */
563 public function get_keys( $force_test_mode = false ) {
564 $keys = [];
565
566 if ( charitable_get_option( 'test_mode' ) || $force_test_mode ) {
567 $keys['secret_key'] = trim( $this->get_value( 'test_secret_key' ) );
568 $keys['public_key'] = trim( $this->get_value( 'test_public_key' ) );
569 } else {
570 $keys['secret_key'] = trim( $this->get_value( 'live_secret_key' ) );
571 $keys['public_key'] = trim( $this->get_value( 'live_public_key' ) );
572 }
573
574 return $keys;
575 }
576
577 /**
578 * Check if Stripe keys exist.
579 *
580 * @since 1.7.0
581 * @param string $mode The mode to check for keys.
582 * @return boolean
583 */
584 public function check_keys_exist( $mode ) {
585
586 $secret_key = trim( $this->get_value( $mode . '_secret_key' ) );
587 $publishable_key = trim( $this->get_value( $mode . '_public_key' ) );
588
589 if ( ! empty( $secret_key ) && ! empty( $publishable_key ) ) {
590 return true;
591 }
592
593 return false;
594 }
595
596 /**
597 * Return the submitted value for a gateway field.
598 *
599 * @since 1.7.0
600 *
601 * @param string $key The key of the value we want to get.
602 * @param mixed[] $values An values in which to search.
603 * @return string|false
604 */
605 public function get_gateway_value( $key, $values ) {
606 if ( isset( $values['gateways']['stripe'][ $key ] ) ) {
607 return $values['gateways']['stripe'][ $key ];
608 }
609
610 return false;
611 }
612
613 /**
614 * Return the submitted value for a gateway field.
615 *
616 * @since 1.7.0
617 *
618 * @param string $key The key of the value we want to get.
619 * @param Charitable_Donation_Processor $processor The Donation Processor helper object.
620 * @return string|false
621 */
622 public function get_gateway_value_from_processor( $key, Charitable_Donation_Processor $processor ) {
623 return $this->get_gateway_value( $key, $processor->get_donation_data() );
624 }
625
626 /**
627 * Add the connected account & charge owner setting to the donation form's hidden fields.
628 *
629 * @since 1.4.0
630 *
631 * @param string[] $fields The hidden fields as key=>value pairs.
632 * @param Charitable_Donation_Form $form The donation form instance.
633 * @return string[]
634 */
635 public static function add_hidden_stripe_account_field( $fields, $form ) {
636 if ( ! class_exists( 'Charitable_Stripe_Connect' ) ) {
637 return $fields;
638 }
639
640 $connected_account = charitable_stripe_get_connected_account_for_campaign( $form->get_campaign()->ID );
641
642 if ( $connected_account ) {
643 $fields['connected_account'] = $connected_account;
644 $fields['connect_charge_owner'] = charitable_get_option( [ 'gateways_stripe', 'charge_owner' ], 'direct' );
645 }
646
647 return $fields;
648 }
649
650 /**
651 * Returns an array of credit card fields.
652 *
653 * If the gateway requires different fields, this can simply be redefined
654 * in the child class.
655 *
656 * @since 1.7.0
657 * @version 1.8.9.1
658 *
659 * @return array[]
660 */
661 public function get_credit_card_fields() {
662 /* If Stripe Checkout is enabled, remove the credit card fields. */
663 if ( $this->get_value( 'enable_stripe_checkout' ) ) {
664 return [];
665 }
666
667 $fields = parent::get_credit_card_fields();
668
669 $cc_fields_format = empty( $this->get_value( 'cc_fields_format' ) ) ? 'standard' : $this->get_value( 'cc_fields_format' );
670
671 if ( 'multiple' === $cc_fields_format ) {
672 /* Remove all fields except for the cc_name field. */
673 unset(
674 $fields['cc_number'],
675 $fields['cc_cvc'],
676 $fields['cc_expiration']
677 );
678
679 /* Check for an existing payment intent. */
680 $intent = Charitable_Stripe_Payment_Intent::init_from_session();
681 $intent = false;
682
683 ob_start();
684
685 /**
686 * Render a template with our custom CSS. NOTE: We pass a non-empty array
687 * as the second parameter to ensure that the template is rendered immediately.
688 *
689 * @see charitable_template
690 */
691 charitable_template(
692 'stripe-elements.css.php',
693 [ 1 ],
694 'Charitable_Stripe_Template'
695 );
696 ?>
697
698 <div class="charitable_stripe_multiple_field">
699 <label for="charitable_stripe_card_expiration_field"><?php esc_html_e( 'Expiration Date', 'charitable' ); ?></label>
700 <div id="charitable_stripe_card_expiration_field">
701 <!-- A Stripe Element will be inserted here -->
702 </div>
703 </div>
704 <div class="charitable_stripe_multiple_field">
705 <label for="charitable_stripe_card_number_field"><?php esc_html_e( 'Credit/Debit Number', 'charitable' ); ?></label>
706 <div id="charitable_stripe_card_number_field">
707 <!-- A Stripe Element will be inserted here -->
708 </div>
709 </div>
710 <div class="charitable_stripe_multiple_field">
711 <label for="charitable_stripe_card_cvc_field"><?php esc_html_e( 'CVC', 'charitable' ); ?></label>
712 <div id="charitable_stripe_card_cvc_field">
713 <!-- A Stripe Element will be inserted here -->
714 </div>
715 </div>
716
717
718 <div id="charitable_stripe_card_errors" role="alert"></div>
719 <input type="hidden" name="stripe_payment_method" />
720
721 <?php
722
723 $fields['cc_element'] = [
724 'type' => 'content',
725 'content' => ob_get_clean(),
726 'priority' => 2,
727 ];
728
729 } else {
730 /* Remove all fields except for the cc_name field. */
731 unset(
732 $fields['cc_number'],
733 $fields['cc_cvc'],
734 $fields['cc_expiration']
735 );
736
737 /* Check for an existing payment intent. */
738 $intent = Charitable_Stripe_Payment_Intent::init_from_session();
739 $intent = false;
740
741 ob_start();
742
743 /**
744 * Render a template with our custom CSS. NOTE: We pass a non-empty array
745 * as the second parameter to ensure that the template is rendered immediately.
746 *
747 * @see charitable_template
748 */
749 charitable_template(
750 'stripe-elements.css.php',
751 [ 1 ],
752 'Charitable_Stripe_Template'
753 );
754 ?>
755 <label for="charitable_stripe_card_field"><?php esc_html_e( 'Credit/Debit Card', 'charitable' ); ?></label>
756 <div id="charitable_stripe_card_field" data-secret="<?php echo esc_attr( $intent ? $intent->get( 'client_secret' ) : '' ); ?>" data-intent="<?php echo esc_attr( $intent ? $intent->get( 'id' ) : '' ); ?>"></div>
757 <div id="charitable_stripe_card_errors" role="alert"></div>
758 <input type="hidden" name="stripe_payment_method" />
759 <?php
760
761 $fields['cc_element'] = [
762 'type' => 'content',
763 'content' => ob_get_clean(),
764 'priority' => 2,
765 ];
766
767 $fields['cc_name']['attrs'] = [
768 'data-input' => 'cc_name',
769 ];
770 }
771
772 return $fields;
773 }
774
775 /**
776 * Set up an output buffer before the donation form fields are displayed.
777 *
778 * @since 1.3.0
779 * @version 1.8.9.1
780 *
781 * @param Charitable_Form $form Form object.
782 * @return void
783 */
784 public static function setup_donation_form_field_output_buffer( Charitable_Form $form ) {
785 if ( 'Charitable_Donation_Form' != get_class( $form ) ) {
786 return;
787 }
788
789 ob_start();
790
791 echo '<noscript><div class="charitable-notice charitable-form-errors">
792 <ul class="charitable-notice-errors errors">
793 <li>' . esc_html__( 'For security reasons, credit card donations require Javascript. Please enable Javascript in your browser before continuing.', 'charitable' ) . '</li>
794 </ul><!-- charitable-notice- -->
795 </div></noscript>';
796 }
797
798 /**
799 * Get the donation form fields from the buffer and remove the name attribute
800 * from cc fields.
801 *
802 * @since 1.3.0
803 * @version 1.8.9.1
804 *
805 * @param Charitable_Form $form Form object.
806 * @return void
807 */
808 public static function remove_name_attribute_from_cc_fields( Charitable_Form $form ) {
809 if ( 'Charitable_Donation_Form' != get_class( $form ) ) {
810 return;
811 }
812
813 $fields = ob_get_clean();
814 $fields = str_replace( 'name="cc_name"', '', $fields );
815
816 echo $fields; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
817 }
818
819 /**
820 * Validate the submitted credit card details.
821 *
822 * @since 1.7.0
823 * @since 1.4.0 Deprecated.
824 * @since 1.4.7 Restored. No longer deprecated.
825 *
826 * @param boolean $valid Whether the donation is valid.
827 * @param string $gateway The chosen gateway.
828 * @param mixed[] $values The filtered values from the donation form submission.
829 * @return boolean
830 */
831 public static function validate_donation( $valid, $gateway, $values ) {
832 if ( 'stripe' !== $gateway ) {
833 return $valid;
834 }
835
836 $gateway = new Charitable_Gateway_Stripe_AM();
837 $keys = $gateway->get_keys();
838
839 /* Make sure that the keys are set. */
840 if ( empty( $keys['secret_key'] ) || empty( $keys['public_key'] ) ) {
841 charitable_get_notices()->add_error( __( 'Missing keys for Stripe payment gateway. Unable to proceed with payment.', 'charitable' ) );
842 return false;
843 }
844
845 /* If we're using Payment Intents, make sure we have a payment method. */
846 if ( ! $gateway->get_value( 'enable_stripe_checkout' ) ) {
847 // phpcs:disable WordPress.Security.NonceVerification.Missing
848 $stripe_payment_method = isset( $_POST['stripe_payment_method'] ) ? sanitize_text_field( wp_unslash( $_POST['stripe_payment_method'] ) ) : '';
849 if ( empty( $stripe_payment_method ) ) {
850 // phpcs:enable WordPress.Security.NonceVerification.Missing
851 charitable_get_notices()->add_error(
852 __( '<strong>Missing payment details.</strong> <a href="#charitable-gateway-fields">Click here to double-check that all required payment fields are completed.</a>', 'charitable' )
853 );
854 return false;
855 }
856 // phpcs:enable WordPress.Security.NonceVerification.Missing
857 }
858
859 return $valid;
860 }
861
862 /**
863 * Checks whether the donation being processed is recurring.
864 *
865 * @since 1.3.0
866 *
867 * @param Charitable_Donation_Processor $processor The Donation Processor helper.
868 * @return boolean
869 */
870 public static function is_recurring_donation( Charitable_Donation_Processor $processor ) {
871 return $processor->get_donation_data_value( 'donation_plan', false );
872 }
873
874 /**
875 * Process the donation with the gateway, seamlessly over the Stripe API.
876 *
877 * @since 1.7.0
878 *
879 * @param mixed $return The result of the gateway processing.
880 * @param int $donation_id The donation ID.
881 * @param Charitable_Donation_Processor $processor The Donation Processor helper.
882 * @return boolean
883 */
884 public static function process_donation( $return, $donation_id, Charitable_Donation_Processor $processor ) {
885 if ( charitable_get_option( [ 'gateways_stripe', 'enable_stripe_checkout' ] ) && charitable()->is_stripe_connect_addon() ) {
886 /**
887 * Filter the processor used for handling Checkout donations.
888 *
889 * @since 1.4.0
890 *
891 * @param string $class The name of the Stripe gateway processor class.
892 * @param Charitable_Donation_Processor $processor The Donation Processor helper.
893 */
894 $processor_class = apply_filters( 'charitable_stripe_gateway_processor_checkout', 'Charitable_Stripe_Gateway_Processor_Checkout', $processor );
895 } else {
896 /**
897 * Filter the processor used for handling PaymentIntent donations.
898 *
899 * @since 1.4.0
900 *
901 * @param string $class The name of the Stripe gateway processor class.
902 * @param Charitable_Donation_Processor $processor The Donation Processor helper.
903 */
904 $processor_class = apply_filters( 'charitable_stripe_gateway_processor_payment_intents', 'Charitable_Stripe_Gateway_Processor_Payment_Intents', $processor );
905 }
906
907 $gateway_processor = new $processor_class( $donation_id, $processor );
908
909 /* Ensure we have a valid processor. */
910 if ( ! $gateway_processor instanceof Charitable_Stripe_Gateway_Processor ) {
911 $gateway_processor = new Charitable_Stripe_Gateway_Processor_Payment_Intents( $donation_id, $processor );
912 }
913
914 return $gateway_processor->run();
915 }
916
917 /**
918 * Sets up the API.
919 *
920 * This sets the API key, specifies an API version to use, and also
921 * sets the App info.
922 *
923 * @since 1.3.0
924 *
925 * @param string|null $api_key The API key. If null, will use the secret key.
926 * @return boolean
927 */
928 public function setup_api( $api_key = null ) {
929 if ( is_null( $api_key ) ) {
930 $keys = $this->get_keys();
931
932 if ( ! array_key_exists( 'secret_key', $keys ) ) {
933 if ( charitable_is_debug( 'stripe' ) ) {
934 error_log( '[Charitable Stripe] setup_api FAILED: No secret_key in keys array.' ); // phpcs:ignore
935 }
936 return false;
937 }
938
939 $api_key = $keys['secret_key'];
940 }
941
942 if ( charitable_is_debug( 'stripe' ) ) {
943 $key_preview = ! empty( $api_key ) ? substr( $api_key, 0, 7 ) . '...' . substr( $api_key, -4 ) : 'EMPTY';
944 $test_mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
945 error_log( sprintf( '[Charitable Stripe] setup_api called. Mode: %s, Key: %s', $test_mode, $key_preview ) ); // phpcs:ignore
946 }
947
948 /**
949 * On certain versions of CentOS, curl requests to Stripe fail consistently.
950 * The following filter provides a way to work around this with the following:
951 *
952 add_filter( 'charitable_stripe_http_curl_client', function( \Stripe\HttpClient\CurlClient $curl ) {
953 $curl->setEnablePersistentConnections( false );
954 return $curl;
955 } );
956 *
957 * @see https://github.com/stripe/stripe-php/issues/918
958 *
959 * @since 1.4.11
960 *
961 * @param \Stripe\HttpClient\CurlClient $curl The CurlClient instance.
962 */
963 $curl = apply_filters( 'charitable_stripe_http_curl_client', new \Stripe\HttpClient\CurlClient() );
964
965 /* Allow the Curl client object to be changed. */
966 \Stripe\ApiRequestor::setHttpClient( $curl );
967
968 \Stripe\Stripe::setApiKey( $api_key );
969 \Stripe\Stripe::setApiVersion( self::STRIPE_API_VERSION );
970 \Stripe\Stripe::setAppInfo(
971 'WordPress CharitablePlugin',
972 charitable_stripe()->get_version(),
973 'https://www.wpcharitable.com',
974 'pp_partner_Ee8qdpxEQjiEne'
975 );
976
977 return true;
978 }
979
980 /**
981 * Check whether a particular donation can be refunded automatically in Stripe.
982 *
983 * @since 1.3.0
984 *
985 * @param Charitable_Donation $donation The donation object.
986 * @return boolean
987 */
988 public function is_donation_refundable( Charitable_Donation $donation ) {
989 $secret_key = $donation->get_test_mode( false ) ? 'test_secret_key' : 'live_secret_key';
990
991 if ( ! $this->get_value( $secret_key ) ) {
992 return false;
993 }
994
995 // todo: determine why this function might not be available when this is called on the donation detail page.
996 if ( function_exists( 'charitable_stripe_get_charge_id_for_donation' ) ) {
997 return false !== charitable_stripe_get_charge_id_for_donation( $donation->ID );
998 } else {
999 return false;
1000 }
1001 }
1002
1003 /**
1004 * Process a refund initiated in the WordPress dashboard.
1005 *
1006 * @since 1.3.0
1007 *
1008 * @param int $donation_id The donation ID.
1009 * @return boolean
1010 */
1011 public static function refund_donation_from_dashboard( $donation_id ) {
1012 $donation = charitable_get_donation( $donation_id );
1013
1014 if ( ! $donation ) {
1015 return false;
1016 }
1017
1018 $charge = charitable_stripe_get_charge_id_for_donation( $donation_id );
1019
1020 if ( ! $charge ) {
1021 return false;
1022 }
1023
1024 $gateway = new Charitable_Gateway_Stripe_AM();
1025 $key = $donation->get_test_mode( false ) ? 'test_secret_key' : 'live_secret_key';
1026 $api_key = $gateway->get_value( $key );
1027 $account = charitable_stripe_get_account_id_for_donation( $donation->ID );
1028 $options = $account ? [ 'stripe_account' => $account ] : null;
1029
1030 if ( ! $api_key ) {
1031 return false;
1032 }
1033
1034 $gateway->setup_api( $api_key );
1035
1036 $data = 0 === strpos( $charge, 'pi_' ) ? [ 'payment_intent' => $charge ] : [ 'charge' => $charge ];
1037
1038 try {
1039 $refund = \Stripe\Refund::create( $data, $options );
1040
1041 update_post_meta( $donation_id, '_stripe_refunded', true );
1042 update_post_meta( $donation_id, '_stripe_refund_id', $refund->id );
1043
1044 $donation->log()->add(
1045 sprintf(
1046 /* translators: %s: transaction reference. */
1047 __( 'Stripe refund transaction ID: %s', 'charitable' ),
1048 $refund->id
1049 )
1050 );
1051
1052 return true;
1053 } catch ( \Stripe\Error\InvalidRequest $e ) {
1054 $donation->log()->add(
1055 sprintf(
1056 /* translators: %s: error message. */
1057 __( 'Stripe refund failed: %s', 'charitable' ),
1058 $e->getStripeCode()
1059 )
1060 );
1061 return false;
1062 }
1063 }
1064
1065 /**
1066 * Check whether a recurring donation can be cancelled automatically in Stripe.
1067 *
1068 * @since 1.3.0
1069 *
1070 * @param boolean $can_cancel Whether the subscription can be cancelled.
1071 * @param Charitable_Recurring_Donation $donation The donation object.
1072 * @return boolean
1073 */
1074 public static function is_subscription_cancellable( $can_cancel, Charitable_Recurring_Donation $donation ) {
1075 if ( ! $can_cancel ) {
1076 return $can_cancel;
1077 }
1078
1079 $secret_key = $donation->get_test_mode( false ) ? 'test_secret_key' : 'live_secret_key';
1080
1081 if ( ! charitable_get_option( [ 'gateways_stripe', $secret_key ] ) ) {
1082 return false;
1083 }
1084
1085 return ! empty( $donation->get_gateway_subscription_id() );
1086 }
1087
1088 /**
1089 * Cancel a subscription.
1090 *
1091 * This can be triggered via the WordPress dashboard when editing a recurring
1092 * donation, or via the user's own account area.
1093 *
1094 * @since 1.3.0
1095 *
1096 * @param boolean $cancelled Whether the subscription was cancelled successfully in the gateway.
1097 * @param Charitable_Recurring_Donation $donation The recurring donation object.
1098 * @return boolean
1099 */
1100 public static function cancel_subscription( $cancelled, Charitable_Recurring_Donation $donation ) {
1101 // Enhanced logging for cancellation debugging
1102 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1103 if ( charitable_is_debug( 'stripe' ) ) {
1104 error_log( sprintf(
1105 'CHARITABLE_STRIPE_CANCEL_DEBUG: Starting cancellation for donation #%d',
1106 $donation->get_donation_id()
1107 ) );
1108 }
1109 }
1110
1111 $subscription_id = $donation->get_gateway_subscription_id();
1112
1113 if ( ! $subscription_id ) {
1114 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1115 if ( charitable_is_debug( 'stripe' ) ) {
1116 error_log( 'CHARITABLE_STRIPE_CANCEL_DEBUG: No subscription ID found - cancellation aborted' );
1117 }
1118 }
1119 $donation->log()->add( __( 'Cancellation failed: No gateway subscription ID found.', 'charitable' ) );
1120 return false;
1121 }
1122
1123 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1124 if ( charitable_is_debug( 'stripe' ) ) {
1125 error_log( sprintf(
1126 'CHARITABLE_STRIPE_CANCEL_DEBUG: Attempting to cancel subscription %s',
1127 $subscription_id
1128 ) );
1129 }
1130 }
1131
1132 $gateway = new Charitable_Gateway_Stripe_AM();
1133 $key = $donation->get_test_mode( false ) ? 'test_secret_key' : 'live_secret_key';
1134 $api_key = $gateway->get_value( $key );
1135 $account = charitable_stripe_get_account_id_for_donation( $donation->ID );
1136 $options = $account ? [ 'stripe_account' => $account ] : null;
1137
1138 if ( ! $api_key ) {
1139 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1140 if ( charitable_is_debug( 'stripe' ) ) {
1141 error_log( sprintf(
1142 'CHARITABLE_STRIPE_CANCEL_DEBUG: No API key found for mode %s - cancellation aborted',
1143 $donation->get_test_mode( false ) ? 'test' : 'live'
1144 ) );
1145 }
1146 }
1147 $donation->log()->add( __( 'Cancellation failed: No Stripe API key configured.', 'charitable' ) );
1148 return false;
1149 }
1150
1151 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1152 if ( charitable_is_debug( 'stripe' ) ) {
1153 error_log( sprintf(
1154 'CHARITABLE_STRIPE_CANCEL_DEBUG: Using %s mode, account: %s',
1155 $donation->get_test_mode( false ) ? 'test' : 'live',
1156 $account ? $account : 'none'
1157 ) );
1158 }
1159 }
1160
1161 $gateway->setup_api( $api_key );
1162
1163 try {
1164 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1165 if ( charitable_is_debug( 'stripe' ) ) {
1166 error_log( sprintf(
1167 'CHARITABLE_STRIPE_CANCEL_DEBUG: Retrieving subscription %s from Stripe',
1168 $subscription_id
1169 ) );
1170 }
1171 }
1172
1173 $subscription = \Stripe\Subscription::retrieve( $subscription_id, $options );
1174
1175 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1176 if ( charitable_is_debug( 'stripe' ) ) {
1177 error_log( sprintf(
1178 'CHARITABLE_STRIPE_CANCEL_DEBUG: Retrieved subscription status: %s',
1179 $subscription->status
1180 ) );
1181 }
1182 }
1183
1184 // Log current subscription state before cancellation
1185 $donation->log()->add( sprintf(
1186 __( 'Retrieved subscription from Stripe - Current status: %s', 'charitable' ),
1187 $subscription->status
1188 ) );
1189
1190 $subscription->cancel( null, $options );
1191
1192 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1193 if ( charitable_is_debug( 'stripe' ) ) {
1194 error_log( 'CHARITABLE_STRIPE_CANCEL_DEBUG: Stripe cancellation API call completed successfully' );
1195 }
1196 }
1197
1198 $donation->log()->add( __( 'Subscription cancelled in Stripe.', 'charitable' ) );
1199
1200 $cancelled = true;
1201
1202 } catch ( Exception $e ) {
1203 $body = $e->getJsonBody();
1204 $message = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'Unknown error.', 'charitable' );
1205
1206 $donation->log()->add(
1207 sprintf(
1208 /* translators: %s: error message */
1209 __( 'Stripe cancellation failed: %s', 'charitable' ),
1210 $message
1211 )
1212 );
1213
1214 $cancelled = false;
1215
1216 } finally {
1217 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1218 if ( charitable_is_debug( 'stripe' ) ) {
1219 error_log( sprintf(
1220 'CHARITABLE_STRIPE_CANCEL_DEBUG: Cancellation result: %s',
1221 $cancelled ? 'SUCCESS' : 'FAILED'
1222 ) );
1223 }
1224 }
1225 return $cancelled;
1226 }
1227 }
1228
1229 /**
1230 * Cancel a subscription in Stripe after the recurring donation is marked as completed.
1231 *
1232 * @since 1.4.9
1233 * @since 1.8.11.3 Made static. It is registered as a static callback on
1234 * charitable_recurring_donation_completed, which fataled
1235 * on PHP 8+ while the method was non-static. The body only
1236 * calls the already-static cancel_subscription(), so this
1237 * is backwards compatible (instance calls to a static
1238 * method still work).
1239 *
1240 * @param Charitable_Recurring_Donation $recurring_donation The recurring donation object.
1241 * @return boolean
1242 */
1243 public static function process_completed_subscription( $recurring_donation ) {
1244 return self::cancel_subscription( true, $recurring_donation );
1245 }
1246
1247 /**
1248 * Get the donation amount in the smallest common currency unit.
1249 *
1250 * @since 1.2.3
1251 *
1252 * @param float $amount The donation amount in dollars.
1253 * @param string|null $currency The currency of the donation. If null, the site currency will be used.
1254 * @return int
1255 */
1256 public static function get_amount( $amount, $currency = null ) {
1257 return Charitable_Stripe_Gateway_Processor::get_sanitized_donation_amount( $amount, $currency );
1258 }
1259
1260 /**
1261 * Returns whether the currency is a zero decimal currency.
1262 *
1263 * @since 1.2.3
1264 *
1265 * @param string $currency The currency for the charge. If left blank, will check for the site currency.
1266 * @return boolean
1267 */
1268 public static function is_zero_decimal_currency( $currency = null ) {
1269 return Charitable_Stripe_Gateway_Processor::is_zero_decimal_currency( $currency );
1270 }
1271
1272 /**
1273 * Return all zero-decimal currencies supported by Stripe.
1274 *
1275 * @since 1.2.3
1276 *
1277 * @return array
1278 */
1279 public static function get_zero_decimal_currencies() {
1280 return Charitable_Stripe_Gateway_Processor::get_zero_decimal_currencies();
1281 }
1282
1283 /**
1284 * Attemps a Stripe charge and returns the status of the charge.
1285 *
1286 * @deprecated 1.5.0 To be removed in 1.5.0.
1287 *
1288 * @since 1.1.0
1289 * @since 1.3.0 Deprecated.
1290 *
1291 * @param array $charge_args The arguments for the charge API request.
1292 * @param Charitable_Donor $donor The current user.
1293 * @param Charitable_Donation_Processor $processor The Donation Processor helper object.
1294 */
1295 public function make_charge( $charge_args, $donor, $processor ) {
1296 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1297 }
1298
1299 /**
1300 * Set the $charges property to empty.
1301 *
1302 * @deprecated 1.5.0 To be removed in 1.5.0.
1303 *
1304 * @since 1.1.0
1305 * @since 1.3.0 Deprecated.
1306 *
1307 * @return void
1308 */
1309 public function clear_charges() {
1310 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1311 }
1312
1313 /**
1314 * Return the results of all charges.
1315 *
1316 * @deprecated 1.5.0 To be removed in 1.5.0.
1317 *
1318 * @since 1.1.0
1319 * @since 1.3.0 Deprecated.
1320 */
1321 public function get_charges() {
1322 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1323 }
1324
1325 /**
1326 * Saves the results of a charge.
1327 *
1328 * @deprecated 1.5.0 To be removed in 1.5.0.
1329 *
1330 * @since 1.1.0
1331 * @since 1.3.0 Deprecated.
1332 *
1333 * @param array $result The result of the charge.
1334 * @param string $status The status of the charge.
1335 *
1336 * @return void
1337 */
1338 public function save_charge_results( $result, $status ) {
1339 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1340 }
1341
1342 /**
1343 * Returns a card ID for the customer.
1344 *
1345 * @deprecated 1.5.0 To be removed in 1.5.0.
1346 *
1347 * @since 1.7.0
1348 * @since 1.3.0 Deprecated.
1349 *
1350 * @param string $customer Stripe's customer ID.
1351 * @param string $card The customer's card details or token.
1352 * @return string|false Card ID or false if Stripe returns an error.
1353 */
1354 public function get_customer_card( $customer, $card ) {
1355 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1356
1357 try {
1358
1359 $cu = \Stripe\Customer::retrieve( $customer );
1360 $card = $cu->sources->create( [ 'source' => $card ] );
1361 $card_id = $card->id;
1362
1363 } catch ( Exception $e ) {
1364
1365 $body = $e->getJsonBody();
1366 $message = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'Something went wrong.', 'charitable' );
1367 charitable_get_notices()->add_error( $message );
1368 $card_id = false;
1369
1370 }
1371
1372 return $card_id;
1373 }
1374
1375 /**
1376 * Return the Stripe Customer ID for the current customer.
1377 *
1378 * If the donor has donated previously through Stripe, this will return
1379 * their ID from the database. If not, this will first set them up as a
1380 * customer in Stripe, store their customer ID and then return it.
1381 *
1382 * @deprecated 1.5.0 To be removed in 1.5.0.
1383 *
1384 * @since 1.7.0
1385 * @since 1.3.0 Deprecated.
1386 *
1387 * @param Charitable_User|Charitable_Donor $donor The donor/user object for the logged in user.
1388 * @param Charitable_Donation_Processor $processor The Donation Procesor helper.
1389 * @return string|false
1390 */
1391 public function get_stripe_customer( $donor, Charitable_Donation_Processor $processor ) {
1392 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1393
1394 $key = charitable_get_option( 'test_mode' ) ? self::STRIPE_CUSTOMER_ID_KEY_TEST : self::STRIPE_CUSTOMER_ID_KEY;
1395
1396 /**
1397 * Retrieve current customer ID and verify that the customer still exists
1398 * in Stripe.
1399 */
1400 $stripe_customer_id = $donor->$key;
1401
1402 if ( $stripe_customer_id ) {
1403
1404 try {
1405 /* Retrieve the customer object from Stripe. */
1406 $cu = \Stripe\Customer::retrieve( $stripe_customer_id );
1407
1408 } catch ( Stripe\Error\InvalidRequest $e ) {
1409 $cu = null;
1410 }
1411
1412 if ( is_null( $cu ) || ( isset( $cu->deleted ) && $cu->deleted ) ) {
1413 $stripe_customer_id = false;
1414 }
1415 }
1416
1417 /* No Stripe Customer ID found, so we're going to create one. */
1418 if ( ! $stripe_customer_id ) {
1419
1420 $stripe_customer_id = $this->create_stripe_customer( $donor, $processor );
1421
1422 /* Store the customer ID for logged in users. */
1423 if ( $stripe_customer_id && $donor->ID ) {
1424 update_user_meta( $donor->ID, $key, $stripe_customer_id );
1425 }
1426 }
1427
1428 return $stripe_customer_id;
1429 }
1430
1431 /**
1432 * Create a Stripe Customer object through the API.
1433 *
1434 * @deprecated 1.5.0 To be removed in 1.5.0 or 2.0.0.
1435 *
1436 * @since 1.2.2
1437 * @since 1.3.0 Deprecated.
1438 *
1439 * @param Charitable_Donor $donor The Donor object.
1440 * @param Charitable_Donation_Processor $processor The Donation Procesor helper.
1441 * @return string|false
1442 */
1443 public function create_stripe_customer( $donor, Charitable_Donation_Processor $processor ) {
1444 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1445
1446 $stripe_customer_args = apply_filters(
1447 'charitable_stripe_customer_args',
1448 array(
1449 'description' => sprintf( '%s %s', __( 'Donor for', 'charitable' ), $donor->get_email() ),
1450 'email' => $donor->get_email(),
1451 'metadata' => array(
1452 'donor_id' => $processor->get_donor_id(),
1453 'user_id' => $donor->ID,
1454 ),
1455 ),
1456 $donor,
1457 $processor
1458 );
1459
1460 try {
1461 $customer = \Stripe\Customer::create( $stripe_customer_args );
1462
1463 return $customer->id;
1464
1465 } catch ( Exception $e ) {
1466 $body = $e->getJsonBody();
1467 $message = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'Something went wrong.', 'charitable' );
1468 charitable_get_notices()->add_error( $message );
1469
1470 return false;
1471 }
1472 }
1473
1474 /**
1475 * Returns the payment source.
1476 *
1477 * This may return a string, identifying the ID of a payment source such as
1478 * a credit card. It may also be an associative array containing the user's
1479 * credit card details.
1480 *
1481 * @deprecated 1.5.0 To be removed in 1.5.0.
1482 *
1483 * @since 1.7.0
1484 * @since 1.3.0 Deprecated.
1485 *
1486 * @param Charitable_User|Charitable_Donor $donor The donor/user object for the logged in user.
1487 * @param Charitable_Donation_Processor $processor The Donation Procesor helper.
1488 * @return string|array
1489 */
1490 public function get_source( $donor, Charitable_Donation_Processor $processor ) {
1491 charitable_stripe_get_deprecated()->deprecated_function( __METHOD__, '1.3.0' );
1492
1493 $values = $processor->get_donation_data();
1494
1495 /**
1496 * If the donation is made by a logged in user who selected
1497 * a source (card), return that.
1498 */
1499 if ( $this->get_gateway_value( 'source', $values ) ) {
1500 return $this->get_gateway_value( 'source', $values );
1501 }
1502
1503 /**
1504 * If we have a token available, return that.
1505 */
1506 if ( $this->get_gateway_value( 'token', $values ) ) {
1507 return $this->get_gateway_value( 'token', $values );
1508 }
1509
1510 charitable_get_notices()->add_error( __( 'Missing credit card details. Unable to proceed with payment.', 'charitable' ) );
1511
1512 return false;
1513 }
1514
1515 /**
1516 * Load Stripe JS, as well as our handling scripts.
1517 *
1518 * @deprecated 1.6.0
1519 *
1520 * @since 1.1.0
1521 * @since 1.4.0 Deprecated. Method moved to Charitable_Stripe class.
1522 *
1523 * @return boolean
1524 */
1525 public static function enqueue_scripts() {
1526 charitable_stripe_get_deprecated()->deprecated_function(
1527 __METHOD__,
1528 '1.4.0',
1529 'charitable_stripe()->enqueue_scripts()'
1530 );
1531
1532 return charitable_stripe()->enqueue_scripts();
1533 }
1534
1535 /**
1536 * Load Stripe JS or Stripe Checkout, as well as our handling scripts.
1537 *
1538 * @deprecated 1.6.0
1539 *
1540 * @since 1.1.2
1541 * @since 1.4.0 Deprecated. Method moved to Charitable_Stripe class.
1542 *
1543 * @param Charitable_Donation_Form $form The current form object.
1544 * @return boolean
1545 */
1546 public static function maybe_setup_scripts_in_donation_form( $form ) {
1547 charitable_stripe_get_deprecated()->deprecated_function(
1548 __METHOD__,
1549 '1.4.0',
1550 'charitable_stripe()->maybe_setup_scripts_in_donation_form( $form )'
1551 );
1552
1553 return charitable_stripe()->maybe_setup_scripts_in_donation_form( $form );
1554 }
1555
1556 /**
1557 * Enqueue the Stripe JS/Checkout scripts after a campaign loop if modal donations are in use.
1558 *
1559 * @deprecated 1.6.0
1560 *
1561 * @since 1.1.2
1562 * @since 1.4.0 Deprecated. Method moved to Charitable_Stripe class.
1563 *
1564 * @return boolean
1565 */
1566 public static function maybe_setup_scripts_in_campaign_loop() {
1567 charitable_stripe_get_deprecated()->deprecated_function(
1568 __METHOD__,
1569 '1.4.0',
1570 'charitable_stripe()->maybe_setup_scripts_in_campaign_loop()'
1571 );
1572
1573 return charitable_stripe()->maybe_setup_scripts_in_campaign_loop();
1574 }
1575
1576
1577 /**
1578 * Checks if credit card details are required.
1579 *
1580 * Credit card details are required UNLESS the donation is being
1581 * made by a logged in user who has donated before and is using
1582 * one of their stored cards (stored on Stripe's server, not ours).
1583 *
1584 * @deprecated 1.6.0
1585 *
1586 * @since 1.7.0
1587 * @since 1.4.0 Deprecated.
1588 *
1589 * @param array $values The filtered values from the donation form submission.
1590 * @return boolean
1591 */
1592 public function require_credit_card_details( $values ) {
1593 charitable_stripe_get_deprecated()->deprecated_function(
1594 __METHOD__,
1595 '1.4.0'
1596 );
1597
1598 return false;
1599 }
1600
1601 /**
1602 * Determine if the Stripe addon should be shown on the frontend if no keys are found
1603 *
1604 * @param array[] $active_gateways The list of registered gateways.
1605 * @return string[]
1606 * @since 1.7.0
1607 */
1608 public function maybe_active_public_gateway( $active_gateways ) {
1609
1610 if ( ! isset( $active_gateways['stripe'] ) ) {
1611 return $active_gateways;
1612 }
1613
1614 if ( is_admin() ) {
1615 // if this is the admin backend don't mess with this list.
1616 return $active_gateways;
1617 }
1618
1619 global $post;
1620
1621 if ( false === $post || ! isset( $post->ID ) ) {
1622 return $active_gateways;
1623 }
1624
1625 // ok, this is being loaded as an active gateway - but do we have any keys?
1626 $keys = $this->get_keys();
1627
1628 /* Make sure that the keys are set, otherwise there will likely be JS error in the template that will effect other JS generated by the plugin on the page. */
1629 if ( empty( $keys['secret_key'] ) || empty( $keys['public_key'] ) ) {
1630 charitable_get_notices()->add_error( __( 'Missing keys for Stripe payment gateway. Unable to proceed with payment.', 'charitable' ) );
1631 unset( $active_gateways['stripe'] );
1632 }
1633
1634 return $active_gateways;
1635 }
1636
1637 /**
1638 * Register the Stripe payment gateway class.
1639 *
1640 * @param string $setting Setting, which might have been stored in the database by an addon.
1641 * @param string $original_key Oringial key.
1642 * @param string $default Default setting.
1643 * @return string
1644 * @since 1.7.0
1645 */
1646 public function maybe_return_stripe_connect_setting( $setting, $original_key, $default ) {
1647
1648 if ( ! charitable()->is_stripe_connect_addon() ) {
1649 // if the stripe connect addon isn't activated, then this setting shouldn't be retreavable (although it stays in the dataabase).
1650 $setting = null;
1651 }
1652
1653 return $setting;
1654 }
1655
1656
1657 /**
1658 * Stripe shows errors for amounts less tha $1, so set the min. amount to 1. This filter catches the ajax request.
1659 *
1660 * @param integer $minimum_amount The minimum donation amount.
1661 *
1662 * @return integer
1663 * @since 1.7.0.2
1664 */
1665 public function set_minimum_donation_amount( $minimum_amount ) {
1666
1667 if ( isset( $_POST['gateway'] ) && false !== strpos( $_POST['gateway'], 'stripe' ) ) { // phpcs:ignore
1668 // Stripe gateway detected - make sure the minimum aount is at least 1 (logic revised in 1.7.0.9).
1669 if ( intval( $minimum_amount ) < 1 ) {
1670 $minimum_amount = 1;
1671 }
1672 }
1673
1674 return $minimum_amount;
1675 }
1676
1677 /**
1678 * Use existing pre-1.7.0 hook to process webhooks that are for Stripe Connect AM accounts.
1679 *
1680 * @param string $event_type The type of event.
1681 * @param object $event The event object.
1682 *
1683 * @since 1.7.0
1684 */
1685 public function maybe_process_webhook_with_stripe_connect( $event_type, $event ) {
1686
1687 // a reminder on this check:
1688 // the option gets written when the stripe connect in the core plugin (starting in v1.7.0) is connected in gateway settings in the admin.
1689 // the option is removed when, after the stripe connect is connected, the user clicks on the "disconnect" link is clicked in the settings.
1690 if ( ! charitable_using_stripe_connect() ) {
1691 return;
1692 }
1693
1694 if ( ( charitable_is_debug() ) ) {
1695 error_log( 'maybe_process_webhook_with_stripe_connect' ); // phpcs:ignore
1696 error_log( print_r( $event_type, true ) ); // phpcs:ignore
1697 error_log( print_r( $event, true ) ); // phpcs:ignore
1698 }
1699
1700 $processor = new Charitable_Stripe_Webhook_Processor( $event );
1701
1702 $default_processors = apply_filters(
1703 'charitable_stripe_default_event_processors',
1704 [
1705 'charge.refunded' => [ $processor, 'process_refund' ],
1706 'invoice.created' => [ $processor, 'process_invoice_created' ],
1707 'invoice.payment_failed' => [ $processor, 'process_invoice_payment_failed' ],
1708 'invoice.payment_succeeded' => [ $processor, 'process_invoice_payment_succeeded' ],
1709 'customer.subscription.updated' => [ $processor, 'process_customer_subscription_updated' ],
1710 'customer.subscription.deleted' => [ $processor, 'process_customer_subscription_deleted' ],
1711 'payment_intent.payment_failed' => [ $processor, 'process_payment_intent_payment_failed' ],
1712 'payment_intent.succeeded' => [ $processor, 'process_payment_intent_succeeded' ],
1713 'checkout.session.completed' => [ $processor, 'process_checkout_session_completed' ],
1714 ]
1715 );
1716
1717 if ( array_key_exists( $event_type, $default_processors ) ) {
1718 $processor = $default_processors[ $event_type ];
1719 if ( is_callable( $processor ) ) {
1720 $message = $processor( $event );
1721 } else {
1722 $message = 'Invalid processor';
1723 }
1724 /* Kill processing with a message returned by the event processor. */
1725 die( $message ); // phpcs:ignore
1726 }
1727 }
1728 }
1729
1730 endif;
1731