get_config(); return $c['app_id'] !== '' && $c['private_key'] !== '' && $c['app_public_cert'] !== '' && $c['alipay_public_cert'] !== '' && $c['alipay_root_cert'] !== ''; } public function create_payment(array $order): array { if (!$this->is_configured()) { return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')]; } $order_number = (string) ($order['order_number'] ?? ''); $out_trade_no = (string) ($order['out_trade_no'] ?? ''); if ($out_trade_no === '') { $out_trade_no = class_exists('\AKSRT\Payment\OrderRepository') ? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order) : $order_number; } $amount = $this->amount_string($order['amount'] ?? null); $subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt')); $payment_type = $this->get_config()['payment_type']; if ($order_number === '' || $out_trade_no === '' || $amount === '') { return ['success' => false, 'message' => __('订单参数无效', 'aksrt')]; } try { YansongdaPayFactory::boot_alipay(); $payload = [ '_method' => 'GET', 'out_trade_no' => $out_trade_no, 'total_amount' => $amount, 'subject' => mb_substr($subject, 0, 256), ]; $result = $payment_type === 'wap' ? Pay::alipay()->h5($payload) : Pay::alipay()->web($payload); $payment_url = YansongdaPayFactory::extract_payment_url($result); if ($payment_url === '') { return ['success' => false, 'message' => __('支付宝支付链接生成失败', 'aksrt')]; } return [ 'success' => true, 'payment_url' => $payment_url, 'payment_data' => [ 'order_number' => $order_number, 'out_trade_no' => $out_trade_no, ], 'message' => __('支付宝支付链接已生成', 'aksrt'), ]; } catch (\Throwable $e) { return ['success' => false, 'message' => $e->getMessage()]; } } public function verify_callback(\WP_REST_Request $request): array { try { YansongdaPayFactory::boot_alipay(); // Use the unsanitized form/query payload; route params must not enter the signature base. $callback_input = strtoupper($request->get_method()) === 'POST' ? $request->get_body_params() : $request->get_query_params(); if (!is_array($callback_input)) { $callback_input = []; } unset($callback_input['gateway']); $data_obj = Pay::alipay()->callback($callback_input); $data = YansongdaPayFactory::to_array($data_obj); $config = $this->get_config(); $trade_status = (string) ($data['trade_status'] ?? ''); $paid = in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true); $order_number = (string) ($data['out_trade_no'] ?? ''); $trade_no = (string) ($data['trade_no'] ?? ''); $amount = $this->amount_string($data['total_amount'] ?? null); if (($data['app_id'] ?? '') !== $config['app_id'] || $order_number === '' || ($paid && ($trade_no === '' || $amount === ''))) { return ['success' => false, 'message' => __('支付宝回调身份或订单数据无效', 'aksrt')]; } return [ 'success' => true, 'order_number' => $order_number, 'amount' => $amount, 'currency' => 'CNY', 'transaction_id' => $trade_no, 'status' => $paid ? 'paid' : 'pending', ]; } catch (\Throwable $e) { return ['success' => false, 'message' => $e->getMessage()]; } } /** * Alipay async notify expects plain text success/fail (Gateway also handles this). */ public function output_callback_response(array $result): void { try { if (!empty($result['success'])) { YansongdaPayFactory::boot_alipay(); YansongdaPayFactory::emit_response(Pay::alipay()->success(), 'success'); } } catch (\Throwable $e) { // fall through } header('Content-Type: text/plain; charset=utf-8'); echo 'fail'; exit; } public function query_status(string $order_number): array { if (!$this->is_configured()) { return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')]; } $out_trade_no = $this->resolve_out_trade_no($order_number); try { YansongdaPayFactory::boot_alipay(); $result = Pay::alipay()->query([ 'out_trade_no' => $out_trade_no, ]); $data = YansongdaPayFactory::to_array($result); $trade_status = (string) ($data['trade_status'] ?? ''); $status = 'pending'; if (in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) { $status = 'paid'; } elseif ($trade_status === 'TRADE_CLOSED') { $status = 'cancelled'; } return [ 'success' => true, // Keep provider out_trade_no for Gateway::complete_order resolve. 'order_number' => (string) ($data['out_trade_no'] ?? $out_trade_no), 'amount' => $this->amount_string($data['total_amount'] ?? null), 'currency' => 'CNY', 'transaction_id' => (string) ($data['trade_no'] ?? ''), 'status' => $status, ]; } catch (\Throwable $e) { return ['success' => false, 'message' => $e->getMessage()]; } } /** * Best-effort close of a previous provider trade before re-pay. */ public function close_trade(string $out_trade_no): void { $out_trade_no = sanitize_text_field($out_trade_no); if ($out_trade_no === '' || !$this->is_configured()) { return; } try { YansongdaPayFactory::boot_alipay(); // alipay.trade.close — supported by yansongda as close(). Pay::alipay()->close([ 'out_trade_no' => $out_trade_no, ]); } catch (\Throwable $e) { // ignore } } public function refund(string $order_number, string $amount, string $request_id = ''): array { try { $refund_amount = \AKSRT\Ledger\AssetAmount::balance($amount); } catch (\InvalidArgumentException $e) { return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')]; } if ($refund_amount->minor() <= 0) { return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')]; } if (!$this->is_configured()) { return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')]; } $out_trade_no = $this->resolve_out_trade_no($order_number); try { YansongdaPayFactory::boot_alipay(); $refund_request_id = (string) preg_replace( '/[^A-Za-z0-9_-]/', '', $request_id !== '' ? $request_id : $order_number . '_refund' ); $result = Pay::alipay()->refund([ 'out_trade_no' => $out_trade_no, 'refund_amount' => $refund_amount->decimal(), 'refund_reason' => __('用户申请退款', 'aksrt'), 'out_request_no' => $refund_request_id, ]); $data = YansongdaPayFactory::to_array($result); $fund_change = strtoupper((string) ($data['fund_change'] ?? '')); $status = ($fund_change === 'Y' || $this->amount_string($data['refund_fee'] ?? null) !== '') ? 'succeeded' : 'unknown'; return [ 'success' => true, 'status' => $status, 'refund_id' => (string) ($data['trade_no'] ?? $data['out_trade_no'] ?? ''), 'request_id' => $refund_request_id, 'message' => $status === 'succeeded' ? __('退款成功', 'aksrt') : __('退款结果待确认', 'aksrt'), ]; } catch (\Throwable $e) { return ['success' => false, 'message' => $e->getMessage()]; } } public function query_refund(string $request_id, string $order_number = ''): array { if (!$this->is_configured() || $request_id === '') { return ['success' => false, 'status' => 'failed', 'message' => __('支付宝退款查询参数无效', 'aksrt')]; } try { YansongdaPayFactory::boot_alipay(); $params = ['out_request_no' => $request_id, '_action' => 'refund']; if ($order_number !== '') { $params['out_trade_no'] = $this->resolve_out_trade_no($order_number); } $result = Pay::alipay()->query($params); $data = YansongdaPayFactory::to_array($result); $refund_status = strtoupper((string) ($data['refund_status'] ?? '')); $refund_amount = $this->amount_string($data['refund_amount'] ?? null); $status = ($refund_status === 'REFUND_SUCCESS' || $refund_amount !== '') ? 'succeeded' : ($refund_status === 'REFUND_FAIL' ? 'failed' : 'pending'); return [ 'success' => true, 'status' => $status, 'refund_id' => (string) ($data['trade_no'] ?? $data['out_request_no'] ?? ''), ]; } catch (\Throwable $e) { return ['success' => false, 'status' => 'unknown', 'message' => $e->getMessage()]; } } /** * @return array{app_id:string,private_key:string,app_public_cert:string,alipay_public_cert:string,alipay_root_cert:string,payment_type:string} */ private function get_config(): array { $all = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : []; $type = sanitize_key((string) ($all['alipay_payment_type'] ?? 'page')); if (!in_array($type, ['page', 'wap'], true)) { $type = 'page'; } return [ 'app_id' => (string) ($all['alipay_appid'] ?? ''), 'private_key' => (string) ($all['alipay_privatekey'] ?? ''), 'app_public_cert' => (string) ($all['alipay_app_public_cert_path'] ?? ''), 'alipay_public_cert' => (string) ($all['alipay_public_cert_path'] ?? ''), 'alipay_root_cert' => (string) ($all['alipay_root_cert_path'] ?? ''), 'payment_type' => $type, ]; } private function amount_string(mixed $value): string { if (!is_string($value)) { return ''; } try { $amount = \AKSRT\Ledger\AssetAmount::balance(trim($value)); return $amount->minor() > 0 ? $amount->decimal() : ''; } catch (\InvalidArgumentException $e) { return ''; } } /** * Map local order_number → provider out_trade_no (supports resume attempts). */ private function resolve_out_trade_no(string $order_number): string { if (class_exists('\AKSRT\Payment\OrderRepository')) { $repo = \AKSRT\Payment\OrderRepository::get_instance(); // A provider attempt id must be queried verbatim, including superseded Pn. if ($repo->resolve_by_out_trade_no($order_number) && !$repo->get($order_number)) { return $order_number; } $order = $repo->get($order_number); if ($order) { return \AKSRT\Payment\OrderRepository::provider_out_trade_no($order); } } return $order_number; } }