開電商的目的是什麼?賺錢!!
怎麼賺錢? 接外包!! 賣商品!!
所以 我們的基本畫面之中 一定會有下訂單購買的地方!今天就來完成基本的結帳畫面。
就用結帳來當作 小電商專案的最後一步,首先 因為我一開始故意忘記規劃購物車,所以其實這個結帳頁面只能夠有一個商品。
但是後來我想到一個問題:如果沒有做購物車,直接點結帳進到結帳頁,變成我要把商品跟數量的資訊都放到網址,這樣才能順利get結帳頁面
但我覺得這樣不太好,所以結帳邏輯我會把它改成:在商品詳細頁點選 結帳去 的時候,發axios post給後端 新增一條訂單紀錄,然後把訂單id回傳之後再把使用者導到下單的地方。
因為要這樣做,所以我發現一開始order這張表規劃的type不太夠用,要多新增幾個狀態:未下單(預開訂單)、已下單(客戶端完成送單)
然後order_detail 好像還少一個紀錄數量的欄位XD。
看來一開始的規畫不夠完善,還是需要再多練習。
然後這裡可以在navicate改,這樣不用再跑一次migrate的應用,migration也要改,不過因為我們是一人團隊!!就不用應用了(要有信心自己改的是對的)
好處是:這樣推上git 之後 其他協作的夥伴才可以用到最新的資料表結構。
api.php
我先定義了一個post的group (雖然我覺得後面不會再多了
然後把路徑的註冊寫進去,寫法跟在web.php的時候一樣。
Route::group(['prefix' => 'post'], function () {
Route::post('order/create' , [\App\Http\Controllers\APIController::class,'createOrder'])
->name('create-order');
});
然後在Http/Controllers
底下新建立一個APIController.php
把基本的function寫起來就好,接下來要做一個 "表單驗證的檔案"
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
class APIController extends Controller
{
public function __construct()
{
}
public function createOrder()
{
}
}
檔案路徑在:Http/Requests 底下創建一個:OrderCreateRequest.php
這個檔案,主要是用來幫我們驗證丟進這隻api的資料,有沒有符合我們設定的規則,如果有符合我們才讓程式繼續往下跑,不符合就直接擋掉。
Laravel 官方文件說明
OrderCreateRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class OrderCreateRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'customer_id' => 'required',
'product' => 'required',
'amount' => 'required',
];
}
}
我們回到APIController,要在createOrder這個function之中使用驗證
public function createOrder(OrderCreateRequest $request)
{
}
改成這樣即可,因為我使用phpStorm 上面的use會自動幫我帶入,如果沒有use的話記得要use 歐~
接下來我們把request dd出來:dd($requset->validated())
validated()
這個方法,會回傳一個已驗證的資料給我們。
然後我們用postman這個軟體 來發送一組資料來看看:
注意:我們現在的這個網址是:http://{你的ip}/api/post/order/create
中間的api是laravel 幫我們加上去的:路由由 RouteServiceProvider 在一個組內加載,該組被分配了“api”中間件組。
Ok,這組api是可以正常運行的,接下來就是要把接收的資料塞回資料庫
在開始塞資料之前,因為我們要處理的資料表示訂單,訂單就有它的狀態。
為了定義明確,我這裡在Http底下建立一個Constant的direct然後 再建立一個 OrderConst.php 來明確規範每個狀態,如此可以避免其他地方打錯字而造成錯誤。
OrderConst.php
<?php
namespace App\Constant;
class OrderConst
{
const UNCHECK = 'UNCHECK' ;//未成立訂單
const CHECKOUT = 'CHECKOUT';//已成立訂單
const PREPARE = 'PREPARE';//訂單準備中
const DELIVERY = 'DELIVERY';//訂單運送中
const COMPLETE = 'COMPLETE';//完成
const RETURN = 'RETURN';//退貨
}
接下來 先塞資料 為了開發的方便,身分驗證等之後再補:
public function createOrder(OrderCreateRequest $request)
{
$post_data = $request->validated();
$product_model = Product::with('prices')->where('id' , $post_data['product'])->first();
$tmp_price = $product_model->prices->first()->price;
$create_order = Order::create([
'number' => (string)Str::uuid(),
'user_id' => $post_data['customer_id'],
'type' => OrderConst::UNCHECK,
'phone_number' => '尚未成立',
'address' => '尚未成立',
'price' => (int)$tmp_price * $post_data['amount']
]);
OrderDetail::create([
'order_id' => $create_order->id,
'product_id' => $post_data['product'],
'amount' => $post_data['amount'],
'price' => $tmp_price
]);
return $create_order->number;
}
ps.這裡可以依據不同的情形判斷是不是要使用 beginTransaction,不過因為預開訂單的邏輯並沒有很難,所以我就不用了
最後再發送一次postman,資料有成功新增!!
接著我們回到商品詳細頁button的地方,要來發送axios~
function goToCheckOut(e)
{
e.preventDefault();
axios.post(route('create-order') , {
"customer_id": 1,
"product": 1,
"amount": amount,
}).then( response => {
console.log(response.data);
}).catch( error => {
console.error(error);
});
}
注意:為了防止重複點擊而發送,可以加入sweetalert2來轉圈圈、也可以用點擊後加個 disabled 之類的方法,我這裡就用比較基礎的方式:
<button className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-indigo-50 hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
onClick={duplicateSend === 0 ? goToCheckOut : '' } >
結帳去
</button>
const [duplicateSend , setDuplicateSend] = useState(0);
function goToCheckOut(e)
{
setDuplicateSend(1);
axios.post(route('create-order') , {
"customer_id": 1,
"product": 1,
"amount": amount,
}).then( response => {
console.log(response.data);
}).catch( error => {
setDuplicateSend(0);
console.error(error);
});
}
接著我們發送一次axios然後到資料庫看,確實有新增成功order。
接下來就要做auth的限制了~
我這邊先修改 HomeConmntroller
多丟一個資料,把user一起丟到畫面上~
public function product($product_id)
{
$product = $this->product_service->getSingleProduct('id' , $product_id)->first();
$new_product = new ProductVariable($product);
$new_product->setProductPrice($product->prices
->where('user_type',$this->user_type)->first()->price);
return Inertia::render('ProductDetail' , [
'product' => $new_product,
'user' => $this->user ?? null
]);
}
其實如果前端直接用props接收資料,不用解構的話,依據 解讀breeze的code,好像也是可以拿到使用者的(不確定,要實際測試看看)
然後 Prodetail.jsx 改成以下:
如此一來,在有登錄跟沒登陸的情況下就會看到不同的按鈕,各別去做不同事情了!
export default function Home({product , user}){
const [amount , setAmount] = useState(1);
const [duplicateSend , setDuplicateSend] = useState(0);
function amountChange(e)
{
if(!isNaN(Math.round(e.target.value)))
setAmount(Math.round(e.target.value));
}
function goToCheckOut(e)
{
setDuplicateSend(1);
axios.post(route('create-order') , {
"customer_id": user.id,
"product": product.id,
"amount": amount,
}).then( response => {
document.location.href = route('send-order') + `/${response.data}`;
}).catch( error => {
setDuplicateSend(0);
console.error(error);
});
}
function goToLogin(e)
{
e.preventDefault();
document.location.href = route('login');
}
return(
<div className="flex justify-center">
<div className="pt-8 flex-col max-w-6xl">
<div className="max-w-6xl flex justify-center">
<div className="w-6/12">
<img src={product.product_image} alt="商品圖片" />
</div>
<div className="w-6/12 flex flex-col justify-around pl-3">
<p>商品名稱:{product.product_title}</p>
<p className="font-bold text-lg text-red-600">
價格:{product.product_price}
</p>
</div>
</div>
<div className="max-w-6xl pt-2">
<h3>商品敘述</h3>
<div>
<p>{product.product_description}</p>
</div>
</div>
<div className="pt-2 max-w-6xl">
<label>數量:</label>
<input type="number" value={amount} onChange={ amountChange }/>
<p className="text-red-600">總金額:{ product.product_price * amount}</p>
</div>
<div className="pt-2 max-w-6xl text-center">
<button className="inline-flex items-center px-3 py-2 border
border-transparent text-sm leading-4 font-medium rounded-md
text-gray-500 bg-indigo-50 hover:text-gray-700 focus:outline-none
transition ease-in-out duration-150"
onClick={
duplicateSend === 0 && user !== null ? goToCheckOut : goToLogin
}
>
{
`${user !== null ? '去結帳' : '去登錄'}`
}
</button>
</div>
</div>
</div>
);
}
明天,我們也把api的地方加個登入驗證,以及製作 送出訂單的畫面~
那就明天見啦!