ในการทดสอบเว็บฮุคของ Lemon Squeezy บน localhost ให้เริ่มแอปของคุณ เปิดเผยเส้นทางเว็บฮุคของมันด้วยอุโมงค์ HTTPS สาธารณะ สร้างเว็บฮุคโหมดทดสอบใน การตั้งค่า → เว็บฮุค และตรวจสอบร่างคำขอดิบกับ X-Signature ส่วนหัว. ใช้การชำระเงินทดสอบสำหรับข้อมูลคำสั่งซื้อจริง และควบคุมการจำลองการสมัครสมาชิกของ Lemon Squeezy สำหรับเหตุการณ์วงจรชีวิตที่ยากต่อการรอ
ลักษณะของการตั้งค่าในพื้นที่ที่ถูกต้อง
Lemon Squeezy ส่ง HTTP POST จากโครงสร้างพื้นฐานของมัน ดังนั้นจึงไม่สามารถเข้าถึง localhost บนคอมพิวเตอร์ของคุณ ทางอุโมงค์จะสิ้นสุด TLS สาธารณะและส่งต่อวิธีการ เนื้อหา และส่วนหัวที่ไม่เปลี่ยนแปลงไปยังเส้นทางท้องถิ่น เช่น http://localhost:3000/api/webhooks/lemonsqueezy.
การทดสอบครบถ้วนมีสี่ส่วน: โหมดทดสอบ Lemon Squeezy, URL การเรียกกลับสาธารณะ, รหัสลับสำหรับการลงชื่อเดียวกันทั้งในแดชบอร์ดและสภาพแวดล้อมท้องถิ่น, และตัวจัดการที่ตรวจสอบก่อนที่จะเปลี่ยนสถานะการสมัครหรือใบอนุญาต สิ่งนี้มีประโยชน์มากกว่าการโพสต์ JSON ที่คัดลอกมา curlเพราะการจัดส่งจริงจะทดสอบทั้งการกำหนดเส้นทางและลายเซ็นที่สร้างโดยผู้ให้บริการ
สร้างเว็บฮุคและส่งเหตุการณ์ทดสอบ
- เรียกใช้แอปของคุณบนพอร์ต 3000 และตรวจสอบว่าเส้นทางยอมรับ
POST. - เริ่มอุโมงค์ด้วย
npx portpreview 3000. - สลับแดชบอร์ด Lemon Squeezy ไปยังโหมดทดสอบ
- เปิดการตั้งค่า → เว็บฮุคและเพิ่ม
https://your-subdomain.portpreview.dev/api/webhooks/lemonsqueezy. - สร้างความลับการลงชื่อแบบสุ่มที่แข็งแรง เก็บไว้เป็น
LEMONSQUEEZY_WEBHOOK_SECRETและเลือกเฉพาะเหตุการณ์ที่แอปพลิเคชันของคุณใช้งาน - สร้างการซื้อทดสอบหรือใช้การดำเนินการจำลองการสมัครสมาชิกโหมดทดสอบที่มีอยู่ จากนั้นตรวจสอบบันทึกการส่งและการตอบสนองภายในเครื่องของคุณ
ทางการ เอกสารเว็บฮุกของ Lemon Squeezy ช่วยให้คุณตรวจสอบ payloads ล่าสุดและส่ง webhook ที่บันทึกไว้ใหม่จากหน้าการตั้งค่า การส่งซ้ำมีประโยชน์สำหรับการทดสอบ regression แต่แอปพลิเคชันของคุณต้องถือว่าเป็นรายการซ้ำแทนที่จะเป็นเหตุการณ์ทางธุรกิจครั้งที่สอง
ตรวจสอบ X-Signature ก่อนวิเคราะห์ JSON
Lemon Squeezy คำนวณ digest แบบ HMAC-SHA256 เหนือ payload ของคำขอโดยใช้ความลับการเซ็นและส่ง digest ในรูปแบบเลขฐานสิบหก X-Signature. คำนวณแฮชจากไบต์ของเนื้อหาดิบที่แน่นอนและเปรียบเทียบบัฟเฟอร์ที่มีความยาวเท่ากันด้วยฟังก์ชันที่ใช้เวลาคงที่ หลัก เอกสารการขอลงนาม รวมตัวอย่างสำหรับ Node, PHP, Python และ Ruby
// app/api/webhooks/lemonsqueezy/route.ts
import crypto from 'node:crypto';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET;
if (!secret) return new NextResponse('server misconfigured', { status: 500 });
const rawBody = await request.text();
const suppliedHex = request.headers.get('x-signature') ?? '';
if (!/^[0-9a-f]{64}$/i.test(suppliedHex)) {
return new NextResponse('invalid signature', { status: 401 });
}
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest();
const supplied = Buffer.from(suppliedHex, 'hex');
if (supplied.length !== expected.length ||
!crypto.timingSafeEqual(supplied, expected)) {
return new NextResponse('invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
await acceptEvent(event);
return NextResponse.json({ received: true });
}
อย่าโทร request.json() อันดับแรก การทำการเรียงลำดับใหม่ของวัตถุที่ได้อาจเปลี่ยนไบต์ที่ดูไม่สำคัญและทำให้ HMAC เสีย การตรวจสอบความยาวก็เป็นสิ่งสำคัญเช่นกันเพราะ Node's timingSafeEqual ทำงานผิดพลาดเมื่อความยาวบัฟเฟอร์แตกต่างกัน อย่างเป็นทางการของ Lemon Squeezy บทเรียนเว็บฮุก Next.js ตามลำดับร่างดิบเดียวกัน
เข้าใจข้อมูลเพย์โหลดก่อนอัปเดตฐานข้อมูลของคุณ
ร่างกายคือวัตถุทรัพยากร JSON:API meta.event_name ระบุเหตุการณ์ meta.custom_data ส่งข้อมูลที่ผ่านการชำระเงินมา และ data ประกอบด้วยคำสั่งซื้อ การสมัครสมาชิก หรือทรัพยากรคีย์ใบอนุญาต Lemon Squeezy ยังส่ง X-Event-Name; พิจารณาร่างที่ลงนามแล้วเป็นข้อมูลหลักและใช้ส่วนหัวสำหรับวินิจฉัยการกำหนดเส้นทาง
คำสั่งซื้อและตัวตนของลูกค้า
สำหรับ order_created, ทำการแมปการซื้อไปยังผู้ใช้ภายในโดยใช้ตัวระบุที่สร้างโดยเซิร์ฟเวอร์ในข้อมูลกำหนดเองของการชำระเงิน ไม่ควรเชื่อถือรหัสผู้ใช้ที่ผู้ใช้ให้มาโดยไม่ตรวจสอบความเป็นเจ้าของ เก็บ ID ของทรัพยากรและ ID ของตัวแปรของ Lemon Squeezy ไว้เพื่อให้สามารถปรับยอดเหตุการณ์การสมัครสมาชิกหรือการคืนเงินในภายหลังได้
การสมัครสมาชิกเป็นเครื่องจักรสถานะ
จัดการ subscription_created, subscription_updated, subscription_cancelled, subscription_resumed, และ subscription_expired เป็นการเปลี่ยนผ่านที่แตกต่างกัน การยกเลิกอาจหมายความว่าการเข้าถึงยังคงใช้ได้จนกว่าจะสิ้นสุดรอบการเรียกเก็บเงิน; การหมดอายุเป็นสัญญาณที่ชัดเจนกว่าว่าสมาชิกภาพได้สิ้นสุดลง แล้วจัดการวันที่และสถานะของโมเดลแทนการสลับค่า boolean เพียงค่าเดียว
ความล้มเหลวในการชำระเงินและการกู้คืน
ทดสอบการล้มเหลวและการกู้คืนกระบวนการต่ออายุในกรณีที่โหมดทดสอบรองรับ การชำระเงินล้มเหลวไม่จำเป็นต้องยกเลิกการเข้าถึงทันที ให้ใช้ระยะเวลาปลอดชำระหนี้ตามที่นโยบายผลิตภัณฑ์ของคุณกำหนด และปล่อยให้การชำระเงินหรือเหตุการณ์การสมัครสมาชิกในภายหลังทำให้บัญชีสอดคล้องกับสถานะผู้ให้บริการล่าสุด
การลองใหม่ต้องใช้การประมวลผลแบบไม่เปลี่ยนผลลัพธ์
ตามที่ Lemon Squeezy กล่าว การอ้างอิงคำขอเว็บฮุค, a 200 เครื่องหมายการตอบสนองจับได้ว่าเป็นความสำเร็จ สถานะอื่น ๆ จะถูกลองใหม่อีกสูงสุดสามครั้งโดยมีการหน่วงเวลาตามกำลังที่แสดงเป็นประมาณ 5, 25, และ 125 วินาที แดชบอร์ดส่งซ้ำสร้างเหตุผลอีกครั้งว่ากิจกรรมทางตรรกะเดียวกันสามารถมาถึงมากกว่าหนึ่งครั้ง
สร้างคีย์ idempotency จากฟิลด์ payload ที่ลงนามอย่างมั่นคง วิธีปฏิบัติที่เหมาะสมคือต้องมีแถวฐานข้อมูลที่ไม่ซ้ำกันสำหรับ ID ของ resource ของ webhook, ชื่อเหตุการณ์, และเวลาประทับเหตุการณ์ หรือแฮชของเนื้อหาที่ลงนามดิบเมื่อไม่มี ID ของเหตุการณ์เฉพาะ แทรกรายการนั้นและอัปเดตสถานะของแอปพลิเคชันในธุรกรรมเดียว คืนค่า 200 สำหรับสำเนาที่เสร็จสิ้นแล้วก่อนหน้านี้
BEGIN;
INSERT INTO webhook_receipts (fingerprint, received_at)
VALUES ($1, now())
ON CONFLICT DO NOTHING;
-- Continue only if one row was inserted.
UPDATE subscriptions SET provider_status = $2 WHERE provider_id = $3;
COMMIT;
อย่านำการส่งอีเมลหรือการเรียก API ของบุคคลที่สามที่ช้าไปเป็นส่วนหนึ่งของเส้นทางการยืนยัน ให้เก็บงานในธุรกรรมเดียวกัน ตอบกลับ และให้พนักงานจัดการผลข้างเคียง ส่วนที่กว้างขึ้น คู่มือการลองส่งเว็บฮุคใหม่และความไม่ซ้ำกันของคำสั่ง ครอบคลุมรูปแบบนี้
การแก้ไขปัญหาการทดสอบ webhook ของ Lemon Squeezy บน localhost
ไม่มีคำขอใดเข้าถึงแอป
ตรวจสอบว่าท่อยังทำงานอยู่หรือไม่, การเรียกกลับมีส่วนต่อท้ายเส้นทาง, และเฟรมเวิร์กของคุณมีตัวจัดการ POST ที่เส้นทางนั้นพอดี ลองเรียกใช้งานเอ็นด์พอยต์สาธารณะด้วยตัวเอง ท่อ 502 ชี้ไปที่พอร์ตภายในเครื่อง; เฟรมเวิร์ก 404 ชี้ไปที่การวางเส้นทาง
ลายเซ็นทุกอันไม่ถูกต้อง
ยืนยันว่าโหมดทดสอบและโหมดใช้งานจริงไม่ได้ใช้บันทึก webhook หรือความลับที่ต่างกัน อ่านเนื้อหาเพียงครั้งเดียวในรูปแบบข้อความหรือไบต์ อย่าตัดเนื้อหาหรือความลับ และตรวจสอบให้แน่ใจว่า middleware JSON แบบทั่วโลกไม่ทำงานก่อน ตรวจสอบว่า X-Signature มาถึงแล้ว แต่ไม่เคยพิมพ์ความลับในการลงชื่อ
การจัดส่งพยายามส่งซ้ำอยู่ตลอด
เลมอน สกีซซี่ คาดหวัง 200, ไม่ใช่การเปลี่ยนเส้นทางไปยังหน้าล็อกอิน ยกเว้นเส้นทาง webhook จาก CSRF และ middleware ของเซสชันผู้ใช้ในขณะที่ยังคงการตรวจสอบลายเซ็น ตรวจจับข้อผิดพลาดของฐานข้อมูล ทำให้การทำงานสั้น และตรวจสอบให้แน่ใจว่ามีการส่งการตอบกลับความสำเร็จจริง ๆ ในทุกสาขาของเหตุการณ์ที่จัดการแล้ว
การจำลองการสมัครใช้งานไม่สามารถใช้งานได้
การควบคุมการจำลองต้องการข้อมูลการสมัครใช้งานโหมดทดสอบที่เหมาะสม การจำลองการชำระเงินบางรายการจำเป็นต้องมีประวัติการต่ออายุ สร้างการสมัครใช้งานทดสอบหรือการซื้อทดสอบที่จำเป็นก่อน จากนั้นใช้เมนูการดำเนินการสมัครสมาชิกตามที่อธิบายใน Lemon Squeezy คู่มือสำหรับนักพัฒนาเว็บฮุก.
รายการตรวจสอบความปลอดภัยและการเริ่มใช้งานจริง
- สร้างรหัสลับสำหรับการเซ็นแบบสุ่ม เก็บไว้ในการตั้งค่าสภาพแวดล้อม และหมุนมันหากถูกเปิดเผย
- ตรวจสอบ HMAC บนตัวเนื้อหาดิบก่อนการแยกวิเคราะห์ JSON การเขียนฐานข้อมูล การสร้างใบอนุญาต หรือการเปลี่ยนแปลงการเข้าถึง
- ใช้การเปรียบเทียบที่ปลอดภัยต่อเวลาและปฏิเสธลายเซ็นที่หายไปหรือมีรูปแบบไม่ถูกต้อง
- เก็บความลับและบันทึกของโหมดทดสอบและเว็บฮุคสดแยกจากกัน
- อนุญาตเฉพาะ POST บังคับใช้ขีดจำกัดขนาดเนื้อหาที่เหมาะสม จำกัดความถี่ของคำขอที่ไม่ถูกต้อง และลบข้อมูลลูกค้าออกจากบันทึก
- ทดสอบการทำซ้ำ เหตุการณ์วงจรชีวิตที่ไม่เรียงลำดับ การหยุดทำงานของฐานข้อมูล และการกู้คืนเมื่อหมดเวลา ก่อนที่จะเปลี่ยนช่องท่อเป็น URL การผลิตของคุณ
การชำระเงินแบบเส้นทางความสุขที่ผ่านไปได้พิสูจน์เพียงว่าเหตุการณ์หนึ่งเกิดขึ้น การรวมระบบที่พร้อมใช้งานในสภาพการผลิตพิสูจน์การปฏิเสธลายเซ็นต์ ความปลอดภัยต่อการซ้ำ การรวมวงจรชีวิต และการกู้คืนจากการตอบสนองที่ไม่ใช่ 200 สำหรับกับดักการเข้ารหัสลับพื้นฐาน ดูที่ คู่มือการตรวจสอบลายเซ็น.
