| @@ -486,19 +486,25 @@ | ||
| 486 | 486 | ->where('order_id', $this->orderModel->id) |
| 487 | 487 | ->first(); |
| 488 | 488 | |
| 489 | 489 | if ($existingTransaction) { |
| 490 | + $meta = $existingTransaction->meta ?: []; | |
| 491 | + | |
| 490 | 492 | // Retry vs duplicate for gateway idempotency (PaymentInstance::getIdempotencySeed): |
| 491 | 493 | // re-submitting a pending transaction is a duplicate (keep attempt -> gateway |
| 492 | 494 | // dedupes); re-submitting a FAILED one is a retry (bump attempt -> fresh seed, |
| 493 | 495 | // never answered with the failed attempt's cached gateway response). |
| 494 | - $attempt = (int) Arr::get($existingTransaction->meta ?: [], 'payment_attempt', 0); | |
| 496 | + $attempt = (int) Arr::get($meta, 'payment_attempt', 0); | |
| 495 | 497 | if ($existingTransaction->status === Status::PAYMENT_FAILED) { |
| 496 | 498 | $attempt++; |
| 497 | 499 | } |
| 500 | + | |
| 501 | + // The gateway object prepared last time (a Paddle transaction, a PayPal | |
| 502 | + // order) is kept so the gateway can reuse it instead of creating another. | |
| 498 | 503 | if ($attempt) { |
| 499 | - $transactionData['meta'] = ['payment_attempt' => $attempt]; | |
| 504 | + $meta['payment_attempt'] = $attempt; | |
| 500 | 505 | } |
| 506 | + $transactionData['meta'] = $meta; | |
| 501 | 507 | |
| 502 | 508 | $existingTransaction->fill($transactionData); |
| 503 | 509 | $existingTransaction->save(); |
| 504 | 510 | $this->transactionModel = $existingTransaction; |
| @@ -925,9 +931,9 @@ | ||
| 925 | 931 | 'line_meta' => Arr::get($item, 'line_meta', []), |
| 926 | 932 | 'signup_fee' => $signupFee, |
| 927 | 933 | 'signup_fee_tax' => $signupFeeTax, |
| 928 | 934 | 'first_iteration_tax' => $firstIterationTax, |
| 929 | - 'is_recurring_coupon' => Arr::get($item, 'is_recurring_coupon', 'no'), | |
| 935 | + 'recurring_discount' => $recurringDiscountAmount, | |
| 930 | 936 | 'total_discount' => $discountTotal |
| 931 | 937 | ]); |
| 932 | 938 | |
| 933 | 939 | // removable upon discussion |
| @@ -951,13 +957,8 @@ | ||
| 951 | 957 | 'variation_type' => Arr::get($item, 'other_info.variation_type', '') |
| 952 | 958 | ] |
| 953 | 959 | ]; |
| 954 | 960 | |
| 955 | - // if recurring coupon is applied, we need to subtract the total discount from the recurring total | |
| 956 | - if (Arr::get($item, 'is_recurring_coupon', 'no') === 'yes') { | |
| 957 | - $subscriptionItem['recurring_total'] -= $discountTotal; | |
| 958 | - } | |
| 959 | - | |
| 960 | 961 | $subscriptionData = wp_parse_args($subscriptionPricing, $subscriptionItem); |
| 961 | 962 | $paymentMethod = Arr::get($this->orderData, 'payment_method', ''); |
| 962 | 963 | |
| 963 | 964 | $collectionMethod = apply_filters('fluent_cart/subscription_collection_method_' . $paymentMethod, $this->determineCollectionMethod()); |
| @@ -1251,8 +1252,9 @@ | ||
| 1251 | 1252 | $signupFee = (int)($inputData['signup_fee'] ?? 0); |
| 1252 | 1253 | $signupFeeTax = (int)($inputData['signup_fee_tax'] ?? 0); |
| 1253 | 1254 | $firstIterationTax = (int)($inputData['first_iteration_tax'] ?? 0); |
| 1254 | 1255 | $totalDiscount = (int)($inputData['total_discount'] ?? 0); |
| 1256 | + $recurringDiscount = (int)($inputData['recurring_discount'] ?? 0); | |
| 1255 | 1257 | |
| 1256 | 1258 | // Determine if THIS subscription item is tax-inclusive (for behavior=3 mixed carts) |
| 1257 | 1259 | $taxBehavior = (int) Arr::get($inputData, 'tax_behavior', 0); |
| 1258 | 1260 | $itemInclusive = (bool) Arr::get($inputData, 'line_meta.tax_config.inclusive', false); |
| @@ -1292,10 +1294,13 @@ | ||
| 1292 | 1294 | } |
| 1293 | 1295 | } else { |
| 1294 | 1296 | $firstCycleCost = $recurringAmount + $signupFee - $totalDiscount; |
| 1295 | 1297 | |
| 1296 | - if (Arr::get($inputData, 'is_recurring_coupon', 'no') === 'yes') { | |
| 1297 | - $recurringAmount -= $totalDiscount; // as now discount applied on recurring amount | |
| 1298 | + // A recurring coupon discounts every cycle, so the per-cycle price itself | |
| 1299 | + // is lower — the first cycle is not cheaper than the ones after it and | |
| 1300 | + // must not be expressed as a trial. | |
| 1301 | + if ($recurringDiscount > 0) { | |
| 1302 | + $recurringAmount -= $recurringDiscount; | |
| 1298 | 1303 | } |
| 1299 | 1304 | |
| 1300 | 1305 | if ($firstCycleCost < $recurringAmount) { |
| 1301 | 1306 | $adjustedTrialDays = Helper::calculateAdjustedTrialDaysForInterval($trialDays, $repeatInterval); |