@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
JavaScript
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