276 lines
7.2 KiB
PHP
276 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* Shared yansongda/pay config bootstrap for WeChat / Alipay gateways.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Payment\Gateways;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
use Yansongda\Pay\Pay;
|
|
|
|
/**
|
|
* Builds Pay::config() payloads from aksrt_settings and boots the SDK.
|
|
*/
|
|
class YansongdaPayFactory {
|
|
|
|
/**
|
|
* Boot Pay with a partial channel config (wechat and/or alipay).
|
|
*
|
|
* @param array<string,mixed> $channels Keys: wechat?, alipay?
|
|
*/
|
|
public static function boot(array $channels): void {
|
|
$config = [
|
|
'logger' => self::logger_config(),
|
|
'http' => [
|
|
'timeout' => 10.0,
|
|
'connect_timeout' => 5.0,
|
|
],
|
|
];
|
|
|
|
if (!empty($channels['wechat']) && is_array($channels['wechat'])) {
|
|
$config['wechat'] = [
|
|
'default' => $channels['wechat'],
|
|
];
|
|
}
|
|
if (!empty($channels['alipay']) && is_array($channels['alipay'])) {
|
|
$config['alipay'] = [
|
|
'default' => $channels['alipay'],
|
|
];
|
|
}
|
|
|
|
// Re-config is safe; Pay caches tenant config per process.
|
|
Pay::config($config);
|
|
}
|
|
|
|
/**
|
|
* WeChat Pay API v3 merchant config.
|
|
*
|
|
* @return array<string,mixed>
|
|
*/
|
|
public static function wechat_from_settings(): array {
|
|
$all = self::settings();
|
|
$notify = home_url('/wp-json/aksrt/v1/payment/wechatpay/notify');
|
|
|
|
return [
|
|
'mch_id' => (string) ($all['wechat_merchantid'] ?? ''),
|
|
'mch_secret_key' => (string) ($all['wechat_api_v3_key'] ?? ''),
|
|
'mch_secret_cert' => (string) ($all['wechat_private_key_path'] ?? ''),
|
|
'mch_public_cert_path' => (string) ($all['wechat_cert_path'] ?? ''),
|
|
'mp_app_id' => (string) ($all['wechat_appid'] ?? ''),
|
|
'notify_url' => $notify,
|
|
'mode' => Pay::MODE_NORMAL,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Alipay cert-mode config (yansongda/pay 3.x requires cert paths for SN + verify).
|
|
*
|
|
* app_secret_cert may be PEM string or file path.
|
|
* Cert path fields accept absolute file paths (or PEM content via get_public_cert).
|
|
*
|
|
* @return array<string,mixed>
|
|
*/
|
|
public static function alipay_from_settings(): array {
|
|
$all = self::settings();
|
|
$notify = home_url('/wp-json/aksrt/v1/payment/alipay/notify');
|
|
$return = home_url('/wp-json/aksrt/v1/payment/alipay/return');
|
|
|
|
return [
|
|
'app_id' => (string) ($all['alipay_appid'] ?? ''),
|
|
'app_secret_cert' => (string) ($all['alipay_privatekey'] ?? ''),
|
|
'app_public_cert_path' => (string) ($all['alipay_app_public_cert_path'] ?? ''),
|
|
'alipay_public_cert_path' => (string) ($all['alipay_public_cert_path'] ?? ''),
|
|
'alipay_root_cert_path' => (string) ($all['alipay_root_cert_path'] ?? ''),
|
|
'return_url' => $return,
|
|
'notify_url' => $notify,
|
|
'mode' => Pay::MODE_NORMAL,
|
|
];
|
|
}
|
|
|
|
public static function boot_wechat(): void {
|
|
self::boot(['wechat' => self::wechat_from_settings()]);
|
|
}
|
|
|
|
public static function boot_alipay(): void {
|
|
self::boot(['alipay' => self::alipay_from_settings()]);
|
|
}
|
|
|
|
/**
|
|
* Convert Collection / ArrayAccess / array to plain array.
|
|
*
|
|
* @param mixed $data
|
|
* @return array<string,mixed>
|
|
*/
|
|
public static function to_array($data): array {
|
|
if (is_array($data)) {
|
|
return $data;
|
|
}
|
|
if ($data instanceof \Yansongda\Supports\Collection) {
|
|
return $data->all();
|
|
}
|
|
if ($data instanceof \ArrayAccess) {
|
|
$out = [];
|
|
foreach ($data as $k => $v) {
|
|
$out[$k] = is_array($v) || $v instanceof \ArrayAccess || $v instanceof \Yansongda\Supports\Collection
|
|
? self::to_array($v)
|
|
: $v;
|
|
}
|
|
return $out;
|
|
}
|
|
if (is_object($data) && method_exists($data, 'toArray')) {
|
|
$arr = $data->toArray();
|
|
return is_array($arr) ? $arr : [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Nested value from array using dot path.
|
|
*
|
|
* @param array<string,mixed> $data
|
|
* @return mixed
|
|
*/
|
|
public static function dig(array $data, string $path, $default = null) {
|
|
$cur = $data;
|
|
foreach (explode('.', $path) as $seg) {
|
|
if (!is_array($cur) || !array_key_exists($seg, $cur)) {
|
|
return $default;
|
|
}
|
|
$cur = $cur[$seg];
|
|
}
|
|
return $cur;
|
|
}
|
|
|
|
/**
|
|
* Extract a redirect / payment URL from alipay web/wap response.
|
|
*
|
|
* @param mixed $result
|
|
*/
|
|
public static function extract_payment_url($result): string {
|
|
if (is_string($result) && $result !== '') {
|
|
return $result;
|
|
}
|
|
if ($result instanceof \Psr\Http\Message\ResponseInterface) {
|
|
$loc = $result->getHeaderLine('Location');
|
|
if ($loc !== '') {
|
|
return $loc;
|
|
}
|
|
$body = (string) $result->getBody();
|
|
if (preg_match('#action=[\'"]([^\'"]+)[\'"]#i', $body, $m)) {
|
|
return html_entity_decode($m[1], ENT_QUOTES);
|
|
}
|
|
if (preg_match('#https?://[^\s"\'<>]+#', $body, $m)) {
|
|
return $m[0];
|
|
}
|
|
// Auto-submit HTML form: callers may open as document; store empty payment_url and put HTML in payment_data.
|
|
return '';
|
|
}
|
|
if (is_object($result)) {
|
|
if (method_exists($result, 'getTargetUrl')) {
|
|
$url = (string) $result->getTargetUrl();
|
|
if ($url !== '') {
|
|
return $url;
|
|
}
|
|
}
|
|
if (method_exists($result, '__toString')) {
|
|
$s = (string) $result;
|
|
if (strpos($s, 'http') === 0) {
|
|
return $s;
|
|
}
|
|
}
|
|
}
|
|
$arr = self::to_array($result);
|
|
foreach (['payment_url', 'h5_url', 'code_url', 'mweb_url'] as $k) {
|
|
if (!empty($arr[$k]) && is_string($arr[$k])) {
|
|
return $arr[$k];
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* HTML body from alipay Response (form auto-submit) when no Location header.
|
|
*
|
|
* @param mixed $result
|
|
*/
|
|
public static function extract_html_body($result): string {
|
|
if ($result instanceof \Psr\Http\Message\ResponseInterface) {
|
|
if ($result->getHeaderLine('Location') !== '') {
|
|
return '';
|
|
}
|
|
return (string) $result->getBody();
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Emit SDK success()/fail() ResponseInterface (or string) and exit.
|
|
*
|
|
* @param mixed $response
|
|
*/
|
|
public static function emit_response($response, string $fallback_body = 'success'): void {
|
|
if ($response instanceof \Psr\Http\Message\ResponseInterface) {
|
|
$status = $response->getStatusCode();
|
|
if ($status > 0 && !headers_sent()) {
|
|
http_response_code($status);
|
|
}
|
|
foreach ($response->getHeaders() as $name => $values) {
|
|
if (strtolower((string) $name) === 'transfer-encoding') {
|
|
continue;
|
|
}
|
|
foreach ($values as $value) {
|
|
header($name . ': ' . $value, false);
|
|
}
|
|
}
|
|
$body = (string) $response->getBody();
|
|
echo $body !== '' ? $body : $fallback_body;
|
|
exit;
|
|
}
|
|
if (is_string($response) && $response !== '') {
|
|
echo $response;
|
|
exit;
|
|
}
|
|
echo $fallback_body;
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* @return array<string,mixed>
|
|
*/
|
|
private static function settings(): array {
|
|
if (class_exists('\AKSRT\Settings\Settings')) {
|
|
return \AKSRT\Settings\Settings::get_all();
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @return array<string,mixed>
|
|
*/
|
|
private static function logger_config(): array {
|
|
$enabled = defined('WP_DEBUG') && WP_DEBUG;
|
|
$file = '';
|
|
if ($enabled && function_exists('wp_upload_dir')) {
|
|
$uploads = wp_upload_dir();
|
|
if (empty($uploads['error']) && !empty($uploads['basedir'])) {
|
|
$dir = trailingslashit((string) $uploads['basedir']) . 'aksrt-pay';
|
|
if (!is_dir($dir)) {
|
|
wp_mkdir_p($dir);
|
|
}
|
|
$file = trailingslashit($dir) . 'pay.log';
|
|
}
|
|
}
|
|
|
|
return [
|
|
'enable' => $enabled && $file !== '',
|
|
'file' => $file,
|
|
'level' => 'info',
|
|
'type' => 'single',
|
|
'max_file' => 30,
|
|
];
|
|
}
|
|
}
|