Transactions

Transactions are how you generate value on your platform, generally with Assets.

An Asset can be assigned to a User, in other words booked by this User, for some duration and/or quantity defined in Transaction object.

Transaction object can impact Asset availability exactly like Availability objects. This means changes in Search results and ability to create other Transactions with the same Asset.

Availabilities are generally created by the Asset owner, while Transactions are best suited to be created by other Users.

Transactions and payments

Transactions can be your single source of truth to proceed with payments, transactional emails, or anything related to your platform transactional process.

Using the payment provider of your choice, such as Stripe, you can store payment info in metadata or platformData attributes. Please make sure you’re not storing any sensitive data such as credit card number.

Creating a Transaction

Let’s create a Transaction locking a single unit of an Asset, for one day:

await stelace.transactions.create({
  assetId: 'ast_2l7fQps1I3a1gJYz2I3a',
  startDate: '2019-09-13T00:00:00.000Z',
  duration: { d: 1 },
  quantity: 1
})

Supposing the Asset is timeBased according to its Asset Type.

Transaction takerId will be automatically set to currently authenticated User ID if applicable (publishable API key needed).

You can define a default currency for all your Assets:

await stelace.config.update({
  stelace: {
    instant: {
      currency: 'USD'
    }
  }
})

During a Transaction creation, the default currency will be used, unless the Asset has a different currency.

Asset currency must be specified before creating the Transaction to apply.

Availability check

When creating a Transaction, Stelace handles availability check for you.

Existing Availability and Transaction objects can block the period or quantity required.

If the following Availability object is created,

await stelace.availabilities.create({
  assetId: 'ast_2l7fQps1I3a1gJYz2I3a',
  startDate: '2019-09-14T00:00:00.000Z',
  endDate: '2019-09-16T00:00:00.000Z',
  quantity: 0
})

the request below will generate an error because the quantity over the period is higher than the available quantity.

const transaction = await stelace.transactions.create({
  assetId: 'ast_2l7fQps1I3a1gJYz2I3a',
  startDate: '2019-09-13T00:00:00.000Z',
  duration: { d: 3 },
  quantity: 1
})

Transaction process

Stelace lets you configure how Transactions can transition from one status to another, and how they impact the Availability of your Assets.

Each Transaction object has a status property representing its current state in your business process.

Transitions define how Transaction status can change depending on your platform requirements.

A simple e-commerce platform might use the following statuses:

  • draft - initial step
  • paid - blocking availability
  • completed - blocking availability, final step
  • cancelled - final step

Transactions impact Availability depending on Asset Type unavailableWhen property, and lock the quantity during the Transaction period if the Asset Type is timeBased.

Default Transaction process

Here is the transactionProcess applied to every Asset Type by default:

{
  initStatus: 'draft',
  cancelStatus: 'cancelled',
  transitions: [
    { name: 'accept', from: 'draft', to: 'accepted', actors: ['owner'] },
    { name: 'confirm', from: 'draft', to: 'confirmed', actors: ['taker'] },
    { name: 'pay', from: 'draft', to: 'pending-acceptance', actors: ['taker'] },
    { name: 'confirmAndPay', from: 'draft', to: 'pending-acceptance', actors: ['taker'] },

    { name: 'pay', from: 'confirmed', to: 'pending-acceptance', actors: ['taker'] },
    { name: 'accept', from: 'confirmed', to: 'pending-payment', actors: ['owner'] },

    { name: 'pay', from: 'accepted', to: 'validated', actors: ['taker'] },
    { name: 'confirmAndPay', from: 'accepted', to: 'validated', actors: ['taker'] },
    { name: 'confirm', from: 'accepted', to: 'pending-payment', actors: ['taker'] },

    { name: 'accept', from: 'pending-acceptance', to: 'validated', actors: ['owner'] },

    { name: 'pay', from: 'pending-payment', to: 'validated', actors: ['taker'] },

    { name: 'complete', from: 'validated', to: 'completed' },
    { name: 'cancel', from: '*', to: 'cancelled' }
  ]
}

corresponding to the following flow chart:

Default Transaction process

For instance, transaction.status is validated once Transaction is confirmed by the taker (and paid if required by non-zero price) and accepted by the owner, making the Asset unavailable with default Asset Type unavailableWhen property.

Cancelling a Transaction

To cancel a transaction, some data.cancellationReason needs to be passed:

await stelace.transactions.createTransition(transaction.id, {
  name: 'cancel',
  data: { cancellationReason: 'expired' }
})

For readability reasons, only "validated" status is linked to "cancelled" final status in the flow chart above, but any Transaction status can be cancelled except for "completed" status which is a final status too.

Transaction Events and Workflows

You can customize your Transaction process to the next level with Stelace Workflows.

As a simple example, let’s imagine we want to increment Asset price by 1 when a related Transaction status is changed to "booked" custom status name.

We just have to create the following Workflow that is triggered when transaction__status_changed Event is emitted.

transaction__status_changed Event is only emitted when you manually update some Transaction status.

await stelace.workflows.create({
  name: 'incrementPriceAfterBooking',
  event: 'transaction__status_changed', // event triggering this workflow
  computed: {
    // Evaluated Javascript expression values can be used in all run steps
    transactionId: 'transaction.id', // Transaction object is exposed
    // "asset", "owner" and "taker" related objects are also available
    newPrice: 'asset.price + 1'
  },
  run: [
    {
      endpointMethod: 'PATCH', // endpoint verb
      // only run this and any following step if transaction.status is "booked"
      stop: 'transaction.status !== "booked"',
      endpointUri: '/assets/${asset.id}',
      endpointPayload: {
        price: 'computed.newPrice'
      }
    }
  ]
})

To trigger this Workflow, you can manually update any Asset Transaction status to "booked", provided your API Key is granted transaction:config:all permission API to bypass Asset Type transactionProcess transitions.

await stelace.transactions.update(transaction.id, {
  status: 'booked'
})

Bravo! You’ve just designed your own Transaction process with Workflows.