# One-time purchase

When a new purchase occurs, the code stores the event in a table called `Order`.

Here are the steps needed to support it.

### Database

Add this table definition to your `prisma/schema.prisma` file:

{% code title="schema.prisma" %}

```prisma
model Order {
  id                  Int       @id @default(autoincrement())
  email               String
  lemonOrderId        String?
  lemonProductId      String
  lemonVariantId      String?
  lemonVariantName    String?
  lemonPlanName       String?
  lemonPlanPrice      String?
  lemonSubscriptionId String?
  createdAt           DateTime  @default(now())
  updatedAt           DateTime  @updatedAt
  validUntil          DateTime?
  cancelUrl           String?
  updateUrl           String?
  status              String?
}
```

{% endcode %}

Push the new table to your database:

{% code title="terminal" %}

```bash
npx prisma db push
```

{% endcode %}

Then open the file `src/app/api/webhooks/lemonsqueezy/route.ts` and replace its content with this:

{% code title="src/app/api/webhooks/lemonsqueezy/route.ts" %}

```typescript
import { NextResponse } from "next/server";
import rawBody from "raw-body";
import crypto from "crypto";
import { Readable } from "stream";
import { headers } from "next/headers";
import { prismaClient } from "@/prisma/db";

type ISODate = string;

export type WebhookRequest = {
  meta: {
    event_name:
      | "order_created"
      | "order_refunded"
      | "subscription_created"
      | "subscription_updated"
      | "subscription_cancelled"
      | "subscription_resumed"
      | "subscription_expired"
      | "subscription_paused"
      | "subscription_unpaused"
      | "subscription_payment_failed"
      | "subscription_payment_success"
      | "subscription_payment_recovered"
      | "license_key_created"
      | "license_key_updated";
  };
  data: {
    type: "subscriptions";
    id: string;
    attributes: {
      store_id: number;
      customer_id: number;
      order_id: number;
      order_item_id: number;
      product_id: number;
      variant_id: number;
      product_name: string;
      variant_name: string;
      user_name: string;
      user_email: string;
      status: string;
      status_formatted: string;
      card_brand: string;
      card_last_four: string;
      pause: null;
      cancelled: boolean;
      trial_ends_at: ISODate;
      billing_anchor: number;
      urls: {
        update_payment_method: string;
      };
      renews_at: "2023-01-24T12:43:48.000000Z";
      ends_at: null;
      created_at: "2023-01-17T12:43:50.000000Z";
      updated_at: "2023-01-17T12:43:51.000000Z";
      test_mode: false;
      first_order_item: {
        id: number;
        price: number;
        order_id: number;
        price_id: number;
        test_mode: boolean;
        created_at: ISODate;
        product_id: number;
        updated_at: ISODate;
        variant_id: number;
        product_name: string;
        variant_name: string;
      };
    };
    relationships: {
      store: {
        links: {
          related: string;
          self: string;
        };
      };
      customer: {
        links: {
          related: string;
          self: string;
        };
      };
      order: {
        links: {
          related: string;
          self: string;
        };
      };
      "order-item": {
        links: {
          related: string;
          self: string;
        };
      };
      product: {
        links: {
          related: string;
          self: string;
        };
      };
      variant: {
        links: {
          related: string;
          self: string;
        };
      };
      "subscription-invoices": {
        links: {
          related: string;
          self: string;
        };
      };
    };
    links: {
      self: string;
    };
  };
};

export async function POST(request: Request) {
  const body = await rawBody(Readable.from(Buffer.from(await request.text())));
  const headersList = headers();
  const payload: WebhookRequest = JSON.parse(body.toString());
  console.log(">>> payload", payload);
  const sigString = headersList.get("x-signature");
  const secret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET as string;
  const hmac = crypto.createHmac("sha256", secret);
  const digest = Buffer.from(hmac.update(body).digest("hex"), "utf8");
  const signature = Buffer.from(
    Array.isArray(sigString) ? sigString.join("") : sigString || "",
    "utf8"
  );

  // validate signature
  if (!crypto.timingSafeEqual(digest, signature)) {
    return NextResponse.json({ message: "Invalid signature" }, { status: 403 });
  }

  const userEmail = payload.data.attributes.user_email;

  const eventName = payload.meta.event_name;

  const userOrder = await prismaClient.order.findFirst({
    where: {
      email: userEmail,
      status: "CREATED",
    },
  });

  if (eventName === "order_created") {
    await prismaClient.order.create({
      data: {
        email: userEmail,
        lemonOrderId:
          payload.data.attributes.first_order_item.order_id.toString(),
        lemonProductId:
          payload.data.attributes.first_order_item.product_id.toString(),
        lemonVariantId:
          payload.data.attributes.first_order_item.variant_id.toString(),
        lemonVariantName: payload.data.attributes.first_order_item.variant_name,
        lemonPlanName: payload.data.attributes.first_order_item.product_name,
        lemonPlanPrice: null,
        lemonSubscriptionId: null,
        validUntil: new Date(),
        updateUrl: null,
        status: "CREATED",
      },
    });

  }

  if (eventName === "order_refunded") {
    if (userOrder) {
      await prismaClient.order.update({
        where: {
          id: userOrder.id,
        },
        data: {
          status: "REFUNDED",
        },
      });
    }

    
  }

  return NextResponse.json({ result: true }, { status: 200 });
}
```

{% endcode %}

### Lemon Squeezy

Enable the order\_created order\_refunded events on Lemon Squeezy, in the Webhook configuration.

**Settings > Webhooks > Edit the WebHook**

<figure><img src="/files/gqOS32oCBZcOevroTSjp" alt=""><figcaption></figcaption></figure>

Now, whenever a customer buys your product, a new record will be created in the table `public.Order`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.shipped.club/features/payments/lemon-squeezy/one-time-purchase.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
