PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.1
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
fluent-cart / app / Models / OrderTransaction.php

OrderTransaction.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.1, at app/Models/OrderTransaction.php

299 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Models;
4
5 use FluentCart\Api\StoreSettings;
6 use FluentCart\App\Helpers\Status;
7 use FluentCart\App\Models\Concerns\CanSearch;
8 use FluentCart\App\Modules\PaymentMethods\Core\AbstractPaymentGateway;
9 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
10 use FluentCart\Framework\Database\Orm\Relations\HasOne;
11 use FluentCart\Framework\Support\Arr;
12
13 /**
14 * OrderTransaction Model - DB Model for Transactions
15 *
16 * Database Model
17 *
18 * @package FluentCart\App\Models
19 *
20 * @version 1.0.0
21 */
22 class OrderTransaction extends Model
23 {
24 use CanSearch;
25
26 protected $table = 'fct_order_transactions';
27
28 protected $appends = ['url', 'refundable'];
29 /**
30 * The attributes that are mass assignable.
31 *
32 * @var array
33 */
34 protected $fillable = [
35 'order_id',
36 'order_type',
37 'vendor_charge_id',
38 'payment_method',
39 'payment_mode',
40 'payment_method_type',
41 'currency',
42 'transaction_type',
43 'subscription_id',
44 'card_last_4',
45 'card_brand',
46 'status',
47 'total',
48 'rate',
49 'meta',
50 'uuid',
51 'created_at'
52 ];
53
54 protected $searchable = [
55 'id',
56 'total',
57 'status',
58 'payment_method',
59 'currency',
60 'created_at',
61 'updated_at',
62 ];
63
64 public function setMetaAttribute($value)
65 {
66
67 if ($value) {
68 $decoded = \json_encode($value, true);
69 if (!($decoded)) {
70 $decoded = '[]';
71 }
72 } else {
73 $decoded = '[]';
74 }
75
76 $this->attributes['meta'] = $decoded;
77 }
78
79 public function getMetaAttribute($value)
80 {
81 if (!$value) {
82 return [];
83 }
84
85 return \json_decode($value, true);
86 }
87
88 public function subscription()
89 {
90 return $this->hasOne(Subscription::class, 'id', 'subscription_id');
91 }
92
93 public function order()
94 {
95 return $this->belongsTo(Order::class, 'order_id', 'id');
96 }
97
98
99 public static function boot()
100 {
101 parent::boot();
102 static::creating(function ($model) {
103 if (empty($model->uuid)) {
104 $model->uuid = md5(time() . wp_generate_uuid4());
105 }
106 });
107 }
108
109 public function getUrlAttribute($value)
110 {
111
112 return apply_filters('fluent_cart/transaction/url_' . $this->getAttribute('payment_method'), '', [
113 'transaction' => $this,
114 'payment_mode' => $this->payment_mode,
115 'vendor_charge_id' => $this->vendor_charge_id,
116 'transaction_type' => $this->transaction_type
117 ]);
118 }
119
120 public function scopeOfStatus($query, $status)
121 {
122 return $query->where('status', $status);
123 }
124
125 public function scopeOfPaymentMethod($query, $methodName)
126 {
127 return $query->where('payment_method', $methodName);
128 }
129
130 public function updateStatus($newStatus, $otherData = [])
131 {
132 $oldStatus = $this->status;
133
134 if ($newStatus == $oldStatus) {
135 return $this;
136 }
137
138 $this->status = $newStatus;
139
140 if ($otherData) {
141 $this->fill($otherData);
142 }
143
144 $this->save();
145
146 return $this;
147 }
148
149 public static function bulkDeleteByOrderIds($ids, $params = [])
150 {
151 return static::getQuery()->whereIn('order_id', $ids)->delete();
152 }
153
154 public function orders(): HasOne
155 {
156 return $this->hasOne(Order::class, 'id', 'order_id');
157 }
158
159 public function getRefundableAttribute(): int
160 {
161 return $this->getMaxRefundableAmount();
162 }
163
164 public function getMaxRefundableAmount()
165 {
166 if ($this->status !== Status::TRANSACTION_SUCCEEDED) {
167 return 0;
168 }
169 $refundAmount = (int)(Arr::get($this->meta, 'refunded_total', 0));
170 $baseAmount = $this->total - $refundAmount;
171 $filtered = apply_filters('fluent_cart/transaction/max_refundable_amount', $baseAmount, $this);
172 return max(0, min((int) $filtered, $baseAmount));
173 }
174
175 public function getPaymentMethodText()
176 {
177 if ($this->card_brand && $this->card_last_4) {
178 return sprintf('%1$s ***%2$s', esc_html($this->card_brand), esc_html($this->card_last_4));
179 }
180
181 return $this->payment_method;
182 }
183
184 public function getReceiptPageUrl($filtered = false)
185 {
186 $url = add_query_arg([
187 'trx_hash' => $this->uuid
188 ], (new StoreSettings())->getReceiptPage());
189
190 if ($filtered) {
191 $context = [
192 'transaction' => $this,
193 'order' => $this->order,
194 ];
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 $url = apply_filters('fluent_cart/transaction/receipt_page_url', $url, $context);
197 }
198
199 return $url;
200 }
201
202 public function syncPendingTransaction()
203 {
204 if ($this->transaction_type !== Status::TRANSACTION_TYPE_CHARGE) {
205 return new \WP_Error('invalid_transaction_type', __('Only charge transactions can be synced from the payment gateway.', 'fluent-cart'));
206 }
207
208 if ($this->status !== Status::TRANSACTION_PENDING) {
209 return new \WP_Error('invalid_transaction_status', __('Only pending transactions can be synced from the payment gateway.', 'fluent-cart'));
210 }
211
212 if (!$this->vendor_charge_id) {
213 return new \WP_Error('missing_vendor_charge_id', __('This transaction has no gateway charge ID to sync against.', 'fluent-cart'));
214 }
215
216 $gateway = GatewayManager::getInstance($this->payment_method);
217 if ($gateway instanceof AbstractPaymentGateway) {
218 return $gateway->syncRemoteTransaction($this);
219 }
220
221 return new \WP_Error('invalid_payment_method', __('This payment method does not support remote transaction sync', 'fluent-cart'));
222 }
223
224 public function acceptDispute($args = [])
225 {
226 if ($this->transaction_type !== Status::TRANSACTION_TYPE_DISPUTE) {
227 return new \WP_Error('No dispute found!', __('The selected transaction is not a dispute', 'fluent-cart'));
228 }
229
230 $gateway = GatewayManager::getInstance($this->payment_method);
231
232 if ($gateway && $gateway->has('dispute_handler')) {
233 $handleRemoteDispute = $gateway->acceptRemoteDispute($this, $args);
234 if (is_wp_error($handleRemoteDispute)) {
235 return $handleRemoteDispute;
236 }
237
238 $this->status = Status::TRANSACTION_DISPUTE_LOST;
239 $this->meta = array_merge($this->meta, [
240 'is_dispute_actionable' => false,
241 'is_charge_refundable' => false
242 ]);
243 $this->save();
244
245 $newPaidAmount = intval($this->order->total_paid - $this->total);
246 $this->order->update([
247 'total_paid' => max($newPaidAmount, 0),
248 'payment_status' => $newPaidAmount > 0 ? Status::PAYMENT_PARTIALLY_PAID : Status::PAYMENT_FAILED,
249 ]);
250
251 if (Arr::get($args, 'dispute_note')) {
252 $this->meta = array_merge($this->meta, [
253 'dispute_note' => $args['dispute_note'],
254 ]);
255 $this->save();
256 }
257
258 fluent_cart_add_log(
259 'Dispute accepted on ' . $this->payment_method,
260 'Dispute accepted! ' . $args['dispute_note'] ?? 'Note: ' . Arr::get($args, 'dispute_note'), 'success', [
261 'module_id' => $this->order->id,
262 'module_name' => 'order',
263 ]
264 );
265 } else {
266 return new \WP_Error('invalid_payment_method', __('This payment method does not support remote dispute management', 'fluent-cart'));
267 }
268 }
269
270 public function scopeSearchByPayerEmail ($query, $data) {
271
272 $operator = Arr::get($data, 'operator', 'contains');
273
274 $search = Arr::get($data, 'value');
275 $search = sanitize_text_field(trim($search));
276
277 switch ($operator) {
278 case 'starts_with':
279 $pattern = $search . '%';
280 break;
281 case 'ends_with':
282 $pattern = '%' . $search;
283 break;
284 case 'equals':
285 $pattern = $search;
286 break;
287 case 'not_like':
288 return $query->whereRaw("JSON_UNQUOTE(JSON_EXTRACT(meta, '$.payer.email_address')) NOT LIKE ?", ['%' . $search . '%']);
289 default: // contains
290 $pattern = '%' . $search . '%';
291 break;
292 }
293
294 return $query->whereRaw("JSON_UNQUOTE(JSON_EXTRACT(meta, '$.payer.email_address')) LIKE ?", [$pattern]);
295
296 }
297
298 }
299