> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe Migration Tool

> Keep your existing Stripe calls and hand expiring subscriptions over to Waffo in three steps.

**What it does:** Before the paid Stripe period ends, your customer completes card entry and any required 3DS challenge on Waffo. Waffo collects nothing early, starts the first charge automatically at handoff, exposes the waiting state through inquiry, and lets you cancel before handoff to prevent that charge.

**Integration takes three steps:** switch to `WaffoStripe.client(...)`, add the routing marker and handoff time to the target subscription, and connect the Waffo Webhook. Your existing `com.stripe.*` types and calling pattern stay in place. [Quick start](#quick-start) shows the complete code change; before production, also persist the idempotency key and subscription mapping described in the integration guide.

<Note>
  The migration tool is available for Java today: `com.waffo:waffo-java-stripe`. Node.js, Python, and Go versions are coming soon.
</Note>

## This is not the native Waffo SDK

Waffo offers two entirely different integration paths. Confirm which one fits you before going further.

|                | Native SDK                                                        | Stripe migration tool                                                                    |
| -------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Artifact       | `waffo-java`, `waffo-node`, and so on                             | `waffo-java-stripe`                                                                      |
| Origin         | Generated from Waffo's `openapi.json`                             | A hand-written compatibility layer wrapping the official `stripe-java` you already bring |
| Code you write | Waffo's interfaces and data structures                            | Stays `com.stripe.*`, unchanged                                                          |
| Coverage       | The full Waffo API                                                | A subset of subscription Checkout                                                        |
| Who it suits   | New integrations, or teams willing to rewrite against Waffo's API | Existing Stripe integrations looking to migrate subscriptions cheaply                    |

**For a new integration, use the [native SDK](/docs/en/developer-docs/sdk/java).** The adapter earns its place in exactly one situation: you already run subscriptions on Stripe and the cost of changing that code is your main concern.

## What the migration tool does for you

<CardGroup cols={2}>
  <Card title="Routing" icon="route">
    Decides per request whether it goes to Waffo or straight to Stripe, so your calling code needs no branching.
  </Card>

  <Card title="Parameter translation" icon="languages">
    Turns Stripe's `SessionCreateParams` into a Waffo subscription request — amount, period, currency, payment method, cashier language.
  </Card>

  <Card title="Response mapping" icon="arrow-right-left">
    Packs Waffo's response back into Stripe `Session` and `Subscription` objects, so your usual getters keep working.
  </Card>

  <Card title="Notification translation" icon="bell">
    Translates Waffo subscription notifications into Stripe `Event`s, so your existing Webhook branches still apply.
  </Card>
</CardGroup>

## How it works

The adapter hands you a standard `StripeClient`. It inspects each request and applies three rules:

| Rule             | Condition                                                          | Destination       |
| ---------------- | ------------------------------------------------------------------ | ----------------- |
| Create routing   | Subscription-mode Checkout create carrying `metadata.source=waffo` | Waffo             |
| Retrieve routing | Object id starting with `wsub_` or `wcs_`                          | Waffo             |
| Pass-through     | Everything else                                                    | Stripe, unchanged |

In other words, **untagged calls are completely unaffected** — one-time payments, untagged subscriptions, customer objects, price objects all behave exactly as they did before. You can move a subset of subscriptions to Waffo and run both sides in parallel.

A complete subscription payment flows like this:

<Steps>
  <Step title="Create the subscription Checkout">
    You call `client.checkout().sessions().create(params)` as usual; `params` just carries one more line, `metadata.source=waffo`. The adapter translates it into a Waffo subscription request.
  </Step>

  <Step title="Receive the cashier URL">
    You still get a Stripe `Session` object, and `session.getUrl()` holds the Waffo cashier URL. **Your redirect code needs no change** — it never cared where that URL pointed.
  </Step>

  <Step title="The customer pays">
    The customer completes payment on the Waffo cashier. Your application is not involved in this leg.
  </Step>

  <Step title="Receive and translate the notification">
    Waffo posts the subscription notification to your configured `notifyUrl`. Your endpoint calls `WaffoStripeWebhooks.handle(...)` and gets a `WaffoStripeWebhookResult`; call `result.getEvent()` to obtain the standard Stripe `Event`.
  </Step>

  <Step title="Existing business logic runs">
    Your existing `switch (event.getType())` handling for `customer.subscription.created` and `invoice.paid` needs no change; it receives the translated event directly.
  </Step>
</Steps>

## What is supported

The first phase targets **hosted-cashier subscriptions**. Use the table below to judge whether your current setup fits.

| Scenario                                                                                | Result                                                                            | What you do                                                                           |
| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Subscription mode, hosted cashier, single line item, card or a recurring-capable wallet | Routed to Waffo                                                                   | Add the routing tag and explicitly pass a persisted idempotency key                   |
| The Stripe subscription is approaching the end of its paid period                       | The customer authorizes on Waffo in advance, and the first charge runs at handoff | Use the Stripe period end as `billing_cycle_anchor` and set `proration_behavior=none` |

For the complete fallback conditions, reason codes, and cancellation boundaries, see [unsupported Stripe usage in the integration guide](/docs/en/developer-docs/tools-and-references/references/stripe-adapter-integration#unsupported-stripe-usage).

## Move expiring Stripe subscriptions to Waffo

Suppose the current paid period of a Stripe subscription ends at time `T`. Use your existing Stripe flow to stop renewal at `T`, then create a Waffo subscription for the same customer that starts at `T`:

<Steps>
  <Step title="Read the handoff time">
    Read the paid-through period end `T` from the Stripe subscription.
  </Step>

  <Step title="Set the Waffo start time">
    Set `subscription_data.billing_cycle_anchor` to `T` on the Waffo-routed request and set `proration_behavior=none`.
  </Step>

  <Step title="Authorize in advance">
    Before `T`, the customer opens the Waffo cashier and completes card entry and any required 3DS challenge. No first charge is collected yet.
  </Step>

  <Step title="Confirm the waiting state">
    While waiting, retrieve the `wsub_…` subscription. Both `billing_cycle_anchor` and `current_period_end` equal `T`; `metadata.waffo_current_period=0` means period 1 has not started.
  </Step>

  <Step title="Charge automatically at handoff">
    At `T`, Waffo initiates the first charge automatically. The customer does not need to return.
  </Step>
</Steps>

If the customer cancels the migration before `T`, `Subscription.cancel("wsub_…")` immediately cancels the Waffo subscription and prevents the scheduled first charge.

<Note>
  The Stripe migration tool manages the Waffo-side application, inquiry, and cancellation. It does not modify the original Stripe subscription. Use your existing Stripe code or dashboard to end the old subscription at the same `T`.
</Note>

## Quick start

The following three steps show the key code changes. In addition to those changes, persist the idempotency key before sending the request and store the relationship between the Waffo subscription id and your business order after creation. These are partial snippets for an existing class and assume your project's surrounding imports, injected fields, and business methods.

### Step 1 — Swap the client constructor

<CodeGroup>
  ```java After theme={null}
  import com.stripe.Stripe;
  import com.stripe.StripeClient;
  import com.waffo.stripe.WaffoStripe;                             // ← new
  import com.waffo.stripe.config.WaffoConfig;                      // ← new

  // ← new: your Waffo credentials. When the env var names match waffo-java's
  //        convention, a single fromEnv() call is enough
  com.waffo.types.config.WaffoConfig waffoJavaConfig =
          com.waffo.types.config.WaffoConfig.fromEnv();

  // ← new: routing configuration
  WaffoConfig routing = WaffoConfig.builder()
          .waffoConfig(waffoJavaConfig)
          .notifyUrl("https://your-app.example/webhooks/waffo")
          .build();

  Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");              // ← changed: key moves to the global setting
  StripeClient client = WaffoStripe.client(routing);                // ← changed: swap the constructor
  ```

  ```java Before theme={null}
  import com.stripe.StripeClient;

  StripeClient client = new StripeClient(System.getenv("STRIPE_SECRET_KEY"));
  ```
</CodeGroup>

The `client` you get back is a standard `StripeClient`. Drop it in where your old one was; every call site stays untouched.

### Step 2 — Add the routing tag and idempotency key

<CodeGroup>
  ```java After theme={null}
  SessionCreateParams params = SessionCreateParams.builder()
          .setMode(SessionCreateParams.Mode.SUBSCRIPTION)
          .setSuccessUrl("https://your-app.example/subscription/success")
          .setCancelUrl("https://your-app.example/subscription/cancel")
          .addLineItem(SessionCreateParams.LineItem.builder()
                  .setPrice("price_123")
                  .setQuantity(1L)
                  .build())
          .putMetadata("source", "waffo")                          // ← new: this line routes it to Waffo
          .build();

  RequestOptions options = RequestOptions.builder()                // ← new: idempotency key, persisted first
          .setIdempotencyKey(persistedSubscriptionRequest)
          .build();

  Session session = client.checkout().sessions()
          .create(params, options);                                // ← changed: pass options too
  saveSubscriptionMapping(orderId, session.getSubscription());     // ← new: map wsub_ to the business order
  redirect(session.getUrl());                                      // unchanged: still read the URL from getUrl()
  ```

  ```java Before theme={null}
  SessionCreateParams params = SessionCreateParams.builder()
          .setMode(SessionCreateParams.Mode.SUBSCRIPTION)
          .setSuccessUrl("https://your-app.example/subscription/success")
          .setCancelUrl("https://your-app.example/subscription/cancel")
          .addLineItem(SessionCreateParams.LineItem.builder()
                  .setPrice("price_123")
                  .setQuantity(1L)
                  .build())
          .build();

  Session session = client.checkout().sessions().create(params);
  redirect(session.getUrl());
  ```
</CodeGroup>

The example intentionally omits `uiMode`. Stripe defaults to a hosted Checkout, and the migration tool also treats an omitted value as hosted. This keeps the code compatible with `stripe-java` 24.11.x, 32.x, and 33.x. Do not substitute `HOSTED_PAGE` on 32.x or 33.x: its serialized `hosted_page` value is classified as a non-hosted Checkout.

Do not rely on an idempotency key generated automatically by `stripe-java`. Explicitly pass and persist a stable key of at most 32 characters, and reuse it on every retry. See the [integration guide](/docs/en/developer-docs/tools-and-references/references/stripe-adapter-integration) for the full rules.

### Step 3 — Integrate the Waffo Webhook

Your application still needs a new HTTP endpoint, while your existing Stripe Webhook endpoint stays unchanged. The SDK's `handle(...)` method already verifies and parses the notification, translates its event, and generates the acknowledgment response. Your endpoint only passes the translated result to your business code and maps the SDK-generated acknowledgment into your Web framework's response.

<CodeGroup>
  ```java After theme={null}
  // The Stripe endpoint stays untouched; add this HTTP route for Waffo
  @PostMapping(value = "/webhooks/waffo", produces = MediaType.APPLICATION_JSON_VALUE)
  public ResponseEntity<String> onWaffo(@RequestBody String body,
                                        @RequestHeader("X-SIGNATURE") String signature) throws StripeException {

      WaffoStripeWebhookResult result = webhooks.handle(body, signature);   // verify, translate, and generate the acknowledgment

      if (result.getPaymentNotification() != null) {
          subscriptionPaymentHandler.handle(                                // subscription billing, recorded separately
                  result.getPaymentNotification().getResult());
      }

      Event event = result.getEvent();
      if (event != null) {
          existingStripeWebhookDispatcher.dispatch(event);                  // unchanged: reuse your dispatch logic
      }

      return ResponseEntity.ok()                                            // map the SDK result to a Spring HTTP response
              .contentType(MediaType.APPLICATION_JSON)
              .body(result.getResponseBody());
  }
  ```

  ```java Before theme={null}
  import com.stripe.net.Webhook;

  // Stripe endpoint only
  @PostMapping("/webhooks/stripe")
  public ResponseEntity<String> onStripe(@RequestBody String body,
                                         @RequestHeader("Stripe-Signature") String signature) {
      try {
          Event event = Webhook.constructEvent(body, signature, endpointSecret);
          existingStripeWebhookDispatcher.dispatch(event);
          return ResponseEntity.ok("ok");    // Stripe only reads the status code
      } catch (SignatureVerificationException e) {
          return ResponseEntity.badRequest().build();
      }
  }
  ```
</CodeGroup>

`WaffoStripeWebhookResult` already contains the acknowledgment body. The final lines only map it into Spring's `ResponseEntity`; use the equivalent mapping for another Web framework. See [the integration guide](/docs/en/developer-docs/tools-and-references/references/stripe-adapter-integration).

<Card title="Integration guide" icon="book-open" href="/docs/en/developer-docs/tools-and-references/references/stripe-adapter-integration">
  The full configuration reference, Webhook handling details, the complete unsupported-usage table, the integration checklist, and Sandbox verification requirements.
</Card>

### Migrate automatically with the AI skill

If you work in Claude Code, Codex, or Cursor, you can let the assistant handle the scan and the rewrite:

```bash theme={null}
npx @waffo/waffo-stripe-migrate
```

After installing, open your project and tell the assistant "migrate from Stripe". It scans every Stripe call in your project, flags what cannot be routed, writes the changes once you approve them, and walks you through Sandbox acceptance.

<Note>
  Scan results only surface risk; they are not an acceptance conclusion. Whichever path you take, the integration is complete only after the full Sandbox flow passes through your own project's endpoints. See [Sandbox verification](/docs/en/developer-docs/tools-and-references/references/stripe-adapter-integration#sandbox-verification).
</Note>

## Version and prerequisites

| Item            | Detail                                                                                                       |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
| Artifact        | `com.waffo:waffo-java-stripe`                                                                                |
| Current version | 0.2.0, published to [Maven Central](https://central.sonatype.com/artifact/com.waffo/waffo-java-stripe/0.2.0) |
| `stripe-java`   | **You provide it.** The adapter does not pin a version. Floor is 24.11.0; verified up to 33.x                |
| `waffo-java`    | 3.0.0, pulled in transitively by the adapter                                                                 |
| Build target    | Java 8; builds on JDK 8 or 17                                                                                |

`stripe-java` is a provided dependency, meaning the adapter will not upgrade or downgrade it for you—the version already in your project stays put. Make sure it is version 24.11.0 or later.

Before integrating, also confirm:

* You have a subscription agreement with Waffo and hold Sandbox credentials
* The currencies you plan to route are within your contract; verify with `paymethodconfig/inquiry`
* You have a publicly reachable HTTPS endpoint for Waffo notifications

## Related resources

* [Integration guide](/docs/en/developer-docs/tools-and-references/references/stripe-adapter-integration) — full steps and technical details
* [Waffo Java SDK](/docs/en/developer-docs/sdk/java) — the native SDK, recommended for new integrations
* [Subscription and recurring payments](/docs/en/essentials/subscription-recurring) — Waffo subscription capabilities
* [GitHub repository](https://github.com/waffo-com/waffo-stripe) — source and changelog
