| @@ -3,11 +3,13 @@ | ||
| 3 | 3 | namespace FluentCart\App\Models; |
| 4 | 4 | |
| 5 | 5 | use FluentCart\Api\StoreSettings; |
| 6 | 6 | use FluentCart\App\Helpers\Status; |
| 7 | +use FluentCart\App\Services\DateTime\DateTime; | |
| 7 | 8 | use FluentCart\App\Models\Concerns\CanSearch; |
| 8 | 9 | use FluentCart\App\Modules\PaymentMethods\Core\AbstractPaymentGateway; |
| 9 | 10 | use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager; |
| 11 | +use FluentCart\App\Services\Payments\PaymentHelper; | |
| 10 | 12 | use FluentCart\Framework\Database\Orm\Relations\HasOne; |
| 11 | 13 | use FluentCart\Framework\Support\Arr; |
| 12 | 14 | |
| 13 | 15 | /** |
| @@ -103,8 +105,23 @@ | ||
| 103 | 105 | if (empty($model->uuid)) { |
| 104 | 106 | $model->uuid = md5(time() . wp_generate_uuid4()); |
| 105 | 107 | } |
| 106 | 108 | }); |
| 109 | + | |
| 110 | + // created_at is the checkout time, which can be weeks before the money | |
| 111 | + // moves (payment links, delayed webhooks) — the settlement moment is | |
| 112 | + // only observable at this transition, so record it here. Gateways that | |
| 113 | + // know the exact remote charge time set meta.settled_at themselves; | |
| 114 | + // this fallback never overwrites it. | |
| 115 | + static::saving(function ($model) { | |
| 116 | + if ($model->status === Status::TRANSACTION_SUCCEEDED && $model->isDirty('status')) { | |
| 117 | + $meta = $model->meta; | |
| 118 | + if (empty($meta['settled_at'])) { | |
| 119 | + $meta['settled_at'] = DateTime::gmtNow()->format('Y-m-d H:i:s'); | |
| 120 | + $model->meta = $meta; | |
| 121 | + } | |
| 122 | + } | |
| 123 | + }); | |
| 107 | 124 | } |
| 108 | 125 | |
| 109 | 126 | public function getUrlAttribute($value) |
| 110 | 127 | { |
| @@ -180,9 +197,16 @@ | ||
| 180 | 197 | |
| 181 | 198 | return $this->payment_method; |
| 182 | 199 | } |
| 183 | 200 | |
| 184 | - public function getReceiptPageUrl($filtered = false) | |
| 201 | + /** | |
| 202 | + * $filtered defaults to true because every caller redirects the customer and | |
| 203 | + * therefore wants the filter. It defaulted to false, so a caller that omitted | |
| 204 | + * the argument silently got an unfilterable URL — which is how the Stripe | |
| 205 | + * confirmation redirect lost its filter in a refactor. The parameter is kept | |
| 206 | + * so an explicit `false` can still ask for the raw URL. | |
| 207 | + */ | |
| 208 | + public function getReceiptPageUrl($filtered = true) | |
| 185 | 209 | { |
| 186 | 210 | $url = add_query_arg([ |
| 187 | 211 | 'trx_hash' => $this->uuid |
| 188 | 212 | ], (new StoreSettings())->getReceiptPage()); |
| @@ -191,13 +215,24 @@ | ||
| 191 | 215 | $context = [ |
| 192 | 216 | 'transaction' => $this, |
| 193 | 217 | 'order' => $this->order, |
| 194 | 218 | ]; |
| 195 | - $url = apply_filters_deprecated('fluentcart/transaction/receipt_page_url', [$url, $context], '1.3.16', 'fluent_cart/transaction/receipt_page_url', 'Use fluent_cart/transaction/receipt_page_url instead of fluentcart/transaction/receipt_page_url. It will be removed in v1.4.3.'); | |
| 196 | 219 | $url = apply_filters('fluent_cart/transaction/receipt_page_url', $url, $context); |
| 197 | 220 | } |
| 198 | 221 | |
| 199 | 222 | return $url; |
| 223 | + } | |
| 224 | + | |
| 225 | + /** | |
| 226 | + * Canonical post-payment redirect URL for this transaction. | |
| 227 | + * Always fires the fluent_cart/payment/success_url filter. | |
| 228 | + * | |
| 229 | + * @param array $args Extra query args merged into the URL | |
| 230 | + * @return string | |
| 231 | + */ | |
| 232 | + public function getSuccessUrl($args = []) | |
| 233 | + { | |
| 234 | + return (new PaymentHelper((string)$this->payment_method))->successUrl($this, $args); | |
| 200 | 235 | } |
| 201 | 236 | |
| 202 | 237 | public function syncPendingTransaction() |
| 203 | 238 | { |