on every order it creates, so * the capture settles directly into the school's own PayPal account. * After capture, order-api.php also books the payment into the * user_account ledger (idempotent on capture_id). * * The button only renders when the school has finished onboarding and * PayPal reports it ready to receive payments — never a dead button. */ // Web path where the ppp/ integration is served. Auto-detected by probing the // known deploy folders under the document root; define PPP_WEB_BASE before // this include to override (e.g. if the folder is renamed again). if (!defined('PPP_WEB_BASE')) { $pppDocroot = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/'); foreach (['/pp-integration', '/sm'] as $pppCandidate) { if (is_file($pppDocroot . $pppCandidate . '/checkout.php')) { define('PPP_WEB_BASE', $pppCandidate); break; } } } // Every hidden-return leaves an HTML comment in the page source saying WHY, // so a missing button can be diagnosed with View Source instead of guesswork. $pppWhy = function (string $reason): void { echo "\n\n"; }; // -- Guard: only render if the checkout page is actually deployed -------------- if (!defined('PPP_WEB_BASE')) { $pppWhy('integration not found — checked ' . ($pppDocroot ?? '(no document root)') . '{/pp-integration,/sm}/checkout.php'); return; } $pppFsBase = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/') . PPP_WEB_BASE; $pppCheckoutFs = $pppFsBase . '/checkout.php'; if (!is_file($pppCheckoutFs)) { $pppWhy('checkout.php missing at ' . $pppCheckoutFs); return; } // -- Gateway condition: Express Checkout ONLY ---------------------------------- // "REST API Checkout" schools have their own existing button — never render // this one for them. Unset/empty counts as Express (the current gateway). // The real column on `schools` is paypal_checkout (verified against the live // schema); the others are kept as fallbacks for older copies of the table. $pppGateway = trim((string)($school_record['paypal_checkout'] ?? $school_record['paypal_gateway'] ?? $school_record['paypal_checkout_type'] ?? '')); if ($pppGateway === '') $pppGateway = 'Express Checkout'; if ($pppGateway !== 'Express Checkout') { $pppWhy('school gateway is "' . $pppGateway . '" — only Express Checkout schools get this button'); return; } // -- School must have completed PayPal onboarding and be ready ----------------- // School name MUST match the key used during onboarding exactly, or the // merchant lookup in order-api.php will fail. $pppSchoolName = trim($school_record['school_name'] ?? ''); if ($pppSchoolName === '') { $pppWhy('school_record has no school_name'); return; } $pppMerchantsFile = $pppFsBase . '/data/school_merchants.json'; if (!is_file($pppMerchantsFile)) { $pppWhy('merchants file missing at ' . $pppMerchantsFile); return; } $pppMerchants = json_decode((string)file_get_contents($pppMerchantsFile), true) ?: []; $pppRecord = $pppMerchants[$pppSchoolName] ?? null; if (!$pppRecord || empty($pppRecord['merchant_id'])) { $pppWhy('school "' . $pppSchoolName . '" not found in merchants file (keys: ' . implode(' | ', array_keys($pppMerchants)) . ') — onboarding name must match exactly'); return; } if (isset($pppRecord['paypal_ready']) && !$pppRecord['paypal_ready']) { $pppWhy('school "' . $pppSchoolName . '" connected but paypal_ready=false — ' . 'PayPal has not confirmed it can receive payments yet'); return; } // -- Amounts ------------------------------------------------------------------- // payment_form.php has just computed: // $balance — account balance owing (or the selected deposit amount) // $trx_fee — the school's PayPal transaction fee // $owing — $balance + $trx_fee ? this is what the buyer must be charged $pppBase = isset($balance) ? (float)$balance : 0.0; $pppFee = isset($trx_fee) ? (float)$trx_fee : 0.0; $pppTotal = isset($owing) ? (float)$owing : $pppBase + $pppFee; if ($pppTotal <= 0) { $pppWhy('nothing to charge (total ' . number_format($pppTotal, 2) . ')'); return; } $pppIsDeposit = isset($selected_amount) && $selected_amount > 0; $pppFeeTitle = trim($school_record['paypal_fee_title'] ?? ''); if ($pppFeeTitle === '' && function_exists('getLanguageText')) $pppFeeTitle = (string)getLanguageText('TEXT-TRX-FEE'); if ($pppFeeTitle === '') $pppFeeTitle = 'Transaction fee'; // Currency CODE (not symbol) — ISO 4217. Prefer the school's own // paypal_currency column; then the PPP_CURRENCY constant; then USD. $pppCurrency = strtoupper(trim((string)($school_record['paypal_currency'] ?? ''))); if (!preg_match('/^[A-Z]{3}$/', $pppCurrency)) { $pppCurrency = defined('PPP_CURRENCY') ? PPP_CURRENCY : 'USD'; } // -- Store the checkout context for /sm/checkout.php --------------------------- // The session (name 'hotlunch') is shared with the checkout page, so amounts // travel server-side and can't be tampered with through the URL. $_SESSION['ppp_checkout'] = [ 'school_num' => (int)($school_num ?? 0), 'school_name' => $pppSchoolName, 'user_num' => (int)($user_num ?? ($_SESSION['UserInfo']['record_num'] ?? 0)), 'buyer_email' => (string)($_SESSION['UserInfo']['email'] ?? ''), 'base_amount' => number_format($pppBase, 2, '.', ''), 'fee_amount' => number_format($pppFee, 2, '.', ''), 'total_amount' => number_format($pppTotal, 2, '.', ''), 'base_label' => $pppIsDeposit ? 'Deposit amount' : 'Account balance', 'fee_label' => $pppFeeTitle, 'currency' => $pppCurrency, 'currency_symbol' => (string)($currencySymbol ?? '$'), 'gateway' => $pppGateway, 'return_url' => (string)('/'), 'created_at' => time(), ]; $pppCheckoutUrl = PPP_WEB_BASE . '/checkout.php'; ?>
Pay with