Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/pod.git

Initial draft of payment process

AuthorDave Jarvis <email>
Date2026-01-17 23:03:02 GMT-0800
Commit8648b58258f4b004161707e99ed20ff7323a86e6
index.html
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>autónoma &ndash; shipping</title>
+ <link rel="stylesheet" href="main.css">
+</head>
+<body>
+ <h1>autónoma &ndash; shipping</h1>
+ <form action="payment.php" method="post">
+ <p>
+ Provide the mailing address. If this is a gift, provide the recipient’s
+ name in the <strong>Gifted to</strong> field.
+ </p>
+
+ <p>
+ <label for="name">Name:</label>
+ <input type="text" id="name" name="name" maxlength="100" required
+ autofocus="autofocus">
+ </p>
+
+ <p>
+ <label for="street1">Address line 1:</label>
+ <input type="text" id="street1" name="street1" maxlength="100" required>
+ </p>
+
+ <p>
+ <label for="street2">Address line 2 (optional):</label>
+ <input type="text" id="street2" name="street2" maxlength="100">
+ </p>
+
+ <p>
+ <label for="city">City:</label>
+ <input type="text" id="city" name="city" maxlength="50" required>
+ </p>
+
+ <p>
+ <label for="state">Prov/State:</label>
+ <input type="text" id="state" name="state" maxlength="50" required>
+ </p>
+
+ <p>
+ <label for="postcode">Postal code:</label>
+ <input type="text" id="postcode" name="postcode" maxlength="20" required>
+ </p>
+
+ <p>
+ <label for="country">Country:</label>
+ <select name="country" id="country" required>
+ <option value="AU">Australia</option>
+ <option value="CA">Canada</option>
+ <option value="IE">Ireland</option>
+ <option value="NZ">New Zealand</option>
+ <option value="GB">United Kingdom</option>
+ <option value="US">United States</option>
+ <hr>
+ <option value="AF">Afghanistan</option>
+ <option value="AX">Åland Islands</option>
+ <option value="AL">Albania</option>
+ <option value="DZ">Algeria</option>
+ <option value="AS">American Samoa</option>
+ <option value="AD">Andorra</option>
+ <option value="AO">Angola</option>
+ <option value="AI">Anguilla</option>
+ <option value="AQ">Antarctica</option>
+ <option value="AG">Antigua and Barbuda</option>
+ <option value="AR">Argentina</option>
+ <option value="AM">Armenia</option>
+ <option value="AW">Aruba</option>
+ <option value="AT">Austria</option>
+ <option value="AZ">Azerbaijan</option>
+ <option value="BS">Bahamas</option>
+ <option value="BH">Bahrain</option>
+ <option value="BD">Bangladesh</option>
+ <option value="BB">Barbados</option>
+ <option value="BY">Belarus</option>
+ <option value="BE">Belgium</option>
+ <option value="BZ">Belize</option>
+ <option value="BJ">Benin</option>
+ <option value="BM">Bermuda</option>
+ <option value="BT">Bhutan</option>
+ <option value="BO">Bolivia</option>
+ <option value="BA">Bosnia and Herzegovina</option>
+ <option value="BW">Botswana</option>
+ <option value="BR">Brazil</option>
+ <option value="BN">Brunei Darussalam</option>
+ <option value="BG">Bulgaria</option>
+ <option value="BF">Burkina Faso</option>
+ <option value="BI">Burundi</option>
+ <option value="KH">Cambodia</option>
+ <option value="CM">Cameroon</option>
+ <option value="CV">Cape Verde</option>
+ <option value="KY">Cayman Islands</option>
+ <option value="CF">Central African Republic</option>
+ <option value="TD">Chad</option>
+ <option value="CL">Chile</option>
+ <option value="CN">China</option>
+ <option value="CO">Colombia</option>
+ <option value="KM">Comoros</option>
+ <option value="CG">Congo</option>
+ <option value="CR">Costa Rica</option>
+ <option value="CI">Côte d'Ivoire</option>
+ <option value="HR">Croatia</option>
+ <option value="CW">Curaçao</option>
+ <option value="CY">Cyprus</option>
+ <option value="CZ">Czech Republic</option>
+ <option value="DK">Denmark</option>
+ <option value="DJ">Djibouti</option>
+ <option value="DM">Dominica</option>
+ <option value="DO">Dominican Republic</option>
+ <option value="EC">Ecuador</option>
+ <option value="EG">Egypt</option>
+ <option value="SV">El Salvador</option>
+ <option value="GQ">Equatorial Guinea</option>
+ <option value="ER">Eritrea</option>
+ <option value="EE">Estonia</option>
+ <option value="ET">Ethiopia</option>
+ <option value="FJ">Fiji</option>
+ <option value="FI">Finland</option>
+ <option value="FR">France</option>
+ <option value="GA">Gabon</option>
+ <option value="GM">Gambia</option>
+ <option value="GE">Georgia</option>
+ <option value="DE">Germany</option>
+ <option value="GH">Ghana</option>
+ <option value="GR">Greece</option>
+ <option value="GD">Grenada</option>
+ <option value="GT">Guatemala</option>
+ <option value="GN">Guinea</option>
+ <option value="GW">Guinea-Bissau</option>
+ <option value="GY">Guyana</option>
+ <option value="HT">Haiti</option>
+ <option value="HN">Honduras</option>
+ <option value="HK">Hong Kong</option>
+ <option value="HU">Hungary</option>
+ <option value="IS">Iceland</option>
+ <option value="IN">India</option>
+ <option value="ID">Indonesia</option>
+ <option value="IQ">Iraq</option>
+ <option value="IL">Israel</option>
+ <option value="IT">Italy</option>
+ <option value="JM">Jamaica</option>
+ <option value="JP">Japan</option>
+ <option value="JO">Jordan</option>
+ <option value="KZ">Kazakhstan</option>
+ <option value="KE">Kenya</option>
+ <option value="KI">Kiribati</option>
+ <option value="KR">Korea, Republic of</option>
+ <option value="KW">Kuwait</option>
+ <option value="KG">Kyrgyzstan</option>
+ <option value="LA">Lao People's Democratic Republic</option>
+ <option value="LV">Latvia</option>
+ <option value="LB">Lebanon</option>
+ <option value="LS">Lesotho</option>
+ <option value="LR">Liberia</option>
+ <option value="LI">Liechtenstein</option>
+ <option value="LT">Lithuania</option>
+ <option value="LU">Luxembourg</option>
+ <option value="MO">Macao</option>
+ <option value="MG">Madagascar</option>
+ <option value="MW">Malawi</option>
+ <option value="MY">Malaysia</option>
+ <option value="MV">Maldives</option>
+ <option value="ML">Mali</option>
+ <option value="MT">Malta</option>
+ <option value="MH">Marshall Islands</option>
+ <option value="MR">Mauritania</option>
+ <option value="MU">Mauritius</option>
+ <option value="MX">Mexico</option>
+ <option value="FM">Micronesia</option>
+ <option value="MD">Moldova</option>
+ <option value="MC">Monaco</option>
+ <option value="MN">Mongolia</option>
+ <option value="ME">Montenegro</option>
+ <option value="MS">Montserrat</option>
+ <option value="MA">Morocco</option>
+ <option value="MZ">Mozambique</option>
+ <option value="MM">Myanmar</option>
+ <option value="NA">Namibia</option>
+ <option value="NR">Nauru</option>
+ <option value="NP">Nepal</option>
+ <option value="NL">Netherlands</option>
+ <option value="NI">Nicaragua</option>
+ <option value="NE">Niger</option>
+ <option value="NG">Nigeria</option>
+ <option value="NO">Norway</option>
+ <option value="OM">Oman</option>
+ <option value="PK">Pakistan</option>
+ <option value="PW">Palau</option>
+ <option value="PA">Panama</option>
+ <option value="PG">Papua New Guinea</option>
+ <option value="PY">Paraguay</option>
+ <option value="PE">Peru</option>
+ <option value="PH">Philippines</option>
+ <option value="PL">Poland</option>
+ <option value="PT">Portugal</option>
+ <option value="QA">Qatar</option>
+ <option value="RE">Réunion</option>
+ <option value="RO">Romania</option>
+ <option value="RW">Rwanda</option>
+ <option value="KN">Saint Kitts and Nevis</option>
+ <option value="LC">Saint Lucia</option>
+ <option value="VC">Saint Vincent and the Grenadines</option>
+ <option value="WS">Samoa</option>
+ <option value="SM">San Marino</option>
+ <option value="ST">São Tomé and Príncipe</option>
+ <option value="SA">Saudi Arabia</option>
+ <option value="SN">Senegal</option>
+ <option value="RS">Serbia</option>
+ <option value="SC">Seychelles</option>
+ <option value="SL">Sierra Leone</option>
+ <option value="SG">Singapore</option>
+ <option value="SK">Slovakia</option>
+ <option value="SI">Slovenia</option>
+ <option value="SB">Solomon Islands</option>
+ <option value="SO">Somalia</option>
+ <option value="ZA">South Africa</option>
+ <option value="ES">Spain</option>
+ <option value="LK">Sri Lanka</option>
+ <option value="SD">Sudan</option>
+ <option value="SR">Suriname</option>
+ <option value="SZ">Swaziland</option>
+ <option value="SE">Sweden</option>
+ <option value="CH">Switzerland</option>
+ <option value="TW">Taiwan</option>
+ <option value="TJ">Tajikistan</option>
+ <option value="TZ">Tanzania</option>
+ <option value="TH">Thailand</option>
+ <option value="TG">Togo</option>
+ <option value="TO">Tonga</option>
+ <option value="TT">Trinidad and Tobago</option>
+ <option value="TN">Tunisia</option>
+ <option value="TR">Turkey</option>
+ <option value="TM">Turkmenistan</option>
+ <option value="TV">Tuvalu</option>
+ <option value="UG">Uganda</option>
+ <option value="UA">Ukraine</option>
+ <option value="AE">United Arab Emirates</option>
+ <option value="UY">Uruguay</option>
+ <option value="UZ">Uzbekistan</option>
+ <option value="VU">Vanuatu</option>
+ <option value="VE">Venezuela</option>
+ <option value="VN">Vietnam</option>
+ <option value="YE">Yemen</option>
+ <option value="ZM">Zambia</option>
+ <option value="ZW">Zimbabwe</option>
+ </select>
+ </p>
+
+ <p>
+ <label for="phone">Phone:</label>
+ <input type="tel" id="phone" name="phone" maxlength="20" required>
+ </p>
+
+ <p>
+ <label for="email">Email:</label>
+ <input type="email" id="email" name="email" maxlength="254" required>
+ </p>
+
+ <p>
+ <label for="gifted">Gifted to (optional):</label>
+ <input type="text" id="gifted" name="gifted" maxlength="100">
+ </p>
+
+ <p>
+ <button type="submit">Proceed to payment</button>
+ </p>
+ </form>
+</body>
+</html>
+
main.css
+body {
+ font-family: sans-serif;
+ background-color: #f5f5f5;
+ padding: 40px 20px;
+ margin: 0;
+ color: #333;
+}
+
+h1, h2 {
+ max-width: 500px;
+ margin: 0 auto 20px auto;
+ text-align: center;
+}
+
+form {
+ max-width: 500px;
+ margin-left: auto;
+ margin-right: auto;
+ background: white;
+ padding: 15px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0 15px;
+}
+
+/* Payment page content styling */
+body:not(:has(form)) > h2 {
+ max-width: 500px;
+ margin-left: auto;
+ margin-right: auto;
+ background: white;
+ padding: 15px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ margin-bottom: 15px;
+}
+
+.payment-content {
+ max-width: 500px;
+ margin: 0 auto;
+ background: white;
+ padding: 15px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+}
+
+.payment-content h2:first-child {
+ margin-top: 0;
+}
+
+.payment-content p.address {
+ line-height: 1.6;
+}
+
+.payment-content p.address br {
+ display: block;
+}
+
+p {
+ margin: 0 0 8px 0;
+}
+
+form p:not(:first-of-type) {
+ display: flex;
+ flex-direction: column;
+}
+
+/* Full-width elements in form */
+form p:nth-of-type(1),
+form p:nth-of-type(2),
+form p:nth-of-type(3),
+form p:nth-of-type(10),
+form p:nth-of-type(11),
+form p:nth-of-type(12) {
+ grid-column: span 2;
+}
+
+label {
+ font-size: 0.85rem;
+ font-weight: bold;
+ margin-bottom: 2px;
+}
+
+input, select {
+ width: 100%;
+ box-sizing: border-box;
+ padding: 6px;
+ font-size: 0.9rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+button {
+ background-color: #007bff;
+ color: white;
+ border: none;
+ cursor: pointer;
+ font-weight: bold;
+ padding: 10px 30px;
+ margin: 10px auto 0 auto;
+ transition: background 0.2s;
+ width: auto;
+ display: block;
+ border-radius: 4px;
+ font-size: 0.9rem;
+}
+
+button:hover {
+ background-color: #0056b3;
+}
+
+br {
+ display: none;
+}
+
+.payment-content section {
+ border-top: 1px solid #eee;
+ padding-top: 1em;
+ margin-top: 1em;
+}
+
+/* Remove the box around the 'Try again' form on the payment page */
+.payment-content form {
+ border: none;
+ background: none;
+ padding: 0;
+ display: block;
+}
+
+.payment-content h3 {
+ margin-bottom: 2em;
+}
+
+.payment-content table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 2em;
+}
+
+.payment-content td:last-child {
+ text-align: right;
+}
+
+.payment-content tr:last-child > td {
+ border-top: 2px solid #333;
+ padding-top: .5em;
+}
+
+.payment-content tr:nth-last-child(2) > td {
+ padding-bottom: .5em;
+}
+
openapi_public.yml
Binary files differ
payment.php
+<?php
+function getHomeDirectory() {
+ $result = '';
+
+ if( function_exists( 'posix_getpwnam' ) ) {
+ $homedir = posix_getpwnam( get_current_user() );
+
+ if( !empty( $homedir[ 'dir' ] ) ) {
+ $result = $homedir[ 'dir' ];
+ }
+ }
+
+ if( empty( $result ) ) {
+ if( !empty( $_SERVER[ 'HOME' ] ) ) {
+ $result = $_SERVER[ 'HOME' ];
+ }
+ elseif( !empty( getenv( 'HOME' ) ) ) {
+ $result = getenv( 'HOME' );
+ }
+ elseif( function_exists( 'posix_getpwuid' ) &&
+ function_exists( 'posix_getuid' ) ) {
+ $userInfo = posix_getpwuid( posix_getuid() );
+
+ if( !empty( $userInfo[ 'dir' ] ) ) {
+ $result = $userInfo[ 'dir' ];
+ }
+ }
+ }
+ return $result;
+}
+
+$configPath = getHomeDirectory() . '/.keys/lulu.config';
+$config = json_decode( file_get_contents( $configPath ), true );
+
+define( 'CLIENT_KEY', $config[ 'CLIENT_KEY' ] );
+define( 'URL_BASE', $config[ 'URL_BASE' ] );
+define( 'ORDER_PAGE_COUNT', $config[ 'ORDER_PAGE_COUNT' ] );
+define( 'ORDER_PACKAGE', $config[ 'ORDER_PACKAGE' ] );
+define( 'ORDER_QUANTITY', $config[ 'ORDER_QUANTITY' ] );
+define( 'ORDER_SHIPPING', $config[ 'ORDER_SHIPPING' ] );
+
+const URL_AUTH = URL_BASE . "auth/realms/glasstree/protocol/openid-connect/token";
+const URL_COST = URL_BASE . "print-job-cost-calculations/";
+
+const CURRENCY_MAP = [
+ 'AU'=>'AUD','CA'=>'CAD','GB'=>'GBP',
+ 'AT'=>'EUR','BE'=>'EUR','CY'=>'EUR','EE'=>'EUR','FI'=>'EUR','FR'=>'EUR',
+ 'DE'=>'EUR','GR'=>'EUR','IE'=>'EUR','IT'=>'EUR','LV'=>'EUR','LT'=>'EUR',
+ 'LU'=>'EUR','MT'=>'EUR','NL'=>'EUR','PT'=>'EUR','SK'=>'EUR','SI'=>'EUR',
+ 'ES'=>'EUR'
+];
+
+$required = [
+ "city", "country", "postcode", "state", "street1", "phone", "name", "email"
+];
+$missing = [];
+
+foreach( $required as $f ) {
+ if( empty( $_POST[ $f ] ) ) {
+ $missing[] = $f;
+ }
+}
+
+$code = 0;
+$raw_response = "";
+$error_message = "";
+
+if( empty( $missing ) ) {
+ define( 'KEEP', false );
+ define( 'UPPER', true );
+
+ $fields = [
+ 'name' => [ 100, KEEP ],
+ 'street1' => [ 100, UPPER ],
+ 'street2' => [ 100, UPPER ],
+ 'city' => [ 50, UPPER ],
+ 'state' => [ 50, UPPER ],
+ 'postcode' => [ 20, UPPER ],
+ 'phone' => [ 20, KEEP ],
+ 'email' => [ 254, KEEP ],
+ 'gifted' => [ 100, KEEP ]
+ ];
+
+ $clean = [];
+
+ foreach( $fields as $f => $cfg ) {
+ $val = mb_substr( trim( $_POST[ $f ] ?? '' ), 0, $cfg[ 0 ], 'UTF-8' );
+ $transform = [
+ KEEP => $val,
+ UPPER => mb_strtoupper( $val, 'UTF-8' )
+ ];
+ $clean[ $f ] = $transform[ $cfg[ 1 ] ];
+ }
+
+ $selected_currency = CURRENCY_MAP[ $_POST[ "country" ] ] ?? 'USD';
+
+ $auth_context = stream_context_create( [
+ "http" => [
+ "method" => "POST",
+ "header" => "Authorization: Basic " .
+ base64_encode( CLIENT_KEY ) .
+ "\r\nContent-Type: application/x-www-form-urlencoded\r\n",
+ "content" => http_build_query( [ "grant_type" => "client_credentials" ] ),
+ ],
+ ] );
+
+ $auth_json = file_get_contents( URL_AUTH, false, $auth_context );
+ $auth_response = json_decode( $auth_json, 1 );
+ $token = $auth_response[ "access_token" ] ?? null;
+
+ if( $token ) {
+ $cost_data = [
+ "line_items" => [ [
+ "page_count" => ORDER_PAGE_COUNT,
+ "quantity" => ORDER_QUANTITY,
+ "pod_package_id" => ORDER_PACKAGE
+ ] ],
+ "shipping_address" => [
+ "phone_number" => $clean[ "phone" ],
+ "name" => $clean[ "name" ],
+ "email" => $clean[ "email" ],
+ "city" => $clean[ "city" ],
+ "country_code" => $_POST[ "country" ],
+ "postcode" => $clean[ "postcode" ],
+ "state_code" => $clean[ "state" ],
+ "street1" => $clean[ "street1" ],
+ "street2" => $clean[ "street2" ],
+ ],
+ "shipping_option" => ORDER_SHIPPING,
+ "currency" => $selected_currency,
+ ];
+
+ $cost_context = stream_context_create( [
+ "http" => [
+ "method" => "POST",
+ "header" => "Authorization: Bearer $token\r\n" .
+ "Content-Type: application/json\r\n",
+ "content" => json_encode( $cost_data ),
+ "ignore_errors" => true,
+ ],
+ ] );
+
+ $raw_response = file_get_contents( URL_COST, false, $cost_context );
+
+ if( !empty( $http_response_header ) ) {
+ preg_match( "/\d{3}/", $http_response_header[ 0 ], $m );
+ $code = intval( $m[ 0 ] );
+ }
+
+ $api_data = json_decode( $raw_response, 1 ) ?? [];
+
+ if( $code !== 201 ) {
+ $error_message =
+ $api_data[ 'shipping_address' ][ 'detail' ][ 'errors' ][ 0 ][ 'message' ]
+ ?? $api_data[ 'detail' ]
+ ?? "An unexpected error occurred (HTTP $code).";
+ }
+ } else {
+ $error_message = "Authentication failed.";
+ }
+}
+
+function escape( $data ) {
+ if( is_array( $data ) ) {
+ return array_map( 'escape', $data );
+ }
+
+ return htmlspecialchars( $data ?? '', ENT_QUOTES, 'UTF-8' );
+}
+
+$response = escape( array_merge(
+ [ 'form' => $_POST, 'missing' => $missing, 'api_error' => $error_message ],
+ $api_data[ 'shipping_address' ] ?? [],
+ $api_data ?? []
+) );
+?>
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>autónoma &ndash; payment</title>
+ <link rel="stylesheet" href="main.css">
+</head>
+<body>
+ <h1>autónoma &ndash; payment</h1>
+ <div class="payment-content">
+ <?php if( !empty( $response[ 'missing' ] ) ) { ?>
+ <h2>Missing fields</h2>
+ <ul>
+ <?php foreach( $response[ 'missing' ] as $m ) { ?>
+ <li><?= $m ?></li>
+ <?php } ?>
+ </ul>
+ <?php } else { ?>
+ <?php if( $code == 201 ) { ?>
+ <h2>Order summary</h2>
+ <section>
+ <p class="address">
+ <strong><?= $_POST[ 'name' ] ?></strong><br>
+ <?= $response[ 'street1' ] ?><br>
+ <?php if( !empty( $response[ 'street2' ] ) ) { ?>
+ <?= $response[ 'street2' ] ?><br>
+ <?php } ?>
+ <?= $response[ 'city' ] ?>
+ <?= $response[ 'state' ] ?>
+ <?= $response[ 'postcode' ] ?><br>
+ <?= $response[ 'country' ] ?><br>
+ </p>
+ <table>
+ <tr>
+ <td>Book price</td>
+ <td><?= $response[ 'line_item_costs' ][0][ 'total_cost_excl_tax' ] ?> <?= $response[ 'currency' ] ?></td>
+ </tr>
+ <tr>
+ <td>Shipping</td>
+ <td><?= $response[ 'shipping_cost' ][ 'total_cost_excl_tax' ] ?> <?= $response[ 'currency' ] ?></td>
+ </tr>
+ <tr>
+ <td>Taxes</td>
+ <td><?= $response[ 'total_tax' ] ?> <?= $response[ 'currency' ] ?></td>
+ </tr>
+ <tr>
+ <td><strong>Amount due</strong></td>
+ <td><strong><?= $response[ 'total_cost_incl_tax' ] ?> <?= $response[ 'currency' ] ?></strong></td>
+ </tr>
+ </table>
+ </section>
+ <?php } else { ?>
+ <h2>Shipping calculation error</h2>
+ <p><?= $response[ 'api_error' ] ?></p>
+ <form action=".">
+ <button type="submit" onclick="history.back(); return false;">
+ Try again
+ </button>
+ </form>
+ <?php } ?>
+ <?php } ?>
+ </div>
+</body>
+</html>
+
Delta666 lines added, 0 lines removed, 666-line increase