Last active
June 11, 2023 03:01
-
-
Save Znote/515e96a0de55ba44998fe88053033964 to your computer and use it in GitHub Desktop.
Znote AAC Paypal REST API prototype
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php require_once 'engine/init.php'; | |
protect_page(); | |
// Import from config: | |
$pagseguro = $config['pagseguro']; | |
$paypal = $config['paypal']; | |
$prices = $config['paypal_prices']; | |
// Begin processing paypal transaction request | |
if (empty($_POST) === false) { | |
$price = intval($_POST['amount']); | |
if(is_int($price) && $price > 0 && in_array($price, array_keys($prices))) { | |
$points = $prices[$price]; | |
if ($paypal['debug']) data_dump($_REQUEST, false, "Request"); | |
// Init curl | |
$ch = curl_init(); | |
// Get token | |
curl_setopt($ch, CURLOPT_URL, "https://api.sandbox.paypal.com/v1/oauth2/token"); | |
curl_setopt($ch, CURLOPT_HEADER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_USERPWD, $paypal['client_id'].":".$paypal['secret_id']); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials"); | |
$result = curl_exec($ch); | |
$token_json = json_decode($result); | |
$token = $token_json->access_token; | |
if ($paypal['debug']) data_dump($token_json, false, "Payment token"); | |
$profile_id = mysql_select_single("SELECT `value` FROM `znote_global_storage` WHERE `key`='paypal_profile_id' LIMIT 1;"); | |
if ($profile_id === false) { | |
// Prepare profile data | |
$buyer_profile = array( | |
"name" => "paypalProfile", | |
"temporary" => false, | |
"input_fields" => array( | |
"no_shipping" => 1, | |
"address_override" => 1 | |
) | |
); | |
// Send profile create request | |
curl_setopt($ch, CURLOPT_URL, "https://api.sandbox.paypal.com/v1/payment-experience/web-profiles"); | |
curl_setopt($ch, CURLOPT_HEADER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_USERPWD, $paypal['client_id'].":".$paypal['secret_id']); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($buyer_profile)); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, array( | |
"Authorization: Bearer ".$token."", | |
"Content-Type: application/json" | |
)); | |
$profile_return = json_decode(curl_exec($ch)); | |
//{"id":"XP-4KHA-UYPT-QNCF-7JYC","name":"webProfile","temporary":false,"input_fields":{"no_shipping":1,"address_override":1}} | |
if ($paypal['debug']) data_dump($profile_return, false, "Profile return data"); | |
$profile_id = (isset($profile_return->id)) ? $profile_return->id : false; | |
if ($profile_id !== false) { | |
mysql_insert("INSERT INTO `znote_global_storage` (`key`, `value`) VALUES ('paypal_profile_id', '$profile_id');"); | |
} | |
} else { | |
$profile_id = $profile_id['value']; | |
} | |
// Prepare payment data | |
$currency = $paypal['currency']; | |
$payment = array( | |
"intent" => "sale", | |
"payer" => array( | |
"payment_method" => "paypal" | |
), | |
"transactions" => array( | |
array( | |
"amount" => array( | |
"currency" => $currency, | |
"total" => $price | |
), | |
"description" => "Shop points on ". $config['site_title'], | |
"item_list" => array( | |
"items" => array( | |
array( | |
"quantity" => "1", | |
"name" => $prices[$price]." shop points.", | |
"price" => $price, | |
"currency" => $currency | |
) | |
) | |
) | |
) | |
), | |
"experience_profile_id" => $profile_id, | |
"redirect_urls" => array( | |
"return_url" => $paypal['process'], | |
"cancel_url" => $paypal['failed'], | |
) | |
); | |
if ($paypal['debug']) data_dump($payment, false, "Payment data"); | |
// Send payment request | |
curl_setopt($ch, CURLOPT_URL, "https://api.sandbox.paypal.com/v1/payments/payment"); | |
curl_setopt($ch, CURLOPT_HEADER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_USERPWD, $paypal['client_id'].":".$paypal['secret_id']); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payment)); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, array( | |
"Authorization: Bearer ".$token."", | |
"Content-Type: application/json" | |
)); | |
$result = curl_exec($ch); | |
$payment_link = json_decode($result); | |
// Close curl | |
curl_close($ch); | |
// Send user to paypal to process payment | |
if (isset($payment_link->links[1]->href)) { | |
// Log the start of the payment process | |
// Account ID, payment ID, price, redirect, execute, status | |
$account_id = (int)$session_user_id; | |
$payment_id = $payment_link->id; | |
$payment_state = $payment_link->state; | |
$time_created = $payment_link->create_time; | |
mysql_insert("INSERT INTO `znote_paypal` (`account_id`,`payment_id`,`payment_state`,`price`,`currency`,`points`, `time_created`) VALUES ('{$account_id}','{$payment_id}','{$payment_state}','{$price}','{$currency}','{$points}','{$time_created}');"); | |
if ($paypal['debug']) { | |
data_dump($payment_link, false, "Payment link success"); | |
echo "<p><a target='_BLANK' href='".$payment_link->links[1]->href."'>Click here to proceed to paypal</a></p>"; | |
} else { | |
header("Location: ".$payment_link->links[1]->href); | |
exit(); | |
} | |
} else { | |
data_dump($payment_link, false, "Payment link ERROR"); | |
} | |
} else { | |
data_dump($_REQUEST, false, "Invalid post data."); | |
} | |
} | |
// Render html | |
include 'layout/overall/header.php'; | |
// PayPal | |
if ($paypal['enabled']): | |
?> | |
<h1>Buy Points</h1> | |
<h2>Buy points using Paypal:</h2> | |
<table id="buypointsTable" class="table table-striped table-hover"> | |
<tr class="yellow"> | |
<th>Price:</th> | |
<th>Points:</th> | |
<?php if ($paypal['showBonus']) echo "<th>Bonus:</th>"; ?> | |
<th>Action:</th> | |
</tr> | |
<?php | |
foreach ($prices as $price => $points): | |
$discount = calculate_discount(($paypal['points_per_currency'] * $price), $points); | |
?> | |
<tr class="special"> | |
<td><?php echo $price; ?>(<?php echo $paypal['currency']; ?>)</td> | |
<td><?php echo $points; ?></td> | |
<?php if ($paypal['showBonus']) echo '<td>'. $discount .' bonus</td>'; ?> | |
<td> | |
<form action="" method="POST"> | |
<input type="hidden" name="amount" value="<?php echo $price; ?>"> | |
<input type="submit" value=" PURCHASE "> | |
</form> | |
</td> | |
</tr> | |
<?php | |
endforeach; | |
?> | |
</table> | |
<?php | |
endif; | |
// PagseGuro | |
if ($config['pagseguro']['enabled'] == true): | |
?> | |
<h2>Buy points using Pagseguro:</h2> | |
<form target="pagseguro" action="https://<?=$pagseguro['urls']['www']?>/checkout/checkout.jhtml" method="post"> | |
<input type="hidden" name="email_cobranca" value="<?=$pagseguro['email']?>"> | |
<input type="hidden" name="tipo" value="CP"> | |
<input type="hidden" name="moeda" value="<?=$pagseguro['currency']?>"> | |
<input type="hidden" name="ref_transacao" value="<?php echo (int)$session_user_id; ?>"> | |
<input type="hidden" name="item_id_1" value="1"> | |
<input type="hidden" name="item_descr_1" value="<?=$pagseguro['product_name']?>"> | |
<input type="number" name="item_quant_1" min="1" step="4" value="1"> | |
<input type="hidden" name="item_peso_1" value="0"> | |
<input type="hidden" name="item_valor_1" value="<?=$pagseguro['price']?>"> | |
<input type="submit" value=" PURCHASE "> | |
</form> | |
<br> | |
<?php | |
endif; | |
// PayGol | |
if ($config['paygol']['enabled'] == true): | |
$paygol = $config['paygol']; | |
?> | |
<!-- PayGol Form using Post method --> | |
<h2>Buy points using Paygol:</h2> | |
<p><?php echo $paygol['price'] ." ". $paygol['currency'] ."~ for ". $paygol['points'] ." points:"; ?></p> | |
<form name="pg_frm" method="post" action="http://www.paygol.com/micropayment/paynow" > | |
<input type="hidden" name="pg_serviceid" value="<?php echo $paygol['serviceID']; ?>"> | |
<input type="hidden" name="pg_currency" value="<?php echo $paygol['currency']; ?>"> | |
<input type="hidden" name="pg_name" value="<?php echo $paygol['name']; ?>"> | |
<input type="hidden" name="pg_custom" value="<?php echo $session_user_id; ?>"> | |
<input type="hidden" name="pg_price" value="<?php echo $paygol['price']; ?>"> | |
<input type="hidden" name="pg_return_url" value="<?php echo $paygol['returnURL']; ?>"> | |
<input type="hidden" name="pg_cancel_url" value="<?php echo $paygol['cancelURL']; ?>"> | |
<input type="image" name="pg_button" src="http://www.paygol.com/micropayment/img/buttons/150/black_en_pbm.png" border="0" alt="Make payments with PayGol: the easiest way!" title="Make payments with PayGol: the easiest way!"> | |
</form> | |
<?php | |
endif; | |
if (!$config['paypal']['enabled'] && !$config['paygol']['enabled'] && !$config['pagseguro']['enabled']): | |
?> | |
<h1>Buy Points system disabled.</h1> | |
<p>Sorry, this functionality is disabled.</p> | |
<?php | |
endif; | |
include 'layout/overall/footer.php'; | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$config['paypal'] = array( | |
'enabled' => true, | |
'debug' => false, // Enable debug info to view what is going on and figure out errors | |
'email' => 'ask@me.com', | |
'client_id' => 'client id from paypal developer center', | |
'secret_id' => 'secret id from paypal developer center', | |
'currency' => 'USD', | |
'points_per_currency' => 10, // 1 currency = ? points? [ONLY used to calculate bonuses] | |
'success' => "http://".$_SERVER['HTTP_HOST']."/success.php", | |
'failed' => "http://".$_SERVER['HTTP_HOST']."/failed.php", | |
'process' => "http://".$_SERVER['HTTP_HOST']."/paypal_process.php", | |
'showBonus' => true | |
); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php require_once 'engine/init.php'; | |
protect_page(); | |
$paypal = $config['paypal']; | |
$payment_id = (isset($_REQUEST['paymentId'])) ? getValue($_REQUEST['paymentId']) : false; | |
$payer_id = (isset($_REQUEST['PayerID'])) ? getValue($_REQUEST['PayerID']) : false; | |
if ($payment_id === false || $payer_id === false) { | |
die("Invalid data, missing payment_id or payer_id"); | |
} | |
// Do local verification on payment and payer ID before we ask paypal about it | |
$exist = mysql_select_single("SELECT `id` FROM `znote_paypal` WHERE `payment_id`='{$payment_id}' LIMIT 1;"); | |
if (!$exist) { | |
die("This transaction is not found in our database."); | |
} | |
if ($paypal['debug']) data_dump($_REQUEST, false, "Request data"); | |
if ($paypal['debug']) data_dump($user_data, false, "User Data"); | |
$ch = curl_init(); | |
curl_setopt($ch, CURLOPT_URL, "https://api.sandbox.paypal.com/v1/oauth2/token"); | |
curl_setopt($ch, CURLOPT_HEADER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_USERPWD, $paypal['client_id'].":".$paypal['secret_id']); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials"); | |
$result = curl_exec($ch); | |
$token_json = json_decode($result); | |
$token = $token_json->access_token; | |
if ($paypal['debug']) data_dump($token_json, false, "Token info"); | |
// Execute the payment | |
$payment = array( | |
"payer_id" => $payer_id | |
); | |
curl_setopt($ch, CURLOPT_URL, "https://api.sandbox.paypal.com/v1/payments/payment/".$payment_id."/execute/"); | |
curl_setopt($ch, CURLOPT_HEADER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_USERPWD, $paypal['client_id'].":".$paypal['secret_id']); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payment)); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, array( | |
"Authorization: Bearer $token", | |
"Content-Type: application/json" | |
)); | |
$result = curl_exec($ch); | |
$payment_info = json_decode($result); | |
curl_close($ch); | |
if ($paypal['debug']) data_dump($payment_info, false, "payment info"); | |
/////////////////////// | |
// Validate response // | |
/////////////////////// | |
$errors = array(); | |
// Already done payment? | |
if (isset($payment_info->name) && $payment_info->name === "PAYMENT_ALREADY_DONE") { | |
include 'layout/overall/header.php'; | |
?> | |
<h1>Transaction already done</h1> | |
<p>Transaction has been done already for this order.</p> | |
<?php | |
include 'layout/overall/footer.php'; | |
exit(); | |
} | |
//////// | |
// Payment ID | |
$paymentId = (isset($payment_info->id)) ? $payment_info->id : false; | |
if (!$paymentId) { | |
$errors[] = "Failed to load payment ID"; | |
} | |
//////// | |
// Load transaction from SQL | |
$db_entry = ($paymentId !== false) | |
? mysql_select_single("SELECT `id`, `account_id`, `payment_id`, `payment_state`, `points`, `transaction_state`, `payer_id`, `email` FROM `znote_paypal` WHERE `payment_id`='{$paymentId}' LIMIT 1;") | |
: false; | |
if (!$db_entry) { | |
$errors[] = "Failed to load the transaction from the database."; | |
} else { | |
if ($paymentId !== false) mysql_update("UPDATE `znote_paypal` SET `payer_id`='{$payer_id}' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
//////// | |
// Payment time | |
$time_updated = (isset($payment_info->create_time)) ? $payment_info->create_time : false; | |
if (!$time_updated) { | |
$errors[] = "Failed to processing timestamp"; | |
if ($paymentId !== false) mysql_update("UPDATE `znote_paypal` SET `time_updated`='Missing' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} else { | |
if ($paymentId !== false) mysql_update("UPDATE `znote_paypal` SET `time_updated`='{$time_updated}' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
//////// | |
// Payment state | |
$paymentState = (isset($payment_info->state)) ? $payment_info->state : false; | |
if (!$paymentState) { | |
$errors[] = "Failed to load payment state"; | |
} elseif ($paymentState != "approved") { | |
$errors[] = "Payment state is not approved, but {$paymentState}."; | |
if ($paymentId !== false) mysql_update("UPDATE `znote_paypal` SET `payment_state`='{$paymentState}' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
//////// | |
// Transaction state | |
$transactionState = (isset($payment_info->transactions[0]->related_resources[0]->sale->state)) | |
? $payment_info->transactions[0]->related_resources[0]->sale->state | |
: false; | |
if (!$transactionState) { | |
$errors[] = "Failed to load Transaction state"; | |
} elseif ($transactionState != "completed") { | |
$errors[] = "Transaction state is not completed, but {$transactionState}."; | |
if ($paymentId !== false) mysql_update("UPDATE `znote_paypal` SET `transaction_state`='{$transactionState}' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
//////// | |
// Paypal Email | |
$paypalEmail = (isset($payment_info->payer->payer_info->email)) ? $payment_info->payer->payer_info->email : false; | |
if (!$paypalEmail) { | |
$errors[] = "Failed to load payer email address"; | |
} else { | |
if ($paymentId !== false) mysql_update("UPDATE `znote_paypal` SET `email`='{$paypalEmail}' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
//////// | |
// Paypal verified | |
$paypalVerified = (isset($payment_info->payer->status)) ? $payment_info->payer->status : false; | |
if (!$paypalVerified) { | |
$errors[] = "Failed to load payer status"; | |
} elseif ($paypalVerified != "VERIFIED") { | |
$errors[] = "Paypal account is not verified, and thus considered untrusted."; | |
if ($paymentId !== false) | |
mysql_update("UPDATE `znote_paypal` SET `transaction_state`='{$transactionState} (untrusted)' WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
//////// | |
// Price | |
$prices = $config['paypal_prices']; | |
$price = (isset($payment_info->transactions[0]->amount->total)) | |
? $payment_info->transactions[0]->amount->total | |
: false; | |
if (!$price) { | |
$errors[] = "Failed to load Transaction price"; | |
} elseif (!in_array($price, array_keys($prices))) { | |
if (!in_array((int)$price, array_keys($prices))) { | |
$errors[] = "Price {$price} mismatch paypal_prices shop configuration."; | |
} else { | |
$price = (int)$price; | |
} | |
} | |
//////// | |
// Currency | |
$currency = (isset($payment_info->transactions[0]->amount->currency)) | |
? $payment_info->transactions[0]->amount->currency | |
: false; | |
if (!$currency) { | |
$errors[] = "Failed to load Transaction currency"; | |
} elseif ($currency !== $paypal['currency']) { | |
$errors[] = "Currency {$currency} mismatch paypal shop configuration (".$paypal['currency'].")."; | |
} | |
if (!empty($errors)) { | |
foreach ($errors as $number => $error) { | |
echo '<p style="font-weight: bold; color: red;">Error '.$number.': '.$error.'</p>'; | |
} | |
data_dump($payment_info, false, "Response dump:"); | |
} else { | |
// Figure out which fields we need to update | |
$update = array(); | |
if ($db_entry['payment_state'] !== $paymentState) $update['payment_state'] = $paymentState; | |
if ($db_entry['transaction_state'] !== $transactionState) $update['transaction_state'] = $transactionState; | |
if ($db_entry['price'] != $price) $update['price'] = $price; | |
if ($db_entry['currency'] !== $currency) $update['currency'] = $currency; | |
if ($db_entry['payer_id'] !== $payer_id) $update['payer_id'] = $payer_id; | |
if ($db_entry['email'] !== $paypalEmail) $update['email'] = $paypalEmail; | |
if (!empty($update)) { | |
$setString = array(); | |
foreach ($update as $column => $value) { | |
$setString[] = "`{$column}`='{$value}'"; | |
} | |
$setString = implode(',', $setString); | |
mysql_update("UPDATE `znote_paypal` SET $setString WHERE `payment_id`='{$paymentId}' LIMIT 1;"); | |
} | |
include 'layout/overall/header.php'; | |
// Add points to the account | |
if ($db_entry['transaction_state'] !== "completed" && $transactionState === "completed") { | |
$new_points = $db_entry['points']; | |
$account_id = $db_entry['account_id']; | |
// Fetch points | |
$account = mysql_select_single("SELECT `points` FROM `znote_accounts` WHERE `account_id`='{$account_id}' LIMIT 1;"); | |
// Calculate new points | |
$new_points = $account['points'] + $new_points; | |
// Update new points | |
mysql_update("UPDATE `znote_accounts` SET `points`='{$new_points}' WHERE `account_id`='{$account_id}' LIMIT 1;"); | |
?> | |
<h1>Success</h1> | |
<p>You now have <?php echo $new_points; ?> shop points.</p> | |
<?php | |
} else { | |
?> | |
<h1>Processing error</h1> | |
<p>This payment is already processed. You should already have gotten the points.</p> | |
<p>Failed operation: <?php echo $db_entry['transaction_state']; ?> !== completed && <?php echo $transactionState; ?> === completed</p> | |
<?php | |
} | |
include 'layout/overall/footer.php'; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php require_once 'engine/init.php'; | |
protect_page(); | |
include 'layout/overall/header.php'; | |
$session_user_id = (int)$session_user_id; | |
$paypal_transactions = mysql_select_multi("SELECT `account_id`, `payment_state`, `transaction_state`, `price`, `currency`, `points`, `email`, `time_created`, `time_updated` FROM `znote_paypal` WHERE `account_id`='{$session_user_id}';"); | |
// data_dump($paypal_transactions, false, "data"); | |
?> | |
<h1>Shop history</h1> | |
<p>This is an overview of your shopping history with us.</p> | |
<h2>Paypal transactions</h2> | |
<table class="table tbl-hover"> | |
<tbody> | |
<tr class="yellow"> | |
<td>Created</td> | |
<td>Payment</td> | |
<td>Transaction</td> | |
<td>Price</td> | |
<td>Email</td> | |
<td>Updated</td> | |
</tr> | |
<?php | |
foreach ($paypal_transactions as $transaction): | |
$state = ($transaction['transaction_state'] !== null) ? $transaction['transaction_state'] : 'waiting for user'; | |
$email = ($transaction['email'] !== null) ? $transaction['email'] : 'Unknown'; | |
?> | |
<tr> | |
<td><?php echo getClock(strtotime($transaction['time_created']), true); ?></td> | |
<td><?php echo $transaction['payment_state']; ?></td> | |
<td><?php echo $state; ?></td> | |
<td><?php echo $transaction['points']." points for ".$transaction['price']." ".$transaction['currency']; ?></td> | |
<td><?php echo $email; ?></td> | |
<td><?php echo getClock(strtotime($transaction['time_updated']), true); ?></td> | |
</tr> | |
<?php | |
endforeach; | |
?> | |
</tbody> | |
</table> | |
<?php include 'layout/overall/footer.php'; ?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE TABLE IF NOT EXISTS `znote_paypal` ( | |
`id` int(11) NOT NULL AUTO_INCREMENT, | |
`account_id` int(11) NOT NULL, | |
`payment_id` varchar(100) NOT NULL, | |
`payment_state` varchar(50) NOT NULL, | |
`price` int(11) NOT NULL, | |
`currency` varchar(10) NOT NULL, | |
`points` int(11) NOT NULL, | |
`transaction_state` varchar(50) DEFAULT NULL, | |
`payer_id` varchar(50) DEFAULT NULL, | |
`email` varchar(255) DEFAULT NULL, | |
`time_created` varchar(30) DEFAULT NULL, | |
`time_updated` varchar(30) DEFAULT NULL, | |
PRIMARY KEY (`id`) | |
) ENGINE=InnoDB; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment