|
|
@@ -140,6 +140,7 @@ class AwxAfterpay extends Payment
|
|
|
throw new \Exception($resultObject->details->original_response_message ?:$resultObject->message);
|
|
|
} else {
|
|
|
$id = $resultObject->id;
|
|
|
+ $this->rememberPaymentIntentId($order, $id);
|
|
|
$confirmResultObject = $this->confirmRequest($id, $order);
|
|
|
if (empty($confirmResultObject->next_action->url)) {
|
|
|
throw new \Exception($confirmResultObject->details->original_response_message ?:$confirmResultObject->message);
|
|
|
@@ -258,6 +259,131 @@ class AwxAfterpay extends Payment
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记下 Airwallex payment intent id,供 fetchOrderStatus 对账查询使用。
|
|
|
+ *
|
|
|
+ * gateway_order_id 存的是跳转地址而不是 intent id,反推不可靠,所以单独存档。
|
|
|
+ */
|
|
|
+ protected function rememberPaymentIntentId($order, string $paymentIntentId): void
|
|
|
+ {
|
|
|
+ if (! $order instanceof Order || ! $order->payment) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $additional = $order->payment->additional ?? [];
|
|
|
+ $additional['airwallex_intent_id'] = $paymentIntentId;
|
|
|
+ $order->payment->additional = $additional;
|
|
|
+ $order->payment->save();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询网关订单状态,供 ReconcilePendingPaymentJob 对账使用。
|
|
|
+ *
|
|
|
+ * 返回统一契约 ['status' => string|null, 'capture' => array|null],
|
|
|
+ * 其中 capture 与 PaymentService::extractCapture() 同结构,未扣款时为 null。
|
|
|
+ *
|
|
|
+ * 这里只做查询,不允许产生扣款副作用;查询失败直接抛出,由调用方重试。
|
|
|
+ */
|
|
|
+ public function fetchOrderStatus($gatewayOrderId = null)
|
|
|
+ {
|
|
|
+ $order = $this->getOrder();
|
|
|
+ $additional = $order?->payment->additional ?? [];
|
|
|
+
|
|
|
+ $paymentIntentId = $additional['airwallex_intent_id']
|
|
|
+ ?? $this->extractPaymentIntentId((string) ($gatewayOrderId ?: ($additional['gateway_order_id'] ?? '')));
|
|
|
+
|
|
|
+ if (empty($paymentIntentId)) {
|
|
|
+ /*
|
|
|
+ * 定位不到 payment intent 就无从判断网关状态;映射成 APPROVED
|
|
|
+ * 让对账任务走人工处理分支,而不是取消一个可能已经付过款的订单。
|
|
|
+ */
|
|
|
+ return [
|
|
|
+ 'status' => 'APPROVED',
|
|
|
+ 'capture' => null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ $response = $this->httpClient()->request('GET', $this->paymentIntentsUlr . $paymentIntentId, [
|
|
|
+ 'headers' => [
|
|
|
+ 'Accept' => 'application/json',
|
|
|
+ 'Content-Type' => 'application/json',
|
|
|
+ 'Authorization' => 'Bearer ' . $this->getToken()
|
|
|
+ ],
|
|
|
+ 'timeout' => 30,
|
|
|
+ 'verify' => false,
|
|
|
+ ]);
|
|
|
+ $responseBody = $response->getBody()->getContents();
|
|
|
+ Log::channel('payment')->info('awxafterpay fetchOrderStatus response', [
|
|
|
+ 'gatewayOrderId' => $gatewayOrderId,
|
|
|
+ 'paymentIntentId' => $paymentIntentId,
|
|
|
+ 'status' => $response->getStatusCode(),
|
|
|
+ 'body' => $responseBody
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $result = json_decode($responseBody, true) ?: [];
|
|
|
+ $gatewayStatus = strtoupper((string) ($result['status'] ?? ''));
|
|
|
+
|
|
|
+ if ($gatewayStatus === 'SUCCEEDED') {
|
|
|
+ $amount = $result['amount'] ?? null;
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'status' => 'COMPLETED',
|
|
|
+ 'capture' => [
|
|
|
+ 'transaction_id' => data_get($result, 'latest_payment_attempt.id', $result['id'] ?? $paymentIntentId),
|
|
|
+ 'gateway_order_id' => $gatewayOrderId,
|
|
|
+ 'order_status' => 'COMPLETED',
|
|
|
+ 'capture_status' => 'COMPLETED',
|
|
|
+ 'intent' => 'CAPTURE',
|
|
|
+ 'amount' => $amount !== null ? (float) $amount : null,
|
|
|
+ 'currency' => $result['currency'] ?? null,
|
|
|
+ 'raw' => $result,
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'status' => $this->normalizeOrderStatus($gatewayStatus),
|
|
|
+ 'capture' => null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 供测试替换的 HTTP 客户端出口。
|
|
|
+ */
|
|
|
+ protected function httpClient(): Client
|
|
|
+ {
|
|
|
+ return new Client();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从跳转地址之类的字符串里捞出 Airwallex payment intent id。
|
|
|
+ */
|
|
|
+ protected function extractPaymentIntentId(string $value): ?string
|
|
|
+ {
|
|
|
+ return preg_match('/int_[A-Za-z0-9_-]+/', $value, $matches) ? $matches[0] : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 把 Airwallex payment intent 状态映射成对账任务认得的终态词。
|
|
|
+ *
|
|
|
+ * 映射成 CREATED 的状态表示买家还没真正付钱,超时之后可以被安全取消;
|
|
|
+ * REQUIRES_CAPTURE 是已授权未扣款,映射成 APPROVED 交给人工处理,
|
|
|
+ * 避免把买家已经承诺的钱丢掉。
|
|
|
+ */
|
|
|
+ protected function normalizeOrderStatus(string $gatewayStatus): ?string
|
|
|
+ {
|
|
|
+ return match ($gatewayStatus) {
|
|
|
+ '' => null,
|
|
|
+ 'CANCELLED' => 'VOIDED',
|
|
|
+ 'REQUIRES_PAYMENT_METHOD' => 'CREATED',
|
|
|
+ 'REQUIRES_CONFIRMATION' => 'CREATED',
|
|
|
+ 'REQUIRES_CAPTURE' => 'APPROVED',
|
|
|
+ 'EXPIRED' => 'EXPIRED',
|
|
|
+ default => $gatewayStatus,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取一个唯一的id
|
|
|
*/
|