@phpsandbox/sdk 0.3.4 → 0.4.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.
@@ -0,0 +1,256 @@
1
+ import { z } from 'zod';
2
+ export const DEFAULT_PAYMENT_CALLBACK_PATH = '/api/phpsandbox-payments/events';
3
+ export const paymentModeSchema = z.enum(['test', 'live']);
4
+ const accountSchema = z.object({
5
+ id: z.string(),
6
+ mode: paymentModeSchema,
7
+ chargesEnabled: z.boolean(),
8
+ payoutsEnabled: z.boolean(),
9
+ detailsSubmitted: z.boolean(),
10
+ requirements: z.array(z.string()),
11
+ disabledReason: z.string().nullable(),
12
+ });
13
+ const accessSchema = z.object({
14
+ subscribed: z.boolean(),
15
+ liveAllowed: z.boolean(),
16
+ testConfigured: z.boolean(),
17
+ liveConfigured: z.boolean(),
18
+ feeBasisPoints: z.number().int().min(0).max(10000).nullable(),
19
+ canRead: z.boolean(),
20
+ canManage: z.boolean(),
21
+ canRefund: z.boolean(),
22
+ recoveryEndsAt: z.string().nullable().default(null),
23
+ });
24
+ const applicationsSchema = z.object({
25
+ notebookId: z.string(),
26
+ applications: z.array(z.object({
27
+ id: z.string(),
28
+ url: z.string().nullable(),
29
+ healthy: z.boolean(),
30
+ passwordProtected: z.boolean(),
31
+ deployedAt: z.string().datetime().nullable(),
32
+ })),
33
+ });
34
+ const bindingSchema = z.object({
35
+ id: z.string(),
36
+ notebookId: z.string(),
37
+ applicationId: z.string().nullable(),
38
+ mode: paymentModeSchema,
39
+ revision: z.number().int(),
40
+ accountId: z.string(),
41
+ enabled: z.boolean(),
42
+ callbackUrl: z.string().nullable(),
43
+ originUrl: z.string().nullable(),
44
+ runtimeConfigured: z.boolean(),
45
+ });
46
+ const priceSchema = z.object({
47
+ billingInterval: z.enum(['month', 'year']).nullable().default(null),
48
+ id: z.string(),
49
+ name: z.string(),
50
+ amount: z.number().int(),
51
+ currency: z.string(),
52
+ bindingId: z.string(),
53
+ });
54
+ const transactionSchema = z.object({
55
+ subscriptionId: z.string().nullable().default(null),
56
+ periodStart: z.string().datetime({ offset: true }).nullable().default(null),
57
+ periodEnd: z.string().datetime({ offset: true }).nullable().default(null),
58
+ id: z.string(),
59
+ bindingId: z.string(),
60
+ orderId: z.string(),
61
+ priceId: z.string(),
62
+ amount: z.number().int(),
63
+ currency: z.string(),
64
+ feeAmount: z.number().int(),
65
+ revision: z.number().int(),
66
+ refundedAmount: z.number().int(),
67
+ status: z.enum(['pending', 'paid', 'failed', 'refunded', 'partially_refunded']),
68
+ checkoutUrl: z.string().nullable(),
69
+ deliveredAt: z.string().nullable(),
70
+ });
71
+ const subscriptionSchema = z.object({
72
+ id: z.string(),
73
+ bindingId: z.string(),
74
+ priceId: z.string(),
75
+ orderId: z.string(),
76
+ status: z.enum([
77
+ 'pending',
78
+ 'incomplete',
79
+ 'incomplete_expired',
80
+ 'trialing',
81
+ 'active',
82
+ 'past_due',
83
+ 'canceled',
84
+ 'unpaid',
85
+ 'paused',
86
+ ]),
87
+ billingInterval: z.enum(['month', 'year']),
88
+ paidThrough: z.string().datetime({ offset: true }).nullable(),
89
+ cancelAtPeriodEnd: z.boolean(),
90
+ checkoutUrl: z.string().nullable(),
91
+ revision: z.number().int(),
92
+ });
93
+ const portalSchema = z.object({ url: z.string().url() });
94
+ const refundResultSchema = transactionSchema.extend({
95
+ refund: z.object({
96
+ id: z.string(),
97
+ amount: z.number().int().positive(),
98
+ status: z.enum(['pending', 'succeeded', 'failed', 'canceled', 'requires_action']),
99
+ }),
100
+ });
101
+ const activationSchema = z.object({
102
+ revision: z.number().int(),
103
+ reviewId: z.string(),
104
+ expiresAt: z.string(),
105
+ blockers: z.array(z.string()),
106
+ binding: bindingSchema,
107
+ account: accountSchema,
108
+ feeBasisPoints: z.number().int().nullable(),
109
+ prices: z.array(priceSchema),
110
+ });
111
+ export class PaymentsApi {
112
+ constructor(client) {
113
+ this.client = client;
114
+ this.runtime = new PaymentRuntime(client);
115
+ }
116
+ forNotebook(notebookId) {
117
+ return new NotebookPayments(notebookId, this.client);
118
+ }
119
+ async applications(notebookId) {
120
+ return applicationsSchema.parse((await this.client.get(`/notebook/${encodeURIComponent(notebookId)}/payments/applications`)).data);
121
+ }
122
+ async access() {
123
+ return accessSchema.parse((await this.client.get('/platform/payments/access')).data);
124
+ }
125
+ async accounts() {
126
+ return z.array(accountSchema).parse((await this.client.get('/platform/payments/accounts')).data);
127
+ }
128
+ async createAccount(mode, country) {
129
+ return accountSchema.parse((await this.client.put(`/platform/payments/accounts/${mode}`, { country })).data);
130
+ }
131
+ async onboarding(mode) {
132
+ return z
133
+ .object({ url: z.string().url(), expiresAt: z.string() })
134
+ .parse((await this.client.post(`/platform/payments/accounts/${mode}/onboarding`, {})).data);
135
+ }
136
+ }
137
+ export class NotebookPayments {
138
+ constructor(notebookId, client) {
139
+ this.notebookId = notebookId;
140
+ this.client = client;
141
+ }
142
+ path(mode) {
143
+ return `/platform/payments/sandboxes/${encodeURIComponent(this.notebookId)}/${mode}`;
144
+ }
145
+ async get(mode = 'test') {
146
+ return bindingSchema.nullable().parse((await this.client.get(this.path(mode))).data);
147
+ }
148
+ async configure(input, mode = 'test') {
149
+ return bindingSchema.parse((await this.client.put(this.path(mode), input)).data);
150
+ }
151
+ async provision(mode = 'test') {
152
+ return bindingSchema.parse((await this.client.post(`${this.path(mode)}/provision`, {})).data);
153
+ }
154
+ async prices(mode = 'test') {
155
+ return z.array(priceSchema).parse((await this.client.get(`${this.path(mode)}/prices`)).data);
156
+ }
157
+ async createPrice(input, mode = 'test') {
158
+ return priceSchema.parse((await this.client.post(`${this.path(mode)}/prices`, input)).data);
159
+ }
160
+ async prepareActivation(mode = 'test') {
161
+ return activationSchema.parse((await this.client.post(`${this.path(mode)}/activation`, {})).data);
162
+ }
163
+ async activate(reviewId, mode = 'test') {
164
+ return bindingSchema.parse((await this.client.post(`${this.path(mode)}/activate`, { reviewId })).data);
165
+ }
166
+ async disable(mode = 'test') {
167
+ return bindingSchema.parse((await this.client.post(`${this.path(mode)}/disable`, {})).data);
168
+ }
169
+ async checkout(input, mode = 'test') {
170
+ return transactionSchema.parse((await this.client.post(`${this.path(mode)}/checkouts`, input)).data);
171
+ }
172
+ async subscribe(input, mode = 'test') {
173
+ return subscriptionSchema.parse((await this.client.post(`${this.path(mode)}/subscriptions`, input)).data);
174
+ }
175
+ async subscriptions(mode = 'test') {
176
+ return z.array(subscriptionSchema).parse((await this.client.get(`${this.path(mode)}/subscriptions`)).data);
177
+ }
178
+ async subscription(id, mode = 'test') {
179
+ return subscriptionSchema.parse((await this.client.get(`${this.path(mode)}/subscriptions/${encodeURIComponent(id)}`)).data);
180
+ }
181
+ async cancelSubscription(id, mode = 'test') {
182
+ return subscriptionSchema.parse((await this.client.post(`${this.path(mode)}/subscriptions/${encodeURIComponent(id)}/cancel`, {})).data);
183
+ }
184
+ async subscriptionPortal(id, input, mode = 'test') {
185
+ return portalSchema.parse((await this.client.post(`${this.path(mode)}/subscriptions/${encodeURIComponent(id)}/portal`, input)).data);
186
+ }
187
+ async transactions(mode = 'test') {
188
+ return z.array(transactionSchema).parse((await this.client.get(`${this.path(mode)}/transactions`)).data);
189
+ }
190
+ async refund(id, input, mode = 'test') {
191
+ return refundResultSchema.parse((await this.client.post(`${this.path(mode)}/transactions/${encodeURIComponent(id)}/refund`, input)).data);
192
+ }
193
+ }
194
+ const paymentEventSchema = z.discriminatedUnion('type', [
195
+ z.object({
196
+ id: z.string(),
197
+ type: z.literal('payment.updated'),
198
+ mode: paymentModeSchema,
199
+ transaction: transactionSchema,
200
+ }),
201
+ z.object({
202
+ id: z.string(),
203
+ type: z.literal('subscription.updated'),
204
+ mode: paymentModeSchema,
205
+ subscription: subscriptionSchema,
206
+ }),
207
+ ]);
208
+ /** Verify the original bytes before parsing JSON. Use only in trusted server code. */
209
+ export async function verifyPaymentEvent(body, signature, token, mode, now = Date.now()) {
210
+ if (!/^hpay_[a-f0-9]{64}$/.test(token)) {
211
+ throw new Error('Invalid application payment credential.');
212
+ }
213
+ const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signature);
214
+ const timestamp = match?.[1];
215
+ const digest = match?.[2];
216
+ if (!timestamp || !digest || Math.abs(now - Number(timestamp) * 1000) > 300000 || body.length > 262144) {
217
+ throw new Error('Invalid or expired payment signature.');
218
+ }
219
+ const encoder = new TextEncoder();
220
+ const key = await crypto.subtle.importKey('raw', encoder.encode(token), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
221
+ const bytes = Uint8Array.from(digest.match(/../g) ?? [], (byte) => Number.parseInt(byte, 16));
222
+ if (!(await crypto.subtle.verify('HMAC', key, bytes, encoder.encode(`${timestamp}.${body}`)))) {
223
+ throw new Error('Invalid payment signature.');
224
+ }
225
+ const event = paymentEventSchema.parse(JSON.parse(body));
226
+ if (event.mode !== mode) {
227
+ throw new Error('Payment event mode does not match this application.');
228
+ }
229
+ return event;
230
+ }
231
+ export class PaymentRuntime {
232
+ constructor(client) {
233
+ this.client = client;
234
+ }
235
+ async checkout(input) {
236
+ return transactionSchema.parse((await this.client.post('/platform/payments/runtime/checkouts', input)).data);
237
+ }
238
+ async transaction(id) {
239
+ return transactionSchema.parse((await this.client.get(`/platform/payments/runtime/transactions/${encodeURIComponent(id)}`)).data);
240
+ }
241
+ async subscribe(input) {
242
+ return subscriptionSchema.parse((await this.client.post('/platform/payments/runtime/subscriptions', input)).data);
243
+ }
244
+ async subscription(id) {
245
+ return subscriptionSchema.parse((await this.client.get(`/platform/payments/runtime/subscriptions/${encodeURIComponent(id)}`)).data);
246
+ }
247
+ async cancelSubscription(id) {
248
+ return subscriptionSchema.parse((await this.client.post(`/platform/payments/runtime/subscriptions/${encodeURIComponent(id)}/cancel`, {})).data);
249
+ }
250
+ async subscriptionPortal(id, input) {
251
+ return portalSchema.parse((await this.client.post(`/platform/payments/runtime/subscriptions/${encodeURIComponent(id)}/portal`, input)).data);
252
+ }
253
+ async prices() {
254
+ return z.array(priceSchema).parse((await this.client.get('/platform/payments/runtime/prices')).data);
255
+ }
256
+ }
package/dist/servers.d.ts CHANGED
@@ -20,6 +20,16 @@ export interface ServerMemberData {
20
20
  email: string;
21
21
  access: 'deployer';
22
22
  }
23
+ export interface ServerInvitationData {
24
+ recipientOrganizationId: string | null;
25
+ id: string;
26
+ serverId: string;
27
+ serverName: string;
28
+ email: string;
29
+ status: 'pending' | 'accepting' | 'accepted' | 'declined' | 'cancelled' | 'expired';
30
+ expiresAt: string;
31
+ createdAt: string;
32
+ }
23
33
  export interface ServerSshOptions {
24
34
  user?: string;
25
35
  port?: number;
@@ -32,9 +42,23 @@ export interface CreateServerInput {
32
42
  export interface ServerListOptions {
33
43
  page?: number;
34
44
  }
45
+ export interface ServerInvitationTarget {
46
+ email: string;
47
+ organizationId?: never;
48
+ requestId?: string;
49
+ }
50
+ export interface ServerOrganizationTarget {
51
+ organizationId: string;
52
+ email?: never;
53
+ requestId?: string;
54
+ }
55
+ export type ShareServerInput = ServerInvitationTarget | ServerOrganizationTarget;
35
56
  export declare class ServerApi {
36
57
  private readonly client;
37
58
  constructor(client: Client);
59
+ inspectInvitation(id: string, token: string): Promise<ServerInvitationData>;
60
+ acceptInvitation(id: string, token: string): Promise<ServerInvitationData>;
61
+ declineInvitation(id: string, token: string): Promise<ServerInvitationData>;
38
62
  list(options?: ServerListOptions): Promise<PaginatedApiResponse<ServerData>>;
39
63
  create(input: CreateServerInput): Promise<ServerInstance>;
40
64
  get(id: string): Promise<ServerInstance>;
@@ -47,9 +71,13 @@ export declare class ServerInstance {
47
71
  installCommand: string;
48
72
  }>;
49
73
  members(options?: ServerListOptions): Promise<PaginatedApiResponse<ServerMemberData>>;
50
- share(email: string): Promise<ServerMemberData>;
51
- shareWithOrganization(organizationId: string): Promise<ServerMemberData>;
74
+ share(target: ServerInvitationTarget): Promise<ServerInvitationData>;
75
+ share(target: ServerOrganizationTarget): Promise<ServerMemberData>;
76
+ share(target: ShareServerInput): Promise<ServerInvitationData | ServerMemberData>;
52
77
  revokeAccess(memberId: number): Promise<void>;
78
+ invitations(options?: ServerListOptions): Promise<PaginatedApiResponse<ServerInvitationData>>;
79
+ cancelInvitation(id: string): Promise<ServerInvitationData>;
80
+ resendInvitation(id: string): Promise<ServerInvitationData>;
53
81
  refresh(): Promise<ServerInstance>;
54
82
  delete(): Promise<void>;
55
83
  waitReady(options?: {
package/dist/servers.js CHANGED
@@ -1,7 +1,17 @@
1
+ import { PHPSandboxError } from './errors/index.js';
1
2
  export class ServerApi {
2
3
  constructor(client) {
3
4
  this.client = client;
4
5
  }
6
+ async inspectInvitation(id, token) {
7
+ return (await this.client.post(`/platform/server-invitations/${encodeURIComponent(id)}/inspect`, { token })).data;
8
+ }
9
+ async acceptInvitation(id, token) {
10
+ return (await this.client.post(`/platform/server-invitations/${encodeURIComponent(id)}/accept`, { token })).data;
11
+ }
12
+ async declineInvitation(id, token) {
13
+ return (await this.client.post(`/platform/server-invitations/${encodeURIComponent(id)}/decline`, { token })).data;
14
+ }
5
15
  async list(options = {}) {
6
16
  const query = options.page === undefined ? '' : `?${new URLSearchParams({ page: String(options.page) }).toString()}`;
7
17
  return this.client.get(`/servers${query}`);
@@ -27,16 +37,33 @@ export class ServerInstance {
27
37
  const query = options.page === undefined ? '' : `?${new URLSearchParams({ page: String(options.page) }).toString()}`;
28
38
  return this.client.get(`/servers/${encodeURIComponent(this.data.id)}/members${query}`);
29
39
  }
30
- async share(email) {
31
- return (await this.client.post(`/servers/${encodeURIComponent(this.data.id)}/members`, { email })).data;
32
- }
33
- async shareWithOrganization(organizationId) {
34
- return (await this.client.post(`/servers/${encodeURIComponent(this.data.id)}/members`, { organizationId }))
35
- .data;
40
+ async share(target) {
41
+ if (target.email !== undefined) {
42
+ return (await this.client.post(`/platform/servers/${encodeURIComponent(this.data.id)}/invitations`, {
43
+ email: target.email,
44
+ requestId: target.requestId ?? crypto.randomUUID(),
45
+ })).data;
46
+ }
47
+ const body = { organizationId: target.organizationId, requestId: target.requestId };
48
+ return (await this.client.post(`/servers/${encodeURIComponent(this.data.id)}/members`, body)).data;
36
49
  }
37
50
  async revokeAccess(memberId) {
38
51
  await this.client.delete(`/servers/${encodeURIComponent(this.data.id)}/members/${encodeURIComponent(String(memberId))}`);
39
52
  }
53
+ async invitations(options = {}) {
54
+ const query = options.page === undefined ? '' : `?${new URLSearchParams({ page: String(options.page) }).toString()}`;
55
+ return this.client.get(`/platform/servers/${encodeURIComponent(this.data.id)}/invitations${query}`);
56
+ }
57
+ async cancelInvitation(id) {
58
+ const response = await this.client.delete(`/platform/servers/${encodeURIComponent(this.data.id)}/invitations/${encodeURIComponent(id)}`);
59
+ if (response.data === undefined) {
60
+ throw new PHPSandboxError('Missing invitation cancellation response.');
61
+ }
62
+ return response.data;
63
+ }
64
+ async resendInvitation(id) {
65
+ return (await this.client.post(`/platform/servers/${encodeURIComponent(this.data.id)}/invitations/${encodeURIComponent(id)}/resend`, {})).data;
66
+ }
40
67
  refresh() {
41
68
  return this.client.servers.get(this.data.id);
42
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phpsandbox/sdk",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "The TypeScript SDK for PHPSandbox cloud development environments",
5
5
  "keywords": [
6
6
  "cloud",