Skip to content

Instantly share code, notes, and snippets.

@SariSaar
Last active February 21, 2024 11:46
Show Gist options
  • Save SariSaar/011a387db6a4555c7c4d0f1f70974a9b to your computer and use it in GitHub Desktop.
Save SariSaar/011a387db6a4555c7c4d0f1f70974a9b to your computer and use it in GitHub Desktop.
Code examples for building a shopping cart in Sharetribe Web Template – Designing a transaction flow
{:format :v3,
:transitions
[{:name :transition/reserve-stock,
:actor :actor.role/customer,
:actions
[{:name :action/update-protected-data}
{:name :action/create-pending-stock-reservation}],
:to :state/pending-stock}
{:name :transition/confirm-stock,
:actor :actor.role/customer
:actions
[{:name :action/accept-stock-reservation}]
:from :state/pending-stock
:to :state/purchased}
{:name :transition/auto-expire-stock,
:actions [{:name :action/decline-stock-reservation}]
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/pending-stock]}
{:fn/period ["PT15M"]}]},
:from :state/pending-stock
:to :state/canceled}
{:name :transition/cancel-stock,
:actor :actor.role/operator,
:actions
[{:name :action/cancel-stock-reservation}],
:from :state/purchased,
:to :state/canceled}
{:name :transition/complete-stock,
:actor :actor.role/operator
:actions []
:from :state/purchased
:to :state/completed}
{:name :transition/auto-complete-stock,
:actions []
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/purchased]}
{:fn/period ["P60D"]}]},
:from :state/purchased
:to :state/completed}
]
:notifications
[]}
;; Updates to default-purchase process for handling cart transactions
{:format :v3,
:transitions
[{:name :transition/inquire,
:actor :actor.role/customer,
:actions [{:name :action/update-protected-data}],
:to :state/inquiry}
{:name :transition/request-payment,
:actor :actor.role/customer,
:privileged? true,
:actions
[{:name :action/update-protected-data}
{:name :action/privileged-set-line-items}
{:name :action/stripe-create-payment-intent}],
:to :state/pending-update-child-transactions}
{:name :transition/request-payment-after-inquiry,
:actor :actor.role/customer,
:privileged? true,
:actions
[{:name :action/update-protected-data}
{:name :action/privileged-set-line-items}
{:name :action/stripe-create-payment-intent}],
:from :state/inquiry,
:to :state/pending-update-child-transactions}
{:name :transition/update-child-transactions,
:actor :actor.role/customer,
:actions
[{:name :action/update-protected-data}],
:from :state/pending-update-child-transactions
:to :state/pending-payment}
{:name :transition/confirm-payment,
:actor :actor.role/customer,
:actions
[{:name :action/stripe-confirm-payment-intent}
{:name :action/stripe-capture-payment-intent}],
:from :state/pending-payment,
:to :state/purchased}
{:name :transition/expire-payment,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/pending-payment]}
{:fn/period ["PT15M"]}]},
:actions
[{:name :action/calculate-full-refund}
{:name :action/stripe-refund-payment}],
:from :state/pending-payment,
:to :state/payment-expired}
{:name :transition/mark-received-from-purchased,
:actor :actor.role/customer,
:actions [{:name :action/stripe-create-payout}],
:from :state/purchased,
:to :state/received}
{:name :transition/mark-delivered,
:actor :actor.role/provider,
:actions [],
:from :state/purchased,
:to :state/delivered}
{:name :transition/operator-mark-delivered,
:actor :actor.role/operator,
:actions [],
:from :state/purchased,
:to :state/delivered}
{:name :transition/mark-received,
:actor :actor.role/customer,
:actions [{:name :action/stripe-create-payout}],
:from :state/delivered,
:to :state/received}
{:name :transition/auto-mark-received,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/delivered]}
{:fn/period ["P14D"]}]},
:actions [{:name :action/stripe-create-payout}],
:from :state/delivered,
:to :state/received}
{:name :transition/dispute,
:actor :actor.role/customer,
:actions [{:name :action/update-protected-data}],
:from :state/delivered,
:to :state/disputed}
{:name :transition/operator-dispute,
:actor :actor.role/operator,
:actions [],
:from :state/delivered,
:to :state/disputed}
{:name :transition/mark-received-from-disputed,
:actor :actor.role/operator,
:actions [{:name :action/stripe-create-payout}],
:from :state/disputed,
:to :state/received}
{:name :transition/cancel,
:actor :actor.role/operator,
:actions
[{:name :action/calculate-full-refund}
{:name :action/stripe-refund-payment}],
:from :state/purchased,
:to :state/canceled}
{:name :transition/auto-cancel,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/purchased]}
{:fn/period ["P14D"]}]},
:actions
[{:name :action/calculate-full-refund}
{:name :action/stripe-refund-payment}],
:from :state/purchased,
:to :state/canceled}
{:name :transition/operator-pending-partial-refund-from-disputed
:actor :actor.role/operator
:actions []
:from :state/disputed
:to :state/pending-partial-refund}
{:name :transition/operator-mark-received-with-partial-refund
:actor :actor.role/operator
:actions []
:from :state/pending-partial-refund
:to :state/received}
{:name :transition/cancel-from-disputed,
:actor :actor.role/operator,
:actions
[{:name :action/calculate-full-refund}
{:name :action/stripe-refund-payment}],
:from :state/disputed,
:to :state/canceled}
{:name :transition/auto-cancel-from-disputed,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/disputed]}
{:fn/period ["P60D"]}]},
:actions
[{:name :action/calculate-full-refund}
{:name :action/stripe-refund-payment}],
:from :state/disputed,
:to :state/canceled}
{:name :transition/auto-complete,
:at {:fn/timepoint [:time/first-entered-state :state/received]},
:actions [],
:from :state/received,
:to :state/completed}
{:name :transition/review-1-by-provider,
:actor :actor.role/provider,
:actions [{:name :action/post-review-by-provider}],
:from :state/completed,
:to :state/reviewed-by-provider}
{:name :transition/review-2-by-provider,
:actor :actor.role/provider,
:actions
[{:name :action/post-review-by-provider}
{:name :action/publish-reviews}],
:from :state/reviewed-by-customer,
:to :state/reviewed}
{:name :transition/review-1-by-customer,
:actor :actor.role/customer,
:actions [{:name :action/post-review-by-customer}],
:from :state/completed,
:to :state/reviewed-by-customer}
{:name :transition/review-2-by-customer,
:actor :actor.role/customer,
:actions
[{:name :action/post-review-by-customer}
{:name :action/publish-reviews}],
:from :state/reviewed-by-provider,
:to :state/reviewed}
{:name :transition/expire-review-period,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/received]}
{:fn/period ["P7D"]}]},
:actions [],
:from :state/completed,
:to :state/reviewed}
{:name :transition/expire-provider-review-period,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/received]}
{:fn/period ["P7D"]}]},
:actions [{:name :action/publish-reviews}],
:from :state/reviewed-by-customer,
:to :state/reviewed}
{:name :transition/expire-customer-review-period,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/received]}
{:fn/period ["P7D"]}]},
:actions [{:name :action/publish-reviews}],
:from :state/reviewed-by-provider,
:to :state/reviewed}],
:notifications
[{:name :notification/order-receipt,
:on :transition/confirm-payment,
;; This notification is delayed to give the customer a chance to verify their
;; email address, in case they are a new customer.
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/purchased]}
{:fn/period ["PT15M"]}]},
:to :actor.role/customer,
:template :purchase-order-receipt}
{:name :notification/purchase-new-order,
:on :transition/confirm-payment,
:to :actor.role/provider,
:template :purchase-new-order}
{:name :notification/shipping-reminder,
:on :transition/confirm-payment,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/purchased]}
{:fn/period ["P3D"]}]},
:to :actor.role/provider,
:template :purchase-shipping-reminder}
{:name :notification/order-marked-as-delivered,
:on :transition/mark-delivered,
:to :actor.role/customer,
:template :purchase-order-marked-as-delivered}
{:name
:notification/purchase-order-operator-marked-as-delivered-to-customer,
:on :transition/operator-mark-delivered,
:to :actor.role/customer,
:template :purchase-order-marked-as-delivered}
{:name
:notification/purchase-order-operator-marked-as-delivered-to-provider,
:on :transition/operator-mark-delivered,
:to :actor.role/provider,
:template :purchase-order-operator-marked-as-delivered}
{:name :notification/purchase-mark-order-received-reminder,
:on :transition/mark-delivered,
:at
{:fn/plus
[{:fn/timepoint [:time/first-entered-state :state/delivered]}
{:fn/period ["P12D"]}]},
:to :actor.role/customer,
:template :purchase-mark-order-received-reminder}
{:name :notification/order-marked-as-received-from-purchased,
:on :transition/mark-received-from-purchased,
:to :actor.role/provider,
:template :purchase-order-marked-as-received}
{:name :notification/order-marked-as-received,
:on :transition/mark-received,
:to :actor.role/provider,
:template :purchase-order-marked-as-received}
{:name :notification/shipping-time-expired,
:on :transition/auto-cancel,
:to :actor.role/customer,
:template :purchase-shipping-time-expired-customer}
{:name :notification/order-shipping-time-expired,
:on :transition/auto-cancel,
:to :actor.role/provider,
:template :purchase-shipping-time-expired-provider}
{:name :notification/purchase-canceled,
:on :transition/cancel,
:to :actor.role/customer,
:template :purchase-order-canceled-to-customer}
{:name :notification/order-canceled,
:on :transition/cancel,
:to :actor.role/provider,
:template :purchase-order-canceled-to-provider}
{:name :notification/purchase-order-auto-marked-as-received-customer,
:on :transition/auto-mark-received,
:to :actor.role/customer,
:template :purchase-order-auto-marked-as-received-customer}
{:name :notification/purchase-order-auto-marked-as-received-provider,
:on :transition/auto-mark-received,
:to :actor.role/provider,
:template :purchase-order-auto-marked-as-received-provider}
{:name :notification/order-disputed,
:on :transition/dispute,
:to :actor.role/provider,
:template :purchase-order-disputed}
{:name :notification/purchase-order-operator-disputed-to-customer,
:on :transition/operator-dispute,
:to :actor.role/customer,
:template :purchase-order-operator-disputed}
{:name :notification/purchase-order-operator-disputed-to-provider,
:on :transition/operator-dispute,
:to :actor.role/provider,
:template :purchase-order-disputed}
{:name :notification/order-received-from-disputed-customer,
:on :transition/mark-received-from-disputed,
:to :actor.role/customer,
:template :purchase-order-received-from-disputed-customer}
{:name :notification/order-received-from-disputed-provider,
:on :transition/mark-received-from-disputed,
:to :actor.role/provider,
:template :purchase-order-received-from-disputed-provider}
{:name :notification/order-received-from-disputed-partial-refund-customer,
:on :transition/operator-mark-received-with-partial-refund,
:to :actor.role/customer,
:template :purchase-order-received-from-disputed-customer}
{:name :notification/order-received-from-disputed-partial-refund-provider,
:on :transition/operator-mark-received-with-partial-refund,
:to :actor.role/provider,
:template :purchase-order-received-from-disputed-provider}
{:name :notification/canceled-from-disputed-customer,
:on :transition/cancel-from-disputed,
:to :actor.role/customer,
:template :purchase-order-canceled-from-disputed-customer}
{:name :notification/canceled-from-disputed-provider,
:on :transition/cancel-from-disputed,
:to :actor.role/provider,
:template :purchase-order-canceled-from-disputed-provider}
{:name :notification/auto-canceled-from-disputed-customer,
:on :transition/auto-cancel-from-disputed,
:to :actor.role/customer,
:template :purchase-order-auto-canceled-from-disputed-customer}
{:name :notification/auto-canceled-from-disputed-provider,
:on :transition/auto-cancel-from-disputed,
:to :actor.role/provider,
:template :purchase-order-auto-canceled-from-disputed-provider}
{:name :notification/review-period-start-provider,
:on :transition/auto-complete,
:to :actor.role/provider,
:template :purchase-order-review-by-provider-wanted}
{:name :notification/review-period-start-customer,
:on :transition/auto-complete,
:to :actor.role/customer,
:template :purchase-order-review-by-customer-wanted}
{:name :notification/review-by-provider-first,
:on :transition/review-1-by-provider,
:to :actor.role/customer,
:template :purchase-review-by-other-party-unpublished}
{:name :notification/review-by-customer-first,
:on :transition/review-1-by-customer,
:to :actor.role/provider,
:template :purchase-review-by-other-party-unpublished}
{:name :notification/review-by-provider-second,
:on :transition/review-2-by-provider,
:to :actor.role/customer,
:template :purchase-review-by-other-party-published}
{:name :notification/review-by-customer-second,
:on :transition/review-2-by-customer,
:to :actor.role/provider,
:template :purchase-review-by-other-party-published}]}
/**
* Transaction process graph for product order cart items:
* - cart-stock-process
*/
/**
* Transitions
*
* These strings must sync with values defined in Marketplace API,
* since transaction objects given by API contain info about last transitions.
* All the actions in API side happen in transitions,
* so we need to understand what those strings mean.
*/
export const transitions = {
CART_TRANSITION_RESERVE_STOCK: 'transition/reserve-stock',
CART_TRANSITION_CONFIRM_STOCK: 'transition/confirm-stock',
CART_TRANSITION_AUTO_EXPIRE_STOCK: 'transition/auto-expire-stock',
CART_TRANSITION_CANCEL_STOCK: 'transition/cancel-stock',
CART_TRANSITION_COMPLETE_STOCK: 'transition/complete-stock',
CART_TRANSITION_AUTO_COMPLETE_STOCK: 'transition/auto-complete-stock',
};
/**
* States
*
* These constants are only for making it clear how transitions work together.
* You should not use these constants outside of this file.
*
* Note: these states are not in sync with states used transaction process definitions
* in Marketplace API. Only last transitions are passed along transaction object.
*/
export const states = {
INITIAL: 'initial',
PENDING_STOCK: 'pending-stock',
PURCHASED: 'purchased',
CANCELED: 'canceled',
COMPLETED: 'completed',
};
/**
* Description of transaction process graph
*
* You should keep this in sync with transaction process defined in Marketplace API
*
* Note: we don't use yet any state machine library,
* but this description format is following Xstate (FSM library)
* https://xstate.js.org/docs/
*/
export const graph = {
// id is defined only to support Xstate format.
// However if you have multiple transaction processes defined,
// it is best to keep them in sync with transaction process aliases.
id: 'cart-stock-process/release-1',
// This 'initial' state is a starting point for new transaction
initial: states.INITIAL,
// States
states: {
[states.INITIAL]: {
on: {
[transitions.CART_TRANSITION_RESERVE_STOCK]: states.PENDING_STOCK,
},
},
[states.PENDING_STOCK]: {
on: {
[transitions.CART_TRANSITION_CONFIRM_STOCK]: states.PURCHASED,
[transitions.CART_TRANSITION_AUTO_EXPIRE_STOCK]: states.CANCELED,
},
},
[states.PURCHASED]: {
on: {
[transitions.CART_TRANSITION_CANCEL_STOCK]: states.CANCELED,
[transitions.CART_TRANSITION_COMPLETE_STOCK]: states.COMPLETED,
[transitions.CART_TRANSITION_AUTO_COMPLETE_STOCK]: states.COMPLETED,
},
},
[states.CANCELED]: { type: 'final' },
[states.COMPLETED]: { type: 'final' },
},
};
...
export const transitions = {
...
UPDATE_CHILD_TRANSACTIONS: 'transition/update-child-transactions',
...
OPERATOR_PENDING_PARTIAL_REFUND_FROM_DISPUTED:
'transition/operator-pending-partial-refund-from-disputed',
OPERATOR_MARK_RECEIVED_WITH_PARTIAL_REFUND:
'transition/operator-mark-received-with-partial-refund',
...
};
/**
* States
*
* These constants are only for making it clear how transitions work together.
* You should not use these constants outside of this file.
*
* Note: these states are not in sync with states used transaction process definitions
* in Marketplace API. Only last transitions are passed along transaction object.
*/
export const states = {
...
PENDING_UPDATE_CHILD_TRANSACTIONS: 'pending-update-child-transactions',
...
PENDING_PARTIAL_REFUND: 'pending-partial-refund',
...
};
/**
* Description of transaction process graph
*
* You should keep this in sync with transaction process defined in Marketplace API
*
* Note: we don't use yet any state machine library,
* but this description format is following Xstate (FSM library)
* https://xstate.js.org/docs/
*/
export const graph = {
// id is defined only to support Xstate format.
// However if you have multiple transaction processes defined,
// it is best to keep them in sync with transaction process aliases.
id: 'default-purchase/release-1',
// This 'initial' state is a starting point for new transaction
initial: states.INITIAL,
// States
states: {
[states.INITIAL]: {
on: {
[transitions.INQUIRE]: states.INQUIRY,
[transitions.REQUEST_PAYMENT]: states.PENDING_UPDATE_CHILD_TRANSACTIONS,
},
},
[states.INQUIRY]: {
on: {
[transitions.REQUEST_PAYMENT_AFTER_INQUIRY]: states.PENDING_UPDATE_CHILD_TRANSACTIONS,
},
},
[states.PENDING_UPDATE_CHILD_TRANSACTIONS]: {
on: {
[transitions.UPDATE_CHILD_TRANSACTIONS]: states.PENDING_PAYMENT,
},
},
...
[states.DISPUTED]: {
on: {
[transitions.AUTO_CANCEL_FROM_DISPUTED]: states.CANCELED,
[transitions.CANCEL_FROM_DISPUTED]: states.CANCELED,
[transitions.MARK_RECEIVED_FROM_DISPUTED]: states.RECEIVED,
[transitions.OPERATOR_PENDING_PARTIAL_REFUND_FROM_DISPUTED]: states.PENDING_PARTIAL_REFUND,
},
},
[states.PENDING_PARTIAL_REFUND]: {
on: {
[transitions.OPERATOR_MARK_RECEIVED_WITH_PARTIAL_REFUND]: states.RECEIVED,
},
},
...
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment