← Back to Notes

Swell Marketplace

Hamed Bahram /
10 min read--- views

Swell is an API-first headless ecommerce platform with built-in subscription capabilities and marketplace features.

Background reading:What is Swell

You can extend Swell's base data models to build a marketplace. A marketplace's functionality revolves around different user groups, mainly two groups:

  • buyers
  • vendors

Add customer groups

To define different user groups in your marketplace, you can use customer groups within the Swell dashboard.

  1. Navigate to Settings > Customers.
  2. Select Add customer group under the groups section.
  3. Provide a group name and save.

For a marketplace, you'll need at least two customer groups: buyers and vendors.

Adding new accounts

With the customer groups created, you can now add new accounts by posting to the /accounts endpoint and associating them to either the buyers or vendors group.

We can either use the swell.js JavaScript SDK to interface with Swell's Frontend API which implements a subset of operations available in the Backend API, or use the swell-node package which is an API-wrapper for the Backend API on the server-side.

We are using the swell-node interface for these examples:

const swell = require('swell-node')
swell.init('my-store', 'secret-key')
 
try {
  await swell.post('/accounts', {
    email: 'vendor@mail.com',
    first_name: 'Vendor',
    last_name: 'Swell',
    password: 'password123', // will be encrypted using `bcrypt`
    group: 'vendors' // or buyers
  })
} catch (error) {
  console.log(error)
}

Extend Swell models

Leverage Swell's flexible system to expand upon the base functionalities by building off of the Products and Orders models.

Associate products to vendors

You can edit the Products model to include vendor_id as a field to establish a connection between a product and a vendor.

You can accomplish this through the API or the editor in the Swell dashboard.

await swell.put('/:models/products/fields', {
  vendor_id: {
    type: 'objectid',
    required: true // optional
  }
})

From the dashboard:

  • Go to Developers > Models
  • Select the Products model
  • Click Add field; then choose Lookup as the field type
  • Under Label type Vendor
  • Select Customers for Lookup collection
  • Select Details in the Admin field location for displaying the field in the admin dashboard
  • Click Save

This will create a vendor_id field in each product to store a reference to the vendor account.

Creating new products

With the addition of the vendor_id, you can now create new products and link them to a vendor.

await swell.post('/products', {
  name: 'Swell T-Shirt',
  price: 120,
  type: 'standard',
  active: true,
  vendor_id: '609590e5d0ef5f42c5e1ce01' // vendor account id
})

Fetch products associated to a vendor

You can fetch all of a vendor's products by referencing its id:

await swell.get('/products', {
    where: { vendor_id: '609590e5d0ef5f42c5e1ce01'},
    limit 25 // optional, can be up to 1000
});

Associate orders to vendors

It is also necessary to associate orders to vendors. However, instead of adding the relation to the order itself, you can add the relation within an order's items array.

Update the Orders model to include the vendor_id of each item added to the items array since an order might include items from multiple vendors. The vendor_id is retrievable from products using the formula:

await swell.put('/:models/orders/fields/items/fields', {
  vendor_id: {
    type: 'objectid',
    formula: 'product.vendor_id'
  }
})

Fetch orders associated with a vendor

The following query will fetch orders containing products related to a particular vendor:

await swell.get('/orders', {
  where: {
    items: { $elemMatch: { vendor_id: '609590e5d0ef5f42c5e1ce01' } }
  }
})

You'll then need to filter the items array to only show the item that belongs to the specific vendor, as the query will return the entire order.

Shipments and order fulfillment

Vendors can fulfill order items by adding shipments to orders. To create a shipment and fulfill items, simply post to the /shipments endpoint.

await swell.post('/shipments', {
  order_id: '60a3cd3ac2c46f30b6cb3b5d',
  items: [
    {
      order_item_id: '60a3cd3ac2c46f30b6cb3b2a', // item id in the items array in the order
      product_id: '60a3cc9f23ead02dff477b89',
      quantity: 1
    }
  ],
  tracking_code: '787374451654' // optional
})

Swell allows you to assign multiple shipments to one order so that each vendor can fulfill their items separately with individual tracking numbers.

Vendor onboarding

Now that we have laid the foundations, we'll be looking at onboarding vendors, platform fees, and vendor payouts. We'll be using Stripe Connect and serverless functions for this.

In order to set up payouts, you will need to have a Stripe account and make sure you have the proper configurations:

  1. Create a Stripe account
  2. Provide your business details
  3. Complete your platform profile

Configuring serverless functions

We would need to create 3 functions in order to:

  • Create a vendor express account
  • Get the onboarding URL for the vendor to complete registration
  • Create vendor payouts on every order

We'll be using NextJS API routes (deployed as serverless functions) in the examples below, but you can use any other provider/platform.

Create vendor stripe account

The first step is to create an Stripe express account for your vendor and redirect them to complete the onboarding flow:

import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
 
export default async function (req, res) {
  if (req.method === 'POST') {
    try {
      const account = await stripe.accounts.create({
        type: 'express'
      })
 
      const data = await account
      return res.status(201).json({ data })
    } catch (e) {
      return res.status(e.statusCode || 500).json({ message: e.message })
    }
  }
 
  res.setHeader('Allow', 'POST')
  res.status(405).end('Method Not Allowed')
}

Once the account is created, you can retrieve the id from the returned object.

Get the Onboarding URL

This creates an onboarding URL that your vendors need to complete in order to enter their verification and bank info. You need to pass the account id retrieved in the last step to this function:

import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
 
export default async function (req, res) {
  if (req.method === 'POST') {
    try {
      const { accountId } = body
 
      const accountLinks = await stripe.accountLinks.create({
        account: accountId,
        refresh_url: 'https://example.com/reauth',
        return_url: 'https://example.com/return',
        type: 'account_onboarding'
      })
 
      const data = await accountLinks
      return res.status(201).json({ data })
    } catch (e) {
      return res.status(e.statusCode || 500).json({ message: e.message })
    }
  }
 
  res.setHeader('Allow', 'POST')
  res.status(405).end('Method Not Allowed')
}

You can retrieve the onboarding url from the response and redirect the vendor to complete their account registration.

Onboarding completion

A vendor will have to complete their onboarding for their account to be fully functional.

return_url

Stripe issues a redirect to this URL when the user completes the Connect Onboarding flow. This doesn't mean that all information has been collected or that there are no outstanding requirements on the account. This only means the flow was entered and exited properly.

After a user is redirected to your return_url, check the details_submitted parameter on their account by doing either of the following:

You can check for the charges_enabled property to make sure the account is fully onboarded.

refresh_url

Your user is redirected to the refresh_url if:

  • The link is expired.
  • The user already visited the URL.
  • Your platform can no longer access the account.
  • The account has been rejected.

Your refresh_url should trigger the previous step with the same parameters to generate a new URL, and redirect the user to the onboarding flow to create a seamless experience.

Updating the Account model in Swell

Let's update the Account model in Swell to include the Stripe Connect id for each vendor, to manage order payouts and transfers later on:

await swell.put('/:models/accounts/fields', {
  stripe_connect_id: {
    type: 'string',
    label: 'Stripe Connect ID'
  }
})

Assuming that a vendor has completed their onboarding, you can update the vendor's account in Swell:

await swell.put('/accounts/{id}', {
  stripe_connect_id: 'acct_XXXXXXX' // obtained in previous steps
})
Creating a platform fee

Now that we have vendors associated to the Stripe account, we need to establish the transaction logic in Swell to charge platform fees to vendors:

Let's first add a platform_fee field to each item in the items array in order:

await swell.put('/:models/orders/fields/items/fields', {
  platform_fee: {
    type: 'float',
    default: 0.2
  }
})

We can now calculate the item_platform_fee for each item by referencing the platform_fee to capture a portion of each the item's total price in the formula:

await swell.put('/:models/orders/fields/items/fields', {
  item_platform_fee: {
    type: 'float',
    formula: 'platform_fee * price_total'
  }
})

This example calculates 20% of each item's total price as the platform fee. This will deduct the fee from vendor payouts and allocate it as the platform's revenue.

Create vendor payouts

With vendors successfully onboarded, the last step will be setting up the payout logic to ensure vendors can receive payment for their goods purchased on the marketplace.

We need to create another function (endpoint) which is triggered by a webhook that we will configure on Swell to fire after every order.

This function will loop through the items in the order, find the associated vendor for the item and issue Stripe transfers to the vendors Stripe account.

const stripe = require('stripe')('STRIPE_SECRET_KEY')
 
const swell = require('swell-node')
swell.init('SWELL_STORE_ID', 'SWELL_SECRET_KEY')
 
export default async function (req, res) {
  if (req.method !== 'POST') {
    res.setHeader('Allow', 'POST')
    res.status(405).end('Method Not Allowed')
    return
  }
 
  const filteredItems = body.data.items.filter(item => item.vendor_id)
 
  let transfers = []
 
  if (filteredItems.length === 0) {
    res.status(200).json({ transfers })
    return
  }
 
  for (const item of filteredItems) {
    // find vendor account
    const account = await swell.get('/accounts', {
      where: { id: item.vendor_id }
    })
 
    const stripeId = account?.results[0]?.stripe_connect_id
    if (!stripeId) continue
 
    // payout amounts must be in cents.
    const truncAmt = (item.price_total - item.item_platform_fee).toFixed(2)
    const payoutAmt = Number(
      truncAmt.split('.')[0] + truncAmt.split('.')[1]
    )
 
    const transfer = await stripe.transfers.create({
      amount: payoutAmt,
      currency: 'usd', // change to your Stripe currency
      destination: stripeId
    })
 
    const data = await transfer
    transfers = [...transfers, data]
  }
 
  return res.status(200).json({ transfers })
}

Create a Swell webhook

To create webhooks in Swell, in your store dashboard:

  1. Navigate to Developers > Webhooks
  2. Under webhooks, select Add new webhook
  3. Under events, select order.created, this wil trigger our payout endpoint function after every order.
  4. Click Save

Top up Stripe Account

In order to test our live integration, you'll need to load funds in your Stripe account to make transfers to another Stripe Connect account. You can do this in two separate ways:

  1. By the Stripe dashboard by selecting: Balances > Add to balance. This functionality is only available to US Stripe accounts.

  2. By creating a manual charge using a specific card number, You can do this by selecting:

    • Payments > Create payment
    • Enter any amount under Amount
    • Enter 4000 0000 0000 0077 as the card number
    • Enter any future date as the expiration date
    • And any 3 digit number for CVC

Now that we have funds to transfer with our payout functionality, we can test our payout functions. Create a test order from the Swell Dashboard to verify everything is working.

Summary

That's it folks. We have a fully functioning marketplace that can onboard vendors, charge platform fees and transfer funds.

Resources

Here are some of the resources that inspired this note:

Documentation