@birtalanrobert/commerce 1.0.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/CHANGELOG.md +601 -0
- package/LICENSE +661 -0
- package/NOTICE +45 -0
- package/README.md +79 -0
- package/dist/deposits.d.ts +52 -0
- package/dist/deposits.d.ts.map +1 -0
- package/dist/deposits.js +71 -0
- package/dist/deposits.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/1789800000000-CreateCommerce.d.ts +38 -0
- package/dist/migrations/1789800000000-CreateCommerce.d.ts.map +1 -0
- package/dist/migrations/1789800000000-CreateCommerce.js +152 -0
- package/dist/migrations/1789800000000-CreateCommerce.js.map +1 -0
- package/dist/nestjs/commerce.service.d.ts +122 -0
- package/dist/nestjs/commerce.service.d.ts.map +1 -0
- package/dist/nestjs/commerce.service.js +337 -0
- package/dist/nestjs/commerce.service.js.map +1 -0
- package/dist/nestjs/index.d.ts +18 -0
- package/dist/nestjs/index.d.ts.map +1 -0
- package/dist/nestjs/index.js +27 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/nestjs/payment.entity.d.ts +94 -0
- package/dist/nestjs/payment.entity.d.ts.map +1 -0
- package/dist/nestjs/payment.entity.js +181 -0
- package/dist/nestjs/payment.entity.js.map +1 -0
- package/dist/nestjs/payout-account.entity.d.ts +41 -0
- package/dist/nestjs/payout-account.entity.d.ts.map +1 -0
- package/dist/nestjs/payout-account.entity.js +83 -0
- package/dist/nestjs/payout-account.entity.js.map +1 -0
- package/dist/providers/port.d.ts +97 -0
- package/dist/providers/port.d.ts.map +1 -0
- package/dist/providers/port.js +16 -0
- package/dist/providers/port.js.map +1 -0
- package/dist/providers/stripe.d.ts +39 -0
- package/dist/providers/stripe.d.ts.map +1 -0
- package/dist/providers/stripe.js +221 -0
- package/dist/providers/stripe.js.map +1 -0
- package/nestjs/package.json +5 -0
- package/package.json +49 -0
- package/src/deposits.ts +96 -0
- package/src/index.ts +40 -0
- package/src/migrations/1789800000000-CreateCommerce.ts +156 -0
- package/src/nestjs/commerce.service.ts +476 -0
- package/src/nestjs/index.ts +24 -0
- package/src/nestjs/payment.entity.ts +150 -0
- package/src/nestjs/payout-account.entity.ts +56 -0
- package/src/providers/port.ts +108 -0
- package/src/providers/stripe.ts +274 -0
package/NOTICE
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
mortar
|
|
2
|
+
Copyright (C) 2026 Robert Birtalan
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU Affero General Public License as published by the Free
|
|
6
|
+
Software Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
11
|
+
PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
12
|
+
|
|
13
|
+
You should have received a copy of the GNU Affero General Public License along
|
|
14
|
+
with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
--------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
RIGHTS RESERVED BY THE COPYRIGHT HOLDER
|
|
19
|
+
|
|
20
|
+
The AGPL is a grant from the copyright holder to everyone else. The copyright
|
|
21
|
+
holder is not a licensee of their own work, and is therefore not bound by these
|
|
22
|
+
terms.
|
|
23
|
+
|
|
24
|
+
Robert Birtalan, as sole copyright holder, retains the right to use, modify and
|
|
25
|
+
distribute this software under any other terms, including within closed-source
|
|
26
|
+
and commercial products, without triggering any obligation under the AGPL.
|
|
27
|
+
|
|
28
|
+
Commercial licences for third parties who cannot or do not wish to comply with
|
|
29
|
+
the AGPL are available on request.
|
|
30
|
+
|
|
31
|
+
--------------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
CONTRIBUTIONS
|
|
34
|
+
|
|
35
|
+
Contributions are not currently accepted.
|
|
36
|
+
|
|
37
|
+
This is deliberate and load-bearing. A contributor retains copyright in their
|
|
38
|
+
contribution unless they assign it. Accepting outside contributions without a
|
|
39
|
+
copyright assignment or a contributor licence agreement would mean the project
|
|
40
|
+
was no longer wholly owned by one copyright holder — and the reserved rights
|
|
41
|
+
above would no longer apply to those parts, because one cannot relicense code
|
|
42
|
+
one does not own.
|
|
43
|
+
|
|
44
|
+
Should contributions be accepted in future, a CLA assigning copyright to the
|
|
45
|
+
maintainer is a prerequisite, not a formality.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @birtalanrobert/commerce
|
|
2
|
+
|
|
3
|
+
Taking money **on a business's behalf**: payout onboarding, card payments,
|
|
4
|
+
manually recorded takings, refunds, and the record of all of it.
|
|
5
|
+
|
|
6
|
+
**We never hold anybody's funds.** The customer pays the business directly and
|
|
7
|
+
our cut is taken on top as an application fee. This is an architectural rule
|
|
8
|
+
rather than a preference — holding third-party money turns a software company
|
|
9
|
+
into a regulated payments business — and everything here follows from it.
|
|
10
|
+
|
|
11
|
+
Not to be confused with `@birtalanrobert/billing`, which is the other direction:
|
|
12
|
+
the business paying _us_ for a subscription. They differ in who pays whom, in
|
|
13
|
+
which provider account, and in what happens when one fails.
|
|
14
|
+
|
|
15
|
+
## What is decided without a provider
|
|
16
|
+
|
|
17
|
+
The root entry point is pure, because a console shows these numbers while
|
|
18
|
+
somebody drags a slider:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { depositFor, canTakeMoney } from '@birtalanrobert/commerce';
|
|
22
|
+
|
|
23
|
+
depositFor(15_000, { kind: 'percentage', value: 30 }); // 4_500
|
|
24
|
+
canTakeMoney('pending'); // false — the provider has not verified them yet
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Using it in a NestJS application
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { StripeConnect } from '@birtalanrobert/commerce';
|
|
31
|
+
import {
|
|
32
|
+
COMMERCE_PROVIDER,
|
|
33
|
+
CommerceService,
|
|
34
|
+
commerceEntities,
|
|
35
|
+
commerceMigrations,
|
|
36
|
+
} from '@birtalanrobert/commerce/nestjs';
|
|
37
|
+
|
|
38
|
+
@Module({
|
|
39
|
+
providers: [
|
|
40
|
+
{
|
|
41
|
+
provide: COMMERCE_PROVIDER,
|
|
42
|
+
useFactory: (config: AppConfig) =>
|
|
43
|
+
new StripeConnect({
|
|
44
|
+
secretKey: config.STRIPE_SECRET_KEY,
|
|
45
|
+
webhookSecret: config.STRIPE_WEBHOOK_SECRET,
|
|
46
|
+
}),
|
|
47
|
+
inject: [ConfigModule.token()],
|
|
48
|
+
},
|
|
49
|
+
CommerceService,
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
export class PaymentsModule {}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Register `commerceEntities` and `commerceMigrations` with the data source, as
|
|
56
|
+
with every other package here.
|
|
57
|
+
|
|
58
|
+
## The parts worth knowing before using it
|
|
59
|
+
|
|
60
|
+
- **A business cannot be charged for until the provider says it may be paid
|
|
61
|
+
out.** `take` refuses before the provider is called. A charge that succeeded
|
|
62
|
+
into an account with no destination would leave the customer debited and the
|
|
63
|
+
money nowhere anybody can see it.
|
|
64
|
+
- **`authorized` is not `captured`.** A card held against a no-show fee is
|
|
65
|
+
authorised and charged only if the fee is actually applied — and _that
|
|
66
|
+
decision is a human one_. This package offers the mechanism and never the
|
|
67
|
+
trigger, because charging a customer automatically is how a business loses
|
|
68
|
+
them permanently.
|
|
69
|
+
- **Cash, terminal, voucher and transfer payments are recorded, not
|
|
70
|
+
processed** — and recording them is not a lesser feature. A salon is mostly
|
|
71
|
+
cash, a restaurant takes meal vouchers, a box office takes notes. A report
|
|
72
|
+
counting only what a provider processed tells a business a fraction of its own
|
|
73
|
+
takings while looking complete.
|
|
74
|
+
- **The record outlives the provider.** Amounts, dates, what it was for and who
|
|
75
|
+
decided are stored in full rather than as identifiers to fetch, because a
|
|
76
|
+
business must be able to produce its own takings after the account is closed.
|
|
77
|
+
- **There is no foreign key to whatever was paid for.** One product takes a
|
|
78
|
+
deposit against an appointment, another against a seat, a third against a
|
|
79
|
+
table's tab; a key to any one of them is what would stop this being shared.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a customer has to pay up front, worked out the way a person would check.
|
|
3
|
+
*
|
|
4
|
+
* **Pure, and at the root entry point on purpose.** A console shows "30% of
|
|
5
|
+
* 150,00 lei is 45,00 lei" while somebody drags a slider, and a booking page
|
|
6
|
+
* shows the same number before anybody types a card into it. Neither may reach
|
|
7
|
+
* for a database, and both must arrive at exactly the number the charge will be.
|
|
8
|
+
*/
|
|
9
|
+
/** How a business asks for money before the work. */
|
|
10
|
+
export type DepositKind = 'none' | 'percentage' | 'fixed' | 'full';
|
|
11
|
+
export interface DepositPolicy {
|
|
12
|
+
readonly kind: DepositKind;
|
|
13
|
+
/**
|
|
14
|
+
* Whole percent for `percentage`, minor units for `fixed`, ignored otherwise.
|
|
15
|
+
*
|
|
16
|
+
* Whole percent rather than a fraction because it is typed into a box by a
|
|
17
|
+
* person: "30" is what a tattoo studio says, and `0.3` is what turns into
|
|
18
|
+
* `0.30000000000000004` two operations later.
|
|
19
|
+
*/
|
|
20
|
+
readonly value: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The deposit for a given total.
|
|
24
|
+
*
|
|
25
|
+
* **Rounded to the nearest minor unit, and never past the total.** A percentage
|
|
26
|
+
* of an odd amount is a fraction of a ban, and a deposit larger than the price
|
|
27
|
+
* is what a misconfigured 150% produces — both are refused here rather than at
|
|
28
|
+
* the provider, where the message is in English and mentions an integer.
|
|
29
|
+
*/
|
|
30
|
+
export declare function depositFor(total: number, policy: DepositPolicy): number;
|
|
31
|
+
/** What is still owed after a deposit. Never negative, whatever was paid. */
|
|
32
|
+
export declare const balanceAfter: (total: number, paid: number) => number;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a business may take money at all.
|
|
35
|
+
*
|
|
36
|
+
* The provider's onboarding is the gate, and it is a hard one: funds go
|
|
37
|
+
* directly to the business and our fee is taken on top, so until the provider
|
|
38
|
+
* has verified who they are there is nowhere for the money to go. Every
|
|
39
|
+
* consumer of this package has to answer the same question in front of the
|
|
40
|
+
* same button, which is why it is here rather than written three times.
|
|
41
|
+
*/
|
|
42
|
+
export type PayoutStatus = 'none' | 'pending' | 'restricted' | 'ready';
|
|
43
|
+
export declare const canTakeMoney: (status: PayoutStatus) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* What to tell a business that cannot yet, in the order it becomes true.
|
|
46
|
+
*
|
|
47
|
+
* Returned as a key rather than a sentence: the words belong to whichever
|
|
48
|
+
* product is showing them, and a package that shipped English into a Romanian
|
|
49
|
+
* console would be worse than one that shipped nothing.
|
|
50
|
+
*/
|
|
51
|
+
export declare const payoutBlockReason: (status: PayoutStatus) => "not-started" | "in-progress" | "needs-attention" | null;
|
|
52
|
+
//# sourceMappingURL=deposits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposits.d.ts","sourceRoot":"","sources":["../src/deposits.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qDAAqD;AACrD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAuBvE;AAED,6EAA6E;AAC7E,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,MAAM,MAAM,KAAG,MAAmC,CAAC;AAI/F;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC;AAEvE,eAAO,MAAM,YAAY,GAAI,QAAQ,YAAY,KAAG,OAA6B,CAAC;AAElF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,YAAY,KACnB,aAAa,GAAG,aAAa,GAAG,iBAAiB,GAAG,IAWtD,CAAC"}
|
package/dist/deposits.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What a customer has to pay up front, worked out the way a person would check.
|
|
4
|
+
*
|
|
5
|
+
* **Pure, and at the root entry point on purpose.** A console shows "30% of
|
|
6
|
+
* 150,00 lei is 45,00 lei" while somebody drags a slider, and a booking page
|
|
7
|
+
* shows the same number before anybody types a card into it. Neither may reach
|
|
8
|
+
* for a database, and both must arrive at exactly the number the charge will be.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.payoutBlockReason = exports.canTakeMoney = exports.balanceAfter = void 0;
|
|
12
|
+
exports.depositFor = depositFor;
|
|
13
|
+
/**
|
|
14
|
+
* The deposit for a given total.
|
|
15
|
+
*
|
|
16
|
+
* **Rounded to the nearest minor unit, and never past the total.** A percentage
|
|
17
|
+
* of an odd amount is a fraction of a ban, and a deposit larger than the price
|
|
18
|
+
* is what a misconfigured 150% produces — both are refused here rather than at
|
|
19
|
+
* the provider, where the message is in English and mentions an integer.
|
|
20
|
+
*/
|
|
21
|
+
function depositFor(total, policy) {
|
|
22
|
+
if (total <= 0)
|
|
23
|
+
return 0;
|
|
24
|
+
switch (policy.kind) {
|
|
25
|
+
case 'none':
|
|
26
|
+
return 0;
|
|
27
|
+
case 'full':
|
|
28
|
+
return total;
|
|
29
|
+
case 'fixed':
|
|
30
|
+
// A fixed deposit above the price is a configuration mistake, and taking
|
|
31
|
+
// more than the thing costs is the worst possible way to surface it.
|
|
32
|
+
return clamp(Math.round(policy.value), total);
|
|
33
|
+
case 'percentage':
|
|
34
|
+
/*
|
|
35
|
+
* Rounded half away from zero, which is what a person doing it by hand
|
|
36
|
+
* produces. `Math.round` rounds half *up*, which differs on negatives —
|
|
37
|
+
* irrelevant here because a total is never negative, and stated so that
|
|
38
|
+
* the day it is, this is the line to look at.
|
|
39
|
+
*/
|
|
40
|
+
return clamp(Math.round((total * clamp(policy.value, 100)) / 100), total);
|
|
41
|
+
default:
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** What is still owed after a deposit. Never negative, whatever was paid. */
|
|
46
|
+
const balanceAfter = (total, paid) => Math.max(0, total - paid);
|
|
47
|
+
exports.balanceAfter = balanceAfter;
|
|
48
|
+
const clamp = (value, max) => Math.min(Math.max(0, value), max);
|
|
49
|
+
const canTakeMoney = (status) => status === 'ready';
|
|
50
|
+
exports.canTakeMoney = canTakeMoney;
|
|
51
|
+
/**
|
|
52
|
+
* What to tell a business that cannot yet, in the order it becomes true.
|
|
53
|
+
*
|
|
54
|
+
* Returned as a key rather than a sentence: the words belong to whichever
|
|
55
|
+
* product is showing them, and a package that shipped English into a Romanian
|
|
56
|
+
* console would be worse than one that shipped nothing.
|
|
57
|
+
*/
|
|
58
|
+
const payoutBlockReason = (status) => {
|
|
59
|
+
switch (status) {
|
|
60
|
+
case 'none':
|
|
61
|
+
return 'not-started';
|
|
62
|
+
case 'pending':
|
|
63
|
+
return 'in-progress';
|
|
64
|
+
case 'restricted':
|
|
65
|
+
return 'needs-attention';
|
|
66
|
+
case 'ready':
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.payoutBlockReason = payoutBlockReason;
|
|
71
|
+
//# sourceMappingURL=deposits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposits.js","sourceRoot":"","sources":["../src/deposits.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAyBH,gCAuBC;AA/BD;;;;;;;GAOG;AACH,SAAgB,UAAU,CAAC,KAAa,EAAE,MAAqB;IAC7D,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAEzB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,MAAM;YACT,OAAO,CAAC,CAAC;QACX,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,OAAO;YACV,yEAAyE;YACzE,qEAAqE;YACrE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;QAChD,KAAK,YAAY;YACf;;;;;eAKG;YACH,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QAC5E;YACE,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,6EAA6E;AACtE,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;AAAlF,QAAA,YAAY,gBAAsE;AAE/F,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,GAAW,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;AAajF,MAAM,YAAY,GAAG,CAAC,MAAoB,EAAW,EAAE,CAAC,MAAM,KAAK,OAAO,CAAC;AAArE,QAAA,YAAY,gBAAyD;AAElF;;;;;;GAMG;AACI,MAAM,iBAAiB,GAAG,CAC/B,MAAoB,EACsC,EAAE;IAC5D,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,OAAO,aAAa,CAAC;QACvB,KAAK,SAAS;YACZ,OAAO,aAAa,CAAC;QACvB,KAAK,YAAY;YACf,OAAO,iBAAiB,CAAC;QAC3B,KAAK,OAAO;YACV,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC,CAAC;AAbW,QAAA,iBAAiB,qBAa5B"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taking money on a business's behalf.
|
|
3
|
+
*
|
|
4
|
+
* **We never hold anybody's funds.** The customer pays the business directly
|
|
5
|
+
* and our fee is taken on top as an application fee — a hard architectural rule
|
|
6
|
+
* rather than a preference, because holding third-party money turns a software
|
|
7
|
+
* company into a regulated payments business. Every design decision here
|
|
8
|
+
* follows from it.
|
|
9
|
+
*
|
|
10
|
+
* Not to be confused with `@birtalanrobert/billing`, which is the other
|
|
11
|
+
* direction: the business paying *us* for a subscription. Conflating the two is
|
|
12
|
+
* the mistake that makes both hard to reason about — they differ in who pays
|
|
13
|
+
* whom, in which Stripe account, and in what happens when one fails.
|
|
14
|
+
*
|
|
15
|
+
* **This entry point is pure.** What a deposit comes to and whether a business
|
|
16
|
+
* may sell yet are decided without a database or a provider, because a console
|
|
17
|
+
* shows both while somebody drags a slider. Everything needing storage or
|
|
18
|
+
* somebody else's money-moving licence is behind `/nestjs`.
|
|
19
|
+
*/
|
|
20
|
+
export { balanceAfter, canTakeMoney, depositFor, payoutBlockReason, type DepositKind, type DepositPolicy, type PayoutStatus, } from './deposits';
|
|
21
|
+
export type { ChargeRequest, ChargeResult, OnboardingLink, PaymentProvider, ProviderAccount, ProviderEvent, RefundRequest, } from './providers/port';
|
|
22
|
+
export { StripeConnect, type StripeConnectOptions } from './providers/stripe';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StripeConnect = exports.payoutBlockReason = exports.depositFor = exports.canTakeMoney = exports.balanceAfter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Taking money on a business's behalf.
|
|
6
|
+
*
|
|
7
|
+
* **We never hold anybody's funds.** The customer pays the business directly
|
|
8
|
+
* and our fee is taken on top as an application fee — a hard architectural rule
|
|
9
|
+
* rather than a preference, because holding third-party money turns a software
|
|
10
|
+
* company into a regulated payments business. Every design decision here
|
|
11
|
+
* follows from it.
|
|
12
|
+
*
|
|
13
|
+
* Not to be confused with `@birtalanrobert/billing`, which is the other
|
|
14
|
+
* direction: the business paying *us* for a subscription. Conflating the two is
|
|
15
|
+
* the mistake that makes both hard to reason about — they differ in who pays
|
|
16
|
+
* whom, in which Stripe account, and in what happens when one fails.
|
|
17
|
+
*
|
|
18
|
+
* **This entry point is pure.** What a deposit comes to and whether a business
|
|
19
|
+
* may sell yet are decided without a database or a provider, because a console
|
|
20
|
+
* shows both while somebody drags a slider. Everything needing storage or
|
|
21
|
+
* somebody else's money-moving licence is behind `/nestjs`.
|
|
22
|
+
*/
|
|
23
|
+
var deposits_1 = require("./deposits");
|
|
24
|
+
Object.defineProperty(exports, "balanceAfter", { enumerable: true, get: function () { return deposits_1.balanceAfter; } });
|
|
25
|
+
Object.defineProperty(exports, "canTakeMoney", { enumerable: true, get: function () { return deposits_1.canTakeMoney; } });
|
|
26
|
+
Object.defineProperty(exports, "depositFor", { enumerable: true, get: function () { return deposits_1.depositFor; } });
|
|
27
|
+
Object.defineProperty(exports, "payoutBlockReason", { enumerable: true, get: function () { return deposits_1.payoutBlockReason; } });
|
|
28
|
+
var stripe_1 = require("./providers/stripe");
|
|
29
|
+
Object.defineProperty(exports, "StripeConnect", { enumerable: true, get: function () { return stripe_1.StripeConnect; } });
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,uCAQoB;AAPlB,wGAAA,YAAY,OAAA;AACZ,wGAAA,YAAY,OAAA;AACZ,sGAAA,UAAU,OAAA;AACV,6GAAA,iBAAiB,OAAA;AAgBnB,6CAA8E;AAArE,uGAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* Taking money on a business's behalf.
|
|
4
|
+
*
|
|
5
|
+
* **We never hold anybody's funds.** The customer pays the business directly
|
|
6
|
+
* and our fee is taken on top — a hard architectural rule rather than a
|
|
7
|
+
* preference, because holding third-party money turns a software company into a
|
|
8
|
+
* regulated payments business. Everything here follows from that.
|
|
9
|
+
*
|
|
10
|
+
* ## Three tables, and why each is separate
|
|
11
|
+
*
|
|
12
|
+
* `mortar_payout_accounts` is the gate. Until the provider has verified who a
|
|
13
|
+
* business is, there is nowhere for a payment to land, and every product that
|
|
14
|
+
* sells on somebody's behalf has to answer that same question in front of the
|
|
15
|
+
* same button.
|
|
16
|
+
*
|
|
17
|
+
* `mortar_payments` is the record, and it **outlives the provider**: amounts,
|
|
18
|
+
* dates, what it was for and who decided are stored in full rather than as
|
|
19
|
+
* identifiers to fetch later. A business must be able to produce its own
|
|
20
|
+
* takings years after the provider account is closed or the vendor replaced.
|
|
21
|
+
*
|
|
22
|
+
* `mortar_payment_refunds` is one row per act of giving money back. Several
|
|
23
|
+
* partial refunds against one payment is ordinary, and a single column cannot
|
|
24
|
+
* say when each happened or why.
|
|
25
|
+
*
|
|
26
|
+
* ## No foreign key to whatever was paid for
|
|
27
|
+
*
|
|
28
|
+
* One product takes a deposit against an appointment, another against a seat, a
|
|
29
|
+
* third against a table's tab. A key to any one of them is exactly what would
|
|
30
|
+
* stop this table being shared, so the subject is a string the owning product
|
|
31
|
+
* writes and reads.
|
|
32
|
+
*/
|
|
33
|
+
export declare class CreateCommerce1789800000000 implements MigrationInterface {
|
|
34
|
+
name: string;
|
|
35
|
+
up(queryRunner: QueryRunner): Promise<void>;
|
|
36
|
+
down(queryRunner: QueryRunner): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=1789800000000-CreateCommerce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1789800000000-CreateCommerce.d.ts","sourceRoot":"","sources":["../../src/migrations/1789800000000-CreateCommerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,2BAA4B,YAAW,kBAAkB;IACpE,IAAI,SAAiC;IAExB,EAAE,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiH3C,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;CAK3D"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CreateCommerce1789800000000 = void 0;
|
|
4
|
+
const tenancy_1 = require("@birtalanrobert/tenancy");
|
|
5
|
+
/**
|
|
6
|
+
* Taking money on a business's behalf.
|
|
7
|
+
*
|
|
8
|
+
* **We never hold anybody's funds.** The customer pays the business directly
|
|
9
|
+
* and our fee is taken on top — a hard architectural rule rather than a
|
|
10
|
+
* preference, because holding third-party money turns a software company into a
|
|
11
|
+
* regulated payments business. Everything here follows from that.
|
|
12
|
+
*
|
|
13
|
+
* ## Three tables, and why each is separate
|
|
14
|
+
*
|
|
15
|
+
* `mortar_payout_accounts` is the gate. Until the provider has verified who a
|
|
16
|
+
* business is, there is nowhere for a payment to land, and every product that
|
|
17
|
+
* sells on somebody's behalf has to answer that same question in front of the
|
|
18
|
+
* same button.
|
|
19
|
+
*
|
|
20
|
+
* `mortar_payments` is the record, and it **outlives the provider**: amounts,
|
|
21
|
+
* dates, what it was for and who decided are stored in full rather than as
|
|
22
|
+
* identifiers to fetch later. A business must be able to produce its own
|
|
23
|
+
* takings years after the provider account is closed or the vendor replaced.
|
|
24
|
+
*
|
|
25
|
+
* `mortar_payment_refunds` is one row per act of giving money back. Several
|
|
26
|
+
* partial refunds against one payment is ordinary, and a single column cannot
|
|
27
|
+
* say when each happened or why.
|
|
28
|
+
*
|
|
29
|
+
* ## No foreign key to whatever was paid for
|
|
30
|
+
*
|
|
31
|
+
* One product takes a deposit against an appointment, another against a seat, a
|
|
32
|
+
* third against a table's tab. A key to any one of them is exactly what would
|
|
33
|
+
* stop this table being shared, so the subject is a string the owning product
|
|
34
|
+
* writes and reads.
|
|
35
|
+
*/
|
|
36
|
+
class CreateCommerce1789800000000 {
|
|
37
|
+
name = 'CreateCommerce1789800000000';
|
|
38
|
+
async up(queryRunner) {
|
|
39
|
+
await queryRunner.query(`
|
|
40
|
+
CREATE TABLE "mortar_payout_accounts" (
|
|
41
|
+
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
42
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
43
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
44
|
+
"tenant_id" uuid NOT NULL,
|
|
45
|
+
"provider" varchar(32) NOT NULL DEFAULT 'stripe',
|
|
46
|
+
"external_id" varchar(128) NOT NULL,
|
|
47
|
+
"status" varchar(16) NOT NULL DEFAULT 'pending',
|
|
48
|
+
-- What the provider still wants, in its own words. "A photograph of the
|
|
49
|
+
-- director's identity document" is actionable; "restricted" is a
|
|
50
|
+
-- support conversation.
|
|
51
|
+
"requirements" jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
52
|
+
"ready_at" timestamptz,
|
|
53
|
+
CONSTRAINT "pk_payout_accounts" PRIMARY KEY ("id"),
|
|
54
|
+
CONSTRAINT "uq_payout_accounts_tenant" UNIQUE ("tenant_id", "provider"),
|
|
55
|
+
CONSTRAINT "ck_payout_accounts_status"
|
|
56
|
+
CHECK ("status" IN ('none', 'pending', 'restricted', 'ready'))
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
/* A webhook arrives naming the provider's account, not ours. */
|
|
60
|
+
await queryRunner.query(`
|
|
61
|
+
CREATE INDEX "ix_payout_accounts_external"
|
|
62
|
+
ON "mortar_payout_accounts" ("provider", "external_id")
|
|
63
|
+
`);
|
|
64
|
+
await queryRunner.query(`
|
|
65
|
+
CREATE TABLE "mortar_payments" (
|
|
66
|
+
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
67
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
68
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
69
|
+
"tenant_id" uuid NOT NULL,
|
|
70
|
+
-- What it was for, as the owning product names it. No foreign key: see
|
|
71
|
+
-- the note above.
|
|
72
|
+
"subject" varchar(160) NOT NULL,
|
|
73
|
+
-- 'card' is the only one this package processes. The rest are recorded
|
|
74
|
+
-- rather than taken, and recording them is not a lesser feature: a
|
|
75
|
+
-- report that counts only what a provider processed tells a salon a
|
|
76
|
+
-- fraction of its own takings and looks complete.
|
|
77
|
+
"method" varchar(16) NOT NULL,
|
|
78
|
+
"state" varchar(24) NOT NULL DEFAULT 'pending',
|
|
79
|
+
"amount" bigint NOT NULL,
|
|
80
|
+
"currency" varchar(3) NOT NULL,
|
|
81
|
+
-- Recorded even when zero: "charged nothing" and "nobody wrote down
|
|
82
|
+
-- what was charged" are different facts.
|
|
83
|
+
"application_fee" bigint NOT NULL DEFAULT 0,
|
|
84
|
+
"refunded" bigint NOT NULL DEFAULT 0,
|
|
85
|
+
"provider" varchar(32),
|
|
86
|
+
"external_id" varchar(128),
|
|
87
|
+
-- "Visa ending 4242". Never the number, never anything chargeable from
|
|
88
|
+
-- a database dump.
|
|
89
|
+
"instrument" varchar(40),
|
|
90
|
+
"taken_at" timestamptz,
|
|
91
|
+
"detail" varchar(400),
|
|
92
|
+
"recorded_by" uuid,
|
|
93
|
+
CONSTRAINT "pk_payments" PRIMARY KEY ("id"),
|
|
94
|
+
CONSTRAINT "uq_payments_tenant_id" UNIQUE ("tenant_id", "id"),
|
|
95
|
+
CONSTRAINT "ck_payments_method"
|
|
96
|
+
CHECK ("method" IN ('card', 'cash', 'terminal', 'voucher', 'transfer')),
|
|
97
|
+
CONSTRAINT "ck_payments_state"
|
|
98
|
+
CHECK ("state" IN ('pending', 'authorized', 'captured', 'failed',
|
|
99
|
+
'refunded', 'partially_refunded', 'cancelled')),
|
|
100
|
+
-- Money is never negative here, and nothing may be given back twice.
|
|
101
|
+
CONSTRAINT "ck_payments_amount" CHECK ("amount" >= 0),
|
|
102
|
+
CONSTRAINT "ck_payments_refunded"
|
|
103
|
+
CHECK ("refunded" >= 0 AND "refunded" <= "amount")
|
|
104
|
+
)
|
|
105
|
+
`);
|
|
106
|
+
await queryRunner.query(`
|
|
107
|
+
CREATE INDEX "ix_payments_subject" ON "mortar_payments" ("tenant_id", "subject")
|
|
108
|
+
`);
|
|
109
|
+
/* Every revenue report reads this: a tenant's takings over a period. */
|
|
110
|
+
await queryRunner.query(`
|
|
111
|
+
CREATE INDEX "ix_payments_taken" ON "mortar_payments" ("tenant_id", "taken_at")
|
|
112
|
+
`);
|
|
113
|
+
/* And a webhook arrives naming the provider's payment. */
|
|
114
|
+
await queryRunner.query(`
|
|
115
|
+
CREATE INDEX "ix_payments_external" ON "mortar_payments" ("provider", "external_id")
|
|
116
|
+
`);
|
|
117
|
+
await queryRunner.query(`
|
|
118
|
+
CREATE TABLE "mortar_payment_refunds" (
|
|
119
|
+
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
120
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
121
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
122
|
+
"tenant_id" uuid NOT NULL,
|
|
123
|
+
"payment_id" uuid NOT NULL,
|
|
124
|
+
"amount" bigint NOT NULL,
|
|
125
|
+
-- Required. "We refunded her ninety lei in March" is a question
|
|
126
|
+
-- somebody asks a year later, and a blank reason cannot be defended.
|
|
127
|
+
"reason" varchar(400) NOT NULL,
|
|
128
|
+
"external_id" varchar(128),
|
|
129
|
+
"refunded_by" uuid,
|
|
130
|
+
CONSTRAINT "pk_payment_refunds" PRIMARY KEY ("id"),
|
|
131
|
+
CONSTRAINT "fk_payment_refunds_payment" FOREIGN KEY ("tenant_id", "payment_id")
|
|
132
|
+
REFERENCES "mortar_payments" ("tenant_id", "id") ON DELETE RESTRICT,
|
|
133
|
+
CONSTRAINT "ck_payment_refunds_amount" CHECK ("amount" > 0)
|
|
134
|
+
)
|
|
135
|
+
`);
|
|
136
|
+
await queryRunner.query(`
|
|
137
|
+
CREATE INDEX "ix_payment_refunds_payment"
|
|
138
|
+
ON "mortar_payment_refunds" ("tenant_id", "payment_id")
|
|
139
|
+
`);
|
|
140
|
+
for (const table of ['mortar_payout_accounts', 'mortar_payments', 'mortar_payment_refunds']) {
|
|
141
|
+
for (const statement of (0, tenancy_1.enableRlsSql)(table))
|
|
142
|
+
await queryRunner.query(statement);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async down(queryRunner) {
|
|
146
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "mortar_payment_refunds" CASCADE`);
|
|
147
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "mortar_payments" CASCADE`);
|
|
148
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "mortar_payout_accounts" CASCADE`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.CreateCommerce1789800000000 = CreateCommerce1789800000000;
|
|
152
|
+
//# sourceMappingURL=1789800000000-CreateCommerce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1789800000000-CreateCommerce.js","sourceRoot":"","sources":["../../src/migrations/1789800000000-CreateCommerce.ts"],"names":[],"mappings":";;;AACA,qDAAuD;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAa,2BAA2B;IACtC,IAAI,GAAG,6BAA6B,CAAC;IAE9B,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,WAAW,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;KAmBvB,CAAC,CAAC;QAEH,gEAAgE;QAChE,MAAM,WAAW,CAAC,KAAK,CAAC;;;KAGvB,CAAC,CAAC;QAEH,MAAM,WAAW,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyCvB,CAAC,CAAC;QAEH,MAAM,WAAW,CAAC,KAAK,CAAC;;KAEvB,CAAC,CAAC;QACH,wEAAwE;QACxE,MAAM,WAAW,CAAC,KAAK,CAAC;;KAEvB,CAAC,CAAC;QACH,0DAA0D;QAC1D,MAAM,WAAW,CAAC,KAAK,CAAC;;KAEvB,CAAC,CAAC;QAEH,MAAM,WAAW,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;KAkBvB,CAAC,CAAC;QAEH,MAAM,WAAW,CAAC,KAAK,CAAC;;;KAGvB,CAAC,CAAC;QAEH,KAAK,MAAM,KAAK,IAAI,CAAC,wBAAwB,EAAE,iBAAiB,EAAE,wBAAwB,CAAC,EAAE,CAAC;YAC5F,KAAK,MAAM,SAAS,IAAI,IAAA,sBAAY,EAAC,KAAK,CAAC;gBAAE,MAAM,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,WAAW,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACjF,MAAM,WAAW,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAC1E,MAAM,WAAW,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;IACnF,CAAC;CACF;AAzHD,kEAyHC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { DataSource } from 'typeorm';
|
|
2
|
+
import { type PayoutStatus } from '../deposits';
|
|
3
|
+
import type { PaymentProvider, ProviderEvent } from '../providers/port';
|
|
4
|
+
import { PayoutAccount } from './payout-account.entity';
|
|
5
|
+
import { Payment, PaymentRefund, type PaymentMethod } from './payment.entity';
|
|
6
|
+
/** The provider this deployment uses, injected so tests can supply their own. */
|
|
7
|
+
export declare const COMMERCE_PROVIDER: unique symbol;
|
|
8
|
+
export interface TakePayment {
|
|
9
|
+
readonly subject: string;
|
|
10
|
+
readonly amount: number;
|
|
11
|
+
readonly currency: string;
|
|
12
|
+
readonly applicationFee?: number;
|
|
13
|
+
readonly description?: string;
|
|
14
|
+
/** False holds the money instead of taking it. See `capture`. */
|
|
15
|
+
readonly capture?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Makes the charge idempotent across retries of the same act.
|
|
18
|
+
*
|
|
19
|
+
* A person pressing a button twice on a slow connection is the ordinary case,
|
|
20
|
+
* not an exotic one, and the second press must not produce a second charge.
|
|
21
|
+
*/
|
|
22
|
+
readonly reference: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RecordPayment {
|
|
25
|
+
readonly subject: string;
|
|
26
|
+
readonly amount: number;
|
|
27
|
+
readonly currency: string;
|
|
28
|
+
readonly method: Exclude<PaymentMethod, 'card'>;
|
|
29
|
+
readonly detail?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Money between a customer and a business, and the record of it.
|
|
33
|
+
*
|
|
34
|
+
* **We never hold their funds** — the customer pays the business directly and
|
|
35
|
+
* our fee is taken on top — so almost everything here is about the *record*
|
|
36
|
+
* rather than the movement. That record has to be complete enough to produce a
|
|
37
|
+
* business's own takings years later, after the provider account is closed.
|
|
38
|
+
*
|
|
39
|
+
* Every method runs inside the tenant's policy, reads included: these tables are
|
|
40
|
+
* under row-level security, and an unbound read returns *nothing* rather than
|
|
41
|
+
* failing — which here means a revenue report of zero for a business that took
|
|
42
|
+
* money all month.
|
|
43
|
+
*/
|
|
44
|
+
export declare class CommerceService {
|
|
45
|
+
private readonly dataSource;
|
|
46
|
+
private readonly provider;
|
|
47
|
+
constructor(dataSource: DataSource, provider: PaymentProvider);
|
|
48
|
+
/** Where a business's money goes, and whether the provider will send it yet. */
|
|
49
|
+
payoutAccount(tenantId: string): Promise<PayoutAccount | null>;
|
|
50
|
+
payoutStatus(tenantId: string): Promise<PayoutStatus>;
|
|
51
|
+
/**
|
|
52
|
+
* Starts or resumes onboarding, creating the provider's account if needed.
|
|
53
|
+
*
|
|
54
|
+
* Called again as often as somebody presses the button: onboarding is a form
|
|
55
|
+
* people abandon and come back to, and the link is short-lived, so "resume"
|
|
56
|
+
* is the common case rather than the exception.
|
|
57
|
+
*/
|
|
58
|
+
startOnboarding(tenantId: string, options: {
|
|
59
|
+
country: string;
|
|
60
|
+
email?: string;
|
|
61
|
+
returnUrl: string;
|
|
62
|
+
refreshUrl: string;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
url: string;
|
|
65
|
+
expiresAt: Date;
|
|
66
|
+
}>;
|
|
67
|
+
/** Asks the provider where onboarding stands, and records the answer. */
|
|
68
|
+
refreshPayoutAccount(tenantId: string): Promise<PayoutStatus>;
|
|
69
|
+
/**
|
|
70
|
+
* Charges a card, or holds one.
|
|
71
|
+
*
|
|
72
|
+
* Refused before the provider is called if the business cannot be paid out:
|
|
73
|
+
* a charge that succeeds into an account with no destination leaves the
|
|
74
|
+
* customer debited and the money nowhere anybody can see it, and the first
|
|
75
|
+
* the business hears is asking where it went.
|
|
76
|
+
*/
|
|
77
|
+
take(tenantId: string, input: TakePayment): Promise<Payment>;
|
|
78
|
+
/**
|
|
79
|
+
* Writes down money that arrived some other way.
|
|
80
|
+
*
|
|
81
|
+
* Cash at the counter, a card terminal, a meal voucher, a bank transfer.
|
|
82
|
+
* **Recording is not a lesser feature**: a salon is mostly cash, a restaurant
|
|
83
|
+
* takes vouchers, a box office takes notes — and a report that counted only
|
|
84
|
+
* what a provider processed would tell a business a fraction of its own
|
|
85
|
+
* takings while looking complete.
|
|
86
|
+
*/
|
|
87
|
+
record(tenantId: string, input: RecordPayment): Promise<Payment>;
|
|
88
|
+
/**
|
|
89
|
+
* Takes money that was only held.
|
|
90
|
+
*
|
|
91
|
+
* **The human decision has been made by the time this is called.** Charging a
|
|
92
|
+
* no-show fee automatically is how a business loses that customer
|
|
93
|
+
* permanently, so this package offers the mechanism and never the trigger.
|
|
94
|
+
*/
|
|
95
|
+
capture(tenantId: string, paymentId: string, amount?: number): Promise<Payment>;
|
|
96
|
+
/** Lets a held card go without taking anything. */
|
|
97
|
+
release(tenantId: string, paymentId: string): Promise<Payment>;
|
|
98
|
+
/**
|
|
99
|
+
* Refunds some or all of a payment, with a reason.
|
|
100
|
+
*
|
|
101
|
+
* The reason is required because "we refunded her ninety lei in March" is a
|
|
102
|
+
* question somebody asks a year later, and a blank answer cannot be defended.
|
|
103
|
+
*/
|
|
104
|
+
refund(tenantId: string, paymentId: string, amount: number, reason: string): Promise<Payment>;
|
|
105
|
+
/** Everything taken for one thing: a booking, an order, a tab. */
|
|
106
|
+
forSubject(tenantId: string, subject: string): Promise<Payment[]>;
|
|
107
|
+
/** What a payment has had given back, and why. */
|
|
108
|
+
refundsFor(tenantId: string, paymentId: string): Promise<PaymentRefund[]>;
|
|
109
|
+
/**
|
|
110
|
+
* Records what a webhook said, whatever it was about.
|
|
111
|
+
*
|
|
112
|
+
* A payment the deployment has no record of is **ignored rather than
|
|
113
|
+
* inserted**: it belongs to another environment sharing the provider account,
|
|
114
|
+
* and inventing a row for it would put another system's money in this one's
|
|
115
|
+
* books.
|
|
116
|
+
*/
|
|
117
|
+
settle(event: ProviderEvent): Promise<Payment | PayoutAccount | undefined>;
|
|
118
|
+
private find;
|
|
119
|
+
private update;
|
|
120
|
+
private saveAccount;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=commerce.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commerce.service.d.ts","sourceRoot":"","sources":["../../src/nestjs/commerce.service.ts"],"names":[],"mappings":"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"}
|