npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@trap_stevo/merchtide

v0.0.20

Published

Empowering the future of digital commerce, this API delivers the ultimate solution for building and managing online superstores. Seamlessly integrating product management, payment processing, and order tracking, it offers unmatched flexibility and scalabi

Readme

🛍️ @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

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

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)

{
  "unitAmount": 4999,
  "currency": "usd",
  "recurring": { "interval": "month" } // optional
}

priceDetails (PayPal)

{
  "unitAmount": 4999,
  "currency": "usd",
  "recurring": { /* PayPal billing_cycles config */ }
}

Example

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

await merchTide.createPayment("stripe", {
     productID      : hoodie.id,
     quantity       : 1,
     payment_method : "pm_card_visa",
     currencySymbol : "usd",
     receiptEmail   : "[email protected]",
     automaticPaymentMethods : { enabled : true, allowRedirects : "never" },
     requestOptions : { stripe : { idempotencyKey : "pay:ORD-991" } }
});

Example – PayPal Arbitrary Payment

await merchTide.invokePayment("paypal", {
     amount          : 49.99,
     currencySymbol  : "USD",
     payment_method  : "PAYPAL-ALT",
     receiptEmail    : "[email protected]",
     passthrough     : { intent : "CAPTURE" }
});

Refund Example

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:

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

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 : "[email protected]",

     // --- 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)

await merchTide.invokePayment("paypal", {
     amount          : 49.99,          // major units
     taxAmount       : 0,              // major units
     currencySymbol  : "USD",
     payment_method  : "PAYPAL-ALT",   // see notes below
     receiptEmail    : "[email protected]",

     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

// Create
const customer = await merchTide.createCustomer("stripe", {
     name  : "Jane Doe",
     email : "[email protected]"
});

// 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

const customer = await merchTide.createCustomer("stripe", { name : "Jane", email : "[email protected]" });

const pm = await merchTide.createPaymentMethod("stripe", {
     type           : "card",
     token          : "tok_visa",
     billingDetails : { name : "Jane Doe", email : "[email protected]" }
});

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)

const result = await merchTide.attachTemporaryPaymentMethod("stripe", {
     customerID : customer.id,
     token      : "tok_visa",
     perform    : {
          type : "createPayment",
          args : { productID : hoodie.id, quantity : 1, currencySymbol : "usd", receiptEmail : "[email protected]" }
     },
     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

// 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) | ✅ |

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" }
});
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

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):

{
  "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

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:

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).

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)

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:

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)

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)

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)

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.
// 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.

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)

automaticPaymentMethods : {
     enabled         : true,
     allowRedirects  : "never", // "always" | "required"
     preferredMethods: ["card", "us_bank_account"]
}

4) Inline Billing + PM data (Stripe)

payment_method_data : {
     type            : "card",
     card            : { token : "tok_visa" }, // or number/exp/cvc
     billing_details : {
          name   : "Jane Doe",
          email  : "[email protected]",
          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 : truetrial_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

npm install @trap_stevo/merchtide

⚡ Quick Start

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 : "[email protected]"
});

const stripe = merchTide.getPayCore("stripe");
const customer = await merchTide.createCustomer("stripe", { name : "Trial User", email : "[email protected]" });

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

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  : "[email protected]"
});

// Customer + PM
const customer = await merchTide.createCustomer("stripe", { name : "Trial User", email : "[email protected]" });
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   : "[email protected]"
     });
     merchTide.updateOrderStatus(order.id, "paid-stripe");
} catch {
     await merchTide.invokePayment("paypal", {
          amount         : 49.99,
          currencySymbol : "USD",
          payment_method : "PAYPAL-ALT",
          receiptEmail   : "[email protected]"
     });
     merchTide.updateOrderStatus(order.id, "paid-paypal");
}

📜 License

See License in 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.