# 🛍️ @trap_stevo/merchtide

**Empower the future of digital commerce.**  
Unify **product management**, **payment processing**, and **order tracking** under a modular, event-driven API.  
**Build dynamic superstores, link products to multi-core payment systems, and sync customer activity in real time.**

Craft frictionless storefronts, automate transactions, and merge every interaction between products, customers, and payments.  
From microbrands to enterprise networks, this framework transforms commerce pipelines into streamlined digital ecosystems built for scale.

---

## ⚙️ Core Features

- 💳 **Multi-Core Payments** — Stripe & PayPal with instant switching  
- 🧾 **Order Orchestration** — Create, track, update orders dynamically  
- 🏷️ **Product Management** — Clean catalog API with processor registration  
- 🔁 **Subscriptions** — Create, fetch, cancel (immediate or period-end), reactivate, refund  
- 💼 **Payment Methods** — Tokenized creation, attach to customer, set default  
- 💰 **Refunds & Reactivations** — One-line recovery or refund  
- 🔔 **Events** — Lifecycle hooks for payments, refunds, subscriptions  
- 🧠 **Utilities** — Monetary formatting, totals, normalization helpers

> **Design goals:** minimal surface, explicit outcomes, configurable request payloads, and zero lock-in to a single processor.

---

## 🧠 System Requirements

| Requirement | Version |
|---|---|
| Node.js | ≥ 18.x |
| npm | ≥ 9.x |
| OS | Windows · macOS · Linux |

---

## 🧭 Quick Map

```txt
Payments         → createPayment, invokePayment, createRefund
Subscriptions    → createSubscription, cancelSubscription, reactivateSubscription, refundSubscription, getSubscriptionDetails
Customers        → createCustomer, getCustomer, updateCustomer, deleteCustomer
Payment Methods  → createPaymentMethod, attachPaymentMethodToCustomer, listCustomerPaymentMethods,
                   getCustomerDefaultPaymentMethod, containsCustomerPaymentMethod,
                   ensurePaymentMethodAttached, detachCustomerPaymentMethod, detachAllCustomerPaymentMethodsExcept,
                   selectCustomerPaymentMethod, getOrCreateAndAttachPaymentMethod,
                   setCustomerDefaultPaymentMethod, payWithTemporaryPaymentMethod, attachTemporaryPaymentMethod
Products         → addProduct, getProductByID, listProducts, updateProduct, deleteProduct
Product↔Core     → registerProductWithPayCore, updateProductPricingInPayCore
Orders           → createOrder, getOrderByID, listOrders, updateOrder, updateOrderStatus, deleteOrder
Cores            → initializePayCore, getPayCore
Events           → "payment-created", "refund-created", "product-registered", "product-price-updated",
                   "subscription-created", "subscription-cancelled", "subscription-reactivated",
                   "subscription-refunded", "payment-method-attached", "payment-method-set-default"
```

---

## 🏷️ Product Management

| Method | Description | Async |
|---|---|---|
| `addProduct(product)` | Add new product | ❌ |
| `getProductByID(id)` | Retrieve product by ID | ❌ |
| `listProducts()` | List products | ❌ |
| `updateProduct(id, updates)` | Update details | ❌ |
| `deleteProduct(id)` | Remove product | ❌ |

**Example**

```js
const hoodie = merchTide.addProduct({
     name        : "Legendary Hoodie",
     description : "Soft fabric · embroidered logo",
     unitPrice   : 5999 // cents
});
```

---

## 💳 Register Product with a Pay Core

| Method | Description | Async |
|---|---|---|
| `registerProductWithPayCore(core, productID, priceDetails, extra?)` | Create/reuse processor product + price/plan | ✅ |
| `updateProductPricingInPayCore(core, productID, newPriceDetails)` | Roll forward price (Stripe) or update plan (PayPal) | ✅ |

**`priceDetails` (Stripe)**

```json
{
  "unitAmount": 4999,
  "currency": "usd",
  "recurring": { "interval": "month" } // optional
}
```

**`priceDetails` (PayPal)**

```json
{
  "unitAmount": 4999,
  "currency": "usd",
  "recurring": { /* PayPal billing_cycles config */ }
}
```

**Example**

```js
await merchTide.registerProductWithPayCore("stripe", hoodie.id, {
     unitAmount : 4999,
     currency   : "usd",
     recurring  : { interval : "month" }
}, {
     // extra product fields for the processor product (e.g., tax_code, metadata)
});
```

---

## 💳 Payments

| Method | Description | Async |
|---|---|---|
| `createPayment(core, data, eventID?, eventMeta?, processedMeta?)` | Creates a product-based or quantified payment intent (Stripe / PayPal) | ✅ |
| `invokePayment(core, data, eventID?, eventMeta?, processedMeta?)` | Charges an arbitrary total (no product quantity math) | ✅ |

**Example – Stripe Purchase**

```js
await merchTide.createPayment("stripe", {
     productID      : hoodie.id,
     quantity       : 1,
     payment_method : "pm_card_visa",
     currencySymbol : "usd",
     receiptEmail   : "john@example.com",
     automaticPaymentMethods : { enabled : true, allowRedirects : "never" },
     requestOptions : { stripe : { idempotencyKey : "pay:ORD-991" } }
});
```

**Example – PayPal Arbitrary Payment**

```js
await merchTide.invokePayment("paypal", {
     amount          : 49.99,
     currencySymbol  : "USD",
     payment_method  : "PAYPAL-ALT",
     receiptEmail    : "dev@example.com",
     passthrough     : { intent : "CAPTURE" }
});
```

**Refund Example**

```js
await merchTide.createRefund("stripe", {
     charge : "ch_123",
     amount : 1500,
     reason : "requested_by_customer",
     requestOptions : { idempotencyKey : "refund:RMA-991" }
});
```

###💸 Payments — Configuration Matrix & Examples

#### Payment Data — Configuration Reference

> **Applies to `createPayment()` and `invokePayment()` (per core as supported).**

| Key | Type | Purpose |
|---|---|---|
| `productID` | string | Product to derive unit price (createPayment) |
| `quantity` | number | Item count to bill |
| `amount` | number | Arbitrary amount (major units) for `invokePayment` |
| `taxAmount` | number | Additional charge (major units) |
| `currencySymbol` | string | `"usd"` / `"USD"` etc. |
| `payment_method` | string | `"pm_..."` (Stripe PM id) or raw card number in `payment_method_data` (see below) |
| `paymentMethodType` / `type` | string | Stripe PM type (default `"card"`) for inline details |
| `paymentMethodDataConfigurations` | object | Extra keys merged into Stripe `payment_method_data` |
| `billingAddress` | object | `{ name, email, phone, line1, line2, city, state, postalCode, country }` (Stripe) |
| `automaticPaymentMethods` | object | `{ enabled, allowRedirects, preferredMethods }` (Stripe) |
| `returnUrl` | string | Post-confirmation/redirect target (e.g., 3DS) |
| `metadata` | object | Additional processor metadata |
| `passthrough` | object | Per-core payload additions `{ stripe : {...}, paypal : {...} }` or shared `{...}` |
| `override` | boolean | If `true`, passthrough **overrides** the computed base payload |
| `requestOptions` | object | Per-core request opts `{ stripe : {...}, paypal : {...} }` or shared `{...}` |

> **Inline Payment Method (Stripe)**  
> If you don’t pass a `"pm_*"` in `payment_method`, you can supply inline details:
> ```js
> payment_method_data : {
>      type             : "card",
>      card             : { number, exp_month, exp_year, cvc } || { token : "tok_..." },
>      billing_details  : { name, email, phone, address : { line1, line2, city, state, postal_code, country } }
> }
> ```

#### A) One-shot purchase from a product
```js
await merchTide.createPayment("stripe", {
     productID    : hoodie.id,
     quantity     : 1,
     payment_method : "pm_card_visa", // or raw card # via payment_method_data; see below
     currencySymbol : "usd",
     receiptEmail : "john@example.com",

     // --- Optional Price Adders ---
     taxAmount    : 0, // in major units (e.g., 1.99 → $1.99)

     // --- Optional Intent Features (Stripe) ---
     automaticPaymentMethods : {
          enabled         : true,
          allowRedirects  : "never",
          preferredMethods: [/* e.g., "card", "us_bank_account" */]
     },
     returnUrl : "https://example.com/checkout/return",

     // --- Payload shaping ---
     passthrough   : { stripe : { metadata : { orderId : "A123" } }, paypal : { custom : "A123" } },
     override      : false, // if true: passthrough overrides base intent body
     requestOptions: { stripe : { idempotencyKey : "createPayment:A123" } }
});
```

#### B) Arbitrary amount (no product quantity math)
```js
await merchTide.invokePayment("paypal", {
     amount          : 49.99,          // major units
     taxAmount       : 0,              // major units
     currencySymbol  : "USD",
     payment_method  : "PAYPAL-ALT",   // see notes below
     receiptEmail    : "dev@example.com",

     passthrough     : { intent : "CAPTURE" },
     override        : false,
     requestOptions  : { /* PayPal SDK request opts */ }
}, "payment-created");
```

---

## 👥 Customers

| Method | Description | Async |
|---|---|---|
| `createCustomer(core, data)` | Create a new customer in the selected core | ✅ |
| `getCustomer(core, customerID)` | Retrieve customer details by ID | ✅ |
| `updateCustomer(core, customerID, updates)` | Update metadata / invoice settings | ✅ |
| `deleteCustomer(core, customerID)` | Delete a customer (core-specific) | ✅ |

**Example**

```js
// Create
const customer = await merchTide.createCustomer("stripe", {
     name  : "Jane Doe",
     email : "jane@example.com"
});

// Update
await merchTide.updateCustomer("stripe", customer.id, {
     metadata : { tier : "gold", locale : "en-US" }
});

// Retrieve
const current = await merchTide.getCustomer("stripe", customer.id);

// Delete
await merchTide.deleteCustomer("stripe", customer.id);
```

**Notes**
- Customers are validated before subscription or payment methods operations.  
- All customer actions route through initialized payment cores (Stripe / PayPal).  

---

## 💼 Payment Methods

| Method | Description | Async |
|---|---|---|
| `createPaymentMethod(core, data)` | Create PM via token or raw card | ✅ |
| `attachPaymentMethodToCustomer(core, customerID, methodID)` | Attach & set default invoice PM | ✅ |
| `listCustomerPaymentMethods(core, customerID, type?)` | List customer PMs (default `"card"`) | ✅ |
| `getCustomerDefaultPaymentMethod(core, customerID)` | Fetch default PM | ✅ |
| `containsCustomerPaymentMethod(core, { customerID, methodID })` | Check ownership | ✅ |
| `ensurePaymentMethodAttached(core, { customerID, methodID, setAsDefault })` | Attach if missing (+optional default) | ✅ |
| `detachCustomerPaymentMethod(core, customerID, methodID)` | Safe detach (skips default) | ✅ |
| `detachAllCustomerPaymentMethodsExcept(core, customerID, keepID)` | Bulk detach by list | ✅ |
| `selectCustomerPaymentMethod(core, { customerID, strategy })` | Pick `"default"` \| `"latest"` | ✅ |
| `getOrCreateAndAttachPaymentMethod(...)` | Create from token/data, attach, optionally default | ✅ |
| `setCustomerDefaultPaymentMethod(...)` | Idempotent defaulting + optional pruning | ✅ |
| `payWithTemporaryPaymentMethod(...)` | One-off payment with ephemeral PM (auto-detach) | ✅ |
| `attachTemporaryPaymentMethod(...)` | Attach ephemeral PM, perform operation, auto-detach | ✅ |

**Create → Attach → Default**

```js
const customer = await merchTide.createCustomer("stripe", { name : "Jane", email : "jane@example.com" });

const pm = await merchTide.createPaymentMethod("stripe", {
     type           : "card",
     token          : "tok_visa",
     billingDetails : { name : "Jane Doe", email : "jane@example.com" }
});

await merchTide.attachPaymentMethodToCustomer("stripe", customer.id, pm.id);

await merchTide.setCustomerDefaultPaymentMethod("stripe", {
     customerID   : customer.id,
     methodID     : pm.id,
     detachOthers : true
});
```

**Ephemeral (take payment, then clean up)**

```js
const result = await merchTide.attachTemporaryPaymentMethod("stripe", {
     customerID : customer.id,
     token      : "tok_visa",
     perform    : {
          type : "createPayment",
          args : { productID : hoodie.id, quantity : 1, currencySymbol : "usd", receiptEmail : "jane@example.com" }
     },
     autoDetach : true
});
```

---

## 🔁 Subscriptions — Lifecycle & Options

| Method | Purpose | Async |
|---|---|---|
| `createSubscription(core, data)` | Start recurring billing | ✅ |
| `getSubscriptionDetails(core, id)` | Fetch details | ✅ |
| `cancelSubscription(core, id, options?)` | Immediate or period-end (Stripe) | ✅ |
| `reactivateSubscription(core, id)` | Resume if delayed-cancel or suspended | ✅ |

**`createSubscription` (Stripe options)**

| Key | Type | Notes |
|---|---|---|
| `customerID` | string | Required |
| `productID` \| `priceID` | string | Product+registered price or direct price id |
| `startNow` | boolean | If `true`, `trial_end: "now"` |
| `trialEnd` | number (unix) | Future start |
| `collection_method` | string | `"charge_automatically"` \| `"send_invoice"` |
| `payment_behavior` | string | Stripe behavior for invoice/payment creation |
| `proration_behavior` | string | `"create_prorations"` \| `"none"` |
| `default_payment_method` \| `paymentMethodID` | string | Will be attached if not owned by customer |
| `passthrough`, `override`, `requestOptions` | object | See Payments table |

**`createSubscription` (PayPal options)**

| Key | Type | Notes |
|---|---|---|
| `plan_id` \| `priceID` | string | PayPal plan id |
| `productID` | string | If registered with `paypalPlanID` / `paypalPriceID` |
| `customerEmail` | string | Optional |
| `customerFirstName` / `customerLastName` | string | Optional |
| `trialEnd` | number (unix) | Schedules `start_time` |
| `passthrough`, `override`, `requestOptions` | object | See Payments table |

**Cancel / Reactivate / Refund**

```js
// Cancel at period end (Stripe)
await merchTide.cancelSubscription("stripe", "sub_123", { at_period_end : true });

// Immediate cancel
await merchTide.cancelSubscription("stripe", "sub_456");

// Reactivate (e.g., previously set to cancel at period end)
await merchTide.reactivateSubscription("stripe", "sub_123");

// Refund latest invoice (Stripe) — partial $19.99
await merchTide.refundSubscription("stripe", "sub_123", 1999);
```

> **Note:** Stripe supports end-of-period cancellation (`{ at_period_end : true }`).  
> PayPal supports immediate cancellation; reactivation uses status-specific actions.

---

## 💰 Refunds (Standalone)

| Method | Description | Async |
|---|---|---|
| `createRefund(core, data)` | Create a standalone refund for a charge or transaction (Stripe / PayPal) | ✅ |
| `refundSubscription(core, subscriptionID, amount?, config?)` | Refund latest invoice (Stripe) or last payment (PayPal) | ✅ |

```js
await merchTide.createRefund("stripe", {
     charge : "ch_123",
     amount : 1500, // cents
     reason : "requested_by_customer",

     passthrough   : { metadata : { caseId : "RMA-991" } },
     override      : false,
     requestOptions: { idempotencyKey : "refund:RMA-991" }
});
```

```js
await merchTide.createRefund("paypal", {
     transactionID : "9AB12345CD6789012",
     amount        : 15.00, // major units
     currencySymbol: "USD"
});
```

---

## 🧾 Order Management

| Method | Description | Async |
|---|---|---|
| `createOrder(order)` | Create new order | ❌ |
| `getOrderByID(id)` | Retrieve order by ID | ❌ |
| `listOrders()` | List orders | ❌ |
| `updateOrder(id, updates)` | Update order fields | ❌ |
| `updateOrderStatus(id, status)` | Change status | ❌ |
| `deleteOrder(id)` | Delete order | ❌ |

---

## 🔔 Events

| Event | When | Payload (shape) |
|---|---|---|
| `product-registered` | product ↔ core registration | `{ ...updatedProduct }` |
| `product-price-updated` | on price roll-forward | `{ ...updatedProduct }` |
| `payment-created` | after payment intent/order | `{ coreName, paymentData, result, subtotalPaid, totalPaid, taxPaid }` |
| `refund-created` | after refund | `{ coreName, data, result }` |
| `subscription-created` | after subscription create | `{ coreName, data, result }` |
| `subscription-cancelled` | after cancel | `{ coreName, subscriptionID, result, options }` |
| `subscription-reactivated` | after resume | `{ coreName, subscription }` |
| `subscription-refunded` | after subscription refund | `{ coreName, subscriptionID, result }` |
| `payment-method-attached` | on PM attach | `{ coreName, customerID, methodID, created?, attached? }` |
| `payment-method-set-default` | on default set | `{ coreName, customerID, methodID }` |

**Example**

```js
merchTide.on("subscription-created", ({ coreName, data }) => {
     console.log("✨ New subscription via", coreName, "for", data.customerID);
});
```

---

## 🎯 Bidding / Auctions (Event-Driven Pattern)

Bidding rides on the library’s **event bus** and **payment/order** surfaces.  
You define your own auction models and emit events; then settle with `invokePayment` or `createPayment`, and update orders atomically.

> **Why this pattern?** It keeps the auction logic domain-owned while leveraging robust, idempotent payment + refund + order utilities.

---

### Event Contracts (you emit / listen)

| Event          | When                          | Payload (shape)                                                                 |
|---|---|---|
| `bid-placed`   | A user places a bid           | `{ auctionID, userID, amount, currency, timestamp }`                            |
| `bid-outbid`   | A higher bid replaces another | `{ auctionID, previousBid, newBid }`                                            |
| `bid-won`      | Auction closes                | `{ auctionID, winningBid, productID }`                                          |
| `bid-settled`  | Payment completes             | `{ auctionID, orderID, paymentResult }`                                         |
| `bid-failed`   | Payment/settlement failed     | `{ auctionID, orderID, error }`                                                 |

**Payload schema (reference):**
```json
{
  "Bid": {
    "auctionID": "string",
    "userID": "string",
    "amount": "number (major units, e.g., 49.99)",
    "currency": "string (e.g., 'usd' or 'USD')",
    "timestamp": "number (Date.now())"
  },
  "WinningBid": {
    "auctionID": "string",
    "productID": "string",
    "userID": "string",
    "amount": "number",
    "currency": "string",
    "payment_method": "string (e.g., 'pm_...' or PSP alias)",
    "metadata": "object (optional)"
  }
}
```

---

### Recommended flow

1) **User places a bid** → validate → emit `bid-placed`.  
2) **Outbid event** (optional) → emit `bid-outbid`.  
3) **Auction close** → determine winner → emit `bid-won`.  
4) **Settlement** → charge winner via `invokePayment`/`createPayment` → emit `bid-settled` (or `bid-failed`) → update order status.

---

### Place a bid

```js
function placeBid(merchTide, { auctionID, userID, amount, currency = "usd" }) {
     if (!(auctionID && userID) || !(amount > 0)) {
          throw new Error("Invalid bid payload.");
     }

     const bid = {
          auctionID,
          userID,
          amount,
          currency,
          timestamp : Date.now()
     };

     merchTide.emit("bid-placed", bid);
     return bid;
}
```

---

### Anti-sniping (optional)

Extend deadline when a last-minute bid lands:

```js
function maybeExtendAuction({ auction, lastBidAt, windowMs = 15_000 }) {
     const now = Date.now();
     if (auction.endsAt - now <= windowMs) {
          auction.endsAt = now + windowMs;
          return true;
     }
     return false;
}
```

---

### Settlement (Stripe primary → PayPal fallback)

Use **arbitrary-amount** flow (`invokePayment`) to charge the winning amount, while keeping **order status** consistent.  
Supports **passthrough**, **override**, and **requestOptions** (idempotency).

```js
async function settleWinningBid(merchTide, {
     auctionID,
     productID,
     winningBid,        // { userID, amount, currency, payment_method?, metadata? }
     orderID,           // your order id
     receiptEmail
}) {
     try {
          // Primary: Stripe
          const result = await merchTide.invokePayment("stripe", {
               amount          : winningBid.amount,
               currencySymbol  : winningBid.currency,
               productID,
               payment_method  : winningBid.payment_method || "pm_card_visa",

               // Attach business metadata safely (won’t expose internals)
               passthrough     : { stripe : { metadata : {
                    auctionID,
                    orderID,
                    winner : winningBid.userID
               }}},

               // Optional idempotency
               requestOptions  : { stripe : { idempotencyKey : `bid:${auctionID}:order:${orderID}` } }
          }, "bid-settled", { auctionID, orderID, winner : winningBid.userID });

          merchTide.updateOrderStatus(orderID, "paid-stripe");
          merchTide.emit("bid-settled", { auctionID, orderID, paymentResult : result });
          return result;
     }
     catch (err) {
          // Fallback: PayPal
          try {
               const result = await merchTide.invokePayment("paypal", {
                    amount          : winningBid.amount,
                    currencySymbol  : (winningBid.currency || "USD").toUpperCase(),
                    productID,
                    payment_method  : "PAYPAL-ALT",
                    receiptEmail,

                    passthrough     : { paypal : { custom_id : orderID } },
                    requestOptions  : { /* PayPal SDK options if needed */ }
               }, "bid-settled", { auctionID, orderID, winner : winningBid.userID });

               merchTide.updateOrderStatus(orderID, "paid-paypal");
               merchTide.emit("bid-settled", { auctionID, orderID, paymentResult : result });
               return result;
          } catch (fallbackErr) {
               merchTide.updateOrderStatus(orderID, "payment-failed");
               merchTide.emit("bid-failed", { auctionID, orderID, error : String(fallbackErr?.message || fallbackErr) });
               throw fallbackErr;
          }
     }
}
```

---

### Outbid signalling (optional)

```js
function notifyOutbid(merchTide, { auctionID, previousBid, newBid }) {
     merchTide.emit("bid-outbid", { auctionID, previousBid, newBid });
}
```

---

### Refund policy (optional)

If a post-settlement dispute occurs, use normal refunds:

```js
await merchTide.createRefund("stripe", {
     charge         : "ch_123",    // or use subscription refund APIs if recurring
     amount         : 2500,        // cents
     requestOptions : { idempotencyKey : `refund:${auctionID}:${orderID}` }
});
```

---

### Event listeners (analytics, emails, ledger)

```js
merchTide.on("bid-placed", (b) => {
     // analytics.track("BidPlaced", b);
});

merchTide.on("bid-settled", ({ auctionID, orderID, paymentResult }) => {
     // sendReceiptEmail(...), audit ledger, release item, etc.
});

merchTide.on("bid-failed", ({ auctionID, orderID, error }) => {
     // alert ops / notify winner to retry
});
```

---

### Notes & best practices

- Use **idempotency keys** on settlement to prevent duplicate charges.  
- Prefer `invokePayment` for **winner-pays** scenarios (precise arbitrary totals).  
- Use `passthrough` + `override` to enrich gateway payloads without exposing internals.  
- Keep **bid state** in your own storage; use events solely for workflow + side-effects.  
- Emit `bid-won` **before** charging if you need auditability of the decision boundary; otherwise emit **after** successful capture.

---

## 🌐 Dual-Core Checkout (Stripe → PayPal Fallback)

```js
async function handleCheckout(order) {
     try {
          await merchTide.createPayment("stripe", {
               productID      : order.productID,
               quantity       : order.quantity,
               payment_method : "pm_card_visa",
               currencySymbol : "usd",
               receiptEmail   : order.email
          });
          merchTide.updateOrderStatus(order.id, "paid-stripe");
     } catch (err) {
          await merchTide.invokePayment("paypal", {
               amount         : order.total,
               currencySymbol : "USD",
               payment_method : "PAYPAL-ALT",
               receiptEmail   : order.email
          });
          merchTide.updateOrderStatus(order.id, "paid-paypal");
     }
}
```

---

## 🌐 Webhook Synchronization (Unified Live Updates)

```js
import express from "express";
import bodyParser from "body-parser";
import stripeModule from "stripe";
import paypal from "paypal-rest-sdk";
import MerchTide from "@trap_stevo/merchtide";

const stripe = stripeModule(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(bodyParser.raw({ type : "application/json" }));

app.post("/webhook", async (req, res) => {
     const rawBody = req.body;
     const headers = req.headers;

     try {
          if (headers["stripe-signature"]) {
               const event = stripe.webhooks.constructEvent(
                    rawBody,
                    headers["stripe-signature"],
                    process.env.STRIPE_WEBHOOK_SECRET
               );
               MerchTide.emit("stripe-webhook", event);
               return res.status(200).send("Stripe webhook processed");
          }

          if (headers["paypal-transmission-sig"]) {
               paypal.notification.webhookEvent.verify(
                    headers,
                    rawBody.toString(),
                    process.env.PAYPAL_WEBHOOK_ID,
                    (error, response) => {
                         if (response && response.verification_status === "SUCCESS") {
                              const event = JSON.parse(rawBody);
                              MerchTide.emit("paypal-webhook", event);
                              res.status(200).send("PayPal webhook processed");
                         } else {
                              res.status(400).send("Invalid PayPal signature");
                         }
                    }
               );
               return;
          }

          res.status(400).send("No recognized webhook source");
     } catch (err) {
          res.status(400).send("Invalid signature");
     }
});
```

---

## 🧩 Configuration Patterns

**1) Passthrough vs Override**

- `passthrough` lets you add **processor-specific** keys to the final request body:
  - Shape can be **shared** (`{ key: value }`) or **per-core** (`{ stripe : {...}, paypal : {...} }`).
- `override: true` makes your passthrough **win** when keys collide with the computed base body.

```js
// Add Stripe metadata and force it to override base fields if any conflict
await merchTide.createPayment("stripe", {
     productID  : hoodie.id,
     quantity   : 2,
     passthrough: { stripe : { metadata : { campaign : "Q4-HOLIDAY" } } },
     override   : true
});
```

**2) Request Options (Idempotency, headers, etc.)**

Supply per-core `requestOptions` when you need idempotency keys, custom headers, or advanced SDK settings.

```js
await merchTide.createRefund("stripe", {
     charge : "ch_123",
     amount : 500
}, /* eventID */ undefined, /* eventMeta */ undefined, /* resultMeta */ undefined);
// or directly via requestOptions:
await merchTide.createPayment("stripe", {
     productID      : hoodie.id,
     quantity       : 1,
     requestOptions : { stripe : { idempotencyKey : "pay:order-991" } }
});
```

**3) Automatic Payment Methods (Stripe)**

```js
automaticPaymentMethods : {
     enabled         : true,
     allowRedirects  : "never", // "always" | "required"
     preferredMethods: ["card", "us_bank_account"]
}
```

**4) Inline Billing + PM data (Stripe)**

```js
payment_method_data : {
     type            : "card",
     card            : { token : "tok_visa" }, // or number/exp/cvc
     billing_details : {
          name   : "Jane Doe",
          email  : "jane@example.com",
          phone  : "+1-555-555-5555",
          address: {
               line1       : "123 Main St",
               line2       : "",
               city        : "Austin",
               state       : "TX",
               postal_code : "73301",
               country     : "US"
          }
     }
}
```

**5) Subscription Start Control**

- `startNow : true` → `trial_end: "now"` (Stripe)
- `trialEnd : unix` → Defer start
- PayPal uses `start_time` ISO for deferrals.

---

## ✅ Usage Checklist

- Initialize both cores you plan to use.  
- Register products with each core to obtain price/plan IDs if you need recurring billing.  
- Choose `createPayment` for itemized purchases or `invokePayment` for arbitrary amounts.  
- Use events to sync your state machine (order status, customer ledger, analytics).  
- Prefer idempotency keys on payment/refund/subscribe flows.

---

## 🧯 Common Errors & Remedies

| Symptom | Why | Fix |
|---|---|---|
| “Product not found or missing unit price” | `createPayment` expects catalog item | Use `invokePayment` or add `unitPrice` to catalog |
| “Payment core 'x' not initialized” | Core not `initializePayCore(...)` | Initialize before use |
| “Invalid amount / Tax exceeds total payment” | Math guardrails | Check `amount`, `taxAmount`, `currencySymbol` |
| “No invoice/charge found for subscription” | Refund flow requires last invoice | Ensure last invoice exists or use partial refund rules |
| “No such customer / test vs live” | Mode mismatch or wrong ID | Re-create in right mode; verify keys |

---

## 📦 Installation

```bash
npm install @trap_stevo/merchtide
```

---

## ⚡ Quick Start

```js
const MerchTide = require("@trap_stevo/merchtide");

const merchTide = new MerchTide();

await merchTide.initializePayCore("stripe", { apiKey : process.env.STRIPE_SECRET_KEY });
await merchTide.initializePayCore("paypal", {
     client_id : process.env.PAYPAL_CLIENT_ID,
     client_secret : process.env.PAYPAL_CLIENT_SECRET,
     mode : "sandbox"
});

const hoodie = merchTide.addProduct({ name : "Midnight Hoodie", unitPrice : 4999 });

const order = merchTide.createOrder({
     productID : hoodie.id,
     quantity : 1,
     customer : "dev@example.com"
});

const stripe = merchTide.getPayCore("stripe");
const customer = await merchTide.createCustomer("stripe", { name : "Trial User", email : "trial@example.com" });

const list = await stripe.paymentMethods.list({ customer : customer.id, type : "card" });
const pmID = list.data[0]?.id || (await merchTide.createPaymentMethod("stripe", { type : "card", card : { token : "tok_visa" } })).id;

await merchTide.attachPaymentMethodToCustomer("stripe", customer.id, pmID);

await merchTide.createSubscription("stripe", {
     customerID : customer.id,
     productID : hoodie.id,
     trialEnd : Math.floor(Date.now() / 1000) + 7 * 86400
});

await merchTide.cancelSubscription("stripe", "sub_123", { at_period_end : true });
```

---

## 🧪 End-to-End Example

```js
const MerchTide = require("@trap_stevo/merchtide");

const merchTide = new MerchTide();

await merchTide.initializePayCore("stripe", { apiKey : process.env.STRIPE_SECRET_KEY });
await merchTide.initializePayCore("paypal", {
     client_id     : process.env.PAYPAL_CLIENT_ID,
     client_secret : process.env.PAYPAL_CLIENT_SECRET,
     mode          : "sandbox"
});

const hoodie = merchTide.addProduct({ name : "Midnight Hoodie", unitPrice : 4999 });

const order = merchTide.createOrder({
     id        : "ORD-1001",
     productID : hoodie.id,
     quantity  : 1,
     customer  : "trial@example.com"
});

// Customer + PM
const customer = await merchTide.createCustomer("stripe", { name : "Trial User", email : "trial@example.com" });
const pmID = await merchTide.getOrCreateAndAttachPaymentMethod("stripe", {
     customerID : customer.id,
     token      : "tok_visa",
     setAsDefault : true
});

// Subscription with 7-day trial
await merchTide.registerProductWithPayCore("stripe", hoodie.id, {
     unitAmount : 4999,
     currency   : "usd",
     recurring  : { interval : "month" }
});

await merchTide.createSubscription("stripe", {
     customerID  : customer.id,
     productID   : hoodie.id,
     trialEnd    : Math.floor(Date.now() / 1000) + 7 * 86400,
     requestOptions : { stripe : { idempotencyKey : "sub:ORD-1001" } }
});

// One-off purchase fallback to PayPal
try {
     await merchTide.createPayment("stripe", {
          productID      : hoodie.id,
          quantity       : 1,
          payment_method : pmID,
          currencySymbol : "usd",
          receiptEmail   : "trial@example.com"
     });
     merchTide.updateOrderStatus(order.id, "paid-stripe");
} catch {
     await merchTide.invokePayment("paypal", {
          amount         : 49.99,
          currencySymbol : "USD",
          payment_method : "PAYPAL-ALT",
          receiptEmail   : "trial@example.com"
     });
     merchTide.updateOrderStatus(order.id, "paid-paypal");
}
```

---

## 📜 License

See License in [LICENSE.md](./LICENSE.md)

---

## 🌊 Power Commerce. Flow Effortlessly.

Unify the tides of digital commerce — orchestrating payments, subscriptions, and products with seamless synchronization and elegance.  
From indie creators to enterprise networks, make transactions flow like water — secure, adaptive, and built for the future.
