UNPKG

@daimo/pay

Version:

Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.

195 lines (192 loc) 5.24 kB
import { assert, DaimoPayOrderMode, DaimoPayIntentStatus, isHydrated } from '@daimo/pay-common'; import { parseUnits } from 'viem'; import { getDisplayExpiresAt } from './paymentUtils.js'; const initialPaymentState = { type: "idle" }; function paymentReducer(state, event) { switch (state.type) { case "idle": return reduceIdle(state, event); case "preview": return reducePreview(state, event); case "unhydrated": return reduceUnhydrated(state, event); case "payment_unpaid": return reducePaymentUnpaid(state, event); case "payment_started": return reducePaymentStarted(state, event); case "payment_completed": case "payment_bounced": case "error": return reduceTerminal(state, event); /* satisfies exhaustiveness */ default: const _exhaustive = state; return _exhaustive; } } function reduceIdle(state, event) { switch (event.type) { case "preview_generated": { const stateFromOrder = getStateFromOrder(event.order); if (stateFromOrder.type !== "unhydrated") { return stateFromOrder; } return { type: "preview", order: event.order, payParamsData: event.payParamsData }; } case "order_loaded": { return getStateFromOrder(event.order); } case "error": return { type: "error", order: event.order, message: event.message }; case "reset": return initialPaymentState; default: return state; } } function reducePreview(state, event) { assert( state.order.mode !== DaimoPayOrderMode.HYDRATED, "reducePreview called on hydrated order" ); switch (event.type) { case "order_hydrated": return getStateFromHydratedOrder(event.order); case "set_chosen_usd": { const token = state.order.destFinalCallTokenAmount.token; const tokenUnits = (event.usd / token.priceFromUsd).toString(); const tokenAmount = parseUnits(tokenUnits, token.decimals); return { type: "preview", order: { ...state.order, destFinalCallTokenAmount: { token, amount: tokenAmount.toString(), usd: event.usd } }, payParamsData: state.payParamsData }; } case "error": return { type: "error", order: event.order, message: event.message }; case "reset": return initialPaymentState; default: return state; } } function reduceUnhydrated(state, event) { switch (event.type) { case "order_hydrated": return { type: "payment_unpaid", order: event.order }; case "error": return { type: "error", order: event.order, message: event.message }; case "reset": return initialPaymentState; default: return state; } } function reducePaymentUnpaid(state, event) { switch (event.type) { case "payment_verified": { if (event.order.intentStatus === DaimoPayIntentStatus.UNPAID) { return { type: "error", order: event.order, message: "Payment failed" }; } return getStateFromHydratedOrder(event.order); } case "order_refreshed": return getStateFromHydratedOrder(event.order); case "error": return { type: "error", order: event.order, message: event.message }; case "reset": return initialPaymentState; default: return state; } } function reducePaymentStarted(state, event) { switch (event.type) { case "order_refreshed": return getStateFromHydratedOrder(event.order); case "error": return { type: "error", order: event.order, message: event.message }; case "reset": return initialPaymentState; default: return state; } } function getStateFromOrder(order) { if (order.mode === DaimoPayOrderMode.HYDRATED) { return getStateFromHydratedOrder(order); } else { return { type: "unhydrated", order }; } } function getStateFromHydratedOrder(order) { assert(isHydrated(order), `[PAYMENT_REDUCER] unhydrated`); switch (order.intentStatus) { case DaimoPayIntentStatus.COMPLETED: return { type: "payment_completed", order }; case DaimoPayIntentStatus.BOUNCED: return { type: "payment_bounced", order }; } const displayExpiresAt = getDisplayExpiresAt(order); if (Date.now() / 1e3 > displayExpiresAt) { return { type: "error", order, message: "Payment expired. Please restart." }; } switch (order.intentStatus) { case DaimoPayIntentStatus.UNPAID: return { type: "payment_unpaid", order }; case DaimoPayIntentStatus.STARTED: return { type: "payment_started", order }; default: return { type: "error", order, message: `Status: ${order.intentStatus}` }; } } function reduceTerminal(state, event) { switch (event.type) { case "reset": return initialPaymentState; // In terminal states we ignore everything except reset default: return state; } } export { initialPaymentState, paymentReducer }; //# sourceMappingURL=paymentFsm.js.map