PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | app/Modules/PaymentMethods/Core/AbstractPaymentGateway.php +141 -24 1.5.5 → 1.6.5 View file →
@@ -67,10 +67,18 @@
67 67 if (isset($gatewaySettings['checkout_logo']) && !empty($gatewaySettings['checkout_logo'])) {
68 68 $meta['logo'] = $gatewaySettings['checkout_logo'];
69 69 }
70 70
71 - if (isset($gatewaySettings['checkout_instructions']) && !empty($gatewaySettings['checkout_instructions'])) {
72 - $meta['instructions'] = $gatewaySettings['checkout_instructions'];
71 + if (!empty($gatewaySettings['checkout_instructions'])) {
72 + $instructionsText = trim(wp_strip_all_tags(html_entity_decode($gatewaySettings['checkout_instructions'], ENT_QUOTES)));
73 + // Nbsp survives strip_tags/html_entity_decode as a literal
74 + // non-breaking space, so a rich-text editor can save "<p>&nbsp;</p>"
75 + // and still look non-empty to a bare !empty() check.
76 + $instructionsText = str_replace("\xC2\xA0", '', $instructionsText);
77 +
78 + if (trim($instructionsText) !== '') {
79 + $meta['instructions'] = $gatewaySettings['checkout_instructions'];
80 + }
73 81 }
74 82
75 83 if ($key !== '') {
76 84 return Arr::get($meta, $key, '');
@@ -148,12 +156,15 @@
148 156
149 157 return $settings;
150 158 }
151 159
160 + /**
161 + * Back-compat wrapper — the canonical entry point is
162 + * OrderTransaction::getSuccessUrl(); call that directly.
163 + */
152 164 public function getSuccessUrl($transaction, $args = [])
153 165 {
154 - $paymentHelper = new PaymentHelper($this->getMeta('route'));
155 - return $paymentHelper->successUrl($transaction->uuid, $args);
166 + return $transaction->getSuccessUrl($args);
156 167 }
157 168
158 169 public static function getCancelUrl(): string
159 170 {
@@ -218,19 +229,133 @@
218 229 }
219 230
220 231 public function validateSubscriptions($items): bool
221 232 {
222 - $hasSubscription = (new CartResource())->hasSubscriptionProduct($items);
233 + return (new CartResource())->hasSubscriptionProduct($items);
234 + }
223 235
224 - if ($hasSubscription && !$this->has('subscriptions')) {
225 - wp_send_json([
226 - 'status' => 'failed',
227 - 'message' => __('Subscription payment is not avalable for this gateway. Please choose another payment method!', 'fluent-cart')
228 - ], 422);
236 + /**
237 + * Whether this gateway can save a payment method WITHOUT an accompanying
238 + * charge (SetupIntent-style), enabling `system` subscriptions on carts with
239 + * nothing payable now (free trials). Gateways declaring `system_subscription`
240 + * SHOULD override this when their API supports zero-amount setup.
241 + */
242 + public function supportsSetupWithoutCharge(): bool
243 + {
244 + return false;
245 + }
246 +
247 + /**
248 + * Charge a system (auto-charged, store-billed) subscription's renewal invoice
249 + * off-session using the stored token. Gateways declaring the
250 + * `system_subscription` capability MUST override this. A successful charge must
251 + * flow through the gateway's normal charge-confirmation path (so
252 + * syncOrderStatuses / handleRenewalPaid run). Return WP_Error on failure.
253 + *
254 + * @param \FluentCart\App\Services\Payments\PaymentInstance $paymentInstance
255 + * @param array $args ['attempt' => int] — attempt number, used for idempotency keys
256 + * @return true|'processing'|\WP_Error true = payment confirmed; 'processing' =
257 + * charge accepted, the gateway webhook will
258 + * confirm it (invoice stays scheduled)
259 + */
260 + public function chargeRenewal(\FluentCart\App\Services\Payments\PaymentInstance $paymentInstance, $args = [])
261 + {
262 + return new \WP_Error('not_supported', __('This payment method cannot charge saved payment methods.', 'fluent-cart'));
263 + }
264 +
265 + /**
266 + * Re-check an async (processing) renewal charge against the gateway. Called by
267 + * the SystemChargeService reconciliation loop when a charge was accepted but
268 + * the confirming webhook has not arrived. A settled payment must be confirmed
269 + * through the gateway's normal charge-confirmation path before returning true.
270 + *
271 + * @param \FluentCart\App\Services\Payments\PaymentInstance $paymentInstance
272 + * @return true|'processing'|\WP_Error true = settled and confirmed;
273 + * 'processing' = still settling, check again
274 + * later; WP_Error = definitively failed
275 + */
276 + public function reconcileRenewalCharge(\FluentCart\App\Services\Payments\PaymentInstance $paymentInstance)
277 + {
278 + return new \WP_Error('not_supported', __('This payment method cannot reconcile pending charges.', 'fluent-cart'));
279 + }
280 +
281 + /**
282 + * Store-managed subscription mode: a manual subscription's payment — the first
283 + * order or a renewal invoice — must be taken as a plain one-time charge. No
284 + * vendor subscription may be created and no manual→automatic conversion may
285 + * happen. Every gateway with a subscription branch in
286 + * makePaymentFromPaymentInstance() must consult this BEFORE its conversion /
287 + * vendor-subscription logic and route to its single-payment path when true.
288 + */
289 + protected function shouldChargeSubscriptionAsOneTime(\FluentCart\App\Services\Payments\PaymentInstance $paymentInstance): bool
290 + {
291 + $subscription = $paymentInstance->subscription;
292 +
293 + // Manual and system subscriptions are both store-billed — any interactive
294 + // payment against them (first order or renewal invoice) is a one-time charge.
295 + if (!$subscription || !in_array($subscription->collection_method, ['manual', 'system'], true)) {
296 + return false;
229 297 }
230 - return $hasSubscription;
298 +
299 + // Stamped at creation: a subscription born under store-managed mode must
300 + // never be converted to automatic billing, even after the merchant switches
301 + // the store setting back to gateway-managed.
302 + if (\FluentCart\App\Modules\Subscriptions\Services\SubscriptionManagementMode::isSubscriptionStoreManaged($subscription)) {
303 + return true;
304 + }
305 +
306 + // Unstamped manual subscriptions (gateway-managed fallback / pre-feature)
307 + // charge one-time only while the store is currently store-managed; under
308 + // gateway-managed they keep today's manual→automatic conversion behavior.
309 + return \FluentCart\App\Modules\Subscriptions\Services\SubscriptionManagementMode::isStoreManaged();
231 310 }
232 311
312 + //technically not reachable , as conversion to manual subscription is not possible as of now.
313 + protected function maybeConvertToManualSubscription(\FluentCart\App\Services\Payments\PaymentInstance $paymentInstance): void
314 + {
315 + $subscription = $paymentInstance->subscription;
316 + // Manual AND system are already store-billed — nothing to convert. Without
317 + // the system check, a COD payment against a system renewal invoice would
318 + // silently downgrade the subscription to manual and disable auto-charging.
319 + if (!$subscription || in_array($subscription->collection_method, ['manual', 'system'], true)) {
320 + return;
321 + }
322 +
323 + if ($subscription->vendor_subscription_id) {
324 + $oldGateway = App::gateway($subscription->current_payment_method);
325 + if ($oldGateway && $oldGateway->has('subscriptions') && $oldGateway->subscriptions) {
326 + $cancelResult = $oldGateway->subscriptions->cancel($subscription->vendor_subscription_id, [
327 + 'subscription_id' => $subscription->id,
328 + 'parent_order_id' => $subscription->parent_order_id,
329 + 'mode' => $subscription->order ? $subscription->order->mode : 'current',
330 + ]);
331 + if (is_wp_error($cancelResult)) {
332 + fluent_cart_error_log(
333 + 'Failed to cancel vendor subscription during conversion to manual',
334 + $cancelResult->get_error_message()
335 + );
336 + return;
337 + }
338 + }
339 + }
340 +
341 + $subscription->collection_method = 'manual';
342 + $subscription->current_payment_method = $this->getMeta('route');
343 + $subscription->vendor_subscription_id = null;
344 + $subscription->save();
345 +
346 + $subscription->addLog(
347 + 'Converted to manual billing',
348 + sprintf('Subscription converted from automatic to manual — paid via %s', $this->getMeta('label')),
349 + 'warning'
350 + );
351 +
352 + do_action('fluent_cart/subscription_converted_to_manual', [
353 + 'subscription' => $subscription,
354 + 'payment_method' => $this->getMeta('route'),
355 + ]);
356 + }
357 +
233 358 public function validatePaymentMethod($data)
234 359 {
235 360 $isZeroPayment = Arr::get($data, 'isZeroPayment', false);
236 361 if (!$this->isEnabled() && !$isZeroPayment) {
@@ -243,20 +368,8 @@
243 368 )
244 369 ];
245 370 }
246 371
247 - $hasSubscriptions = (CartCheckoutHelper::make())->hasSubscription();
248 - if ($hasSubscriptions === 'yes' && !$this->has('subscriptions')) {
249 - return [
250 - 'isValid' => false,
251 - 'reason' => sprintf(
252 - /* translators: %s is the payment method name */
253 - __('Subscription is not active for Selected payment method %s!', 'fluent-cart'),
254 - $this->getMeta('route')
255 - )
256 - ];
257 - }
258 -
259 372 return [
260 373 'isValid' => true,
261 374 ];
262 375 }
@@ -345,8 +458,13 @@
345 458 {
346 459 return new \WP_Error('not_implemented', __('Refund process is not implemented for this payment gateway.', 'fluent-cart'));
347 460 }
348 461
462 + public function syncRemoteTransaction(OrderTransaction $transaction)
463 + {
464 + return new \WP_Error('not_implemented', __('Remote transaction sync is not available for this payment method.', 'fluent-cart'));
465 + }
466 +
349 467 public function enqueue($hasSubscription): void
350 468 {
351 469 $styles = $this->getEnqueueStyleSrc();
352 470 $scripts = $this->getEnqueueScriptSrc($hasSubscription);
@@ -438,9 +556,8 @@
438 556 {
439 557 $this->beforeRenderPaymentMethod($hasSubscription);
440 558 $this->render($mode);
441 559 $route = $this->getMeta('route');
442 - do_action_deprecated('fluent-cart/after_render_payment_method_' . $route, [], '1.3.16', 'fluent_cart/after_render_payment_method_' . $route, 'Use fluent_cart/after_render_payment_method_' . $route . ' instead of fluent-cart/after_render_payment_method_' . $route . '. It will be removed in v1.4.3.');
443 560 do_action('fluent_cart/after_render_payment_method_' . $route);
444 561 }
445 562
446 563 public function render($mode = 'logo')