@mpgd/adapter-devvit 0.9.19 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commerce-contract.d.ts +85 -0
- package/dist/commerce-contract.js +145 -0
- package/dist/commerce-provider.d.ts +93 -0
- package/dist/commerce-provider.js +472 -0
- package/dist/index.d.ts +41 -2
- package/dist/index.js +154 -3
- package/dist/payments.d.ts +17 -0
- package/dist/payments.js +3 -2
- package/dist/server/commerce-bridge.d.ts +111 -0
- package/dist/server/commerce-bridge.js +402 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { bridgeStorageLoadProtocol, createBridgeError, createCommerceV2UnavailableBridgeError, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
|
|
1
|
+
import { bridgeStorageLoadProtocol, commerceV2UnavailableBridgeErrorCode, createBridgeError, createCommerceV2UnavailableBridgeError, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
|
|
2
2
|
import { createBridgeOrpcClient, defaultBridgeRpcEndpoint, } from '@mpgd/bridge/orpc';
|
|
3
|
+
import { PlatformOperationError, } from '@mpgd/platform';
|
|
4
|
+
import { assertCommerceBridgeGetAvailablePurchasesResult, assertCommerceBridgeGetCapabilitiesResult, CommerceError, commerceProtocol, deriveLegacyCommerceCapabilities, isCommerceErrorCode, } from '@mpgd/platform/commerce';
|
|
5
|
+
import { assertDevvitOrderEvidence } from './commerce-contract.js';
|
|
6
|
+
import { createDevvitCommerceProvider, } from './commerce-provider.js';
|
|
7
|
+
export * from './commerce-contract.js';
|
|
8
|
+
export * from './commerce-provider.js';
|
|
3
9
|
export * from './payments.js';
|
|
4
10
|
export const defaultDevvitRpcEndpoint = defaultBridgeRpcEndpoint;
|
|
11
|
+
/** Bridge error of `createDevvitOrpcBridge` when the oRPC request itself failed. */
|
|
12
|
+
export const devvitBridgeNetworkErrorCode = 'DEVVIT_BRIDGE_NETWORK_ERROR';
|
|
5
13
|
export class DevvitBridgeError extends Error {
|
|
6
14
|
code;
|
|
7
15
|
requestId;
|
|
@@ -36,10 +44,23 @@ export function createDevvitPlatformGateway(input) {
|
|
|
36
44
|
}
|
|
37
45
|
return response.data;
|
|
38
46
|
}
|
|
47
|
+
const commerceV2 = input.commerce === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: createDevvitCommerceProvider({
|
|
50
|
+
...input.commerce,
|
|
51
|
+
orders: createDevvitBridgeOrderSource(request),
|
|
52
|
+
});
|
|
39
53
|
return {
|
|
40
54
|
target: 'reddit',
|
|
41
55
|
async getCapabilities() {
|
|
42
|
-
|
|
56
|
+
const capabilities = { ...await request('runtime.getCapabilities', {}) };
|
|
57
|
+
if (commerceV2 === undefined) {
|
|
58
|
+
return capabilities;
|
|
59
|
+
}
|
|
60
|
+
// The webview provider owns the block: the server snapshot stays honest
|
|
61
|
+
// about the server, and the flags derive from the block it reports.
|
|
62
|
+
const commerce = await commerceV2.getCapabilities();
|
|
63
|
+
return { ...capabilities, ...deriveLegacyCommerceCapabilities(commerce), commerce };
|
|
43
64
|
},
|
|
44
65
|
identity: {
|
|
45
66
|
getPlayer: () => request('identity.getPlayer', {}),
|
|
@@ -64,6 +85,7 @@ export function createDevvitPlatformGateway(input) {
|
|
|
64
85
|
restore: () => request('commerce.restore', {}),
|
|
65
86
|
getEntitlements: () => request('commerce.getEntitlements', {}),
|
|
66
87
|
},
|
|
88
|
+
...(commerceV2 === undefined ? {} : { commerceV2 }),
|
|
67
89
|
ads: {
|
|
68
90
|
preload: (payload) => request('ads.preload', payload),
|
|
69
91
|
showRewarded: (payload) => request('ads.showRewarded', payload),
|
|
@@ -94,6 +116,135 @@ export function createDevvitPlatformGateway(input) {
|
|
|
94
116
|
},
|
|
95
117
|
};
|
|
96
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* The webview provider's order source over the server bridge: the paid orders
|
|
121
|
+
* the server lists as available purchases, mapped back to orders (the
|
|
122
|
+
* provider rebuilds the purchases under its own store id), and the
|
|
123
|
+
* availability the server reports. `COMMERCE_V2_UNAVAILABLE` from a bridge
|
|
124
|
+
* without the commerce handler is `configuration-required`; another bridge
|
|
125
|
+
* failure that is retryable (or the transport failing) keeps the availability
|
|
126
|
+
* the server last answered for the capability read (`temporarily-unavailable`
|
|
127
|
+
* before any answer; a non-retryable failure or an invalid answer fails
|
|
128
|
+
* closed) and is
|
|
129
|
+
* a `CommerceError` for a list read (`NETWORK` for a transport failure,
|
|
130
|
+
* otherwise `UNKNOWN` with the bridge code as `storeCode`).
|
|
131
|
+
*/
|
|
132
|
+
export function createDevvitBridgeOrderSource(request) {
|
|
133
|
+
/** The availability the server last answered; kept across transient read failures. */
|
|
134
|
+
let lastAvailability;
|
|
135
|
+
return {
|
|
136
|
+
async listPaidOrders() {
|
|
137
|
+
let data;
|
|
138
|
+
try {
|
|
139
|
+
data = await request('commerce.v2.getAvailablePurchases', {});
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw toCommerceError(error);
|
|
143
|
+
}
|
|
144
|
+
let purchases;
|
|
145
|
+
try {
|
|
146
|
+
purchases = assertCommerceBridgeGetAvailablePurchasesResult(data).purchases;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
throw new CommerceError('INVALID_RESPONSE', {
|
|
150
|
+
message: 'Devvit server bridge returned invalid data for commerce.v2.getAvailablePurchases.',
|
|
151
|
+
cause: error,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const orders = [];
|
|
155
|
+
for (const purchase of purchases) {
|
|
156
|
+
let environment;
|
|
157
|
+
let delivered;
|
|
158
|
+
try {
|
|
159
|
+
const evidence = assertDevvitOrderEvidence(purchase.evidence);
|
|
160
|
+
environment = evidence.environment;
|
|
161
|
+
delivered = evidence.status === undefined ? undefined : evidence.status === 'DELIVERED';
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// The server listed an order under another evidence schema; the
|
|
165
|
+
// order itself is still the server's word.
|
|
166
|
+
environment = undefined;
|
|
167
|
+
}
|
|
168
|
+
orders.push({
|
|
169
|
+
orderId: purchase.transactionId,
|
|
170
|
+
sku: purchase.storeSku,
|
|
171
|
+
...(purchase.purchasedAt === undefined ? {} : { paidAt: purchase.purchasedAt }),
|
|
172
|
+
...(environment === undefined ? {} : { environment }),
|
|
173
|
+
...(delivered === undefined ? {} : { delivered }),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return orders;
|
|
177
|
+
},
|
|
178
|
+
async getAvailability() {
|
|
179
|
+
let data;
|
|
180
|
+
try {
|
|
181
|
+
data = await request('commerce.v2.getCapabilities', { protocol: commerceProtocol });
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (error instanceof DevvitBridgeError
|
|
185
|
+
&& error.code === commerceV2UnavailableBridgeErrorCode) {
|
|
186
|
+
// The server does not serve commerce v2: fail closed, whatever it answered before.
|
|
187
|
+
lastAvailability = undefined;
|
|
188
|
+
return 'configuration-required';
|
|
189
|
+
}
|
|
190
|
+
if (isTransientBridgeFailure(error)) {
|
|
191
|
+
// A retryable failure keeps the last answer the server gave, so one
|
|
192
|
+
// timed-out read does not hide the shop; the first read fails closed.
|
|
193
|
+
return lastAvailability ?? 'temporarily-unavailable';
|
|
194
|
+
}
|
|
195
|
+
// Anything else is not transient: fail closed and forget the old answer.
|
|
196
|
+
lastAvailability = undefined;
|
|
197
|
+
return 'temporarily-unavailable';
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
lastAvailability = assertCommerceBridgeGetCapabilitiesResult(data).capabilities.availability;
|
|
201
|
+
return lastAvailability;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// The server answered something the contract rejects: fail closed.
|
|
205
|
+
lastAvailability = undefined;
|
|
206
|
+
return 'temporarily-unavailable';
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* A bridge failure as a commerce error: a closed server
|
|
213
|
+
* (`COMMERCE_V2_UNAVAILABLE`) is `CONFIGURATION_REQUIRED`, a transport failure
|
|
214
|
+
* (`DEVVIT_BRIDGE_NETWORK_ERROR`) is `NETWORK`, a v2 code is kept, and any
|
|
215
|
+
* other bridge code is `UNKNOWN` with the code as `storeCode`.
|
|
216
|
+
*/
|
|
217
|
+
/** A retryable bridge error, or the oRPC transport failing: the server may answer again shortly. */
|
|
218
|
+
function isTransientBridgeFailure(error) {
|
|
219
|
+
if (error instanceof DevvitBridgeError) {
|
|
220
|
+
return error.retryable || error.code === devvitBridgeNetworkErrorCode;
|
|
221
|
+
}
|
|
222
|
+
return error instanceof PlatformOperationError && error.retryable;
|
|
223
|
+
}
|
|
224
|
+
function toCommerceError(error) {
|
|
225
|
+
if (error instanceof CommerceError) {
|
|
226
|
+
return error;
|
|
227
|
+
}
|
|
228
|
+
if (error instanceof DevvitBridgeError || error instanceof PlatformOperationError) {
|
|
229
|
+
const code = error.code === commerceV2UnavailableBridgeErrorCode
|
|
230
|
+
? 'CONFIGURATION_REQUIRED'
|
|
231
|
+
: error.code === devvitBridgeNetworkErrorCode
|
|
232
|
+
? 'NETWORK'
|
|
233
|
+
: isCommerceErrorCode(error.code)
|
|
234
|
+
? error.code
|
|
235
|
+
: 'UNKNOWN';
|
|
236
|
+
return new CommerceError(code, {
|
|
237
|
+
message: error.message,
|
|
238
|
+
retryable: code === 'NETWORK' ? true : error.retryable,
|
|
239
|
+
...(code === error.code ? {} : { storeCode: error.code }),
|
|
240
|
+
cause: error,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return new CommerceError('UNKNOWN', {
|
|
244
|
+
message: `Devvit server bridge request failed: ${errorMessage(error)}`,
|
|
245
|
+
cause: error,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
97
248
|
export function createDevvitOrpcBridge(input = {}) {
|
|
98
249
|
const client = input.client ?? createBridgeOrpcClient({
|
|
99
250
|
url: input.endpoint ?? defaultDevvitRpcEndpoint,
|
|
@@ -105,7 +256,7 @@ export function createDevvitOrpcBridge(input = {}) {
|
|
|
105
256
|
return await client.request(bridgeRequest);
|
|
106
257
|
}
|
|
107
258
|
catch (error) {
|
|
108
|
-
return createBridgeError(bridgeRequest.id,
|
|
259
|
+
return createBridgeError(bridgeRequest.id, devvitBridgeNetworkErrorCode, `Devvit oRPC bridge request failed: ${errorMessage(error)}`, true);
|
|
109
260
|
}
|
|
110
261
|
},
|
|
111
262
|
};
|
package/dist/payments.d.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import type { CommerceAdapter, Entitlement, PlatformEvidenceEnvelope, PlatformGateway, ProductInfo, PurchaseResult } from '@mpgd/platform';
|
|
2
|
+
/**
|
|
3
|
+
* Why a Devvit checkout ended without an order (`OrderResultEvent` with
|
|
4
|
+
* `STATUS_ERROR` or `STATUS_CANCELLED`). `code` is a commerce v2 error code
|
|
5
|
+
* when the client could classify the failure, otherwise the platform's own
|
|
6
|
+
* code (`String(errorCode)`), which the v2 provider keeps as `storeCode`.
|
|
7
|
+
*/
|
|
8
|
+
export interface DevvitCheckoutFailure {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly storeCode?: string;
|
|
11
|
+
readonly message?: string;
|
|
12
|
+
readonly retryable?: boolean;
|
|
13
|
+
}
|
|
2
14
|
export interface DevvitCheckoutResult {
|
|
3
15
|
readonly status: PurchaseResult['status'];
|
|
16
|
+
/** `OrderResultEvent.orderId`: present once Reddit created the order. */
|
|
4
17
|
readonly orderId?: string;
|
|
5
18
|
readonly evidence?: PlatformEvidenceEnvelope;
|
|
19
|
+
/** Read by the commerce v2 provider for a `cancelled` or `failed` checkout. */
|
|
20
|
+
readonly error?: DevvitCheckoutFailure;
|
|
6
21
|
}
|
|
7
22
|
export interface DevvitPaymentsClient {
|
|
8
23
|
purchase(sku: string): Promise<DevvitCheckoutResult>;
|
|
@@ -19,3 +34,5 @@ export interface CreateDevvitCommerceAdapterInput {
|
|
|
19
34
|
}
|
|
20
35
|
export declare function createDevvitCommerceAdapter(input: CreateDevvitCommerceAdapterInput): CommerceAdapter;
|
|
21
36
|
export declare function withDevvitCommerceAdapter(gateway: PlatformGateway, commerce: CommerceAdapter): PlatformGateway;
|
|
37
|
+
/** Frozen copies of the configured products; duplicate ids or SKUs and blank SKUs are rejected. */
|
|
38
|
+
export declare function normalizeDevvitCommerceProducts(input: readonly DevvitCommerceProduct[]): readonly DevvitCommerceProduct[];
|
package/dist/payments.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function createDevvitCommerceAdapter(input) {
|
|
2
|
-
const products =
|
|
2
|
+
const products = normalizeDevvitCommerceProducts(input.products);
|
|
3
3
|
const productsById = new Map(products.map((product) => [product.info.id, product]));
|
|
4
4
|
return {
|
|
5
5
|
async getProducts() {
|
|
@@ -53,7 +53,8 @@ export function withDevvitCommerceAdapter(gateway, commerce) {
|
|
|
53
53
|
commerce,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
/** Frozen copies of the configured products; duplicate ids or SKUs and blank SKUs are rejected. */
|
|
57
|
+
export function normalizeDevvitCommerceProducts(input) {
|
|
57
58
|
const productIds = new Set();
|
|
58
59
|
const skus = new Set();
|
|
59
60
|
return Object.freeze(input.map((product) => {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type BridgeRequest, type BridgeResponse } from '@mpgd/bridge';
|
|
2
|
+
import { type StoreId } from '@mpgd/platform/commerce';
|
|
3
|
+
/**
|
|
4
|
+
* Host side of commerce v2 for Devvit: the server bridge serves the six
|
|
5
|
+
* `commerce.v2.*` methods from the Devvit `payments` plugin
|
|
6
|
+
* (`@devvit/payments/server`, re-exported by `@devvit/web/server`). The
|
|
7
|
+
* plugin runs in the request context of the authenticated Reddit user:
|
|
8
|
+
* Reddit documents server-side `payments.getOrders()` as the current user's
|
|
9
|
+
* orders, newest first (Devvit docs, "Manage Payments", "Check orders"),
|
|
10
|
+
* which the fulfillment handler's own user binding backs. The checkout is a webview
|
|
11
|
+
* effect, so `requestPurchase` is answered by the client provider, never
|
|
12
|
+
* here. This module imports no Devvit SDK; the plugin surface it needs is
|
|
13
|
+
* the structural `DevvitServerPaymentsLike`.
|
|
14
|
+
*/
|
|
15
|
+
/** Bridge error for a commerce v2 payload the typia assert rejected. */
|
|
16
|
+
export declare const devvitCommerceV2InvalidPayloadBridgeErrorCode: 'COMMERCE_V2_INVALID_PAYLOAD';
|
|
17
|
+
/** Bridge error for `finishTransaction`: the Devvit profile has nothing to finish. */
|
|
18
|
+
export declare const devvitCommerceV2MethodUnsupportedBridgeErrorCode: 'COMMERCE_V2_METHOD_UNSUPPORTED';
|
|
19
|
+
/** Bridge error for `requestPurchase` reaching the server: the checkout opens in the webview. */
|
|
20
|
+
export declare const devvitCommerceCheckoutClientOnlyBridgeErrorCode: 'DEVVIT_COMMERCE_CHECKOUT_CLIENT_ONLY';
|
|
21
|
+
/** Bridge error when the payments plugin fails; retryable. */
|
|
22
|
+
export declare const devvitPaymentsUnavailableBridgeErrorCode: 'DEVVIT_PAYMENTS_UNAVAILABLE';
|
|
23
|
+
/** `Order.status` as `@devvit/protos` encodes it (`OrderStatus` enum) or as the handler types spell it. */
|
|
24
|
+
export type DevvitPaymentsOrderStatusLike = number | string;
|
|
25
|
+
/** The fields of a `@devvit/payments` `Order` the handler reads. */
|
|
26
|
+
export interface DevvitPaymentsOrderLike {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly status: DevvitPaymentsOrderStatusLike;
|
|
29
|
+
readonly createdAt?: string | null | undefined;
|
|
30
|
+
readonly updatedAt?: string | null | undefined;
|
|
31
|
+
readonly products: readonly {
|
|
32
|
+
readonly sku: string;
|
|
33
|
+
}[];
|
|
34
|
+
readonly environment?: number | string | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** The fields of a `@devvit/payments` `Product` the handler reads. */
|
|
37
|
+
export interface DevvitPaymentsProductLike {
|
|
38
|
+
readonly sku: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly description: string;
|
|
41
|
+
readonly price?: {
|
|
42
|
+
readonly amount: number;
|
|
43
|
+
readonly currency: number | string;
|
|
44
|
+
} | undefined;
|
|
45
|
+
readonly accountingType: number | string;
|
|
46
|
+
}
|
|
47
|
+
/** The subset of the Devvit `payments` plugin the handler calls (`PaymentsClient` fits). */
|
|
48
|
+
export interface DevvitServerPaymentsLike {
|
|
49
|
+
getOrders(filters: {
|
|
50
|
+
readonly limit: number;
|
|
51
|
+
readonly cursor?: string;
|
|
52
|
+
/** `OrderStatus` hint; `@devvit/payments` 0.14 ignores it, so the handler filters itself. */
|
|
53
|
+
readonly status?: number;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
readonly orders: readonly DevvitPaymentsOrderLike[];
|
|
56
|
+
readonly pageInfo?: {
|
|
57
|
+
readonly hasNextPage: boolean;
|
|
58
|
+
readonly endCursor: string;
|
|
59
|
+
} | undefined;
|
|
60
|
+
}>;
|
|
61
|
+
getProducts(filters?: {
|
|
62
|
+
readonly skus?: readonly string[];
|
|
63
|
+
}): Promise<{
|
|
64
|
+
readonly products: readonly DevvitPaymentsProductLike[];
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
export interface DevvitCommerceBridgeProduct {
|
|
68
|
+
readonly productId: string;
|
|
69
|
+
readonly sku: string;
|
|
70
|
+
}
|
|
71
|
+
export interface CreateDevvitCommerceV2BridgeHandlerInput {
|
|
72
|
+
readonly payments: DevvitServerPaymentsLike;
|
|
73
|
+
/** The game's logical products and their Devvit SKUs; orders for other SKUs are not listed. */
|
|
74
|
+
readonly products: readonly DevvitCommerceBridgeProduct[];
|
|
75
|
+
/** Defaults to `devvit`. */
|
|
76
|
+
readonly store?: StoreId;
|
|
77
|
+
readonly warn?: ((message: string) => void) | undefined;
|
|
78
|
+
}
|
|
79
|
+
export interface DevvitCommerceV2BridgeHandler {
|
|
80
|
+
/** Answers one of the six `commerce.v2.*` methods; other methods are rejected. */
|
|
81
|
+
request(input: BridgeRequest): Promise<BridgeResponse>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Serves `commerce.v2.*` from the payments plugin:
|
|
85
|
+
*
|
|
86
|
+
* - `getCapabilities` answers the Devvit profile (`createDevvitCommerceCapabilities`).
|
|
87
|
+
* - `fetchProducts` lists the configured SKUs from `payments.getProducts`
|
|
88
|
+
* (instant and consumable goods as `consumable`, durable goods as
|
|
89
|
+
* `durable`; time-limited goods are not listed) priced in Reddit Gold.
|
|
90
|
+
* - `getAvailablePurchases` lists the authenticated user's `PAID` and
|
|
91
|
+
* `DELIVERED` orders for configured SKUs from `payments.getOrders` as
|
|
92
|
+
* `purchased` (the order id as `transactionId` and `token`,
|
|
93
|
+
* `devvit.order.v1` evidence whose `status` says which), bounded by
|
|
94
|
+
* `maximumDevvitAvailableOrders`. Reddit marks an order `DELIVERED` as soon
|
|
95
|
+
* as the fulfillment handler answered, so the delivered orders are the ones
|
|
96
|
+
* whose ledger entry exists; the webview provider lists only the `PAID`
|
|
97
|
+
* ones as available and reports both as events, so a session that lost its
|
|
98
|
+
* checkout result still finds the order. The scan reads pages of 64
|
|
99
|
+
* (newest first, as the Devvit docs describe) and stops after two
|
|
100
|
+
* consecutive pages without a paid or delivered order or after
|
|
101
|
+
* `maximumOrderPages`, warning when pages are left.
|
|
102
|
+
* - `requestPurchase` is refused with `DEVVIT_COMMERCE_CHECKOUT_CLIENT_ONLY`,
|
|
103
|
+
* `finishTransaction` with `COMMERCE_V2_METHOD_UNSUPPORTED`, and
|
|
104
|
+
* `restorePurchases` answers `{ restored: false }`: the webview provider
|
|
105
|
+
* re-reads the list itself.
|
|
106
|
+
*
|
|
107
|
+
* Payloads and results are validated with the `@mpgd/platform/commerce`
|
|
108
|
+
* asserts; a plugin failure crosses the bridge as the retryable
|
|
109
|
+
* `DEVVIT_PAYMENTS_UNAVAILABLE`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function createDevvitCommerceV2BridgeHandler(input: CreateDevvitCommerceV2BridgeHandlerInput): DevvitCommerceV2BridgeHandler;
|