@queelabs/connectors-coinbase 0.0.1

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,10 @@
1
+ import type { MppPaymentAdapter } from "@queelabs/core";
2
+ /**
3
+ * Coinbase Commerce adapter (MPP-shaped): creates charges via the
4
+ * Coinbase Commerce API and verifies their completion status.
5
+ *
6
+ * Requires `COINBASE_COMMERCE_API_KEY` (from
7
+ * https://beta.commerce.coinbase.com/settings/security).
8
+ */
9
+ export declare function createCoinbaseMppConnector(): MppPaymentAdapter;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,iBAAiB,EAGlB,MAAM,gBAAgB,CAAC;AAaxB;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,iBAAiB,CAgK9D"}
package/dist/index.js ADDED
@@ -0,0 +1,149 @@
1
+ function readRecord(source, key) {
2
+ const value = source[key];
3
+ if (!value || typeof value !== "object" || Array.isArray(value))
4
+ return null;
5
+ return value;
6
+ }
7
+ function readString(source, key) {
8
+ const value = source[key];
9
+ return typeof value === "string" && value.length > 0 ? value : null;
10
+ }
11
+ /**
12
+ * Coinbase Commerce adapter (MPP-shaped): creates charges via the
13
+ * Coinbase Commerce API and verifies their completion status.
14
+ *
15
+ * Requires `COINBASE_COMMERCE_API_KEY` (from
16
+ * https://beta.commerce.coinbase.com/settings/security).
17
+ */
18
+ export function createCoinbaseMppConnector() {
19
+ return {
20
+ async createPurchaseChallenge(input) {
21
+ const apiKey = process.env.COINBASE_COMMERCE_API_KEY;
22
+ if (!apiKey) {
23
+ throw new Error("COINBASE_COMMERCE_API_KEY is not set");
24
+ }
25
+ const response = await fetch("https://api.commerce.coinbase.com/charges", {
26
+ method: "POST",
27
+ headers: {
28
+ "X-CC-Api-Key": apiKey,
29
+ "X-CC-Version": "2018-03-22",
30
+ "Content-Type": "application/json",
31
+ },
32
+ body: JSON.stringify({
33
+ name: `Order ${input.orderId}`,
34
+ description: `Quee order ${input.orderId}`,
35
+ pricing_type: "fixed_price",
36
+ local_price: {
37
+ amount: (input.amountMinor / 100).toFixed(2),
38
+ currency: input.currency.toUpperCase(),
39
+ },
40
+ metadata: {
41
+ orderId: input.orderId,
42
+ marketplaceId: input.marketplaceId,
43
+ },
44
+ redirect_url: process.env.QUEE_CHECKOUT_BASE_URL
45
+ ? `${process.env.QUEE_CHECKOUT_BASE_URL.replace(/\/+$/, "")}/pay/${input.orderId}/success`
46
+ : undefined,
47
+ cancel_url: process.env.QUEE_CHECKOUT_BASE_URL
48
+ ? `${process.env.QUEE_CHECKOUT_BASE_URL.replace(/\/+$/, "")}/pay/${input.orderId}/cancel`
49
+ : undefined,
50
+ }),
51
+ });
52
+ const body = await response.json();
53
+ if (!response.ok) {
54
+ throw new Error(`Coinbase Commerce charge creation failed (${response.status}): ${JSON.stringify(body)}`);
55
+ }
56
+ const charge = body.data;
57
+ return {
58
+ challengeId: `coinbase_ch_${input.orderId}`,
59
+ rail: "coinbase",
60
+ payload: {
61
+ type: "coinbase_checkout_challenge",
62
+ version: "0.2",
63
+ orderId: input.orderId,
64
+ amountMinor: input.amountMinor,
65
+ currency: input.currency,
66
+ coinbase: {
67
+ checkoutUrl: charge.hosted_url,
68
+ chargeId: charge.id,
69
+ chargeCode: charge.code,
70
+ },
71
+ },
72
+ };
73
+ },
74
+ async verifyPurchase(input) {
75
+ const apiKey = process.env.COINBASE_COMMERCE_API_KEY;
76
+ if (!apiKey) {
77
+ throw new Error("COINBASE_COMMERCE_API_KEY is not set");
78
+ }
79
+ const coinbase = readRecord(input.challengePayload, "coinbase") ?? {};
80
+ const expectedChargeId = readString(coinbase, "chargeId");
81
+ const expectedChargeCode = readString(coinbase, "chargeCode");
82
+ const proof = input.proof;
83
+ const chargeId = proof.coinbaseChargeId;
84
+ const chargeCode = proof.coinbaseChargeCode;
85
+ const matchesChargeId = !!expectedChargeId && !!chargeId && chargeId === expectedChargeId;
86
+ const matchesChargeCode = !!expectedChargeCode && !!chargeCode && chargeCode === expectedChargeCode;
87
+ if (!matchesChargeId && !matchesChargeCode) {
88
+ throw new Error("coinbase proof does not match stored challenge");
89
+ }
90
+ const lookupId = chargeId || chargeCode;
91
+ const response = await fetch(`https://api.commerce.coinbase.com/charges/${encodeURIComponent(lookupId)}`, {
92
+ headers: {
93
+ "X-CC-Api-Key": apiKey,
94
+ "X-CC-Version": "2018-03-22",
95
+ },
96
+ });
97
+ const body = await response.json();
98
+ if (!response.ok) {
99
+ throw new Error(`Coinbase Commerce charge lookup failed (${response.status}): ${JSON.stringify(body)}`);
100
+ }
101
+ const charge = body.data;
102
+ if ((expectedChargeId && charge.id !== expectedChargeId) ||
103
+ (expectedChargeCode && charge.code !== expectedChargeCode)) {
104
+ throw new Error("Coinbase charge does not match stored challenge");
105
+ }
106
+ const timeline = charge.timeline;
107
+ const lastStatus = timeline?.[timeline.length - 1]?.status;
108
+ if (lastStatus !== "COMPLETED" && lastStatus !== "RESOLVED") {
109
+ throw new Error(`Coinbase Commerce charge not completed: last status is "${lastStatus}"`);
110
+ }
111
+ const localPrice = readRecord(charge, "local_price");
112
+ if (!localPrice) {
113
+ throw new Error("Coinbase Commerce charge is missing local_price");
114
+ }
115
+ const chargedAmount = Number(readString(localPrice, "amount"));
116
+ if (!Number.isFinite(chargedAmount)) {
117
+ throw new Error("Coinbase Commerce charge amount is invalid");
118
+ }
119
+ if (Math.round(chargedAmount * 100) !== input.expectedAmountMinor) {
120
+ throw new Error("Coinbase Commerce charge amount does not match order");
121
+ }
122
+ if (readString(localPrice, "currency")?.toLowerCase() !==
123
+ input.expectedCurrency.toLowerCase()) {
124
+ throw new Error("Coinbase Commerce charge currency does not match order");
125
+ }
126
+ const metadata = readRecord(charge, "metadata");
127
+ if (metadata?.orderId !== input.orderId) {
128
+ throw new Error("Coinbase Commerce charge orderId metadata does not match order");
129
+ }
130
+ if (metadata?.marketplaceId !== input.marketplaceId) {
131
+ throw new Error("Coinbase Commerce charge marketplaceId metadata does not match marketplace");
132
+ }
133
+ return {
134
+ receiptPayload: {
135
+ verified: true,
136
+ challengeId: input.challengeId,
137
+ orderId: input.orderId,
138
+ settledVia: "coinbase",
139
+ proof,
140
+ providerReference: charge.id,
141
+ chargeCode: charge.code,
142
+ chargeStatus: lastStatus,
143
+ },
144
+ verifiedAt: new Date(),
145
+ };
146
+ },
147
+ };
148
+ }
149
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@queelabs/connectors-coinbase",
3
+ "version": "0.0.1",
4
+ "license": "UNLICENSED",
5
+ "description": "Coinbase payment connector for Quee",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/quee-protocol/quee.git",
18
+ "directory": "connectors/coinbase"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@queelabs/core": "0.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.10.2",
31
+ "typescript": "^5.7.2"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "lint": "echo ok"
36
+ }
37
+ }