自定义 DokuWiki 认证
CREATE TABLE users (
  user VARCHAR(64) PRIMARY KEY,     -- 登录用户名
  pass VARCHAR(255) NOT NULL,       -- 密码哈希
  mail VARCHAR(255) NOT NULL,       -- 邮箱
  name VARCHAR(128) NOT NULL        -- 用户显示名(昵称、真实姓名)
);

CREATE TABLE user_membership (
  user VARCHAR(64) PRIMARY KEY,
  level VARCHAR(20) NOT NULL,
  expires_at DATETIME NOT NULL
);
public function registerUser($user, $pass, $mail, $name) {
    // 检查用户名是否存在
    $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM users WHERE user = ?");
    $stmt->execute([$user]);
    if ($stmt->fetchColumn() > 0) {
        return ['success' => false, 'msg' => '用户名已存在'];
    }

    // 密码加密
    $hash = password_hash($pass, PASSWORD_DEFAULT);

    // 插入用户
    $stmt = $this->pdo->prepare("INSERT INTO users (user, pass, mail, name) VALUES (?, ?, ?, ?)");
    $res = $stmt->execute([$user, $hash, $mail, $name]);
    if (!$res) {
        return ['success' => false, 'msg' => '用户创建失败'];
    }

    // 赋予基础会员等级,默认 30 天有效期
    $level = 'free';
    $expires_at = date('Y-m-d H:i:s', strtotime('+30 days'));
    $stmt = $this->pdo->prepare("INSERT INTO user_membership (user, level, expires_at) VALUES (?, ?, ?)");
    $stmt->execute([$user, $level, $expires_at]);

    return ['success' => true];
}

getUserData() 方法中也要返回 name

public function getUserData($user, $requireGroups = true) {
    $stmt = $this->pdo->prepare("SELECT level, expires_at FROM user_membership WHERE user = ?");
    $stmt->execute([$user]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    // 取用户显示名
    $stmtName = $this->pdo->prepare("SELECT name, mail FROM users WHERE user = ?");
    $stmtName->execute([$user]);
    $userInfo = $stmtName->fetch(PDO::FETCH_ASSOC);

    $groups = ['user'];
    if ($row && strtotime($row['expires_at']) > time()) {
        $groups[] = $row['level'];
    }

    return [
        'user' => $user,
        'name' => $userInfo ? $userInfo['name'] : $user,
        'mail' => $userInfo ? $userInfo['mail'] : '',
        'grps' => $groups
    ];
}

下面是一个相对完整的 DokuWiki Auth 插件示例,支持:

  • MySQL 用户表(含 namemailpass
  • 会员等级管理(单一等级+到期)
  • 用户注册(带昵称)
  • 会员升级、降级
  • 登录验证
  • getUserData() 返回完整信息和组
  • 简单示例的数据库连接(PDO)

你可以根据实际调整数据库连接参数和逻辑。

<?php
/**
 * DokuWiki Plugin authmembership (Auth Component)
 */

use dokuwiki\Extension\AuthPlugin;

class auth_plugin_authmembership extends AuthPlugin {

    /** @var PDO */
    protected $pdo;

    public function __construct() {
        $this->cando['login'] = true;
        $this->cando['logout'] = true;
        $this->cando['getUserData'] = true;
        $this->cando['checkPass'] = true;

        try {
            $this->pdo = new PDO('mysql:host=localhost;dbname=dokuwiki;charset=utf8mb4', 'dbuser', 'dbpass');
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $e) {
            msg("Database connection failed: " . $e->getMessage(), -1);
        }
    }

    /**
     * 验证用户名密码
     */
    public function checkPass($user, $pass) {
        $stmt = $this->pdo->prepare("SELECT pass FROM users WHERE user = ?");
        $stmt->execute([$user]);
        $hash = $stmt->fetchColumn();

        return $hash && password_verify($pass, $hash);
    }

    /**
     * 获取用户数据(包括显示名、邮箱、组)
     */
    public function getUserData($user, $requireGroups = true) {
        $stmt = $this->pdo->prepare("SELECT level, expires_at FROM user_membership WHERE user = ?");
        $stmt->execute([$user]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        $stmtName = $this->pdo->prepare("SELECT name, mail FROM users WHERE user = ?");
        $stmtName->execute([$user]);
        $userInfo = $stmtName->fetch(PDO::FETCH_ASSOC);

        $groups = ['user'];
        if ($row && strtotime($row['expires_at']) > time()) {
            $groups[] = $row['level'];
        }

        return [
            'user' => $user,
            'name' => $userInfo ? $userInfo['name'] : $user,
            'mail' => $userInfo ? $userInfo['mail'] : '',
            'grps' => $groups
        ];
    }

    /**
     * 用户注册
     * @param string $user 用户名
     * @param string $pass 明文密码
     * @param string $mail 邮箱
     * @param string $name 显示名
     * @return array ['success'=>bool, 'msg'=>string]
     */
    public function registerUser($user, $pass, $mail, $name) {
        // 检查用户名是否存在
        $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM users WHERE user = ?");
        $stmt->execute([$user]);
        if ($stmt->fetchColumn() > 0) {
            return ['success' => false, 'msg' => '用户名已存在'];
        }

        // 密码加密
        $hash = password_hash($pass, PASSWORD_DEFAULT);

        // 插入用户
        $stmt = $this->pdo->prepare("INSERT INTO users (user, pass, mail, name) VALUES (?, ?, ?, ?)");
        $res = $stmt->execute([$user, $hash, $mail, $name]);
        if (!$res) {
            return ['success' => false, 'msg' => '用户创建失败'];
        }

        // 赋予基础会员等级,默认 30 天有效期
        $level = 'free';
        $expires_at = date('Y-m-d H:i:s', strtotime('+30 days'));
        $stmt = $this->pdo->prepare("INSERT INTO user_membership (user, level, expires_at) VALUES (?, ?, ?)");
        $stmt->execute([$user, $level, $expires_at]);

        return ['success' => true, 'msg' => '注册成功'];
    }

    /**
     * 升级或续费会员等级(覆盖当前等级和到期时间)
     * @param string $user
     * @param string $newLevel
     * @param string $newExpiry 格式为 Y-m-d H:i:s
     * @return bool
     */
    public function upgradeMembership($user, $newLevel, $newExpiry) {
        $stmt = $this->pdo->prepare("REPLACE INTO user_membership (user, level, expires_at) VALUES (?, ?, ?)");
        return $stmt->execute([$user, $newLevel, $newExpiry]);
    }

    /**
     * 降级为免费用户
     * @param string $user
     * @return bool
     */
    public function downgradeToFree($user) {
        $stmt = $this->pdo->prepare("DELETE FROM user_membership WHERE user = ?");
        return $stmt->execute([$user]);
    }

    /**
     * 获取当前会员状态
     * @param string $user
     * @return array|null
     */
    public function getMembership($user) {
        $stmt = $this->pdo->prepare("SELECT level, expires_at FROM user_membership WHERE user = ?");
        $stmt->execute([$user]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }
}

数据库示例表结构

CREATE TABLE users (
  user VARCHAR(64) PRIMARY KEY,
  pass VARCHAR(255) NOT NULL,
  mail VARCHAR(255) NOT NULL,
  name VARCHAR(128) NOT NULL
);

CREATE TABLE user_membership (
  user VARCHAR(64) PRIMARY KEY,
  level VARCHAR(20) NOT NULL,
  expires_at DATETIME NOT NULL
);

如果你还需要:

  • 后台管理界面代码示例(会员管理)
  • 会员过期检测脚本(cron)
  • 注册表单 HTML/JS 示例

都可以告诉我,我帮你继续完善!

您说:

我需要点击价格表格表后引导到注册,注册后有能跳转到 stripe checkout

ChatGPT 说:

明白,你想实现的完整流程是:

  1. 用户点击价格表里的某个会员等级或产品按钮(如“购买”)
  2. 如果用户未登录或未注册,先跳转到注册页面
  3. 用户注册成功后自动登录
  4. 注册成功后跳转到 Stripe Checkout 页面完成支付
  5. 支付成功后,Stripe Webhook 更新会员状态

下面给你一个示范设计思路和代码实现方向(基于 PHP + DokuWiki + Stripe):


设计要点

1. 价格表按钮

  • 价格表按钮链接,指向类似 /register?plan=vip 的注册页面(plan 为价格计划标识)

2. 注册页面

  • 注册表单收集用户名、邮箱、密码、显示名
  • 隐藏字段存储 plan 信息,注册成功后使用该信息跳转支付

3. 注册处理

  • 注册成功后,自动调用登录函数创建用户会话
  • 跳转到后端生成的 Stripe Checkout 会话地址

4. Stripe Checkout

  • 使用 Stripe PHP SDK 创建 Checkout Session,设置成功和取消跳转地址
  • 用户支付成功后,Stripe Webhook 接收通知,更新会员等级到期时间

代码示例

价格表按钮示例 (HTML)

<!-- 用户点击后,跳转到注册页面并传递计划参数 -->
<a href="/doku.php?id=register&plan=vip" class="btn-buy">购买 VIP 会员</a>

注册页面示例处理逻辑(伪代码)

// 假设你在注册处理页面:

$plan = $_GET['plan'] ?? 'free'; // 获取计划

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $user = $_POST['user'];
    $pass = $_POST['pass'];
    $mail = $_POST['mail'];
    $name = $_POST['name'];
    $plan = $_POST['plan'] ?? 'free';

    $result = $authPlugin->registerUser($user, $pass, $mail, $name);

    if ($result['success']) {
        // 自动登录
        $authPlugin->loginUser($user, $pass);

        // 根据计划创建 Stripe Checkout Session
        $checkoutUrl = createStripeCheckoutSession($user, $plan);

        // 跳转到 Stripe Checkout
        header("Location: $checkoutUrl");
        exit;
    } else {
        echo "注册失败:" . $result['msg'];
    }
}

Stripe Checkout 会话创建示例 (PHP)

require 'vendor/autoload.php';

\Stripe\Stripe::setApiKey('sk_test_...');

function createStripeCheckoutSession($user, $plan) {
    $priceId = getPriceIdByPlan($plan); // 你自己定义,映射 plan => Stripe Price ID

    $session = \Stripe\Checkout\Session::create([
        'payment_method_types' => ['card'],
        'line_items' => [[
            'price' => $priceId,
            'quantity' => 1,
        ]],
        'mode' => 'subscription',
        'customer_email' => getUserEmail($user), // 预填邮箱
        'success_url' => 'https://yourdomain.com/doku.php?id=payment_success',
        'cancel_url' => 'https://yourdomain.com/doku.php?id=payment_cancel',
        'metadata' => ['user' => $user, 'plan' => $plan],
    ]);

    return $session->url;
}

Stripe Webhook 示例 (PHP)

  • 监听订阅支付成功事件 checkout.session.completed
  • metadata 取出用户名和计划
  • 更新数据库会员等级和到期时间
    
    $input = @file_get_contents('php://input');
    $event = null;

try {
$event = \Stripe\Event::constructFrom(json_decode($input, true));
} catch(\UnexpectedValueException $e) {
http_response_code(400);
exit();
}

if ($event->type == 'checkout.session.completed') {
$session = $event->data->object;
$user = $session->metadata->user;
$plan = $session->metadata->plan;

// 根据 plan 设置会员等级和有效期
$newExpiry = date('Y-m-d H:i:s', strtotime('+30 days'));

// 你调用插件升级会员接口
$authPlugin->upgradeMembership($user, $plan, $newExpiry);

}

http_response_code(200);


---

## 总结

| 步骤 | 说明 |
| --- | --- |
| 价格表按钮 | 带 plan 参数跳转到注册页面 |
| 注册页面 | 处理注册,自动登录,跳转 Stripe |
| Stripe Checkout | 完成支付 |
| Webhook | 更新会员状态 |

---

如果你需要,我可以帮你写:

- 注册页面完整示例HTML + PHP处理
- Stripe 创建和 webhook 的完整示例
- DokuWiki 集成方案(插件或页面模板)

你看想要哪个,我帮你细化。

##### 您说:

如果是登录用户,点击价格表按钮就不需要注册了

###### ChatGPT 说:

理解,这种情况下的流程更合理:

- **未登录用户**点击价格表,跳转注册页面,注册成功后跳转 Stripe Checkout
- **已登录用户**点击价格表,直接跳转 Stripe Checkout,无需再注册或登录

---

## 实现思路示例

### 1\. 价格表按钮链接生成逻辑

<?php
// 伪代码示例
if ($auth->isLoggedIn()) {
// 登录用户直接跳转支付链接,携带计划参数
echo '<a href="/doku.php?id=checkout&plan=vip" class="btn-buy">购买 VIP 会员</a>';
} else {
// 未登录跳转注册页面,并传计划参数
echo '<a href="/doku.php?id=register&plan=vip" class="btn-buy">购买 VIP 会员</a>';
}


---

### 2\. 处理 checkout 页面(用于已登录用户发起支付)

<?php
// checkout.php 或 doku.php?id=checkout 的处理逻辑

session_start();

if (!isset($_SESSION['auth']['user'])) {
// 未登录跳转注册页面
header('Location: /doku.php?id=register&plan=' . urlencode($_GET['plan'] ?? 'free'));
exit;
}

$user = $_SESSION['auth']['user'];
$plan = $_GET['plan'] ?? 'free';

// 创建 Stripe Checkout 会话
$checkoutUrl = createStripeCheckoutSession($user, $plan);

// 跳转到 Stripe Checkout
header("Location: $checkoutUrl");
exit;


---

### 3\. 注册页面处理逻辑(未登录用户注册后跳转 Checkout)

<?php
// register.php 或 doku.php?id=register 的处理逻辑

$plan = $_GET['plan'] ?? 'free';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $_POST['user'];
$pass = $_POST['pass'];
$mail = $_POST['mail'];
$name = $_POST['name'];
$plan = $_POST['plan'] ?? 'free';

$result = $authPlugin->registerUser($user, $pass, $mail, $name);

if ($result['success']) {
    // 自动登录
    $authPlugin->loginUser($user, $pass);

    // 跳转到支付
    header('Location: /doku.php?id=checkout&plan=' . urlencode($plan));
    exit;
} else {
    echo "注册失败:" . $result['msg'];
}

}


---

### 4\. 登录状态判断(Auth 插件或模板中)

你可以用 DokuWiki 的全局变量 $USERINFO 判断登录状态,比如:

global $USERINFO;
if (!empty($USERINFO['name'])) {
// 已登录
$user = $USERINFO['name'];
} else {
// 未登录
}


---

## 你需要我帮你写的具体代码点?

- 价格表按钮模板示例
- 注册页面 + 表单
- checkout 页面
- Stripe 创建会话的 PHP 函数
- webhook 处理

我都可以给你。你说说先要哪几个?

##### 您说:

需要保存购买记录

###### ChatGPT 说:

明白,你想要在用户每次通过 Stripe 支付购买会员等级时,**保存一条购买记录**,方便后台查看和管理。

---

## 设计思路

1. **新增数据库表** user_purchases 用于保存购买记录,字段示例:

CREATE TABLE user_purchases (
id INT AUTO_INCREMENT PRIMARY KEY,
user VARCHAR(64) NOT NULL,
plan VARCHAR(50) NOT NULL,
stripe_session_id VARCHAR(255) NOT NULL,
purchase_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
amount INT NOT NULL, -- 单位分
currency VARCHAR(10) NOT NULL,
status VARCHAR(20) NOT NULL -- pending, success, failed
);

2. **创建 Stripe Checkout 会话时**,插入一条状态为 pending 的购买记录。
3. **Stripe webhook** 监听支付成功事件,将对应购买记录状态更新为 success,并根据情况调用升级会员接口。
4. **失败或取消的支付**可选更新为 failed。

---

## 代码示例

### 1\. 创建 Stripe Checkout 会话时保存购买记录

function createStripeCheckoutSession($user, $plan) {
global $pdo; // 你的 PDO 数据库连接

$priceId = getPriceIdByPlan($plan);
$amount = getPriceAmountByPlan($plan); // 单位分,比如 999 表示9.99美元
$currency = 'usd';

\Stripe\Stripe::setApiKey('sk_test_...');

$session = \Stripe\Checkout\Session::create([
    'payment_method_types' => ['card'],
    'line_items' => [[
        'price' => $priceId,
        'quantity' => 1,
    ]],
    'mode' => 'subscription',
    'customer_email' => getUserEmail($user),
    'success_url' => 'https://yourdomain.com/doku.php?id=payment_success',
    'cancel_url' => 'https://yourdomain.com/doku.php?id=payment_cancel',
    'metadata' => ['user' => $user, 'plan' => $plan],
]);

// 保存购买记录,状态为 pending
$stmt = $pdo->prepare("INSERT INTO user_purchases (user, plan, stripe_session_id, amount, currency, status) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([
    $user,
    $plan,
    $session->id,
    $amount,
    $currency,
    'pending'
]);

return $session->url;

}


---

### 2\. Stripe Webhook 事件中更新购买记录状态

if ($event->type == 'checkout.session.completed') {
$session = $event->data->object;
$user = $session->metadata->user;
$plan = $session->metadata->plan;

// 更新购买记录状态为 success
$stmt = $pdo->prepare("UPDATE user_purchases SET status = ? WHERE stripe_session_id = ?");
$stmt->execute(['success', $session->id]);

// 升级会员
$newExpiry = date('Y-m-d H:i:s', strtotime('+30 days'));
$authPlugin->upgradeMembership($user, $plan, $newExpiry);

} elseif ($event->type == 'checkout.session.expired' || $event->type == 'checkout.session.async_payment_failed') {
$session = $event->data->object;
// 标记失败或过期
$stmt = $pdo->prepare("UPDATE user_purchases SET status = ? WHERE stripe_session_id = ?");
$stmt->execute(['failed', $session->id]);
}


---

### 3\. 购买记录查询示例

function getUserPurchases($user) {
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM user_purchases WHERE user = ? ORDER BY purchase_date DESC");
$stmt->execute([$user]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}


---

## 总结

- 购买流程中插入 pending 状态的购买记录
- Webhook 根据支付结果更新状态,成功后调用升级会员
- 可在后台做购买记录管理和查询

---

如果你需要,我可以帮你写:

- 购买记录的管理后台示例页面
- Stripe Webhook 完整示例
- 价格表按钮、注册、支付整合的示例代码

告诉我想先看哪个,我帮你做!

##### 您说:

关于这个 dokuwiki 会员级别支付还有什么补充的地方?

###### ChatGPT 说:

你目前的 authmembership 插件已经实现了核心功能:登录验证、自定义会员组获取、会员升级与降级。针对“Dokuwiki 会员级别支付”系统,下面是**进一步可完善或需要考虑的补充点**:

---

## ✅ 你已具备的功能(已完成):

- ✅ 自定义 checkPass() 验证登录密码
- ✅ 通过 getUserData() 返回会员等级对应的组(grps)
- ✅ 自动判断会员是否过期并排除组信息
- ✅ 提供 upgradeMembership()downgradeToFree() 方法供 webhook 或后台使用

---

## 🔧 推荐扩展功能(建议补充)

### 1\. **支付记录管理(已提出)**

你可以在插件中集成:

public function logPurchase($user, $plan, $amount, $currency, $sessionId, $status = 'pending') {
$stmt = $this->pdo->prepare("
INSERT INTO user_purchases (user, plan, amount, currency, stripe_session_id, status)
VALUES (?, ?, ?, ?, ?, ?)
");
return $stmt->execute([$user, $plan, $amount, $currency, $sessionId, $status]);
}

- 补充更新状态、获取记录等函数。

---

### 2\. **自动到期降级脚本(CRON)**

Dokuwiki 不会自动调用到期逻辑。你应添加一个**CLI 脚本**定时执行降级:

// scripts/cron_expire_members.php
$pdo = new PDO(...);
$stmt = $pdo->prepare("DELETE FROM user_membership WHERE expires_at < NOW()");
$stmt->execute();

- 加入 crontab,比如每天凌晨执行:  
    0 3 * * * php /var/www/dokuwiki/lib/plugins/authmembership/scripts/cron_expire_members.php

---

### 3\. **注册与支付流程整合(建议插件封装)**

可以写一个页面动作插件(action.php):

- 页面 doku.php?id=buy&plan=vip 引导到注册或 checkout
- 自动注册、登录、跳转 checkout
- 注册后存储计划字段、会话中保留 $_SESSION['plan'] 信息

---

### 4\. **后台会员管理 UI(可选)**

如果你希望在 Dokuwiki 后台看到会员状态:

- 添加 Admin 插件模块(继承 DokuWiki_Admin_Plugin)
- 展示用户、等级、到期日,支持搜索、手动降级/续期按钮等
- 提供分页查看购买记录

---

### 5\. **支持多设备/会话限制(高级功能)**

比如防止一个账号共享登录,你可:

- 在 login() 时记录用户 IP + UA
- 可存表:user_sessions(user, ip, user_agent, last_seen)
- 设定一个规则,例如最多允许 2 个设备同时在线

---

### 6\. **Webhook 安全性**

Webhook 处理时需校验签名,避免伪造请求:

$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, $your_endpoint_secret
);


---

### 7\. **补差价升级逻辑(可选)**

用户购买了低级别会员后,可以按比例升级为高级别。你需要:

- 获取当前剩余时间
- 按比例减去已用金额
- 设置新 plan 和到期时间

这涉及价格表的逻辑管理,可封装一个 calculateUpgradePrice($fromLevel, $toLevel, $daysLeft) 函数。

---

## 🧱 数据库补充建议

你目前的 user_membership 表结构建议如下:

CREATE TABLE user_membership (
user VARCHAR(64) PRIMARY KEY,
level VARCHAR(32) NOT NULL,
expires_at DATETIME NOT NULL
);


user_purchases 表推荐为:

CREATE TABLE user_purchases (
id INT AUTO_INCREMENT PRIMARY KEY,
user VARCHAR(64) NOT NULL,
plan VARCHAR(32) NOT NULL,
amount INT NOT NULL,
currency VARCHAR(10) NOT NULL,
stripe_session_id VARCHAR(255) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);


---

## ✅ 最佳实践顺序建议:

| 阶段 | 优先级 | 功能 |
| --- | --- | --- |
| Stripe 支付接入 | 必须 | Checkout 创建 + Webhook + plan 映射逻辑 |
| 保存购买记录 | 必须 | 插入 user_purchases,WebHook 更新状态 |
| 定时过期降级 | 高 | Cron 每天降级过期会员 |
| 补差价升级 | 中 | 保留升级时间与金额,动态计算升级金额 |
| 后台会员管理界面 | 中 | 搜索用户、查看等级、到期、支付记录 |
| 登录用户直接支付跳转 | 必须 | 注册与支付整合,逻辑已完成 |

---

如果你需要,我可以:

- 帮你写完整的 webhook 接收端
- 注册页 + 价格页整合跳转模板
- 定时任务脚本与 SQL 执行
- 管理页面基础 UI

只需告诉我你想要实现哪一部分,我来写。

##### 您说:

注册页 + 价格页整合跳转模板

###### ChatGPT 说:

好的,我帮你设计一个 **注册页 + 价格页整合跳转 Stripe Checkout 的示例流程**,基于 Dokuwiki 和 PHP。

---

# 功能需求简述

- 价格页列出会员等级及价格,点击按钮触发操作:
    - 如果用户未登录,跳转到注册页
    - 注册成功后,跳转到对应会员等级的 Stripe Checkout 页面
    - 如果已登录,点击按钮直接跳转 Checkout

---

# 示例代码结构

假设你有两个页面:

- /doku.php?id=register :注册页
- /doku.php?id=membership :价格页(列会员计划和购买按钮)
- 额外一个 PHP 处理文件 purchase.php 用来处理跳转和创建 Stripe Session

---

## 1\. membership 页面(价格页)

价格页示例用 PHP 输出,主要逻辑是根据登录状态判断跳转链接:

<?php
session_start();
$user = $_SESSION['user'] ?? null; // 根据你的 auth 插件调整

$plans = [
'basic' => ['name'=>'Basic Plan', 'price' => 999],
'vip' => ['name'=>'VIP Plan', 'price' => 2999],
];

?>

<h2>会员价格表</h2>
<table border="1" cellpadding="10">
<tr><th>等级</th><th>价格(元)</th><th>操作</th></tr>
<?php foreach ($plans as $key => $plan): ?>
<tr>
<td><?=htmlspecialchars($plan['name'])?></td>
<td><?=($plan['price']/100)?> 元/月</td>
<td>
<?php if (!$user): ?>
<!-- 未登录跳转注册页,注册成功后跳转购买 -->
<a href="doku.php?id=register&redirect=membership&plan=<?=$key?>">注册购买</a>
<?php else: ?>
<!-- 登录状态直接跳转 purchase.php 创建 Checkout -->
<a href="purchase.php?plan=<?=$key?>">购买</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</table>


---

## 2\. register 页面(注册页)

这里演示一个简单的注册流程,成功注册后跳转到 purchase.php 触发 Stripe 购买:

<?php
session_start();
$redirect = $_GET['redirect'] ?? 'membership';
$plan = $_GET['plan'] ?? null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $_POST['user'];
$pass = $_POST['pass'];

// TODO: 注册逻辑,例如写入数据库
// 示例密码用 password_hash
$hash = password_hash($pass, PASSWORD_DEFAULT);

// 假设有 PDO $pdo
$stmt = $pdo->prepare("INSERT INTO users (user, pass) VALUES (?, ?)");
$stmt->execute([$user, $hash]);

// 登录用户
$_SESSION['user'] = $user;

// 注册成功,跳转到购买页(或计划页)进行购买
if ($plan) {
    header("Location: purchase.php?plan=" . urlencode($plan));
    exit;
} else {
    header("Location: doku.php?id=" . urlencode($redirect));
    exit;
}

}

?>

<h2>注册账号</h2>
<form method="post">
<label>用户名: <input name="user" required></label><br>
<label>密码: <input type="password" name="pass" required></label><br>
<button type="submit">注册并购买</button>
</form>


---

## 3\. purchase.php (购买入口)

该文件负责:

- 判断用户是否登录,未登录跳到注册页
- 根据 plan 参数创建 Stripe Checkout 会话
- 重定向到 Stripe Checkout 页面

示例:

<?php
session_start();
require 'vendor/autoload.php'; // Stripe PHP SDK

$user = $_SESSION['user'] ?? null;
$plan = $_GET['plan'] ?? null;

if (!$user) {
// 未登录跳转注册页,注册成功后回到这里继续购买
$redirectUrl = 'purchase.php?plan=' . urlencode($plan);
header("Location: doku.php?id=register&redirect=" . urlencode($redirectUrl) . "&plan=" . urlencode($plan));
exit;
}

// 你的数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=dokuwiki', 'dbuser', 'dbpass');

\Stripe\Stripe::setApiKey('sktest...');

$priceIds = [
'basic' => 'price_xxxx_basic',
'vip' => 'price_xxxx_vip',
];

if (!isset($priceIds[$plan])) {
die("无效的会员等级");
}

$session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price' => $priceIds[$plan],
'quantity' => 1,
]],
'mode' => 'subscription',
'customer_email' => getUserEmail($user), // 需要你实现这个函数
'success_url' => 'https://yourdomain.com/doku.php?id=payment_success';,
'cancel_url' => 'https://yourdomain.com/doku.php?id=payment_cancel';,
'metadata' => ['user' => $user, 'plan' => $plan],
]);

// 可选:保存购买记录,状态 pending
$stmt = $pdo->prepare("INSERT INTO user_purchases (user, plan, stripe_session_id, amount, currency, status) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([
$user,
$plan,
$session->id,
getPriceAmount($plan),
'usd',
'pending'
]);

header("Location: " . $session->url);
exit;

// helper 函数示例
function getUserEmail($user) {
global $pdo;
$stmt = $pdo->prepare("SELECT email FROM users WHERE user = ?");
$stmt->execute([$user]);
return $stmt->fetchColumn() ?: '';
}

function getPriceAmount($plan) {
$prices = [
'basic' => 999,
'vip' => 2999,
];
return $prices[$plan] ?? 0;
}


---

# 总结

- 价格页检测登录,未登录跳注册,注册成功跳购买
- 购买入口统一由 purchase.php 处理,创建 Stripe Session 并跳转
- 需要实现用户注册写入和邮箱查询等辅助逻辑
- 后续结合 Stripe Webhook 完成支付状态回调和会员升级

---

如果需要,我可以帮你写:

- 更完善的注册验证(邮箱格式、重复名检查)
- Stripe Webhook 接收示例
- 会员购买成功后的回调处理

告诉我!

4/4

##### 您说:

为什么这里一直用 id= 来设计一个功能, id 不应该是 wiki 页面吗?为何不用其他 url 参数

###### ChatGPT 说:

你说得很对!

---

### Dokuwiki 里的 id 参数

- Dokuwiki 里 URL 里 id=xxx,传统上是指**Wiki页面的页面ID**,比如页面路径。
- 因此用 id= 传功能参数(如注册、会员购买)**在语义上是不规范的**,容易和页面冲突,且维护上也混乱。

---

### 为什么示例里用 id= ?

- 我示例是简单直接用 Dokuwiki 现成路由做示范,方便快速试验。
- 但严格讲,真正开发插件或复杂功能,**不应该滥用 id= 来传功能参数**。

---

### 更好的设计思路

1. **使用 Dokuwiki 插件的自定义动作(action)页面**  
    例如 /doku.php?action=registeraction=membershipaction=purchase  
    这样与页面ID区分开,语义更清晰。
2. **使用 GET 参数传递细分信息**  
    如 /doku.php?action=register&plan=vip  
    action 表示功能模块,plan 表示具体参数。
3. **使用更干净的路径形式(路由重写)**  
    在服务器层(nginx/apache)或 Dokuwiki 里实现伪静态,比如 /register, /membership, /purchase。

---

### 结论

- 不建议用 id= 传功能参数,容易引起页面ID冲突和混淆。
- 推荐使用 action 或自定义参数名,明确区分页面内容和功能请求。
- 这样插件开发和后续维护都更规范、稳定。

---

如果你想,我可以帮你写一套基于 action 参数的 Dokuwiki 插件示例,做到注册/购买功能更合规!

##### 您说:

给我示例

###### ChatGPT 说:

好的,下面给你一个基于 Dokuwiki 插件框架,利用 action 参数区分功能页面的简洁示例。  
这个示例展示如何用 action=registeraction=membershipaction=purchase 来分别处理注册页、会员价格页和购买入口,避免使用 id= 混淆页面与功能。

---

# 1\. 插件基本结构

创建一个名为 authmembership 的 Dokuwiki 插件(目录结构示例):

dokuwiki/lib/plugins/authmembership/
action.php
plugin.info.txt


---

# 2\. plugin.info.txt 内容

base authmembership
author YourName
email [email protected]
date 2025-06-10
name AuthMembership
desc 会员注册与购买流程插件示例
url https://example.com


---

# 3\. action.php 示例代码

<?php
// 入口文件,处理自定义 action

if(!defined('DOKU_INC')) die();

class action_plugin_authmembership extends DokuWiki_Action_Plugin {

public function register(Doku_Event_Handler $controller) {
    // 注册 hook 监听所有页面请求的 ACTION
    $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'handleAction');
}

public function handleAction(Doku_Event $event, $param) {
    global $ACT, $ID;

    $action = $_GET['action'] ?? '';

    if (!in_array($action, ['register', 'membership', 'purchase'])) {
        return; // 不处理其它action,正常加载页面
    }

    // 禁止 Dokuwiki 默认页面渲染
    $event->preventDefault();
    $event->stopPropagation();

    switch ($action) {
        case 'register':
            $this->renderRegister();
            break;

        case 'membership':
            $this->renderMembership();
            break;

        case 'purchase':
            $this->handlePurchase();
            break;
    }
}

protected function renderRegister() {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $user = $_POST['user'] ?? '';
        $pass = $_POST['pass'] ?? '';

        if (!$user || !$pass) {
            echo "<p>用户名和密码不能为空</p>";
            $this->renderRegisterForm();
            return;
        }

        // 注册逻辑(示例,需改为你的数据库写入逻辑)
        // TODO: 这里写注册逻辑
        // ...

        echo "<p>注册成功!请<a href='?action=membership'>前往会员价格页</a>购买。</p>";
        return;
    }

    $this->renderRegisterForm();
}

protected function renderRegisterForm() {
    echo <<<HTML
    <h2>注册账号</h2>
    <form method="post">
        <label>用户名: <input name="user" required></label><br>
        <label>密码: <input type="password" name="pass" required></label><br>
        <button type="submit">注册</button>
    </form>
    HTML;
}

protected function renderMembership() {
    // 会员价格页示例
    $plans = [
        'basic' => ['name'=>'Basic Plan', 'price' => 999],
        'vip' => ['name'=>'VIP Plan', 'price' => 2999],
    ];

    $user = $_SESSION['user'] ?? null;

    echo "<h2>会员价格表</h2>";
    echo "<table border='1' cellpadding='10'><tr><th>等级</th><th>价格(元)</th><th>操作</th></tr>";

    foreach ($plans as $key => $plan) {
        echo "<tr>";
        echo "<td>" . htmlspecialchars($plan['name']) . "</td>";
        echo "<td>" . ($plan['price']/100) . " 元/月</td>";
        if (!$user) {
            echo "<td><a href='?action=register&redirect=membership&plan=$key'>注册购买</a></td>";
        } else {
            echo "<td><a href='?action=purchase&plan=$key'>购买</a></td>";
        }
        echo "</tr>";
    }

    echo "</table>";
}

protected function handlePurchase() {
    session_start();
    $user = $_SESSION['user'] ?? null;
    $plan = $_GET['plan'] ?? null;

    if (!$user) {
        // 未登录跳注册,注册成功后跳购买
        $redirectUrl = '?action=purchase&plan=' . urlencode($plan);
        header("Location: ?action=register&redirect=" . urlencode($redirectUrl) . "&plan=" . urlencode($plan));
        exit;
    }

    // 这里写调用 Stripe 创建 Checkout 会话逻辑
    echo "<p>欢迎 $user ,准备购买会员计划:$plan</p>";
    echo "<p>这里应该跳转到 Stripe Checkout</p>";
}

}



---

# 4\. 使用示例

- 访问 https://yourwiki/doku.php?action=register 进入注册页
- 访问 https://yourwiki/doku.php?action=membership 查看价格表
- 点击价格表的“购买”按钮,会调用 ?action=purchase&plan=xxx 做购买逻辑

---

# 总结

- **用 action 作为功能区分,避免用 id 误导页面内容**
- 插件通过监听 DOKUWIKI_STARTED 事件,拦截并自定义输出
- 各个功能页面逻辑集中管理,方便扩展
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇