laowu преди 1 седмица
родител
ревизия
75909e377a
променени са 4 файла, в които са добавени 132 реда и са изтрити 6 реда
  1. 36 4
      app/Http/logic/api/PayPlusCashierLogic.php
  2. 23 2
      app/Services/PayPlus.php
  3. 1 0
      config/payTest.php
  4. 72 0
      tests/Unit/PayPlusCashierLogicTest.php

+ 36 - 4
app/Http/logic/api/PayPlusCashierLogic.php

@@ -126,7 +126,12 @@ class PayPlusCashierLogic implements CashierInterFace
             $post = json_decode($post, true) ?: [];
         }
 
-        $orderId = $post['reference_id'] ?? '';
+        $detail = $this->queryPayoutDetailForNotify($post);
+        if (empty($detail)) {
+            return 'success';
+        }
+
+        $orderId = $detail['reference_id'] ?? ($post['reference_id'] ?? '');
         if ($orderId === '') {
             return 'success';
         }
@@ -139,7 +144,7 @@ class PayPlusCashierLogic implements CashierInterFace
             return 'success';
         }
 
-        $orderStatus = $this->resolvePayoutStatus($post['status'] ?? '');
+        $orderStatus = $this->resolvePayoutStatus($detail['transaction_status'] ?? '');
         if (!$orderStatus) {
             return 'success';
         }
@@ -157,7 +162,10 @@ class PayPlusCashierLogic implements CashierInterFace
         $withdrawData = [
             'agent' => $agentId,
             'finishDate' => $now,
-            'remark' => json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
+            'remark' => json_encode([
+                'notify' => $post,
+                'query' => $detail,
+            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
         ];
 
         if ($orderStatus === 1) {
@@ -165,7 +173,8 @@ class PayPlusCashierLogic implements CashierInterFace
             $this->handleSuccess($query, $takeMoney);
         } else {
             $withdrawData['State'] = 6;
-            PrivateMail::failMail($userId, $orderId, $takeMoney, $post['msg'] ?? 'REJECTED', '30000,' . $takeMoney);
+            $failedMessage = $detail['description'] ?? ($post['msg'] ?? 'REJECTED');
+            PrivateMail::failMail($userId, $orderId, $takeMoney, $failedMessage, '30000,' . $takeMoney);
         }
 
         DB::connection('write')->table('QPAccountsDB.dbo.AccountsRecord')->updateOrInsert(
@@ -185,6 +194,29 @@ class PayPlusCashierLogic implements CashierInterFace
         return 'success';
     }
 
+    public function queryPayoutDetailForNotify(array $post)
+    {
+        $transactionId = (string) ($post['transaction_id'] ?? '');
+        $referenceId = (string) ($post['reference_id'] ?? '');
+        if ($transactionId === '' && $referenceId === '') {
+            return [];
+        }
+
+        try {
+            $result = $this->service->queryPayoutOrder($transactionId, $referenceId);
+        } catch (\Exception $exception) {
+            Util::WriteLog('PayPlus_error', 'payout query failed: ' . $exception->getMessage());
+            return [];
+        }
+
+        if ((int) ($result['code'] ?? 0) !== 200 || empty($result['data']) || !is_array($result['data'])) {
+            Util::WriteLog('PayPlus_error', 'payout query invalid response: ' . json_encode($result));
+            return [];
+        }
+
+        return $result['data'];
+    }
+
     public function resolvePayoutStatus($status)
     {
         switch (strtoupper((string) $status)) {

+ 23 - 2
app/Services/PayPlus.php

@@ -175,14 +175,14 @@ class PayPlus
             $payload = [
                 'platform_order_id' => (string) $platformOrderId,
             ];
-        } else if ($orderId) {
+        } elseif ($orderId) {
             $payload = [
                 'order_id' => (string) $orderId,
             ];
         } else {
             throw new Exception('Either platformOrderId or orderId must be provided for querying PayPlus order.');
         }
-        
+
         Util::WriteLog('PayPlus', 'Query PayPlus order: ' . json_encode($payload));
 
         return $this->postPayinPath(
@@ -210,6 +210,27 @@ class PayPlus
         return $decoded;
     }
 
+    public function queryPayoutOrder($transactionId = '', $referenceId = '')
+    {
+        $payload = [];
+        if ($transactionId !== '') {
+            $payload['transaction_id'] = (string) $transactionId;
+        }
+
+        if ($referenceId !== '') {
+            $payload['reference_id'] = (string) $referenceId;
+        }
+
+        if (empty($payload)) {
+            throw new Exception('Either transactionId or referenceId must be provided for querying PayPlus payout.');
+        }
+
+        return $this->postPayout(
+            $this->config['payout_query_path'] ?? '/rest/v2/payouts/detail',
+            $payload
+        );
+    }
+
     public function postPayout(string $path, array $payload)
     {
         $payload['timestamp'] = $payload['timestamp'] ?? $this->milliseconds();

+ 1 - 0
config/payTest.php

@@ -123,6 +123,7 @@ return [
         'appId' => env('PAYPLUS_PAYOUT_APP_ID', ''),
         'appKey' => env('PAYPLUS_PAYOUT_APP_KEY', ''),
         'currency' => env('PAYPLUS_PAYOUT_CURRENCY', 'USD'),
+        'payout_query_path' => env('PAYPLUS_PAYOUT_QUERY_PATH', '/rest/v2/payouts/detail'),
         'cashNotify' => env('APP_URL', '') . '/api/payplus/payout_notify',
     ],
 

+ 72 - 0
tests/Unit/PayPlusCashierLogicTest.php

@@ -73,6 +73,78 @@ class PayPlusCashierLogicTest extends TestCase
         $this->assertSame(2, $logic->resolvePayoutStatus('REFUNDED'));
         $this->assertSame(0, $logic->resolvePayoutStatus('PROCESSING'));
     }
+
+    /** @test */
+    public function it_queries_payout_detail_for_notify_and_uses_query_status()
+    {
+        $service = new FakePayPlusForCashier([
+            [
+                'code' => 200,
+                'data' => [
+                    'reference_id' => 'TX100',
+                    'transaction_id' => 'T100',
+                    'transaction_status' => 'PAID',
+                    'description' => 'paid',
+                ],
+            ],
+        ]);
+
+        $logic = new PayPlusCashierLogic($service);
+        $detail = $logic->queryPayoutDetailForNotify([
+            'reference_id' => 'TX100',
+            'transaction_id' => 'T100',
+            'status' => 'REJECTED',
+        ]);
+
+        $this->assertSame('/rest/v2/payouts/detail', $service->calls[0]['path']);
+        $this->assertSame('TX100', $service->calls[0]['payload']['reference_id']);
+        $this->assertSame('T100', $service->calls[0]['payload']['transaction_id']);
+        $this->assertSame('PAID', $detail['transaction_status']);
+        $this->assertSame(1, $logic->resolvePayoutStatus($detail['transaction_status']));
+    }
+
+    /** @test */
+    public function it_returns_empty_notify_detail_when_query_has_no_order_data()
+    {
+        $service = new FakePayPlusForCashier([
+            ['code' => 200],
+        ]);
+
+        $logic = new PayPlusCashierLogic($service);
+        $detail = $logic->queryPayoutDetailForNotify([
+            'reference_id' => 'TX100',
+            'transaction_id' => 'T100',
+            'status' => 'PAID',
+        ]);
+
+        $this->assertSame([], $detail);
+        $this->assertCount(1, $service->calls);
+    }
+
+    /** @test */
+    public function it_keeps_processing_payout_notify_unhandled_after_query()
+    {
+        $service = new FakePayPlusForCashier([
+            [
+                'code' => 200,
+                'data' => [
+                    'reference_id' => 'TX100',
+                    'transaction_id' => 'T100',
+                    'transaction_status' => 'PROCESSING',
+                ],
+            ],
+        ]);
+
+        $logic = new PayPlusCashierLogic($service);
+        $detail = $logic->queryPayoutDetailForNotify([
+            'reference_id' => 'TX100',
+            'transaction_id' => 'T100',
+            'status' => 'REJECTED',
+        ]);
+
+        $this->assertSame('PROCESSING', $detail['transaction_status']);
+        $this->assertSame(0, $logic->resolvePayoutStatus($detail['transaction_status']));
+    }
 }
 
 class FakePayPlusForCashier extends PayPlus