@pulgueta/epayco-convex 0.1.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/LICENSE +201 -0
- package/README.md +945 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/index.d.ts +222 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +355 -0
- package/dist/client/index.js.map +1 -0
- package/dist/component/_generated/api.d.ts +78 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +580 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/banks.d.ts +14 -0
- package/dist/component/banks.d.ts.map +1 -0
- package/dist/component/banks.js +37 -0
- package/dist/component/banks.js.map +1 -0
- package/dist/component/cashApi.d.ts +59 -0
- package/dist/component/cashApi.d.ts.map +1 -0
- package/dist/component/cashApi.js +88 -0
- package/dist/component/cashApi.js.map +1 -0
- package/dist/component/chargesApi.d.ts +64 -0
- package/dist/component/chargesApi.d.ts.map +1 -0
- package/dist/component/chargesApi.js +106 -0
- package/dist/component/chargesApi.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +6 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/customers.d.ts +67 -0
- package/dist/component/customers.d.ts.map +1 -0
- package/dist/component/customers.js +103 -0
- package/dist/component/customers.js.map +1 -0
- package/dist/component/customersApi.d.ts +99 -0
- package/dist/component/customersApi.d.ts.map +1 -0
- package/dist/component/customersApi.js +176 -0
- package/dist/component/customersApi.js.map +1 -0
- package/dist/component/daviplataApi.d.ts +43 -0
- package/dist/component/daviplataApi.d.ts.map +1 -0
- package/dist/component/daviplataApi.js +103 -0
- package/dist/component/daviplataApi.js.map +1 -0
- package/dist/component/epaycoClient.d.ts +84 -0
- package/dist/component/epaycoClient.d.ts.map +1 -0
- package/dist/component/epaycoClient.js +422 -0
- package/dist/component/epaycoClient.js.map +1 -0
- package/dist/component/payloads.d.ts +34 -0
- package/dist/component/payloads.d.ts.map +1 -0
- package/dist/component/payloads.js +45 -0
- package/dist/component/payloads.js.map +1 -0
- package/dist/component/plans.d.ts +47 -0
- package/dist/component/plans.d.ts.map +1 -0
- package/dist/component/plans.js +83 -0
- package/dist/component/plans.js.map +1 -0
- package/dist/component/plansApi.d.ts +64 -0
- package/dist/component/plansApi.d.ts.map +1 -0
- package/dist/component/plansApi.js +121 -0
- package/dist/component/plansApi.js.map +1 -0
- package/dist/component/pseApi.d.ts +68 -0
- package/dist/component/pseApi.d.ts.map +1 -0
- package/dist/component/pseApi.js +113 -0
- package/dist/component/pseApi.js.map +1 -0
- package/dist/component/rateLimits.d.ts +69 -0
- package/dist/component/rateLimits.d.ts.map +1 -0
- package/dist/component/rateLimits.js +67 -0
- package/dist/component/rateLimits.js.map +1 -0
- package/dist/component/safetypayApi.d.ts +35 -0
- package/dist/component/safetypayApi.d.ts.map +1 -0
- package/dist/component/safetypayApi.js +68 -0
- package/dist/component/safetypayApi.js.map +1 -0
- package/dist/component/schema.d.ts +200 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +104 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/signature.d.ts +11 -0
- package/dist/component/signature.d.ts.map +1 -0
- package/dist/component/signature.js +28 -0
- package/dist/component/signature.js.map +1 -0
- package/dist/component/status.d.ts +12 -0
- package/dist/component/status.d.ts.map +1 -0
- package/dist/component/status.js +55 -0
- package/dist/component/status.js.map +1 -0
- package/dist/component/subscriptions.d.ts +69 -0
- package/dist/component/subscriptions.d.ts.map +1 -0
- package/dist/component/subscriptions.js +114 -0
- package/dist/component/subscriptions.js.map +1 -0
- package/dist/component/subscriptionsApi.d.ts +62 -0
- package/dist/component/subscriptionsApi.d.ts.map +1 -0
- package/dist/component/subscriptionsApi.js +147 -0
- package/dist/component/subscriptionsApi.js.map +1 -0
- package/dist/component/tokens.d.ts +31 -0
- package/dist/component/tokens.d.ts.map +1 -0
- package/dist/component/tokens.js +79 -0
- package/dist/component/tokens.js.map +1 -0
- package/dist/component/tokensApi.d.ts +18 -0
- package/dist/component/tokensApi.d.ts.map +1 -0
- package/dist/component/tokensApi.js +53 -0
- package/dist/component/tokensApi.js.map +1 -0
- package/dist/component/transactions.d.ts +103 -0
- package/dist/component/transactions.d.ts.map +1 -0
- package/dist/component/transactions.js +177 -0
- package/dist/component/transactions.js.map +1 -0
- package/dist/component/validators.d.ts +571 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +203 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/component/webhooks.d.ts +55 -0
- package/dist/component/webhooks.d.ts.map +1 -0
- package/dist/component/webhooks.js +172 -0
- package/dist/component/webhooks.js.map +1 -0
- package/dist/react/index.d.ts +16 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +43 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +106 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/index.test.ts +66 -0
- package/src/client/index.ts +633 -0
- package/src/client/setup.test.ts +26 -0
- package/src/component/_generated/api.ts +94 -0
- package/src/component/_generated/component.ts +809 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/banks.ts +41 -0
- package/src/component/cashApi.ts +100 -0
- package/src/component/chargesApi.ts +119 -0
- package/src/component/convex.config.ts +7 -0
- package/src/component/customers.test.ts +122 -0
- package/src/component/customers.ts +116 -0
- package/src/component/customersApi.ts +206 -0
- package/src/component/daviplataApi.ts +119 -0
- package/src/component/epaycoApi.test.ts +110 -0
- package/src/component/epaycoClient.ts +578 -0
- package/src/component/payloads.ts +67 -0
- package/src/component/plans.test.ts +129 -0
- package/src/component/plans.ts +86 -0
- package/src/component/plansApi.ts +135 -0
- package/src/component/pseApi.ts +125 -0
- package/src/component/rateLimits.ts +67 -0
- package/src/component/safetypayApi.ts +78 -0
- package/src/component/schema.ts +124 -0
- package/src/component/setup.test.helper.ts +10 -0
- package/src/component/setup.test.ts +22 -0
- package/src/component/signature.ts +38 -0
- package/src/component/status.ts +71 -0
- package/src/component/subscriptions.test.ts +117 -0
- package/src/component/subscriptions.ts +128 -0
- package/src/component/subscriptionsApi.ts +172 -0
- package/src/component/tokens.ts +89 -0
- package/src/component/tokensApi.ts +63 -0
- package/src/component/transactions.test.ts +227 -0
- package/src/component/transactions.ts +200 -0
- package/src/component/validators.ts +245 -0
- package/src/component/webhooks.test.ts +137 -0
- package/src/component/webhooks.ts +229 -0
- package/src/react/index.ts +71 -0
- package/src/test.ts +13 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { exposeApi } from "./index.js";
|
|
3
|
+
import { anyApi, type ApiFromModules } from "convex/server";
|
|
4
|
+
import { components, initConvexTest } from "./setup.test.js";
|
|
5
|
+
|
|
6
|
+
export const { listTransactions, getCustomer, getActiveSubscription } =
|
|
7
|
+
exposeApi(components.epayco, {
|
|
8
|
+
auth: async (ctx, _operation) => {
|
|
9
|
+
return (await ctx.auth.getUserIdentity())?.subject ?? "anonymous";
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const testApi = (
|
|
14
|
+
anyApi as unknown as ApiFromModules<{
|
|
15
|
+
"index.test": {
|
|
16
|
+
listTransactions: typeof listTransactions;
|
|
17
|
+
getCustomer: typeof getCustomer;
|
|
18
|
+
getActiveSubscription: typeof getActiveSubscription;
|
|
19
|
+
};
|
|
20
|
+
}>
|
|
21
|
+
)["index.test"];
|
|
22
|
+
|
|
23
|
+
describe("client tests", () => {
|
|
24
|
+
test("should expose query functions", async () => {
|
|
25
|
+
const t = initConvexTest().withIdentity({
|
|
26
|
+
subject: "user1",
|
|
27
|
+
});
|
|
28
|
+
const transactions = await t.query(testApi.listTransactions, {});
|
|
29
|
+
expect(transactions).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should expose getCustomer", async () => {
|
|
33
|
+
const t = initConvexTest().withIdentity({
|
|
34
|
+
subject: "user1",
|
|
35
|
+
});
|
|
36
|
+
const customer = await t.query(testApi.getCustomer, {});
|
|
37
|
+
expect(customer).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should expose getActiveSubscription", async () => {
|
|
41
|
+
const t = initConvexTest().withIdentity({
|
|
42
|
+
subject: "user1",
|
|
43
|
+
});
|
|
44
|
+
const sub = await t.query(testApi.getActiveSubscription, {});
|
|
45
|
+
expect(sub).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Regression: the exposed queries must never let an authenticated caller
|
|
49
|
+
// widen scope to another user by passing a foreign `userId`. The arg no
|
|
50
|
+
// longer exists, so the request is rejected outright.
|
|
51
|
+
test("rejects a client-supplied userId (no scope widening)", async () => {
|
|
52
|
+
const t = initConvexTest().withIdentity({
|
|
53
|
+
subject: "user1",
|
|
54
|
+
});
|
|
55
|
+
await expect(
|
|
56
|
+
t.query(testApi.listTransactions, {
|
|
57
|
+
userId: "victim",
|
|
58
|
+
} as unknown as Record<string, never>),
|
|
59
|
+
).rejects.toThrow();
|
|
60
|
+
await expect(
|
|
61
|
+
t.query(testApi.getCustomer, {
|
|
62
|
+
userId: "victim",
|
|
63
|
+
} as unknown as Record<string, never>),
|
|
64
|
+
).rejects.toThrow();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { httpActionGeneric, queryGeneric } from "convex/server";
|
|
2
|
+
import type {
|
|
3
|
+
Auth,
|
|
4
|
+
GenericActionCtx,
|
|
5
|
+
GenericDataModel,
|
|
6
|
+
GenericQueryCtx,
|
|
7
|
+
HttpRouter,
|
|
8
|
+
} from "convex/server";
|
|
9
|
+
import type { Infer } from "convex/values";
|
|
10
|
+
import { v } from "convex/values";
|
|
11
|
+
import type { ComponentApi } from "../component/_generated/component.js";
|
|
12
|
+
import {
|
|
13
|
+
chargeInfoValidator,
|
|
14
|
+
customerInfoValidator,
|
|
15
|
+
cashInfoValidator,
|
|
16
|
+
cashProviderValidator,
|
|
17
|
+
daviplataInfoValidator,
|
|
18
|
+
planInfoValidator,
|
|
19
|
+
pseInfoValidator,
|
|
20
|
+
safetypayInfoValidator,
|
|
21
|
+
subscriptionInfoValidator,
|
|
22
|
+
tokenInfoValidator,
|
|
23
|
+
} from "../component/validators.js";
|
|
24
|
+
|
|
25
|
+
type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
|
|
26
|
+
type ActionCtx = Pick<
|
|
27
|
+
GenericActionCtx<GenericDataModel>,
|
|
28
|
+
"runQuery" | "runMutation" | "runAction"
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
type CustomerInfo = Infer<typeof customerInfoValidator>;
|
|
32
|
+
type TokenInfo = Infer<typeof tokenInfoValidator>;
|
|
33
|
+
type ChargeInfo = Infer<typeof chargeInfoValidator>;
|
|
34
|
+
type PseInfo = Infer<typeof pseInfoValidator>;
|
|
35
|
+
type CashInfo = Infer<typeof cashInfoValidator>;
|
|
36
|
+
type CashProvider = Infer<typeof cashProviderValidator>;
|
|
37
|
+
type DaviplataInfo = Infer<typeof daviplataInfoValidator>;
|
|
38
|
+
type SafetyPayInfo = Infer<typeof safetypayInfoValidator>;
|
|
39
|
+
type PlanInfo = Infer<typeof planInfoValidator>;
|
|
40
|
+
type SubscriptionInfo = Infer<typeof subscriptionInfoValidator>;
|
|
41
|
+
|
|
42
|
+
export interface EPaycoOptions {
|
|
43
|
+
/** ePayco PUBLIC_KEY. Falls back to `EPAYCO_PUBLIC_KEY`. */
|
|
44
|
+
publicKey?: string;
|
|
45
|
+
/** ePayco PRIVATE_KEY. Falls back to `EPAYCO_PRIVATE_KEY`. */
|
|
46
|
+
privateKey?: string;
|
|
47
|
+
/** Run against the ePayco sandbox. Falls back to `EPAYCO_TEST_MODE === "true"`. */
|
|
48
|
+
testMode?: boolean;
|
|
49
|
+
/** API language ("ES" | "EN"). Falls back to `EPAYCO_LANG` or "ES". */
|
|
50
|
+
lang?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Host-side client for the ePayco component. Construct it once with the
|
|
55
|
+
* component reference and (optionally) credentials, then call its methods from
|
|
56
|
+
* your own actions/queries. Credentials are read from the environment by
|
|
57
|
+
* default so secrets never need to be hard-coded.
|
|
58
|
+
*/
|
|
59
|
+
export class EPayco {
|
|
60
|
+
public component: ComponentApi;
|
|
61
|
+
private options: EPaycoOptions;
|
|
62
|
+
|
|
63
|
+
constructor(component: ComponentApi, options?: EPaycoOptions) {
|
|
64
|
+
this.component = component;
|
|
65
|
+
this.options = options ?? {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private creds() {
|
|
69
|
+
const apiKey = this.options.publicKey ?? process.env.EPAYCO_PUBLIC_KEY;
|
|
70
|
+
const privateKey =
|
|
71
|
+
this.options.privateKey ?? process.env.EPAYCO_PRIVATE_KEY;
|
|
72
|
+
if (!apiKey || !privateKey) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"ePayco credentials are required: set EPAYCO_PUBLIC_KEY and EPAYCO_PRIVATE_KEY (or pass publicKey/privateKey to the EPayco constructor).",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
apiKey,
|
|
79
|
+
privateKey,
|
|
80
|
+
testMode:
|
|
81
|
+
this.options.testMode ?? process.env.EPAYCO_TEST_MODE === "true",
|
|
82
|
+
lang: this.options.lang ?? process.env.EPAYCO_LANG ?? "ES",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Tokens ---
|
|
87
|
+
|
|
88
|
+
async createToken(
|
|
89
|
+
ctx: ActionCtx,
|
|
90
|
+
args: { userId: string; tokenInfo: TokenInfo },
|
|
91
|
+
) {
|
|
92
|
+
return await ctx.runAction(this.component.tokensApi.createToken, {
|
|
93
|
+
credentials: this.creds(),
|
|
94
|
+
...args,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getLocalTokens(ctx: QueryCtx, args: { userId: string }) {
|
|
99
|
+
return await ctx.runQuery(this.component.tokens.getLocalTokens, args);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Customers ---
|
|
103
|
+
|
|
104
|
+
async createCustomer(
|
|
105
|
+
ctx: ActionCtx,
|
|
106
|
+
args: { userId: string; customerInfo: CustomerInfo },
|
|
107
|
+
) {
|
|
108
|
+
return await ctx.runAction(this.component.customersApi.createCustomer, {
|
|
109
|
+
credentials: this.creds(),
|
|
110
|
+
...args,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getCustomer(
|
|
115
|
+
ctx: ActionCtx,
|
|
116
|
+
args: { epaycoCustomerId: string; userId: string },
|
|
117
|
+
) {
|
|
118
|
+
return await ctx.runAction(this.component.customersApi.getCustomer, {
|
|
119
|
+
credentials: this.creds(),
|
|
120
|
+
...args,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async listCustomers(
|
|
125
|
+
ctx: ActionCtx,
|
|
126
|
+
args: { page?: number; perPage?: number } = {},
|
|
127
|
+
) {
|
|
128
|
+
return await ctx.runAction(this.component.customersApi.listCustomers, {
|
|
129
|
+
credentials: this.creds(),
|
|
130
|
+
...args,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async updateCustomer(
|
|
135
|
+
ctx: ActionCtx,
|
|
136
|
+
args: {
|
|
137
|
+
userId: string;
|
|
138
|
+
epaycoCustomerId: string;
|
|
139
|
+
name?: string;
|
|
140
|
+
lastName?: string;
|
|
141
|
+
email?: string;
|
|
142
|
+
phone?: string;
|
|
143
|
+
cellPhone?: string;
|
|
144
|
+
city?: string;
|
|
145
|
+
address?: string;
|
|
146
|
+
},
|
|
147
|
+
) {
|
|
148
|
+
return await ctx.runAction(this.component.customersApi.updateCustomer, {
|
|
149
|
+
credentials: this.creds(),
|
|
150
|
+
...args,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async deleteCustomerCard(
|
|
155
|
+
ctx: ActionCtx,
|
|
156
|
+
args: { franchise: string; mask: string; customerId: string },
|
|
157
|
+
) {
|
|
158
|
+
return await ctx.runAction(this.component.customersApi.deleteCustomerCard, {
|
|
159
|
+
credentials: this.creds(),
|
|
160
|
+
...args,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async addDefaultCard(
|
|
165
|
+
ctx: ActionCtx,
|
|
166
|
+
args: {
|
|
167
|
+
customerId: string;
|
|
168
|
+
token: string;
|
|
169
|
+
franchise: string;
|
|
170
|
+
mask: string;
|
|
171
|
+
},
|
|
172
|
+
) {
|
|
173
|
+
return await ctx.runAction(this.component.customersApi.addDefaultCard, {
|
|
174
|
+
credentials: this.creds(),
|
|
175
|
+
...args,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async addNewToken(
|
|
180
|
+
ctx: ActionCtx,
|
|
181
|
+
args: { customerId: string; tokenCard: string },
|
|
182
|
+
) {
|
|
183
|
+
return await ctx.runAction(this.component.customersApi.addNewToken, {
|
|
184
|
+
credentials: this.creds(),
|
|
185
|
+
...args,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async getLocalCustomer(ctx: QueryCtx, args: { userId: string }) {
|
|
190
|
+
return await ctx.runQuery(this.component.customers.getLocalCustomer, args);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- Plans ---
|
|
194
|
+
|
|
195
|
+
async createPlan(ctx: ActionCtx, args: { planInfo: PlanInfo }) {
|
|
196
|
+
return await ctx.runAction(this.component.plansApi.createPlan, {
|
|
197
|
+
credentials: this.creds(),
|
|
198
|
+
...args,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async getPlan(ctx: ActionCtx, args: { epaycoPlanId: string }) {
|
|
203
|
+
return await ctx.runAction(this.component.plansApi.getPlan, {
|
|
204
|
+
credentials: this.creds(),
|
|
205
|
+
...args,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async listPlans(ctx: ActionCtx) {
|
|
210
|
+
return await ctx.runAction(this.component.plansApi.listPlans, {
|
|
211
|
+
credentials: this.creds(),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async updatePlan(
|
|
216
|
+
ctx: ActionCtx,
|
|
217
|
+
args: {
|
|
218
|
+
epaycoPlanId: string;
|
|
219
|
+
name?: string;
|
|
220
|
+
description?: string;
|
|
221
|
+
amount?: number;
|
|
222
|
+
currency?: string;
|
|
223
|
+
interval?: string;
|
|
224
|
+
intervalCount?: number;
|
|
225
|
+
trialDays?: number;
|
|
226
|
+
},
|
|
227
|
+
) {
|
|
228
|
+
return await ctx.runAction(this.component.plansApi.updatePlan, {
|
|
229
|
+
credentials: this.creds(),
|
|
230
|
+
...args,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async deletePlan(ctx: ActionCtx, args: { epaycoPlanId: string }) {
|
|
235
|
+
return await ctx.runAction(this.component.plansApi.deletePlan, {
|
|
236
|
+
credentials: this.creds(),
|
|
237
|
+
...args,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async getLocalPlan(ctx: QueryCtx, args: { epaycoPlanId: string }) {
|
|
242
|
+
return await ctx.runQuery(this.component.plans.getLocalPlan, args);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async listLocalPlans(ctx: QueryCtx, args: { status?: string } = {}) {
|
|
246
|
+
return await ctx.runQuery(this.component.plans.listLocalPlans, args);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// --- Subscriptions ---
|
|
250
|
+
|
|
251
|
+
async createSubscription(
|
|
252
|
+
ctx: ActionCtx,
|
|
253
|
+
args: { userId: string; subscriptionInfo: SubscriptionInfo },
|
|
254
|
+
) {
|
|
255
|
+
return await ctx.runAction(
|
|
256
|
+
this.component.subscriptionsApi.createSubscription,
|
|
257
|
+
{ credentials: this.creds(), ...args },
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async getSubscription(
|
|
262
|
+
ctx: ActionCtx,
|
|
263
|
+
args: { epaycoSubscriptionId: string },
|
|
264
|
+
) {
|
|
265
|
+
return await ctx.runAction(
|
|
266
|
+
this.component.subscriptionsApi.getSubscription,
|
|
267
|
+
{ credentials: this.creds(), ...args },
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async listSubscriptionsFromEpayco(ctx: ActionCtx) {
|
|
272
|
+
return await ctx.runAction(this.component.subscriptionsApi.listSubscriptions, {
|
|
273
|
+
credentials: this.creds(),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async cancelSubscription(
|
|
278
|
+
ctx: ActionCtx,
|
|
279
|
+
args: { epaycoSubscriptionId: string },
|
|
280
|
+
) {
|
|
281
|
+
return await ctx.runAction(
|
|
282
|
+
this.component.subscriptionsApi.cancelSubscription,
|
|
283
|
+
{ credentials: this.creds(), ...args },
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async chargeSubscription(
|
|
288
|
+
ctx: ActionCtx,
|
|
289
|
+
args: {
|
|
290
|
+
userId: string;
|
|
291
|
+
idPlan: string;
|
|
292
|
+
customer: string;
|
|
293
|
+
tokenCard: string;
|
|
294
|
+
docType: string;
|
|
295
|
+
docNumber: string;
|
|
296
|
+
ip?: string;
|
|
297
|
+
},
|
|
298
|
+
) {
|
|
299
|
+
return await ctx.runAction(
|
|
300
|
+
this.component.subscriptionsApi.chargeSubscription,
|
|
301
|
+
{ credentials: this.creds(), ...args },
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async getActiveSubscription(ctx: QueryCtx, args: { userId: string }) {
|
|
306
|
+
return await ctx.runQuery(
|
|
307
|
+
this.component.subscriptions.getActiveSubscription,
|
|
308
|
+
args,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async listSubscriptions(ctx: QueryCtx, args: { userId: string }) {
|
|
313
|
+
return await ctx.runQuery(
|
|
314
|
+
this.component.subscriptions.listLocalSubscriptionsByUser,
|
|
315
|
+
args,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// --- Credit-card charges ---
|
|
320
|
+
|
|
321
|
+
async chargeCreditCard(
|
|
322
|
+
ctx: ActionCtx,
|
|
323
|
+
args: { userId: string; chargeInfo: ChargeInfo },
|
|
324
|
+
) {
|
|
325
|
+
return await ctx.runAction(this.component.chargesApi.createCharge, {
|
|
326
|
+
credentials: this.creds(),
|
|
327
|
+
...args,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async getCharge(ctx: ActionCtx, args: { epaycoRef: string }) {
|
|
332
|
+
return await ctx.runAction(this.component.chargesApi.getCharge, {
|
|
333
|
+
credentials: this.creds(),
|
|
334
|
+
...args,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// --- PSE ---
|
|
339
|
+
|
|
340
|
+
async createPseTransaction(
|
|
341
|
+
ctx: ActionCtx,
|
|
342
|
+
args: { userId: string; pseInfo: PseInfo },
|
|
343
|
+
) {
|
|
344
|
+
return await ctx.runAction(this.component.pseApi.createPseTransaction, {
|
|
345
|
+
credentials: this.creds(),
|
|
346
|
+
...args,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async getPseTransaction(ctx: ActionCtx, args: { ticketId: string }) {
|
|
351
|
+
return await ctx.runAction(this.component.pseApi.getPseTransaction, {
|
|
352
|
+
credentials: this.creds(),
|
|
353
|
+
...args,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async getBanks(ctx: ActionCtx) {
|
|
358
|
+
return await ctx.runAction(this.component.pseApi.getBanks, {
|
|
359
|
+
credentials: this.creds(),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async listLocalBanks(ctx: QueryCtx) {
|
|
364
|
+
return await ctx.runQuery(this.component.banks.listLocalBanks, {});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// --- Cash ---
|
|
368
|
+
|
|
369
|
+
async createCashPayment(
|
|
370
|
+
ctx: ActionCtx,
|
|
371
|
+
args: { userId: string; provider: CashProvider; cashInfo: CashInfo },
|
|
372
|
+
) {
|
|
373
|
+
return await ctx.runAction(this.component.cashApi.createCashPayment, {
|
|
374
|
+
credentials: this.creds(),
|
|
375
|
+
...args,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async getCashPayment(ctx: ActionCtx, args: { epaycoRef: string }) {
|
|
380
|
+
return await ctx.runAction(this.component.cashApi.getCashPayment, {
|
|
381
|
+
credentials: this.creds(),
|
|
382
|
+
...args,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// --- Daviplata ---
|
|
387
|
+
|
|
388
|
+
async createDaviplataPayment(
|
|
389
|
+
ctx: ActionCtx,
|
|
390
|
+
args: { userId: string; daviplataInfo: DaviplataInfo },
|
|
391
|
+
) {
|
|
392
|
+
return await ctx.runAction(
|
|
393
|
+
this.component.daviplataApi.createDaviplataPayment,
|
|
394
|
+
{ credentials: this.creds(), ...args },
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async confirmDaviplataPayment(
|
|
399
|
+
ctx: ActionCtx,
|
|
400
|
+
args: { refPayco: string; idSessionToken: string; otp: string },
|
|
401
|
+
) {
|
|
402
|
+
return await ctx.runAction(
|
|
403
|
+
this.component.daviplataApi.confirmDaviplataPayment,
|
|
404
|
+
{ credentials: this.creds(), ...args },
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// --- SafetyPay ---
|
|
409
|
+
|
|
410
|
+
async createSafetyPayPayment(
|
|
411
|
+
ctx: ActionCtx,
|
|
412
|
+
args: { userId: string; safetypayInfo: SafetyPayInfo },
|
|
413
|
+
) {
|
|
414
|
+
return await ctx.runAction(
|
|
415
|
+
this.component.safetypayApi.createSafetyPayPayment,
|
|
416
|
+
{ credentials: this.creds(), ...args },
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// --- Transactions (local reactive reads) ---
|
|
421
|
+
|
|
422
|
+
async getTransaction(ctx: QueryCtx, args: { epaycoRef: string }) {
|
|
423
|
+
return await ctx.runQuery(
|
|
424
|
+
this.component.transactions.getLocalTransaction,
|
|
425
|
+
args,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async listTransactions(
|
|
430
|
+
ctx: QueryCtx,
|
|
431
|
+
args: {
|
|
432
|
+
userId: string;
|
|
433
|
+
status?: string;
|
|
434
|
+
paymentMethod?:
|
|
435
|
+
| "credit_card"
|
|
436
|
+
| "pse"
|
|
437
|
+
| "cash"
|
|
438
|
+
| "daviplata"
|
|
439
|
+
| "safetypay";
|
|
440
|
+
limit?: number;
|
|
441
|
+
},
|
|
442
|
+
) {
|
|
443
|
+
return await ctx.runQuery(
|
|
444
|
+
this.component.transactions.listLocalTransactions,
|
|
445
|
+
args,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Build a set of public, auth-gated queries the host app can re-export directly
|
|
452
|
+
* to its clients, so reactive reads don't need a bespoke wrapper per app.
|
|
453
|
+
*/
|
|
454
|
+
export function exposeApi(
|
|
455
|
+
component: ComponentApi,
|
|
456
|
+
options: {
|
|
457
|
+
auth: (
|
|
458
|
+
ctx: { auth: Auth },
|
|
459
|
+
operation: { type: "read" } | { type: "create" } | { type: "manage" },
|
|
460
|
+
) => Promise<string>;
|
|
461
|
+
},
|
|
462
|
+
) {
|
|
463
|
+
return {
|
|
464
|
+
// NOTE: none of these queries accept a client-supplied `userId`. The scope
|
|
465
|
+
// is always the identity resolved by `options.auth`, so an authenticated
|
|
466
|
+
// caller can never widen access to another user's records.
|
|
467
|
+
listTransactions: queryGeneric({
|
|
468
|
+
args: {
|
|
469
|
+
status: v.optional(v.string()),
|
|
470
|
+
limit: v.optional(v.number()),
|
|
471
|
+
},
|
|
472
|
+
handler: async (ctx, args) => {
|
|
473
|
+
const userId = await options.auth(ctx, { type: "read" });
|
|
474
|
+
return await ctx.runQuery(
|
|
475
|
+
component.transactions.listLocalTransactions,
|
|
476
|
+
{
|
|
477
|
+
userId,
|
|
478
|
+
status: args.status,
|
|
479
|
+
limit: args.limit,
|
|
480
|
+
},
|
|
481
|
+
);
|
|
482
|
+
},
|
|
483
|
+
}),
|
|
484
|
+
getTransaction: queryGeneric({
|
|
485
|
+
args: { epaycoRef: v.string() },
|
|
486
|
+
handler: async (ctx, args) => {
|
|
487
|
+
const userId = await options.auth(ctx, { type: "read" });
|
|
488
|
+
const transaction = await ctx.runQuery(
|
|
489
|
+
component.transactions.getLocalTransaction,
|
|
490
|
+
{ epaycoRef: args.epaycoRef },
|
|
491
|
+
);
|
|
492
|
+
// Transactions carry customer PII; only return it to its owner.
|
|
493
|
+
if (!transaction || transaction.userId !== userId) return null;
|
|
494
|
+
return transaction;
|
|
495
|
+
},
|
|
496
|
+
}),
|
|
497
|
+
getCustomer: queryGeneric({
|
|
498
|
+
args: {},
|
|
499
|
+
handler: async (ctx) => {
|
|
500
|
+
const userId = await options.auth(ctx, { type: "read" });
|
|
501
|
+
return await ctx.runQuery(component.customers.getLocalCustomer, {
|
|
502
|
+
userId,
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
}),
|
|
506
|
+
listSubscriptions: queryGeneric({
|
|
507
|
+
args: {},
|
|
508
|
+
handler: async (ctx) => {
|
|
509
|
+
const userId = await options.auth(ctx, { type: "read" });
|
|
510
|
+
return await ctx.runQuery(
|
|
511
|
+
component.subscriptions.listLocalSubscriptionsByUser,
|
|
512
|
+
{ userId },
|
|
513
|
+
);
|
|
514
|
+
},
|
|
515
|
+
}),
|
|
516
|
+
getActiveSubscription: queryGeneric({
|
|
517
|
+
args: {},
|
|
518
|
+
handler: async (ctx) => {
|
|
519
|
+
const userId = await options.auth(ctx, { type: "read" });
|
|
520
|
+
return await ctx.runQuery(
|
|
521
|
+
component.subscriptions.getActiveSubscription,
|
|
522
|
+
{ userId },
|
|
523
|
+
);
|
|
524
|
+
},
|
|
525
|
+
}),
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Register ePayco webhook (confirmation) and response HTTP routes on the host's
|
|
531
|
+
* router. `custIdCliente` / `pKey` are used to verify the confirmation
|
|
532
|
+
* signature and default to `EPAYCO_P_CUST_ID_CLIENTE` / `EPAYCO_P_KEY`.
|
|
533
|
+
*/
|
|
534
|
+
export function registerRoutes(
|
|
535
|
+
http: HttpRouter,
|
|
536
|
+
component: ComponentApi,
|
|
537
|
+
options: {
|
|
538
|
+
pathPrefix?: string;
|
|
539
|
+
custIdCliente?: string;
|
|
540
|
+
pKey?: string;
|
|
541
|
+
} = {},
|
|
542
|
+
) {
|
|
543
|
+
const pathPrefix = options.pathPrefix ?? "/epayco";
|
|
544
|
+
const custIdCliente =
|
|
545
|
+
options.custIdCliente ?? process.env.EPAYCO_P_CUST_ID_CLIENTE ?? "";
|
|
546
|
+
const pKey = options.pKey ?? process.env.EPAYCO_P_KEY ?? "";
|
|
547
|
+
|
|
548
|
+
const handleConfirmation = async (
|
|
549
|
+
ctx: { runAction: GenericActionCtx<GenericDataModel>["runAction"] },
|
|
550
|
+
payload: Record<string, unknown>,
|
|
551
|
+
) => {
|
|
552
|
+
return await ctx.runAction(component.webhooks.processConfirmation, {
|
|
553
|
+
custIdCliente,
|
|
554
|
+
pKey,
|
|
555
|
+
payload,
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
http.route({
|
|
560
|
+
path: `${pathPrefix}/confirmation`,
|
|
561
|
+
method: "POST",
|
|
562
|
+
handler: httpActionGeneric(async (ctx, request) => {
|
|
563
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
564
|
+
let payload: Record<string, unknown> = {};
|
|
565
|
+
if (contentType.includes("application/json")) {
|
|
566
|
+
payload = await request.json();
|
|
567
|
+
} else {
|
|
568
|
+
const form = await request.formData();
|
|
569
|
+
form.forEach((value, key) => {
|
|
570
|
+
payload[key] = typeof value === "string" ? value : "";
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
const result = await handleConfirmation(ctx, payload);
|
|
574
|
+
return new Response(JSON.stringify(result), {
|
|
575
|
+
status: 200,
|
|
576
|
+
headers: { "Content-Type": "application/json" },
|
|
577
|
+
});
|
|
578
|
+
}),
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
http.route({
|
|
582
|
+
path: `${pathPrefix}/confirmation`,
|
|
583
|
+
method: "GET",
|
|
584
|
+
handler: httpActionGeneric(async (ctx, request) => {
|
|
585
|
+
const url = new URL(request.url);
|
|
586
|
+
const payload: Record<string, string> = {};
|
|
587
|
+
url.searchParams.forEach((value, key) => {
|
|
588
|
+
payload[key] = value;
|
|
589
|
+
});
|
|
590
|
+
const result = await handleConfirmation(ctx, payload);
|
|
591
|
+
return new Response(JSON.stringify(result), {
|
|
592
|
+
status: 200,
|
|
593
|
+
headers: { "Content-Type": "application/json" },
|
|
594
|
+
});
|
|
595
|
+
}),
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
http.route({
|
|
599
|
+
path: `${pathPrefix}/response`,
|
|
600
|
+
method: "GET",
|
|
601
|
+
handler: httpActionGeneric(async (ctx, request) => {
|
|
602
|
+
const url = new URL(request.url);
|
|
603
|
+
const refPayco = url.searchParams.get("ref_payco");
|
|
604
|
+
if (!refPayco) {
|
|
605
|
+
return new Response(
|
|
606
|
+
JSON.stringify({ error: "ref_payco parameter required" }),
|
|
607
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const transaction = await ctx.runQuery(
|
|
611
|
+
component.transactions.getLocalTransaction,
|
|
612
|
+
{ epaycoRef: refPayco },
|
|
613
|
+
);
|
|
614
|
+
// This endpoint is public (ePayco redirects the buyer's browser here),
|
|
615
|
+
// so expose only a minimal status payload — never the full row, which
|
|
616
|
+
// holds customerEmail, responseMessage, splitReceivers and rawResponse.
|
|
617
|
+
const body = transaction
|
|
618
|
+
? {
|
|
619
|
+
found: true,
|
|
620
|
+
ref_payco: transaction.epaycoRef,
|
|
621
|
+
status: transaction.status,
|
|
622
|
+
paymentMethod: transaction.paymentMethod,
|
|
623
|
+
amount: transaction.amount,
|
|
624
|
+
currency: transaction.currency,
|
|
625
|
+
}
|
|
626
|
+
: { found: false };
|
|
627
|
+
return new Response(JSON.stringify(body), {
|
|
628
|
+
status: 200,
|
|
629
|
+
headers: { "Content-Type": "application/json" },
|
|
630
|
+
});
|
|
631
|
+
}),
|
|
632
|
+
});
|
|
633
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import { test } from "vitest";
|
|
3
|
+
import { convexTest } from "convex-test";
|
|
4
|
+
export const modules = import.meta.glob("./**/*.*s");
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
defineSchema,
|
|
8
|
+
type GenericSchema,
|
|
9
|
+
type SchemaDefinition,
|
|
10
|
+
} from "convex/server";
|
|
11
|
+
import { type ComponentApi } from "../component/_generated/component.js";
|
|
12
|
+
import { componentsGeneric } from "convex/server";
|
|
13
|
+
import { register } from "../test.js";
|
|
14
|
+
|
|
15
|
+
export function initConvexTest<
|
|
16
|
+
Schema extends SchemaDefinition<GenericSchema, boolean>,
|
|
17
|
+
>(schema?: Schema) {
|
|
18
|
+
const t = convexTest(schema ?? defineSchema({}), modules);
|
|
19
|
+
register(t);
|
|
20
|
+
return t;
|
|
21
|
+
}
|
|
22
|
+
export const components = componentsGeneric() as unknown as {
|
|
23
|
+
epayco: ComponentApi;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
test("setup", () => {});
|