PHP 怎么合同签订

wen PHP项目 3

本文目录导读:

PHP 怎么合同签订

  1. 方案一:简易内部合同(基于数据库 + 文件存储)
  2. 方案二:接入第三方电子签平台(推荐,有法律效力)
  3. 方案三:最简逻辑(防止篡改版)
  4. 安全与合规注意事项(必须做)
  5. 总结建议

在 PHP 中实现“合同签订”功能,通常指的是电子合同签署,这不仅仅是简单的表单提交,而是涉及身份认证、意愿确认、防篡改的法律行为。

根据你的需求是企业内部简易流程还是对外具有法律效力的电子签,实现方式截然不同,以下是分层次的实现方案:


简易内部合同(基于数据库 + 文件存储)

适用于内部协议、员工确认书等,不需要第三方公证。

核心逻辑:生成合同内容 -> 用户确认 -> 存储签名数据 -> 生成 PDF 快照。

数据库设计

CREATE TABLE contracts (
    id INT PRIMARY KEY AUTO_INCREMENT,VARCHAR(255),
    content TEXT,           -- 合同正文 (HTML或Markdown)
    signer_name VARCHAR(100),
    signer_email VARCHAR(100),
    status ENUM('pending', 'signed', 'rejected'),
    signature_data TEXT,    -- 签名图片路径 (Base64或路径)
    ip_address VARCHAR(45), -- 留痕
    user_agent VARCHAR(255),-- 留痕
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    signed_at TIMESTAMP NULL
);

核心 PHP 代码示例(签名提交)

<?php
// submit_signature.php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $contract_id = $_POST['contract_id'];
    $signature_data = $_POST['signature'] ?? ''; // 前端canvas生成的Base64图片
    // 1. 防篡改校验:重新计算合同内容Hash
    $stmt = $pdo->prepare("SELECT content FROM contracts WHERE id = ?");
    $stmt->execute([$contract_id]);
    $contract = $stmt->fetch();
    // 计算本次签名的唯一标识 (绑定IP+时间+内容)
    $signature_token = hash('sha256', $contract['content'] . $_SERVER['REMOTE_ADDR'] . time());
    // 2. 存储签名
    $update = $pdo->prepare("UPDATE contracts SET 
                status = 'signed', 
                signature_data = ?,
                signed_at = NOW(),
                signature_token = ? WHERE id = ?");
    $update->execute([$signature_data, $signature_token, $contract_id]);
    // 3. 生成PDF快照 (可选)
    // 使用 Dompdf/TCPDF 将合同内容+签名图片渲染成PDF
    // $pdf->Output('/storage/contracts/'.$contract_id.'.pdf');
    redirect('/contract/success');
}
?>

前端签名板(JavaScript) 使用 Canvas 捕获手写签名:

<canvas id="signaturePad" width="400" height="200"></canvas>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.0.0/dist/signature_pad.umd.min.js"></script>
<script>
    const canvas = document.getElementById('signaturePad');
    const signaturePad = new SignaturePad(canvas);
    document.getElementById('signBtn').addEventListener('click', function() {
        if (signaturePad.isEmpty()) {
            alert('请签名');
            return;
        }
        const signatureData = signaturePad.toDataURL('image/png');
        document.getElementById('signatureInput').value = signatureData;
        document.getElementById('signForm').submit();
    });
</script>

接入第三方电子签平台(推荐,有法律效力)

如果需要真正法律效力的电子合同(如法大大、E签宝、腾讯电子签),必须使用 CA 机构证书,不建议自行实现加密(《电子签名法》要求可靠电子签名)。

推荐 SDK

  • E签宝 (相对成熟)
  • 腾讯电子签 (微信生态好)
  • 法大大 (庭审认可度高)

PHP 对接核心流程(以标准 REST API 为例):

初始化客户端

use GuzzleHttp\Client;
class EsignClient {
    private $host = 'https://openapi.esign.cn';
    private $appId = 'your_app_id';
    private $appSecret = 'your_secret';
    private function getHeaders($method, $path, $body = '') {
        $timestamp = time() . '000'; // 毫秒
        $bodyStr = ($method == 'GET') ? '' : json_encode($body);
        // 计算签名 (具体算法见第三方文档)
        $contentMd5 = base64_encode(md5($bodyStr, true));
        $stringToSign = $method . "\n" . $contentMd5 . "\n" . $this->getContentType() . "\n" . $timestamp . "\n" . $path;
        $signature = base64_encode(hash_hmac('sha256', $stringToSign, $this->appSecret, true));
        return [
            'Content-Type' => 'application/json',
            'X-Tsign-Open-App-Id' => $this->appId,
            'X-Tsign-Open-Auth-Mode' => 'APP',
            'X-Tsign-Open-Timestamp' => $timestamp,
            'X-Tsign-Open-Signature' => $signature,
        ];
    }
}

发起签署流程(关键代码)

public function createSignFlow($contractName, $signers) {
    $path = '/v3/sign-flow/create';
    $body = [
        'docs' => [
            [
                'fileId' => $this->uploadFile('/path/to/contract.pdf'),
                'fileName' => $contractName
            ]
        ],
        'signerInfo' => [
            [
                'signerType' => 'PERSON',
                'signerAccount' => [
                    'thirdPartyUserId' => $signers['user_id'],
                    'name' => $signers['name'],
                    'idType' => 'CRED_PSN_CH_IDCARD',
                    'idNumber' => $signers['id_card']
                ],
                'signFields' => [
                    [
                        'fileId' => $uploadedFileId,
                        'type' => 1, // 单行签名
                        'page' => 2,  // 页码
                        'positions' => ['x' => 300, 'y' => 700] // 坐标
                    ]
                ]
            ]
        ]
    ];
    $client = new Client(['base_uri' => $this->host]);
    $response = $client->request('POST', $path, [
        'headers' => $this->getHeaders('POST', $path, $body),
        'json' => $body
    ]);
    return json_decode($response->getBody(), true)['data']['signFlowId'];
}

获取签署链接

// 生成链接返回给前端进行跳转签署
$path = '/v3/sign-flow/execute-url';
$body = [
    'signFlowId' => $flowId,
    'account' => ['thirdPartyUserId' => $userId]
];
// 返回的 shortUrl 用于跳转

注意:第三方签需要企业认证(营业执照),个人实名认证(人脸/银行卡),成本约 2-5 元/次,但具有《电子签名法》效力。


最简逻辑(防止篡改版)

如果只是想在 PHP 中做一个“我已阅读并同意”的确认(非正式合同),可以用 Token 防伪造:

<?php
// 生成合同签署Token
function generateContractToken($user_id, $contract_id, $secret_key) {
    $data = $user_id . $contract_id . time();
    $token = hash_hmac('sha256', $data, $secret_key);
    return ['token' => $token, 'expires' => time() + 3600];
}
// 验证
function verifyContractToken($token, $user_id, $contract_id, $secret_key) {
    // 重新计算并比对
    return hash_equals($expectedToken, $token);
}
?>

安全与合规注意事项(必须做)

  1. 时间戳服务:如果自建系统,务必记录服务器时间,并加盖 RFC3161 时间戳。
  2. 哈希链:将上一份合同的 Hash 值存于下一份合同元数据中,防止批量替换。
  3. 日志审计:记录每次签署的 IP、设备指纹、Cookies。
  4. 强制 HTTPS:必须使用 TLS 1.2+ 加密传输。
  5. 不可抵赖性:自建系统很难证明“自己签的”,建议用户注册时加入短信/邮件验证码二次确认。

总结建议

  • 如果是个人项目/演示:使用 方案一(Canvas签名 + Dompdf导出PDF)即可。
  • 如果是企业正式合同:强烈建议接通 E签宝/腾讯电子签 的 API,总成本最低,且免去法律纠纷,PHP 代码只需要处理 API 签名和回调逻辑,大约 200 行核心代码搞定。

你目前的具体应用场景是什么?(是电商平台入驻协议,还是企业内部审批流?)可以告诉我,我帮你细化那部分核心代码。

抱歉,评论功能暂时关闭!