<form method="post" action="https://www.paypal.com/cgi-bin/webscr">
<!-- 測試環境用 https://www.sandbox.paypal.com/cgi-bin/webscr -->
<input type="hidden" name="cmd" value="_xclick" />
<input type="hidden" name="business" value="merchant@example.com" />
<input type="hidden" name="item_name" value="產品名稱" />
<input type="hidden" name="item_number" value="ORD-202312001" />
<input type="hidden" name="amount" value="99.00" />
<input type="hidden" name="currency_code" value="USD" />
<input type="hidden" name="quantity" value="1" />
<!-- 回調 URL -->
<input type="hidden" name="return" value="https://yoursite.com/payment/success" />
<input type="hidden" name="cancel_return" value="https://yoursite.com/payment/cancel" />
<input type="hidden" name="notify_url" value="https://yoursite.com/payment/ipn" />
<input type="hidden" name="rm" value="2" /> <!-- POST 方式返回 -->
<!-- 自定義參數 (會原樣回傳) -->
<input type="hidden" name="custom" value="user_id=123|order_id=456" />
<button type="submit">前往 PayPal 付款</button>
</form>
<?php
class PayPalIPN {
private $useSandbox = true;
private $ipnUrl;
private $verified = false;
private $data = [];
public function __construct($sandbox = true) {
$this->useSandbox = $sandbox;
$this->ipnUrl = $sandbox
? 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'
: 'https://ipnpb.paypal.com/cgi-bin/webscr';
}
public function verify() {
// 1. 取得原始 POST 資料
$rawPost = file_get_contents('php://input');
if (empty($rawPost)) return false;
// 2. 解析並儲存
parse_str($rawPost, $this->data);
// 3. 回傳驗證給 PayPal (必須加上 cmd=_notify-validate)
$validationPost = $rawPost . '&cmd=_notify-validate';
// 4. 使用 cURL 發送驗證
$ch = curl_init($this->ipnUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $validationPost,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 5. 判斷結果
if ($httpCode === 200 && $response === 'VERIFIED') {
$this->verified = true;
return true;
}
// 記錄失敗
error_log("IPN 驗證失敗: HTTP $httpCode, Response: $response");
return false;
}
public function isVerified() { return $this->verified; }
public function getData() { return $this->data; }
// 便利存取器
public function __get($key) {
return $this->data[$key] ?? null;
}
}
// 使用範例:ipn.php
$ipn = new PayPalIPN(true); // 測試環境
if ($ipn->verify() && $ipn->isVerified()) {
// 關鍵業務檢查
if ($ipn->payment_status === 'Completed' &&
$ipn->receiver_email === 'merchant@example.com' &&
$ipn->mc_currency === 'USD') {
$orderId = $ipn->item_number;
$amount = (float)$ipn->mc_gross;
$custom = $ipn->custom; // user_id=123|order_id=456
// TODO: 更新資料庫訂單狀態、發送確認信、觸發發貨流程
// 防重複處理:檢查訂單狀態、交易 ID (txn_id) 是否已處理
error_log("IPN 成功處理訂單: $orderId, 金額: $amount");
}
}
// 回應 200 OK 給 PayPal (否則會重試)
http_response_code(200);
?>
<?php
// success.php - 用戶付款完成返回頁面
$tx = $_GET['tx'] ?? '';
$amt = $_GET['amt'] ?? '';
$cm = $_GET['cm'] ?? '';
if ($tx) {
// 可選:再次驗證 PDT (需 PDT Identity Token)
$ch = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => "cmd=_notify-synch&tx=$tx&at=YOUR_PDT_TOKEN",
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);
if (strpos($response, 'SUCCESS') === 0) {
echo "付款成功!訂單處理中...";
} else {
echo "付款驗證中,請稍後查看訂單狀態";
}
} else {
echo "歡迎回來,請查看訂單狀態";
}
?>
| 檢查項目 | 說明 |
|---|---|
驗證 receiver_email |
確認款項進入正確商家帳戶 |
驗證 mc_currency |
防止幣別竄改 (USD ≠ TWD) |
驗證 payment_status = Completed |
僅處理完成付款,忽略 Pending/Failed |
| 防重複處理 | 用 txn_id 或 item_number 去重 |
| 金額核對 | 比對 mc_gross 與訂單金額 |
| HTTPS 端點 | IPN/Return URL 必須 HTTPS |
| IP 白名單 (可選) | 限制 PayPal 官方 IP 範圍 |
ngrok http 80<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD"></script>
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{amount: {value: '99.00'}}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
alert('付款完成: ' + details.payer.name.given_name);
// AJAX 通知後端驗證
});
}
}).render('#paypal-button-container');
</script>
傳統表單+IPN 方式適合簡單整合,新專案建議採用 PayPal Checkout JS SDK 提供更佳用戶體驗與安全性。