@birtalanrobert/commerce 2.0.0 → 3.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/README.md +15 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -7
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/providers/none.d.ts +50 -0
- package/dist/providers/none.d.ts.map +1 -0
- package/dist/providers/none.js +75 -0
- package/dist/providers/none.js.map +1 -0
- package/dist/providers/port.d.ts +8 -1
- package/dist/providers/port.d.ts.map +1 -1
- package/package.json +8 -3
- package/src/index.ts +18 -5
- package/src/providers/none.ts +92 -0
- package/src/providers/port.ts +8 -1
- package/stripe/package.json +5 -0
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ canTakeMoney('pending'); // false — the provider has not verified them yet
|
|
|
27
27
|
## Using it in a NestJS application
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { StripeConnect } from '@birtalanrobert/commerce';
|
|
30
|
+
import { StripeConnect } from '@birtalanrobert/commerce/stripe';
|
|
31
31
|
import {
|
|
32
32
|
COMMERCE_PROVIDER,
|
|
33
33
|
CommerceService,
|
|
@@ -55,6 +55,20 @@ export class PaymentsModule {}
|
|
|
55
55
|
Register `commerceEntities` and `commerceMigrations` with the data source, as
|
|
56
56
|
with every other package here.
|
|
57
57
|
|
|
58
|
+
Where a deployment takes no card payments — which is most of them in some
|
|
59
|
+
categories — provide `NoPayments` instead of `StripeConnect`. It reports `none`
|
|
60
|
+
and refuses every operation, so every gate that already asks whether a business
|
|
61
|
+
can be paid out answers no. **Do not construct the vendor client with an empty
|
|
62
|
+
key**: it throws inside its own constructor and the application does not start.
|
|
63
|
+
|
|
64
|
+
**Three entry points, and the split is deliberate.** The root is pure — what a
|
|
65
|
+
deposit comes to, whether a business may sell yet, whether a payment can still
|
|
66
|
+
be refunded — because a console decides all three while somebody drags a
|
|
67
|
+
slider. `/nestjs` holds the service, the entities and the migrations.
|
|
68
|
+
`/stripe` holds the vendor's client on its own, because importing it pulls in
|
|
69
|
+
the whole SDK, and a barrel that re-exported it once put a payments SDK into a
|
|
70
|
+
booking console's first load.
|
|
71
|
+
|
|
58
72
|
## The parts worth knowing before using it
|
|
59
73
|
|
|
60
74
|
- **A business cannot be charged for until the provider says it may be paid
|
package/dist/index.d.ts
CHANGED
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
* the mistake that makes both hard to reason about — they differ in who pays
|
|
13
13
|
* whom, in which Stripe account, and in what happens when one fails.
|
|
14
14
|
*
|
|
15
|
-
* **This entry point is pure
|
|
16
|
-
*
|
|
17
|
-
* shows both while somebody drags a
|
|
18
|
-
*
|
|
15
|
+
* **This entry point is pure**, and stays that way by leaving things out. What
|
|
16
|
+
* a deposit comes to and whether a business may sell yet are decided without a
|
|
17
|
+
* database or a provider, because a console shows both while somebody drags a
|
|
18
|
+
* slider. Storage and the container are behind `/nestjs`; the Stripe client is
|
|
19
|
+
* behind `/stripe`, because a barrel that re-exports it puts the vendor's whole
|
|
20
|
+
* SDK into every bundle that wanted to divide a price by three.
|
|
19
21
|
*/
|
|
20
22
|
export { balanceAfter, canTakeMoney, depositFor, payoutBlockReason, type DepositKind, type DepositPolicy, type PayoutStatus, } from './deposits';
|
|
21
23
|
export { isRefundable, refundableAmount, type PaymentKind, type PaymentMethod, type PaymentState, } from './payments';
|
|
24
|
+
export { NoPayments } from './providers/none';
|
|
22
25
|
export type { ChargeRequest, ChargeResult, OnboardingLink, PaymentProvider, ProviderAccount, ProviderEvent, RefundRequest, SaveCardRequest, SaveCardResult, StoredCard, } from './providers/port';
|
|
23
|
-
export { StripeConnect, type StripeConnectOptions } from './providers/stripe';
|
|
24
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.NoPayments = exports.refundableAmount = exports.isRefundable = exports.payoutBlockReason = exports.depositFor = exports.canTakeMoney = exports.balanceAfter = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Taking money on a business's behalf.
|
|
6
6
|
*
|
|
@@ -15,10 +15,12 @@ exports.StripeConnect = exports.refundableAmount = exports.isRefundable = export
|
|
|
15
15
|
* the mistake that makes both hard to reason about — they differ in who pays
|
|
16
16
|
* whom, in which Stripe account, and in what happens when one fails.
|
|
17
17
|
*
|
|
18
|
-
* **This entry point is pure
|
|
19
|
-
*
|
|
20
|
-
* shows both while somebody drags a
|
|
21
|
-
*
|
|
18
|
+
* **This entry point is pure**, and stays that way by leaving things out. What
|
|
19
|
+
* a deposit comes to and whether a business may sell yet are decided without a
|
|
20
|
+
* database or a provider, because a console shows both while somebody drags a
|
|
21
|
+
* slider. Storage and the container are behind `/nestjs`; the Stripe client is
|
|
22
|
+
* behind `/stripe`, because a barrel that re-exports it puts the vendor's whole
|
|
23
|
+
* SDK into every bundle that wanted to divide a price by three.
|
|
22
24
|
*/
|
|
23
25
|
var deposits_1 = require("./deposits");
|
|
24
26
|
Object.defineProperty(exports, "balanceAfter", { enumerable: true, get: function () { return deposits_1.balanceAfter; } });
|
|
@@ -28,6 +30,16 @@ Object.defineProperty(exports, "payoutBlockReason", { enumerable: true, get: fun
|
|
|
28
30
|
var payments_1 = require("./payments");
|
|
29
31
|
Object.defineProperty(exports, "isRefundable", { enumerable: true, get: function () { return payments_1.isRefundable; } });
|
|
30
32
|
Object.defineProperty(exports, "refundableAmount", { enumerable: true, get: function () { return payments_1.refundableAmount; } });
|
|
31
|
-
var
|
|
32
|
-
Object.defineProperty(exports, "
|
|
33
|
+
var none_1 = require("./providers/none");
|
|
34
|
+
Object.defineProperty(exports, "NoPayments", { enumerable: true, get: function () { return none_1.NoPayments; } });
|
|
35
|
+
/*
|
|
36
|
+
* `StripeConnect` is deliberately NOT re-exported here.
|
|
37
|
+
*
|
|
38
|
+
* It lives at `@birtalanrobert/commerce/stripe`, because importing it pulls in
|
|
39
|
+
* the vendor's whole SDK — and this entry point is imported by browser code
|
|
40
|
+
* that only wants to know what a deposit comes to. Re-exporting it put 30 KB of
|
|
41
|
+
* a payments SDK into a console's first load for a screen that never calls it:
|
|
42
|
+
* a barrel is a bundling decision, and the only way to keep this one pure is to
|
|
43
|
+
* leave the heavy thing out of it.
|
|
44
|
+
*/
|
|
33
45
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,uCAQoB;AAPlB,wGAAA,YAAY,OAAA;AACZ,wGAAA,YAAY,OAAA;AACZ,sGAAA,UAAU,OAAA;AACV,6GAAA,iBAAiB,OAAA;AAMnB,uCAMoB;AALlB,wGAAA,YAAY,OAAA;AACZ,4GAAA,gBAAgB,OAAA;AAMlB,yCAA8C;AAArC,kGAAA,UAAU,OAAA;AAenB;;;;;;;;;GASG"}
|
package/dist/nestjs/index.d.ts
CHANGED
|
@@ -16,6 +16,6 @@ import { Payment, PaymentRefund } from './payment.entity';
|
|
|
16
16
|
import { SavedCard } from './saved-card.entity';
|
|
17
17
|
import { CreateCommerce1789800000000 } from '../migrations/1789800000000-CreateCommerce';
|
|
18
18
|
/** Register with the data source, the way every other mortar package is. */
|
|
19
|
-
export declare const commerceEntities: (typeof PayoutAccount | typeof Payment | typeof
|
|
19
|
+
export declare const commerceEntities: (typeof PayoutAccount | typeof Payment | typeof SavedCard | typeof PaymentRefund)[];
|
|
20
20
|
export declare const commerceMigrations: (typeof CreateCommerce1789800000000)[];
|
|
21
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChargeRequest, ChargeResult, OnboardingLink, PaymentProvider, ProviderAccount, RefundRequest, SaveCardRequest, SaveCardResult, StoredCard } from './port';
|
|
2
|
+
/**
|
|
3
|
+
* No payment provider at all, said out loud rather than by failing.
|
|
4
|
+
*
|
|
5
|
+
* **Online payment is optional and never mandatory**, and this is what makes
|
|
6
|
+
* that true in the code rather than only in a document. A salon that takes cash
|
|
7
|
+
* at the counter and has never heard of Stripe runs its whole diary, writes
|
|
8
|
+
* down what it took, and reports on it — and nothing about that deployment
|
|
9
|
+
* should require a payment account to exist, least of all the application
|
|
10
|
+
* starting up.
|
|
11
|
+
*
|
|
12
|
+
* The alternative that looks simpler is constructing the vendor's client with
|
|
13
|
+
* an empty key. It throws inside its own constructor, and the whole API fails
|
|
14
|
+
* to boot — which is how this was found.
|
|
15
|
+
*
|
|
16
|
+
* **Not a permissive no-op.** Every operation refuses, and the account reports
|
|
17
|
+
* `none`, so every gate that already asks "can this business be paid out?"
|
|
18
|
+
* answers no without a special case. A provider that quietly returned success
|
|
19
|
+
* would write a trail of payments that never happened, which is worse than the
|
|
20
|
+
* crash it replaced.
|
|
21
|
+
*/
|
|
22
|
+
export declare class NoPayments implements PaymentProvider {
|
|
23
|
+
readonly name = "none";
|
|
24
|
+
onboard(): Promise<OnboardingLink>;
|
|
25
|
+
/**
|
|
26
|
+
* `none` rather than `pending`.
|
|
27
|
+
*
|
|
28
|
+
* They are different facts: `pending` means the provider is still checking,
|
|
29
|
+
* and a business told that will wait for an email that is never coming.
|
|
30
|
+
*/
|
|
31
|
+
account(externalId: string | null): Promise<ProviderAccount>;
|
|
32
|
+
charge(_request: ChargeRequest): Promise<ChargeResult>;
|
|
33
|
+
capture(): Promise<ChargeResult>;
|
|
34
|
+
release(): Promise<void>;
|
|
35
|
+
refund(_request: RefundRequest): Promise<{
|
|
36
|
+
externalId: string;
|
|
37
|
+
}>;
|
|
38
|
+
saveCard(_request: SaveCardRequest): Promise<SaveCardResult>;
|
|
39
|
+
storedCard(): Promise<StoredCard | undefined>;
|
|
40
|
+
forgetCard(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Never genuine.
|
|
43
|
+
*
|
|
44
|
+
* A deployment with no provider has no webhook to receive, so anything
|
|
45
|
+
* arriving at that address came from somebody else — and `undefined` is how
|
|
46
|
+
* the port says "not from the provider".
|
|
47
|
+
*/
|
|
48
|
+
verify(): undefined;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=none.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"none.d.ts","sourceRoot":"","sources":["../../src/providers/none.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,EACf,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,EACX,MAAM,QAAQ,CAAC;AAEhB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,UAAW,YAAW,eAAe;IAChD,QAAQ,CAAC,IAAI,UAAU;IAEjB,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC;IAIxC;;;;;OAKG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC;IAI5D,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAQtD,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;IAIhC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhE,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAI5D,UAAU,IAAI,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAI7C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;;;;OAMG;IACH,MAAM,IAAI,SAAS;CAGpB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoPayments = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* No payment provider at all, said out loud rather than by failing.
|
|
6
|
+
*
|
|
7
|
+
* **Online payment is optional and never mandatory**, and this is what makes
|
|
8
|
+
* that true in the code rather than only in a document. A salon that takes cash
|
|
9
|
+
* at the counter and has never heard of Stripe runs its whole diary, writes
|
|
10
|
+
* down what it took, and reports on it — and nothing about that deployment
|
|
11
|
+
* should require a payment account to exist, least of all the application
|
|
12
|
+
* starting up.
|
|
13
|
+
*
|
|
14
|
+
* The alternative that looks simpler is constructing the vendor's client with
|
|
15
|
+
* an empty key. It throws inside its own constructor, and the whole API fails
|
|
16
|
+
* to boot — which is how this was found.
|
|
17
|
+
*
|
|
18
|
+
* **Not a permissive no-op.** Every operation refuses, and the account reports
|
|
19
|
+
* `none`, so every gate that already asks "can this business be paid out?"
|
|
20
|
+
* answers no without a special case. A provider that quietly returned success
|
|
21
|
+
* would write a trail of payments that never happened, which is worse than the
|
|
22
|
+
* crash it replaced.
|
|
23
|
+
*/
|
|
24
|
+
class NoPayments {
|
|
25
|
+
name = 'none';
|
|
26
|
+
async onboard() {
|
|
27
|
+
throw new Error('This deployment has no payment provider configured.');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `none` rather than `pending`.
|
|
31
|
+
*
|
|
32
|
+
* They are different facts: `pending` means the provider is still checking,
|
|
33
|
+
* and a business told that will wait for an email that is never coming.
|
|
34
|
+
*/
|
|
35
|
+
async account(externalId) {
|
|
36
|
+
return { externalId: externalId ?? '', status: 'none', requirements: [] };
|
|
37
|
+
}
|
|
38
|
+
async charge(_request) {
|
|
39
|
+
return {
|
|
40
|
+
externalId: '',
|
|
41
|
+
state: 'failed',
|
|
42
|
+
detail: 'This business cannot take card payments.',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async capture() {
|
|
46
|
+
return { externalId: '', state: 'failed', detail: 'There is nothing to capture.' };
|
|
47
|
+
}
|
|
48
|
+
async release() {
|
|
49
|
+
// Nothing was ever held.
|
|
50
|
+
}
|
|
51
|
+
async refund(_request) {
|
|
52
|
+
throw new Error('Nothing was taken through a provider, so nothing can be given back.');
|
|
53
|
+
}
|
|
54
|
+
async saveCard(_request) {
|
|
55
|
+
throw new Error('This deployment has no payment provider configured.');
|
|
56
|
+
}
|
|
57
|
+
async storedCard() {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
async forgetCard() {
|
|
61
|
+
// There is nothing stored anywhere to forget.
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Never genuine.
|
|
65
|
+
*
|
|
66
|
+
* A deployment with no provider has no webhook to receive, so anything
|
|
67
|
+
* arriving at that address came from somebody else — and `undefined` is how
|
|
68
|
+
* the port says "not from the provider".
|
|
69
|
+
*/
|
|
70
|
+
verify() {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.NoPayments = NoPayments;
|
|
75
|
+
//# sourceMappingURL=none.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"none.js","sourceRoot":"","sources":["../../src/providers/none.ts"],"names":[],"mappings":";;;AAYA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,UAAU;IACZ,IAAI,GAAG,MAAM,CAAC;IAEvB,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,UAAyB;QACrC,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAuB;QAClC,OAAO;YACL,UAAU,EAAE,EAAE;YACd,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,0CAA0C;SACnD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,OAAO;QACX,yBAAyB;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAuB;QAClC,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACzF,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAyB;QACtC,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,8CAA8C;IAChD,CAAC;IAED;;;;;;OAMG;IACH,MAAM;QACJ,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AA3DD,gCA2DC"}
|
package/dist/providers/port.d.ts
CHANGED
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
/** Where a business's money goes, as the provider knows it. */
|
|
15
15
|
export interface ProviderAccount {
|
|
16
16
|
readonly externalId: string;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* `none` is a real answer, not an absence.
|
|
19
|
+
*
|
|
20
|
+
* It is what a deployment with no payment provider configured says, and it is
|
|
21
|
+
* different from `pending`: `pending` means somebody is checking, and a
|
|
22
|
+
* business told that will wait for an email nobody is going to send.
|
|
23
|
+
*/
|
|
24
|
+
readonly status: 'none' | 'pending' | 'restricted' | 'ready';
|
|
18
25
|
/** What the provider still wants, in its own words. */
|
|
19
26
|
readonly requirements: readonly string[];
|
|
20
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/providers/port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,+DAA+D;AAC/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/providers/port.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@birtalanrobert/commerce",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Taking money on a business's behalf: payout onboarding, payments, refunds and the record of both",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Robert Birtalan",
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/index.d.ts",
|
|
21
21
|
"default": "./dist/index.js"
|
|
22
22
|
},
|
|
23
|
+
"./stripe": {
|
|
24
|
+
"types": "./dist/providers/stripe.d.ts",
|
|
25
|
+
"default": "./dist/providers/stripe.js"
|
|
26
|
+
},
|
|
23
27
|
"./nestjs": {
|
|
24
28
|
"types": "./dist/nestjs/index.d.ts",
|
|
25
29
|
"default": "./dist/nestjs/index.js"
|
|
@@ -34,16 +38,17 @@
|
|
|
34
38
|
"README.md",
|
|
35
39
|
"LICENSE",
|
|
36
40
|
"NOTICE",
|
|
41
|
+
"stripe",
|
|
37
42
|
"nestjs",
|
|
38
43
|
"CHANGELOG.md"
|
|
39
44
|
],
|
|
40
45
|
"dependencies": {
|
|
41
46
|
"stripe": "^22.6.0",
|
|
42
|
-
"@birtalanrobert/database": "^1.0.0",
|
|
43
47
|
"@birtalanrobert/context": "^1.1.0",
|
|
44
48
|
"@birtalanrobert/http": "^2.0.0",
|
|
49
|
+
"@birtalanrobert/money": "^1.0.0",
|
|
45
50
|
"@birtalanrobert/tenancy": "^1.1.0",
|
|
46
|
-
"@birtalanrobert/
|
|
51
|
+
"@birtalanrobert/database": "^1.0.0"
|
|
47
52
|
},
|
|
48
53
|
"peerDependencies": {
|
|
49
54
|
"@nestjs/common": "^11.0.0",
|
package/src/index.ts
CHANGED
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
* the mistake that makes both hard to reason about — they differ in who pays
|
|
13
13
|
* whom, in which Stripe account, and in what happens when one fails.
|
|
14
14
|
*
|
|
15
|
-
* **This entry point is pure
|
|
16
|
-
*
|
|
17
|
-
* shows both while somebody drags a
|
|
18
|
-
*
|
|
15
|
+
* **This entry point is pure**, and stays that way by leaving things out. What
|
|
16
|
+
* a deposit comes to and whether a business may sell yet are decided without a
|
|
17
|
+
* database or a provider, because a console shows both while somebody drags a
|
|
18
|
+
* slider. Storage and the container are behind `/nestjs`; the Stripe client is
|
|
19
|
+
* behind `/stripe`, because a barrel that re-exports it puts the vendor's whole
|
|
20
|
+
* SDK into every bundle that wanted to divide a price by three.
|
|
19
21
|
*/
|
|
20
22
|
export {
|
|
21
23
|
balanceAfter,
|
|
@@ -35,6 +37,8 @@ export {
|
|
|
35
37
|
type PaymentState,
|
|
36
38
|
} from './payments';
|
|
37
39
|
|
|
40
|
+
export { NoPayments } from './providers/none';
|
|
41
|
+
|
|
38
42
|
export type {
|
|
39
43
|
ChargeRequest,
|
|
40
44
|
ChargeResult,
|
|
@@ -48,4 +52,13 @@ export type {
|
|
|
48
52
|
StoredCard,
|
|
49
53
|
} from './providers/port';
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
/*
|
|
56
|
+
* `StripeConnect` is deliberately NOT re-exported here.
|
|
57
|
+
*
|
|
58
|
+
* It lives at `@birtalanrobert/commerce/stripe`, because importing it pulls in
|
|
59
|
+
* the vendor's whole SDK — and this entry point is imported by browser code
|
|
60
|
+
* that only wants to know what a deposit comes to. Re-exporting it put 30 KB of
|
|
61
|
+
* a payments SDK into a console's first load for a screen that never calls it:
|
|
62
|
+
* a barrel is a bundling decision, and the only way to keep this one pure is to
|
|
63
|
+
* leave the heavy thing out of it.
|
|
64
|
+
*/
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChargeRequest,
|
|
3
|
+
ChargeResult,
|
|
4
|
+
OnboardingLink,
|
|
5
|
+
PaymentProvider,
|
|
6
|
+
ProviderAccount,
|
|
7
|
+
RefundRequest,
|
|
8
|
+
SaveCardRequest,
|
|
9
|
+
SaveCardResult,
|
|
10
|
+
StoredCard,
|
|
11
|
+
} from './port';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* No payment provider at all, said out loud rather than by failing.
|
|
15
|
+
*
|
|
16
|
+
* **Online payment is optional and never mandatory**, and this is what makes
|
|
17
|
+
* that true in the code rather than only in a document. A salon that takes cash
|
|
18
|
+
* at the counter and has never heard of Stripe runs its whole diary, writes
|
|
19
|
+
* down what it took, and reports on it — and nothing about that deployment
|
|
20
|
+
* should require a payment account to exist, least of all the application
|
|
21
|
+
* starting up.
|
|
22
|
+
*
|
|
23
|
+
* The alternative that looks simpler is constructing the vendor's client with
|
|
24
|
+
* an empty key. It throws inside its own constructor, and the whole API fails
|
|
25
|
+
* to boot — which is how this was found.
|
|
26
|
+
*
|
|
27
|
+
* **Not a permissive no-op.** Every operation refuses, and the account reports
|
|
28
|
+
* `none`, so every gate that already asks "can this business be paid out?"
|
|
29
|
+
* answers no without a special case. A provider that quietly returned success
|
|
30
|
+
* would write a trail of payments that never happened, which is worse than the
|
|
31
|
+
* crash it replaced.
|
|
32
|
+
*/
|
|
33
|
+
export class NoPayments implements PaymentProvider {
|
|
34
|
+
readonly name = 'none';
|
|
35
|
+
|
|
36
|
+
async onboard(): Promise<OnboardingLink> {
|
|
37
|
+
throw new Error('This deployment has no payment provider configured.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `none` rather than `pending`.
|
|
42
|
+
*
|
|
43
|
+
* They are different facts: `pending` means the provider is still checking,
|
|
44
|
+
* and a business told that will wait for an email that is never coming.
|
|
45
|
+
*/
|
|
46
|
+
async account(externalId: string | null): Promise<ProviderAccount> {
|
|
47
|
+
return { externalId: externalId ?? '', status: 'none', requirements: [] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async charge(_request: ChargeRequest): Promise<ChargeResult> {
|
|
51
|
+
return {
|
|
52
|
+
externalId: '',
|
|
53
|
+
state: 'failed',
|
|
54
|
+
detail: 'This business cannot take card payments.',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async capture(): Promise<ChargeResult> {
|
|
59
|
+
return { externalId: '', state: 'failed', detail: 'There is nothing to capture.' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async release(): Promise<void> {
|
|
63
|
+
// Nothing was ever held.
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async refund(_request: RefundRequest): Promise<{ externalId: string }> {
|
|
67
|
+
throw new Error('Nothing was taken through a provider, so nothing can be given back.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async saveCard(_request: SaveCardRequest): Promise<SaveCardResult> {
|
|
71
|
+
throw new Error('This deployment has no payment provider configured.');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async storedCard(): Promise<StoredCard | undefined> {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async forgetCard(): Promise<void> {
|
|
79
|
+
// There is nothing stored anywhere to forget.
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Never genuine.
|
|
84
|
+
*
|
|
85
|
+
* A deployment with no provider has no webhook to receive, so anything
|
|
86
|
+
* arriving at that address came from somebody else — and `undefined` is how
|
|
87
|
+
* the port says "not from the provider".
|
|
88
|
+
*/
|
|
89
|
+
verify(): undefined {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/providers/port.ts
CHANGED
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
/** Where a business's money goes, as the provider knows it. */
|
|
16
16
|
export interface ProviderAccount {
|
|
17
17
|
readonly externalId: string;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* `none` is a real answer, not an absence.
|
|
20
|
+
*
|
|
21
|
+
* It is what a deployment with no payment provider configured says, and it is
|
|
22
|
+
* different from `pending`: `pending` means somebody is checking, and a
|
|
23
|
+
* business told that will wait for an email nobody is going to send.
|
|
24
|
+
*/
|
|
25
|
+
readonly status: 'none' | 'pending' | 'restricted' | 'ready';
|
|
19
26
|
/** What the provider still wants, in its own words. */
|
|
20
27
|
readonly requirements: readonly string[];
|
|
21
28
|
}
|