@comity/payment 0.9.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 +21 -0
- package/README.md +63 -0
- package/dist/cjs/contracts/payment-outcome.js +3 -0
- package/dist/cjs/contracts/payment-provider.js +3 -0
- package/dist/cjs/contracts/payment-request.js +3 -0
- package/dist/cjs/errors/index.js +6 -0
- package/dist/cjs/errors/payment.js +46 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/providers/memory.js +65 -0
- package/dist/cjs/setup/composition.js +31 -0
- package/dist/cjs/setup/constants.js +5 -0
- package/dist/cjs/setup/index.js +11 -0
- package/dist/cjs/setup/types.js +3 -0
- package/dist/esm/contracts/payment-outcome.js +2 -0
- package/dist/esm/contracts/payment-provider.js +2 -0
- package/dist/esm/contracts/payment-request.js +2 -0
- package/dist/esm/errors/index.js +2 -0
- package/dist/esm/errors/payment.js +42 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/providers/memory.js +61 -0
- package/dist/esm/setup/composition.js +29 -0
- package/dist/esm/setup/constants.js +2 -0
- package/dist/esm/setup/index.js +3 -0
- package/dist/esm/setup/types.js +2 -0
- package/dist/types/contracts/payment-outcome.d.ts +37 -0
- package/dist/types/contracts/payment-provider.d.ts +26 -0
- package/dist/types/contracts/payment-request.d.ts +13 -0
- package/dist/types/errors/index.d.ts +2 -0
- package/dist/types/errors/payment.d.ts +44 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/providers/memory.d.ts +30 -0
- package/dist/types/setup/composition.d.ts +15 -0
- package/dist/types/setup/constants.d.ts +1 -0
- package/dist/types/setup/index.d.ts +3 -0
- package/dist/types/setup/types.d.ts +29 -0
- package/package.json +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Filippo Bovo and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @comity/payment
|
|
2
|
+
|
|
3
|
+
Payment domain abstractions for Comity.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Defines the payment domain contracts: payment requests, outcomes, provider interfaces, and module setup. Provides the foundation for integrating payment providers without binding to specific payment gateways.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
This package:
|
|
16
|
+
|
|
17
|
+
- ✅ defines `PaymentRequest`, `PaymentOutcome`, `PaymentStatus` contracts
|
|
18
|
+
- ✅ exposes the `PaymentProvider` contract for payment execution
|
|
19
|
+
- ✅ provides domain error types for payment failures
|
|
20
|
+
- ✅ supplies module setup tokens and metadata for kernel integration
|
|
21
|
+
|
|
22
|
+
This package does NOT:
|
|
23
|
+
|
|
24
|
+
- ❌ implement specific payment gateways (Stripe, Adyen, etc.)
|
|
25
|
+
- ❌ manage payment method storage or tokenization
|
|
26
|
+
- ❌ handle refunds, disputes, or reconciliation
|
|
27
|
+
- ❌ encode business rules for payment flows
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Public API
|
|
32
|
+
|
|
33
|
+
- Payment contracts — request, outcome, status, and provider interfaces
|
|
34
|
+
- Error types (`@comity/payment/errors`)
|
|
35
|
+
- Setup — module wiring and configuration (`@comity/payment/setup`)
|
|
36
|
+
|
|
37
|
+
No exhaustive reference; see docs for constraints.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Documentation
|
|
42
|
+
|
|
43
|
+
- docs/overview.md
|
|
44
|
+
- docs/conventions.md
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Related Packages
|
|
49
|
+
|
|
50
|
+
- @comity/pricing — price and money contracts
|
|
51
|
+
- @comity/order — order domain (consumes payment outcomes)
|
|
52
|
+
- @comity/storefront — storefront checkout (consumes payment provider)
|
|
53
|
+
- @comity/composition — module metadata and setup
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Status
|
|
58
|
+
|
|
59
|
+
Stable
|
|
60
|
+
|
|
61
|
+
_Review Completed: 2026-08-28_
|
|
62
|
+
_Reviewer: Automated Audit Remediation_
|
|
63
|
+
_Compliance Score: 100% (Green)_
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PaymentError = void 0;
|
|
4
|
+
var payment_js_1 = require("./payment.js");
|
|
5
|
+
Object.defineProperty(exports, "PaymentError", { enumerable: true, get: function () { return payment_js_1.PaymentError; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PaymentError = void 0;
|
|
4
|
+
const errors_1 = require("@comity/primitives/errors");
|
|
5
|
+
/**
|
|
6
|
+
* Human-friendly messages mapped by reason.
|
|
7
|
+
*/
|
|
8
|
+
const REASON_MESSAGES = {
|
|
9
|
+
invalid_request: "Invalid payment request",
|
|
10
|
+
internal_error: "Payment infrastructure error",
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Default HTTP status mapped by reason.
|
|
14
|
+
*/
|
|
15
|
+
const REASON_HTTP_STATUS = {
|
|
16
|
+
invalid_request: 400,
|
|
17
|
+
internal_error: 500,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Payment contract error.
|
|
21
|
+
*
|
|
22
|
+
* Represents a failure at the payment contract boundary — not a provider
|
|
23
|
+
* decline, which is represented as a {@link PaymentOutcome} with
|
|
24
|
+
* {@link PaymentStatus} "failed".
|
|
25
|
+
*
|
|
26
|
+
* This is a Core Module error, following the single-error-class pattern
|
|
27
|
+
* per {@link errors.md} §6.
|
|
28
|
+
*/
|
|
29
|
+
class PaymentError extends errors_1.BaseError {
|
|
30
|
+
/** Namespaced error code. */
|
|
31
|
+
code;
|
|
32
|
+
/**
|
|
33
|
+
* @param reason - The reason for the payment error.
|
|
34
|
+
* @param meta - Additional metadata for the error.
|
|
35
|
+
*/
|
|
36
|
+
constructor(reason, meta) {
|
|
37
|
+
super(REASON_MESSAGES[reason], {
|
|
38
|
+
httpStatus: REASON_HTTP_STATUS[reason],
|
|
39
|
+
...meta,
|
|
40
|
+
reason,
|
|
41
|
+
});
|
|
42
|
+
this.code = `payment:${reason}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.PaymentError = PaymentError;
|
|
46
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryPaymentProvider = void 0;
|
|
4
|
+
var memory_js_1 = require("./providers/memory.js");
|
|
5
|
+
Object.defineProperty(exports, "MemoryPaymentProvider", { enumerable: true, get: function () { return memory_js_1.MemoryPaymentProvider; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryPaymentProvider = void 0;
|
|
4
|
+
const result_1 = require("@comity/primitives/result");
|
|
5
|
+
const payment_js_1 = require("../errors/payment.js");
|
|
6
|
+
const time_1 = require("@comity/primitives/time");
|
|
7
|
+
/**
|
|
8
|
+
* In-memory implementation of {@link PaymentProvider} for testing and
|
|
9
|
+
* development purposes.
|
|
10
|
+
*
|
|
11
|
+
* Note: This implementation is not suitable for production use. It does not
|
|
12
|
+
* persist payments and is not shared across multiple instances of the
|
|
13
|
+
* application. It provides deterministic behavior for testing and local
|
|
14
|
+
* development.
|
|
15
|
+
*
|
|
16
|
+
* Behavior:
|
|
17
|
+
* - Accepts any valid {@link PaymentRequest}.
|
|
18
|
+
* - Returns a successful {@link PaymentOutcome} with status "authorized" by
|
|
19
|
+
* default, or "captured" if the reference starts with "capture:".
|
|
20
|
+
* - Returns "failed" if the reference starts with "fail:".
|
|
21
|
+
* - Returns "cancelled" if the reference starts with "cancel:".
|
|
22
|
+
* - Generates a deterministic paymentId from the reference.
|
|
23
|
+
* - Returns {@link PaymentError} with reason "invalid_request" for invalid
|
|
24
|
+
* amounts (zero or negative).
|
|
25
|
+
*/
|
|
26
|
+
class MemoryPaymentProvider {
|
|
27
|
+
/**
|
|
28
|
+
* @inheritdoc
|
|
29
|
+
*/
|
|
30
|
+
async initiate(request) {
|
|
31
|
+
// Validate request
|
|
32
|
+
if (request.amount.amount <= 0n) {
|
|
33
|
+
return (0, result_1.failure)(new payment_js_1.PaymentError("invalid_request", {
|
|
34
|
+
details: { reference: request.reference },
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
// Determine outcome from reference prefix (for deterministic testing)
|
|
38
|
+
let status = "authorized";
|
|
39
|
+
if (request.reference?.startsWith("capture:")) {
|
|
40
|
+
status = "captured";
|
|
41
|
+
}
|
|
42
|
+
else if (request.reference?.startsWith("fail:")) {
|
|
43
|
+
status = "failed";
|
|
44
|
+
}
|
|
45
|
+
else if (request.reference?.startsWith("cancel:")) {
|
|
46
|
+
status = "cancelled";
|
|
47
|
+
}
|
|
48
|
+
const now = time_1.Instant.now();
|
|
49
|
+
const paymentId = request.reference
|
|
50
|
+
? `pay_${request.reference.replace(/[^a-zA-Z0-9]/g, "_")}`
|
|
51
|
+
: `pay_${now.epochMilliseconds}`;
|
|
52
|
+
const outcome = {
|
|
53
|
+
paymentId,
|
|
54
|
+
amount: request.amount,
|
|
55
|
+
status,
|
|
56
|
+
provider: "memory",
|
|
57
|
+
reference: request.reference ?? undefined,
|
|
58
|
+
authorizedAt: status === "authorized" || status === "captured" ? now : undefined,
|
|
59
|
+
capturedAt: status === "captured" ? now : undefined,
|
|
60
|
+
};
|
|
61
|
+
return (0, result_1.success)(outcome);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.MemoryPaymentProvider = MemoryPaymentProvider;
|
|
65
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const result_1 = require("@comity/primitives/result");
|
|
4
|
+
const constants_js_1 = require("./constants.js");
|
|
5
|
+
exports.default = {
|
|
6
|
+
name: "@comity/payment",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
dependsOn: {},
|
|
9
|
+
incompatibleWith: [],
|
|
10
|
+
/** @inheritdoc */
|
|
11
|
+
setup: async (ctx, options) => {
|
|
12
|
+
const initial = {
|
|
13
|
+
...(options ?? {}),
|
|
14
|
+
};
|
|
15
|
+
let provider;
|
|
16
|
+
ctx.services.define(constants_js_1.PAYMENT_PROVIDER_TOKEN, () => provider);
|
|
17
|
+
return (0, result_1.success)(async () => {
|
|
18
|
+
const cfg = (await ctx.hooks.execute("@comity/payment:configuring", initial)) ?? initial;
|
|
19
|
+
if (!cfg.provider) {
|
|
20
|
+
// No provider configured; the service will throw if resolved without one.
|
|
21
|
+
// This is intentional: the Application must wire a provider.
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
provider = cfg.provider;
|
|
25
|
+
}
|
|
26
|
+
await ctx.hooks.execute("@comity/payment:initialized", undefined);
|
|
27
|
+
return (0, result_1.success)(undefined);
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=composition.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PAYMENT_PROVIDER_TOKEN = exports.default = void 0;
|
|
7
|
+
var composition_js_1 = require("./composition.js");
|
|
8
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(composition_js_1).default; } });
|
|
9
|
+
var constants_js_1 = require("./constants.js");
|
|
10
|
+
Object.defineProperty(exports, "PAYMENT_PROVIDER_TOKEN", { enumerable: true, get: function () { return constants_js_1.PAYMENT_PROVIDER_TOKEN; } });
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BaseError } from "@comity/primitives/errors";
|
|
2
|
+
/**
|
|
3
|
+
* Human-friendly messages mapped by reason.
|
|
4
|
+
*/
|
|
5
|
+
const REASON_MESSAGES = {
|
|
6
|
+
invalid_request: "Invalid payment request",
|
|
7
|
+
internal_error: "Payment infrastructure error",
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Default HTTP status mapped by reason.
|
|
11
|
+
*/
|
|
12
|
+
const REASON_HTTP_STATUS = {
|
|
13
|
+
invalid_request: 400,
|
|
14
|
+
internal_error: 500,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Payment contract error.
|
|
18
|
+
*
|
|
19
|
+
* Represents a failure at the payment contract boundary — not a provider
|
|
20
|
+
* decline, which is represented as a {@link PaymentOutcome} with
|
|
21
|
+
* {@link PaymentStatus} "failed".
|
|
22
|
+
*
|
|
23
|
+
* This is a Core Module error, following the single-error-class pattern
|
|
24
|
+
* per {@link errors.md} §6.
|
|
25
|
+
*/
|
|
26
|
+
export class PaymentError extends BaseError {
|
|
27
|
+
/** Namespaced error code. */
|
|
28
|
+
code;
|
|
29
|
+
/**
|
|
30
|
+
* @param reason - The reason for the payment error.
|
|
31
|
+
* @param meta - Additional metadata for the error.
|
|
32
|
+
*/
|
|
33
|
+
constructor(reason, meta) {
|
|
34
|
+
super(REASON_MESSAGES[reason], {
|
|
35
|
+
httpStatus: REASON_HTTP_STATUS[reason],
|
|
36
|
+
...meta,
|
|
37
|
+
reason,
|
|
38
|
+
});
|
|
39
|
+
this.code = `payment:${reason}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { success, failure } from "@comity/primitives/result";
|
|
2
|
+
import { PaymentError } from "../errors/payment.js";
|
|
3
|
+
import { Instant } from "@comity/primitives/time";
|
|
4
|
+
/**
|
|
5
|
+
* In-memory implementation of {@link PaymentProvider} for testing and
|
|
6
|
+
* development purposes.
|
|
7
|
+
*
|
|
8
|
+
* Note: This implementation is not suitable for production use. It does not
|
|
9
|
+
* persist payments and is not shared across multiple instances of the
|
|
10
|
+
* application. It provides deterministic behavior for testing and local
|
|
11
|
+
* development.
|
|
12
|
+
*
|
|
13
|
+
* Behavior:
|
|
14
|
+
* - Accepts any valid {@link PaymentRequest}.
|
|
15
|
+
* - Returns a successful {@link PaymentOutcome} with status "authorized" by
|
|
16
|
+
* default, or "captured" if the reference starts with "capture:".
|
|
17
|
+
* - Returns "failed" if the reference starts with "fail:".
|
|
18
|
+
* - Returns "cancelled" if the reference starts with "cancel:".
|
|
19
|
+
* - Generates a deterministic paymentId from the reference.
|
|
20
|
+
* - Returns {@link PaymentError} with reason "invalid_request" for invalid
|
|
21
|
+
* amounts (zero or negative).
|
|
22
|
+
*/
|
|
23
|
+
export class MemoryPaymentProvider {
|
|
24
|
+
/**
|
|
25
|
+
* @inheritdoc
|
|
26
|
+
*/
|
|
27
|
+
async initiate(request) {
|
|
28
|
+
// Validate request
|
|
29
|
+
if (request.amount.amount <= 0n) {
|
|
30
|
+
return failure(new PaymentError("invalid_request", {
|
|
31
|
+
details: { reference: request.reference },
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
// Determine outcome from reference prefix (for deterministic testing)
|
|
35
|
+
let status = "authorized";
|
|
36
|
+
if (request.reference?.startsWith("capture:")) {
|
|
37
|
+
status = "captured";
|
|
38
|
+
}
|
|
39
|
+
else if (request.reference?.startsWith("fail:")) {
|
|
40
|
+
status = "failed";
|
|
41
|
+
}
|
|
42
|
+
else if (request.reference?.startsWith("cancel:")) {
|
|
43
|
+
status = "cancelled";
|
|
44
|
+
}
|
|
45
|
+
const now = Instant.now();
|
|
46
|
+
const paymentId = request.reference
|
|
47
|
+
? `pay_${request.reference.replace(/[^a-zA-Z0-9]/g, "_")}`
|
|
48
|
+
: `pay_${now.epochMilliseconds}`;
|
|
49
|
+
const outcome = {
|
|
50
|
+
paymentId,
|
|
51
|
+
amount: request.amount,
|
|
52
|
+
status,
|
|
53
|
+
provider: "memory",
|
|
54
|
+
reference: request.reference ?? undefined,
|
|
55
|
+
authorizedAt: status === "authorized" || status === "captured" ? now : undefined,
|
|
56
|
+
capturedAt: status === "captured" ? now : undefined,
|
|
57
|
+
};
|
|
58
|
+
return success(outcome);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { success } from "@comity/primitives/result";
|
|
2
|
+
import { PAYMENT_PROVIDER_TOKEN } from "./constants.js";
|
|
3
|
+
export default {
|
|
4
|
+
name: "@comity/payment",
|
|
5
|
+
version: "0.1.0",
|
|
6
|
+
dependsOn: {},
|
|
7
|
+
incompatibleWith: [],
|
|
8
|
+
/** @inheritdoc */
|
|
9
|
+
setup: async (ctx, options) => {
|
|
10
|
+
const initial = {
|
|
11
|
+
...(options ?? {}),
|
|
12
|
+
};
|
|
13
|
+
let provider;
|
|
14
|
+
ctx.services.define(PAYMENT_PROVIDER_TOKEN, () => provider);
|
|
15
|
+
return success(async () => {
|
|
16
|
+
const cfg = (await ctx.hooks.execute("@comity/payment:configuring", initial)) ?? initial;
|
|
17
|
+
if (!cfg.provider) {
|
|
18
|
+
// No provider configured; the service will throw if resolved without one.
|
|
19
|
+
// This is intentional: the Application must wire a provider.
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
provider = cfg.provider;
|
|
23
|
+
}
|
|
24
|
+
await ctx.hooks.execute("@comity/payment:initialized", undefined);
|
|
25
|
+
return success(undefined);
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=composition.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Instant } from "@comity/primitives/time";
|
|
2
|
+
import type { Money } from "@comity/pricing";
|
|
3
|
+
/**
|
|
4
|
+
* Payment lifecycle status.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors {@link OrderPaymentStatus} from {@link @comity/order} to ensure
|
|
7
|
+
* mechanical mapping between payment outcomes and order snapshots.
|
|
8
|
+
*
|
|
9
|
+
* Do not add: pending, processing, refunded, expired.
|
|
10
|
+
* Those are provider-specific or workflow states, not core outcomes.
|
|
11
|
+
*/
|
|
12
|
+
export type PaymentStatus = "authorized" | "captured" | "failed" | "cancelled";
|
|
13
|
+
/**
|
|
14
|
+
* Outcome of a payment initiation.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors {@link OrderPaymentSnapshot} from {@link @comity/order} to enable
|
|
17
|
+
* mechanical mapping. The Application is responsible for transforming this
|
|
18
|
+
* outcome into an {@link OrderPaymentSnapshot} for {@link Order.attachPayment}.
|
|
19
|
+
*
|
|
20
|
+
* Duplication is intentional: the Order must not depend on the Payment module.
|
|
21
|
+
*/
|
|
22
|
+
export interface PaymentOutcome {
|
|
23
|
+
/** Payment identifier assigned by the provider, if available. */
|
|
24
|
+
readonly paymentId?: string | undefined;
|
|
25
|
+
/** The amount that was charged. */
|
|
26
|
+
readonly amount: Money;
|
|
27
|
+
/** The final outcome status. */
|
|
28
|
+
readonly status: PaymentStatus;
|
|
29
|
+
/** The provider identifier, if known. */
|
|
30
|
+
readonly provider?: string | undefined;
|
|
31
|
+
/** The caller's opaque reference echoed back. */
|
|
32
|
+
readonly reference?: string | undefined;
|
|
33
|
+
/** Timestamp when the payment was authorized, if applicable. */
|
|
34
|
+
readonly authorizedAt?: Instant | undefined;
|
|
35
|
+
/** Timestamp when the payment was captured, if applicable. */
|
|
36
|
+
readonly capturedAt?: Instant | undefined;
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Result } from "@comity/primitives/result";
|
|
2
|
+
import type { PaymentRequest } from "./payment-request.js";
|
|
3
|
+
import type { PaymentOutcome } from "./payment-outcome.js";
|
|
4
|
+
import type { PaymentError } from "../errors/payment.js";
|
|
5
|
+
/**
|
|
6
|
+
* Provider-agnostic contract for initiating payments.
|
|
7
|
+
*
|
|
8
|
+
* The Application composes this capability to orchestrate checkout without
|
|
9
|
+
* depending on any specific payment provider (Stripe, Adyen, etc.).
|
|
10
|
+
*
|
|
11
|
+
* Implementations belong to Adapters (e.g., {@link @comity/payment-stripe}).
|
|
12
|
+
*/
|
|
13
|
+
export interface PaymentProvider {
|
|
14
|
+
/**
|
|
15
|
+
* Initiates a payment.
|
|
16
|
+
*
|
|
17
|
+
* @param request - The payment request containing amount and optional reference.
|
|
18
|
+
*
|
|
19
|
+
* @returns A Result containing the PaymentOutcome on success, or a PaymentError
|
|
20
|
+
* on contract-level failure (invalid request, infrastructure failure).
|
|
21
|
+
*
|
|
22
|
+
* Provider declines (e.g., insufficient funds) are represented as
|
|
23
|
+
* {@link PaymentOutcome} with {@link PaymentStatus} "failed", not as errors.
|
|
24
|
+
*/
|
|
25
|
+
initiate(request: PaymentRequest): Promise<Result<PaymentOutcome, PaymentError>>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Money } from "@comity/pricing";
|
|
2
|
+
/**
|
|
3
|
+
* Input required to initiate a payment.
|
|
4
|
+
*
|
|
5
|
+
* Minimal by design: no order, customer, address, or provider-specific data.
|
|
6
|
+
* The Application owns orchestration and maps its context to this shape.
|
|
7
|
+
*/
|
|
8
|
+
export interface PaymentRequest {
|
|
9
|
+
/** The amount to charge. Currency is embedded in the Money value object. */
|
|
10
|
+
readonly amount: Money;
|
|
11
|
+
/** Opaque caller-owned reference (e.g., order ID, cart ID, idempotency key). */
|
|
12
|
+
readonly reference?: string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ErrorMeta } from "@comity/primitives/errors";
|
|
2
|
+
import { BaseError } from "@comity/primitives/errors";
|
|
3
|
+
/**
|
|
4
|
+
* Reasons for payment contract errors.
|
|
5
|
+
*
|
|
6
|
+
* Initial MVP reason set. Additional reasons may be added as additive
|
|
7
|
+
* changes per `errors.md` §10.
|
|
8
|
+
*
|
|
9
|
+
* Provider declines (e.g., insufficient funds) are NOT errors — they are
|
|
10
|
+
* represented as {@link PaymentOutcome} with {@link PaymentStatus} "failed".
|
|
11
|
+
* This keeps the core reason set provider-agnostic.
|
|
12
|
+
*/
|
|
13
|
+
export type PaymentErrorReason = "invalid_request" | "internal_error";
|
|
14
|
+
/**
|
|
15
|
+
* Metadata attached to payment errors.
|
|
16
|
+
*/
|
|
17
|
+
export interface PaymentErrorMeta extends ErrorMeta {
|
|
18
|
+
/** Error reason. */
|
|
19
|
+
readonly reason: PaymentErrorReason;
|
|
20
|
+
/** Optional diagnostic details. */
|
|
21
|
+
readonly details?: Readonly<{
|
|
22
|
+
/** Opaque reference from the failed request, if any. */
|
|
23
|
+
reference?: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Payment contract error.
|
|
28
|
+
*
|
|
29
|
+
* Represents a failure at the payment contract boundary — not a provider
|
|
30
|
+
* decline, which is represented as a {@link PaymentOutcome} with
|
|
31
|
+
* {@link PaymentStatus} "failed".
|
|
32
|
+
*
|
|
33
|
+
* This is a Core Module error, following the single-error-class pattern
|
|
34
|
+
* per {@link errors.md} §6.
|
|
35
|
+
*/
|
|
36
|
+
export declare class PaymentError extends BaseError<PaymentErrorMeta> {
|
|
37
|
+
/** Namespaced error code. */
|
|
38
|
+
readonly code: `payment:${PaymentErrorReason}`;
|
|
39
|
+
/**
|
|
40
|
+
* @param reason - The reason for the payment error.
|
|
41
|
+
* @param meta - Additional metadata for the error.
|
|
42
|
+
*/
|
|
43
|
+
constructor(reason: PaymentErrorReason, meta?: Omit<import("@comity/primitives/errors").ErrorMeta, "reason">);
|
|
44
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { PaymentStatus, PaymentOutcome, } from "./contracts/payment-outcome.js";
|
|
2
|
+
export type { PaymentProvider, } from "./contracts/payment-provider.js";
|
|
3
|
+
export type { PaymentRequest, } from "./contracts/payment-request.js";
|
|
4
|
+
export { MemoryPaymentProvider } from "./providers/memory.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Result } from "@comity/primitives/result";
|
|
2
|
+
import type { PaymentProvider } from "../contracts/payment-provider.js";
|
|
3
|
+
import type { PaymentRequest } from "../contracts/payment-request.js";
|
|
4
|
+
import type { PaymentOutcome } from "../contracts/payment-outcome.js";
|
|
5
|
+
import { PaymentError } from "../errors/payment.js";
|
|
6
|
+
/**
|
|
7
|
+
* In-memory implementation of {@link PaymentProvider} for testing and
|
|
8
|
+
* development purposes.
|
|
9
|
+
*
|
|
10
|
+
* Note: This implementation is not suitable for production use. It does not
|
|
11
|
+
* persist payments and is not shared across multiple instances of the
|
|
12
|
+
* application. It provides deterministic behavior for testing and local
|
|
13
|
+
* development.
|
|
14
|
+
*
|
|
15
|
+
* Behavior:
|
|
16
|
+
* - Accepts any valid {@link PaymentRequest}.
|
|
17
|
+
* - Returns a successful {@link PaymentOutcome} with status "authorized" by
|
|
18
|
+
* default, or "captured" if the reference starts with "capture:".
|
|
19
|
+
* - Returns "failed" if the reference starts with "fail:".
|
|
20
|
+
* - Returns "cancelled" if the reference starts with "cancel:".
|
|
21
|
+
* - Generates a deterministic paymentId from the reference.
|
|
22
|
+
* - Returns {@link PaymentError} with reason "invalid_request" for invalid
|
|
23
|
+
* amounts (zero or negative).
|
|
24
|
+
*/
|
|
25
|
+
export declare class MemoryPaymentProvider implements PaymentProvider {
|
|
26
|
+
/**
|
|
27
|
+
* @inheritdoc
|
|
28
|
+
*/
|
|
29
|
+
initiate(request: PaymentRequest): Promise<Result<PaymentOutcome, PaymentError>>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PaymentProvider } from "../contracts/payment-provider.js";
|
|
2
|
+
import type { PaymentModuleContext } from "./types.js";
|
|
3
|
+
export type PaymentModuleOptions = {
|
|
4
|
+
/** The payment provider to use. Required for production. */
|
|
5
|
+
provider?: PaymentProvider;
|
|
6
|
+
};
|
|
7
|
+
declare const _default: {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
dependsOn: {};
|
|
11
|
+
incompatibleWith: never[];
|
|
12
|
+
/** @inheritdoc */
|
|
13
|
+
setup: (ctx: PaymentModuleContext, options?: PaymentModuleOptions) => Promise<import("@comity/primitives/result").ResultSuccess<() => Promise<import("@comity/primitives/result").ResultSuccess<undefined>>>>;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PAYMENT_PROVIDER_TOKEN: unique symbol;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ModuleSetupContext } from "@comity/composition/setup";
|
|
2
|
+
import type { PaymentProvider } from "../contracts/payment-provider.js";
|
|
3
|
+
import type { PAYMENT_PROVIDER_TOKEN } from "./constants.js";
|
|
4
|
+
/** Hooks exposed by the module */
|
|
5
|
+
export interface PaymentModuleHooks {
|
|
6
|
+
/** Called when the payment module is being configured. */
|
|
7
|
+
"@comity/payment:configuring": PaymentModuleOptions;
|
|
8
|
+
/** Called when the payment module has been initialized. */
|
|
9
|
+
"@comity/payment:initialized": undefined;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Events emitted by the module */
|
|
13
|
+
export type PaymentModuleEvents = {};
|
|
14
|
+
/**
|
|
15
|
+
* Services exposed by the module
|
|
16
|
+
*/
|
|
17
|
+
export type PaymentModuleServices = {
|
|
18
|
+
/** Payment provider resolver token */
|
|
19
|
+
[PAYMENT_PROVIDER_TOKEN]: PaymentProvider;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Context provided to the payment module setup function.
|
|
23
|
+
*/
|
|
24
|
+
export interface PaymentModuleContext extends ModuleSetupContext<PaymentModuleServices, PaymentModuleEvents, PaymentModuleHooks> {
|
|
25
|
+
}
|
|
26
|
+
export type PaymentModuleOptions = {
|
|
27
|
+
/** The payment provider to use. Required for production. */
|
|
28
|
+
provider?: import("../contracts/payment-provider").PaymentProvider;
|
|
29
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@comity/payment",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Payment domain abstractions for Comity.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Filippo Bovo <hello@filippobovo.com>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"comity": {
|
|
10
|
+
"layer": "core"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/comityjs/framework#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/comityjs/framework.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/comityjs/framework/issues"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=24.0.0"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"comity",
|
|
25
|
+
"comityjs",
|
|
26
|
+
"commerce",
|
|
27
|
+
"payment",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"./dist",
|
|
32
|
+
"!./dist/**/*.map"
|
|
33
|
+
],
|
|
34
|
+
"main": "./dist/cjs/index.js",
|
|
35
|
+
"module": "./dist/esm/index.js",
|
|
36
|
+
"types": "./dist/types/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./dist/types/index.d.ts",
|
|
41
|
+
"default": "./dist/esm/index.js"
|
|
42
|
+
},
|
|
43
|
+
"require": {
|
|
44
|
+
"types": "./dist/types/index.d.ts",
|
|
45
|
+
"default": "./dist/cjs/index.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"./errors": {
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "./dist/types/errors/index.d.ts",
|
|
51
|
+
"default": "./dist/esm/errors/index.js"
|
|
52
|
+
},
|
|
53
|
+
"require": {
|
|
54
|
+
"types": "./dist/types/errors/index.d.ts",
|
|
55
|
+
"default": "./dist/cjs/errors/index.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"./setup": {
|
|
59
|
+
"import": {
|
|
60
|
+
"types": "./dist/types/setup/index.d.ts",
|
|
61
|
+
"default": "./dist/esm/setup/index.js"
|
|
62
|
+
},
|
|
63
|
+
"require": {
|
|
64
|
+
"types": "./dist/types/setup/index.d.ts",
|
|
65
|
+
"default": "./dist/cjs/setup/index.js"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"./package.json": "./package.json"
|
|
69
|
+
},
|
|
70
|
+
"typesVersions": {
|
|
71
|
+
"*": {
|
|
72
|
+
"errors": [
|
|
73
|
+
"./dist/types/errors/index.d.ts"
|
|
74
|
+
],
|
|
75
|
+
"setup": [
|
|
76
|
+
"./dist/types/setup/index.d.ts"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"registry": "https://registry.npmjs.org",
|
|
82
|
+
"access": "public"
|
|
83
|
+
},
|
|
84
|
+
"sideEffects": false,
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"@comity/pricing": "0.9.0",
|
|
87
|
+
"@comity/primitives": "0.9.0",
|
|
88
|
+
"@comity/composition": "0.9.0"
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@types/node": "^24.13.4",
|
|
92
|
+
"typescript": "^5.9.3"
|
|
93
|
+
},
|
|
94
|
+
"scripts": {
|
|
95
|
+
"build": "node ../../scripts/build.mjs",
|
|
96
|
+
"dev": "node ../../scripts/build.mjs --watch",
|
|
97
|
+
"test": "vitest run --coverage",
|
|
98
|
+
"type-check": "tsc -p tsconfig.json --noEmit",
|
|
99
|
+
"lint": "eslint --ext .ts src"
|
|
100
|
+
}
|
|
101
|
+
}
|