ลองเล่น Stripe payment gateway กัน 30 สิงหาคม 2566 / 6 min read
สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
Content นี้สนับสนุนโดย เหล่าผู้ตามของ Mikelopster เช่นเดิม (มีคนทักมาถามเรื่อง Payment เลยรู้สึกอยากแชร์เรื่องนี้ไว้ให้เป็นความรู้เพิ่มเติม)
เลยรู้สึกว่า อยากลอง Stripe (ปกติเราเคยลอง Omise) และจะได้มาเล่าเรื่อง payment flow ไปเลย
Stripe คืออะไร ?
Stripe คือ Platform ชำระเงินที่ออกแบบมาเพื่อให้รับชำระเงินและส่งเงินได้ทั่วโลก ซึ่งโจทย์ของ Stripe ก็คือการเป็นตัวกลางในการชำระเงินระหว่าง user และธนาคาร (Payment gateway)
อ่านเพิ่มเติมกันได้
https://stripe.com/th-be/payments
เราจะลองเล่น Stripe กันยังไงดี
ทีนี้ Stripe เองก็มี API ที่ให้นักพัฒนามาลองใช้เพื่อใช้สำหรับพัฒนาช่องทางชำระเงินของตัวเอง ซึ่งมีทั้ง การ implement โดยการทำ form ขึ้นมาและใช้การสร้าง token ผ่าน Stripe หรือการใช้ redirect payment อย่างหน้า checkout ของ Stripe ก็สามารถทำได้
เคสที่เราจะมาลองเล่นกันเป็นเคสแบบ one time checkout คือ เป็นการชำระเงินแบบจ่ายเงินครั้งเดียวจบ ซึ่งเป็นตัวอย่างที่เล่นง่ายที่สุด และผมคิดว่าเห็นภาพง่ายที่สุด
ตามเอกสารนี้เลย (ช่องทาง Online Payment)
https://stripe.com/docs/payments/checkout/how-checkout-works
ซึ่งจริงๆ มันมีรูปแบบ payment ที่หลากหลายมากทั้งจะ จ่ายปกติ (ในตัวอย่างที่เราจะทำ) หรือ subscription
https://stripe.com/docs/development/quickstart
จะมาทำ
payment api (ผ่าน node) สำหรับสร้าง order และรับชำระเงิน
ทำหน้า UI สำหรับชำระเงิน
และทำหน้า success, fail เพื่อมารองรับหลังจาก payment เสร็จ
เป็นการจำลอง flow ชำระเงินอย่างง่ายกัน
Config เริ่มต้น เตรียมตัว project กันก่อน
1. setup docker สำหรับ project นี้ (docker-compose.yml)
สำหรับ project นี้เราจะ setup docker 2 ตัวคือ mysql และ phpmyadmin เท่านั้น
โดยโจทย์ของ mysql คือการเก็บข้อมูล user และ order คู่กันไว้ เป็นการเก็บ status ที่ได้รับจาก Stripe มาว่าส่ง status กลับมาถูกต้องหรือไม่
docker-compose.yml
command : --default-authentication-plugin=mysql_native_password
MYSQL_ROOT_PASSWORD : root
- mysql_data:/var/lib/mysql
image : phpmyadmin/phpmyadmin:latest
container_name : phpmyadmin
2. setup folder project
./src
เป็นสถานที่เก็บไฟล์ html (ฝั่ง Frontend) ของหน้าเว็บไว้ประกอบด้วย
checkout.html = หน้าสำหรับรับชำระเงิน
main.js = สำหรับเก็บ function สำหรับการชำระเงินและยิง API เอาไว้
cancel.html = หน้าที่บอกว่าชำระเงินไม่สำเร็จ
success.html = หน้าที่บอกว่าชำระเงินสำเร็จแล้ว
index.js
จะใช้สำหรับเก็บ nodejs ของฝั่ง API เอาไว้ เพื่อใช้สำหรับสร้าง API Placeorder, Webhook และ API สำหรับ check status ของ order
3. code index.js
เราจะทำการ setup code ไว้เริ่มต้นเป็นแบบนี้
const cors = require ( " cors " );
const express = require ( " express " );
const mysql = require ( " mysql2/promise " );
const { v4 : uuidv4 } = require ( " uuid " );
require ( " dotenv " ). config ();
const stripe = require ( " stripe " )(process.env. STRIPE_SECRET_KEY );
// This is your Stripe CLI webhook secret for testing your endpoint locally.
const endpointSecret = " xxxx " ; // เอาได้จากเว็บของ Stripe
const initMySQL = async () => {
conn = await mysql. createConnection ({
/* code ที่เขียนด้านล่างนี้จะเป็นการเพิ่มเติมส่วนจากตรงนี้ */
app. listen (port, async () => {
console. log ( " Server started at port 8000 " );
พาเล่น Dashboard Stripe ก่อนว่ามีอะไรบ้าง
ให้ทุกคนทำการสมัคร Stripe ผ่าน https://stripe.com/
Stripe มี sandbox เอาไว้ให้ developer เล่น = ไม่ต้องจ่ายเงินจริงก็สามารถยิงจ่ายเงินทดสอบได้
หน้าที่สำคัญ
เมนู การชำระเงิน: หน้าสำหรับดูการชำระเงิน (สามารถดู log การชำระเงินได้)
หน้านักพัฒนา เป็นการดูการ Call API ว่ามีการยิงมาเท่าไหร่บ้าง
หน้านักพัฒนา > คีย์ API สำหรับดู Public, Secret key สำหรับการยิง Payment Stripe
หน้านักพัฒนา > Webhook สำหรับการใส่ API webhook
เริ่ม implement !
เรามาดูภาพรวมก่อนว่าเราจะทำอะไรกันบ้าง
Stripe Database Backend Frontend Stripe Database Backend Frontend 1. Make payment create uuid (unique id for order) = order_id use session_id in redirectPayment (Stripe SDK) 2. Webhook (receive result) when payment finish 3. Check success (from database) when payment finish if status is not 'completed' = redirect to cancel page send user, address (/api/checkout) make session payment (ขอจ่ายเงิน) return session_id create order with order_id, user, address, session_id (เก็บคู่กันไว้) return status: ok return session_id redirect to payment page (go to step 3) send data to /api/webhook (success or fail) update status to database filter from "session_id" redirect back to success (or cancel) page send order_id to /api/order/:id (for recheck status) check status from order_id return order data return order data
เราจะแบ่งงานออกเป็น 2 ฝั่งคือ Backend และ Frontend โดยเราจะเริ่มทำจาก Backend ก่อน
1. Backend (API)
/api/checkout
(Placeorder) สำหรับเก็บข้อมูล order และสร้าง Payment
app. post ( " /api/checkout " , express. json (), async ( req , res ) => {
const { product , information } = req.body;
// create payment session
const orderId = uuidv4 ();
const session = await stripe.checkout.sessions. create ({
payment_method_types : [ " card " ],
unit_amount : product.price * 100 ,
quantity : product.quantity,
success_url : `http://localhost:8888/success.html?id= ${ orderId } ` ,
cancel_url : `http://localhost:8888/cancel.html?id= ${ orderId } ` ,
// create order in database (name, address, session id, status)
console. log ( " session " , session);
address : information.address,
const [ result ] = await conn. query ( " INSERT INTO orders SET ? " , data);
message : " Checkout success. " ,
console. error ( " Error creating user: " , error.message);
res. status ( 400 ). json ({ error : " Error payment " });
/api/webhook
สำหรับรับข้อมูลจาก Stripe มา update ที่ database ของเรา
app. post ( " /webhook " , express. raw ({ type : " application/json " }), async ( req , res ) => {
const sig = req.headers[ " stripe-signature " ];
event = stripe.webhooks. constructEvent (req.body, sig, endpointSecret);
res. status ( 400 ). send ( `Webhook Error: ${ err . message } ` );
case " checkout.session.completed " :
const paymentSuccessData = event.data.object;
const sessionId = paymentSuccessData.id;
status : paymentSuccessData.status,
const result = await conn. query ( " UPDATE orders SET ? WHERE session_id = ? " , [
console. log ( " === update result " , result);
// event.data.object.id = session.id
// event.data.object.customer_details คือข้อมูลลูกค้า
console. log ( `Unhandled event type ${ event . type } ` );
// Return a 200 response to acknowledge receipt of the event
/api/order/:id
สำหรับรับ order_id มาเพื่อ recheck status ก่อนแสดงว่า success หรือไม่
app. get ( " /order/:id " , async ( req , res ) => {
const orderId = req.params.id;
const [ result ] = await conn. query ( " SELECT * from orders where order_id = ? " , orderId);
const selectedOrder = result[ 0 ];
errorMessage : " Order not found " ,
console. log ( " error " , error);
res. status ( 404 ). json ({ error : error.errorMessage || " System error " });
ก่อนเริ่มให้ทำการ binding webhook เข้า local ก่อน
เมื่อทุกอย่างเรียบร้อยลองทดสอบยิงข้อมูลด้วย API กันดูก่อน
Test card สามารถดูได้จาก
https://stripe.com/docs/testing
ลองทดสอบการเช็ค status order ดูว่าถูกไหม
2. Frontend
เราจะทำทั้งหมด 4 อย่างคือ
สร้าง main.js ก่อนเพื่อรวมคำสั่งสำหรับการส่ง payment เอาไว้ (และเรียกใช้ stripe library)
const stripe = Stripe ( " ... " );
const placeorder = async ( data ) => {
const response = await axios. post ( " http://localhost:8000/api/checkout " , requestData);
const session = response.data;
stripe. redirectToCheckout ({
console. log ( " error " , error);
checkout.html จำลองหน้าสำหรับเตรียมจ่ายเงิน
< div >Name < input type = " text " name = " name " /></ div >
< textarea name = " address " ></ textarea >
< button id = " checkout " >Checkout</ button >
< script src = " https://cdnjs.cloudflare.com/ajax/libs/axios/1.4.0/axios.min.js " ></ script >
< script src = " https://js.stripe.com/v3/ " ></ script >
< script src = " ./main.js " ></ script >
checkout. addEventListener ( " click " , async () => {
const name = document. querySelector ( " input[name=name] " ).value;
const address = document. querySelector ( " textarea[name=address] " ).value;
success.html จำลองหน้าสำหรับ success
< div >ชื่อ: < span id = " name " ></ span ></ div >
< div >ที่อยู่: < span id = " address " ></ span ></ div >
< script src = " https://cdnjs.cloudflare.com/ajax/libs/axios/1.4.0/axios.min.js " ></ script >
window. onload = async () => {
const urlParams = new URLSearchParams (window.location.search);
const orderId = urlParams. get ( " id " );
const response = await axios. get ( `http://localhost:8000/order/ ${ orderId } ` );
const orderData = response.data;
if (orderData.status !== " complete " ) {
window.location.href = " http://localhost:8888/cancel.html " ;
document. getElementById ( " name " ).innerText = orderData.name;
address.innerText = orderData.address;
cancal.html จำลองหน้ากรณี fail หรือ cancel
ไม่มีอะไร เพราะหน้านี้เป็น fallback
การถึงหน้านี้ = ไม่สามารถไปไหนต่อได้นอกจากต้องชำระเงินใหม่ (ต่อให้เช็คท้ายที่สุดก็ต้องพาไปชำระเงินใหม่)
เว้นแต่ อยาก handle case ว่า ถ้า success จริงๆ พากลับไปหน้า success
Github code
สำหรับใครที่อยากดู source code ฉบับเต็ม มาดูที่นี่ได้
https://github.com/mikelopster/stripe-payment-example
Reference เพิ่มเติม
Related Post
13 ธ.ค. 2566 14 มี.ค. 2567 1 ก.พ. 2567 13 ก.ย. 2566