} */ public static function pagePayForm(string $orderNo, int $amountCents, string $subject): array { $cfg = Config::get('alipay', []); $params = [ 'app_id' => (string)($cfg['app_id'] ?? ''), 'method' => 'alipay.trade.page.pay', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'notify_url' => (string)Config::get('site.base_url', '') . '/api/v1/pay/alipay/notify', 'return_url' => (string)Config::get('site.front_base_url', Config::get('site.base_url', '')) . '/pages/member.web.html?paid=' . rawurlencode($orderNo), 'biz_content' => json_encode([ 'out_trade_no' => $orderNo, 'product_code' => 'FAST_INSTANT_TRADE_PAY', 'total_amount' => number_format($amountCents / 100, 2, '.', ''), 'subject' => $subject, ], JSON_UNESCAPED_UNICODE), ]; $params['sign'] = self::sign($params, (string)($cfg['private_key'] ?? '')); $gateway = ($cfg['sandbox'] ?? false) ? 'https://openapi.alipaydev.com/gateway.do' : 'https://openapi.alipay.com/gateway.do'; return ['gateway' => $gateway, 'params' => $params]; } public static function pagePay(string $orderNo, int $amountCents, string $subject): string { $form = self::pagePayForm($orderNo, $amountCents, $subject); return $form['gateway'] . '?' . http_build_query($form['params']); } public static function verifyNotify(array $params): bool { $cfg = Config::get('alipay', []); $sign = (string)($params['sign'] ?? ''); if ($sign === '') { return false; } unset($params['sign'], $params['sign_type']); ksort($params); $query = ''; foreach ($params as $k => $v) { if ($v === '' || $v === null) { continue; } $query .= $k . '=' . $v . '&'; } $query = rtrim($query, '&'); return self::verifyNotifySign($query, $sign, (string)($cfg['public_key'] ?? '')); } public static function verifyNotifySign(string $content, string $signBase64, string $alipayPublicKey): bool { $pub = openssl_get_publickey(self::normalizePem($alipayPublicKey, 'PUBLIC KEY')); if ($pub === false) { return false; } $sig = base64_decode($signBase64, true); if ($sig === false) { openssl_free_key($pub); return false; } $ok = openssl_verify($content, $sig, $pub, OPENSSL_ALGO_SHA256); openssl_free_key($pub); return $ok === 1; } private static function sign(array $params, string $privateKey): string { ksort($params); $query = ''; foreach ($params as $k => $v) { if ($v === '' || $v === null) { continue; } $query .= $k . '=' . $v . '&'; } $query = rtrim($query, '&'); $res = openssl_get_privatekey(self::normalizePem($privateKey, 'PRIVATE KEY')); if ($res === false) { return ''; } $signature = ''; openssl_sign($query, $signature, $res, OPENSSL_ALGO_SHA256); openssl_free_key($res); return base64_encode($signature); } public static function refund(string $orderNo, int $amountCents): bool { $cfg = Config::get('alipay', []); $appId = (string)($cfg['app_id'] ?? ''); $privateKey = (string)($cfg['private_key'] ?? ''); if ($appId === '' || $privateKey === '') { return false; } $params = [ 'app_id' => $appId, 'method' => 'alipay.trade.refund', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'biz_content' => json_encode([ 'out_trade_no' => $orderNo, 'refund_amount' => number_format($amountCents / 100, 2, '.', ''), ], JSON_UNESCAPED_UNICODE), ]; $params['sign'] = self::sign($params, $privateKey); $gateway = ($cfg['sandbox'] ?? false) ? 'https://openapi.alipaydev.com/gateway.do' : 'https://openapi.alipay.com/gateway.do'; $ch = curl_init($gateway); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_TIMEOUT => 20, ]); $resp = curl_exec($ch); curl_close($ch); if (!is_string($resp) || $resp === '') { return false; } $data = json_decode($resp, true); $key = 'alipay_trade_refund_response'; return is_array($data) && ($data[$key]['code'] ?? '') === '10000'; } private static function normalizePem(string $key, string $type): string { $key = trim($key); if (str_contains($key, 'BEGIN')) { return $key; } $body = chunk_split(preg_replace('/\s+/', '', $key), 64, "\n"); return "-----BEGIN {$type}-----\n{$body}-----END {$type}-----\n"; } }