@gravito/satellite-payment 0.1.4 → 0.2.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @gravito/satellite-payment
2
2
 
3
+ ## 0.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @gravito/core@1.6.1
9
+ - @gravito/enterprise@1.0.4
10
+ - @gravito/stasis@3.1.1
11
+
3
12
  ## 0.1.4
4
13
 
5
14
  ### Patch Changes
package/dist/index.js CHANGED
@@ -63,12 +63,17 @@ var ProcessPayment = class extends UseCase {
63
63
  this.manager = manager;
64
64
  }
65
65
  async execute(input) {
66
- const transaction = Transaction.create(crypto.randomUUID(), {
66
+ const id = crypto.randomUUID();
67
+ const transaction = Transaction.create(id, {
67
68
  orderId: input.orderId,
68
69
  amount: input.amount,
69
70
  currency: input.currency,
70
71
  gateway: input.gateway || "default"
71
72
  });
73
+ if (input.metadata) {
74
+ ;
75
+ transaction.props.metadata = { ...input.metadata };
76
+ }
72
77
  const gateway = this.manager.driver(input.gateway);
73
78
  const intent = await gateway.createIntent(transaction);
74
79
  transaction.authorize(intent.gatewayTransactionId);
@@ -108,7 +113,9 @@ var StripeGateway = class {
108
113
  currency: rawProps.currency.toLowerCase(),
109
114
  metadata: {
110
115
  orderId: transaction.orderId,
111
- transactionId: transaction.id
116
+ transactionId: transaction.id,
117
+ // 包含自定義 metadata(如 lockId)
118
+ ...rawProps.metadata
112
119
  },
113
120
  automatic_payment_methods: {
114
121
  enabled: true
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@gravito/satellite-payment",
3
- "version": "0.1.4",
3
+ "sideEffects": false,
4
+ "version": "0.2.0",
4
5
  "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "module": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
8
9
  "scripts": {
9
- "build": "tsup src/index.ts --format esm --dts --clean --external @gravito/atlas --external @gravito/enterprise --external @gravito/stasis --external @gravito/core --external stripe",
10
+ "build": "tsup src/index.ts --format esm --clean --external @gravito/atlas --external @gravito/enterprise --external @gravito/stasis --external @gravito/core --external stripe",
11
+ "build:dts": "tsup src/index.ts --format esm --dts --clean --external @gravito/atlas --external @gravito/enterprise --external @gravito/stasis --external @gravito/core --external stripe",
10
12
  "test": "bun test",
11
- "typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck"
13
+ "typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
14
+ "test:unit": "bun test",
15
+ "test:integration": "bun test"
12
16
  },
13
17
  "dependencies": {
14
18
  "@gravito/atlas": "workspace:*",
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@gravito/satellite-payment",
3
+ "version": "0.1.4",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup src/index.ts --format esm --dts --clean --external @gravito/atlas --external @gravito/enterprise --external @gravito/stasis --external @gravito/core --external stripe",
10
+ "test": "bun test",
11
+ "typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
12
+ "test:unit": "bun test",
13
+ "test:integration": "bun test"
14
+ },
15
+ "dependencies": {
16
+ "@gravito/atlas": "workspace:*",
17
+ "@gravito/enterprise": "workspace:*",
18
+ "@gravito/stasis": "workspace:*",
19
+ "@gravito/core": "workspace:*",
20
+ "stripe": "^20.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.9.3"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/gravito-framework/gravito.git",
32
+ "directory": "satellites/payment"
33
+ }
34
+ }
@@ -8,6 +8,7 @@ export interface ProcessPaymentInput {
8
8
  amount: number
9
9
  currency: string
10
10
  gateway?: string // 現在可以動態指定
11
+ metadata?: Record<string, string> // 自定義 metadata(如 lockId)
11
12
  }
12
13
 
13
14
  export class ProcessPayment extends UseCase<ProcessPaymentInput, PaymentIntent> {
@@ -16,13 +17,19 @@ export class ProcessPayment extends UseCase<ProcessPaymentInput, PaymentIntent>
16
17
  }
17
18
 
18
19
  async execute(input: ProcessPaymentInput): Promise<PaymentIntent> {
19
- const transaction = Transaction.create(crypto.randomUUID(), {
20
+ const id = crypto.randomUUID()
21
+ const transaction = Transaction.create(id, {
20
22
  orderId: input.orderId,
21
23
  amount: input.amount,
22
24
  currency: input.currency,
23
25
  gateway: input.gateway || 'default',
24
26
  })
25
27
 
28
+ // 設置自定義 metadata(如果提供)
29
+ if (input.metadata) {
30
+ ;(transaction as any).props.metadata = { ...input.metadata }
31
+ }
32
+
26
33
  // 從 Manager 取得指定的金流驅動器
27
34
  const gateway = this.manager.driver(input.gateway)
28
35
  const intent = await gateway.createIntent(transaction)
@@ -7,7 +7,7 @@ export class StripeGateway implements IPaymentGateway {
7
7
 
8
8
  constructor(apiKey: string) {
9
9
  this.stripe = new Stripe(apiKey, {
10
- apiVersion: '2025-01-27' as any,
10
+ apiVersion: '2025-01-27' as never,
11
11
  })
12
12
  }
13
13
 
@@ -16,8 +16,10 @@ export class StripeGateway implements IPaymentGateway {
16
16
  }
17
17
 
18
18
  async createIntent(transaction: Transaction): Promise<PaymentIntent> {
19
- // 透過 any 訪問私有屬性以解決跨包訪問問題
20
- const rawProps = (transaction as any).props
19
+ // 透過類型斷言訪問私有屬性以解決跨包訪問問題
20
+ const rawProps = (
21
+ transaction as unknown as { props: { currency: string; metadata?: Record<string, unknown> } }
22
+ ).props
21
23
 
22
24
  const intent = await this.stripe.paymentIntents.create({
23
25
  amount: Math.round(transaction.amount * 100),
@@ -25,6 +27,8 @@ export class StripeGateway implements IPaymentGateway {
25
27
  metadata: {
26
28
  orderId: transaction.orderId,
27
29
  transactionId: transaction.id,
30
+ // 包含自定義 metadata(如 lockId)
31
+ ...rawProps.metadata,
28
32
  },
29
33
  automatic_payment_methods: {
30
34
  enabled: true,
package/src/manifest.json CHANGED
@@ -3,13 +3,7 @@
3
3
  "id": "payment",
4
4
  "version": "0.1.0",
5
5
  "description": "A Gravito Satellite",
6
- "capabilities": [
7
- "create-payment"
8
- ],
9
- "requirements": [
10
- "cache"
11
- ],
12
- "hooks": [
13
- "payment:created"
14
- ]
15
- }
6
+ "capabilities": ["create-payment"],
7
+ "requirements": ["cache"],
8
+ "hooks": ["payment:created"]
9
+ }
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { Transaction, TransactionStatus } from '../src/Domain/Entities/Transaction'
3
+
4
+ describe('Payment Domain - Transaction Entity', () => {
5
+ describe('Transaction Creation & Initial State', () => {
6
+ it('應該建立初始狀態為 PENDING 的交易', () => {
7
+ const tx = Transaction.create('tx-1', {
8
+ orderId: 'ord-123',
9
+ amount: 1000,
10
+ currency: 'TWD',
11
+ gateway: 'stripe',
12
+ })
13
+
14
+ expect(tx.id).toBe('tx-1')
15
+ expect(tx.orderId).toBe('ord-123')
16
+ expect(tx.amount).toBe(1000)
17
+ expect(tx.status).toBe(TransactionStatus.PENDING)
18
+ expect(tx.gateway).toBe('stripe')
19
+ })
20
+ })
21
+
22
+ describe('Transaction Status Transitions', () => {
23
+ it('PENDING → AUTHORIZED: authorize() 應轉移狀態並設置 gatewayId', () => {
24
+ const tx = Transaction.create('tx-1', {
25
+ orderId: 'ord-123',
26
+ amount: 1000,
27
+ currency: 'TWD',
28
+ gateway: 'stripe',
29
+ })
30
+
31
+ tx.authorize('stripe_charge_xyz')
32
+ expect(tx.status).toBe(TransactionStatus.AUTHORIZED)
33
+ })
34
+
35
+ it('AUTHORIZED → CAPTURED: capture() 應轉移狀態', () => {
36
+ const tx = Transaction.create('tx-1', {
37
+ orderId: 'ord-123',
38
+ amount: 1000,
39
+ currency: 'TWD',
40
+ gateway: 'stripe',
41
+ })
42
+
43
+ tx.authorize('stripe_charge_xyz')
44
+ tx.capture()
45
+ expect(tx.status).toBe(TransactionStatus.CAPTURED)
46
+ })
47
+
48
+ it('CAPTURED → REFUNDED: refund() 應轉移狀態', () => {
49
+ const tx = Transaction.create('tx-1', {
50
+ orderId: 'ord-123',
51
+ amount: 1000,
52
+ currency: 'TWD',
53
+ gateway: 'stripe',
54
+ })
55
+
56
+ tx.authorize('stripe_charge_xyz')
57
+ tx.capture()
58
+ tx.refund()
59
+ expect(tx.status).toBe(TransactionStatus.REFUNDED)
60
+ })
61
+
62
+ it('非 PENDING 時,authorize() 應拋出錯誤', () => {
63
+ const tx = Transaction.create('tx-1', {
64
+ orderId: 'ord-123',
65
+ amount: 1000,
66
+ currency: 'TWD',
67
+ gateway: 'stripe',
68
+ })
69
+
70
+ tx.authorize('stripe_charge_xyz')
71
+
72
+ expect(() => tx.authorize('another_id')).toThrow(
73
+ 'Only pending transactions can be authorized'
74
+ )
75
+ })
76
+
77
+ it('非 AUTHORIZED 時,capture() 應拋出錯誤', () => {
78
+ const tx = Transaction.create('tx-1', {
79
+ orderId: 'ord-123',
80
+ amount: 1000,
81
+ currency: 'TWD',
82
+ gateway: 'stripe',
83
+ })
84
+
85
+ expect(() => tx.capture()).toThrow('Only authorized transactions can be captured')
86
+ })
87
+
88
+ it('非 CAPTURED 時,refund() 應拋出錯誤', () => {
89
+ const tx = Transaction.create('tx-1', {
90
+ orderId: 'ord-123',
91
+ amount: 1000,
92
+ currency: 'TWD',
93
+ gateway: 'stripe',
94
+ })
95
+
96
+ tx.authorize('stripe_charge_xyz')
97
+
98
+ expect(() => tx.refund()).toThrow('Only captured transactions can be refunded')
99
+ })
100
+
101
+ it('fail() 應將狀態設為 FAILED 並儲存失敗原因', () => {
102
+ const tx = Transaction.create('tx-1', {
103
+ orderId: 'ord-123',
104
+ amount: 1000,
105
+ currency: 'TWD',
106
+ gateway: 'stripe',
107
+ })
108
+
109
+ tx.fail('Card declined')
110
+ expect(tx.status).toBe(TransactionStatus.FAILED)
111
+ })
112
+ })
113
+
114
+ describe('Multiple Transaction Types', () => {
115
+ it('應該支援不同的支付閘道', () => {
116
+ const stripeTx = Transaction.create('tx-1', {
117
+ orderId: 'ord-1',
118
+ amount: 1000,
119
+ currency: 'TWD',
120
+ gateway: 'stripe',
121
+ })
122
+
123
+ const paypalTx = Transaction.create('tx-2', {
124
+ orderId: 'ord-2',
125
+ amount: 2000,
126
+ currency: 'TWD',
127
+ gateway: 'paypal',
128
+ })
129
+
130
+ expect(stripeTx.gateway).toBe('stripe')
131
+ expect(paypalTx.gateway).toBe('paypal')
132
+ })
133
+ })
134
+ })
@@ -0,0 +1,381 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { ProcessPayment } from '../src/Application/UseCases/ProcessPayment'
3
+ import { RefundPayment } from '../src/Application/UseCases/RefundPayment'
4
+ import type { IPaymentGateway, PaymentIntent } from '../src/Domain/Contracts/IPaymentGateway'
5
+ import { Transaction, TransactionStatus } from '../src/Domain/Entities/Transaction'
6
+
7
+ /**
8
+ * Mock PaymentGateway
9
+ */
10
+ class MockPaymentGateway implements IPaymentGateway {
11
+ private intents: Map<string, PaymentIntent> = new Map()
12
+
13
+ getName(): string {
14
+ return 'mock'
15
+ }
16
+
17
+ async createIntent(transaction: Transaction): Promise<PaymentIntent> {
18
+ const intent: PaymentIntent = {
19
+ gatewayTransactionId: `mock_${transaction.id}`,
20
+ clientSecret: `secret_${transaction.id}`,
21
+ status: 'requires_action',
22
+ }
23
+ this.intents.set(intent.gatewayTransactionId, intent)
24
+ return intent
25
+ }
26
+
27
+ async capture(gatewayTransactionId: string): Promise<boolean> {
28
+ const intent = this.intents.get(gatewayTransactionId)
29
+ if (!intent) {
30
+ return false
31
+ }
32
+ intent.status = 'succeeded'
33
+ return true
34
+ }
35
+
36
+ async refund(gatewayTransactionId: string, amount?: number): Promise<boolean> {
37
+ const intent = this.intents.get(gatewayTransactionId)
38
+ if (!intent) {
39
+ return false
40
+ }
41
+ intent.status = 'refunded'
42
+ return true
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Mock PaymentManager
48
+ */
49
+ class MockPaymentManager {
50
+ private gateways: Map<string, IPaymentGateway> = new Map()
51
+
52
+ constructor() {
53
+ this.gateways.set('mock', new MockPaymentGateway())
54
+ this.gateways.set('stripe', new MockPaymentGateway())
55
+ }
56
+
57
+ driver(name?: string): IPaymentGateway {
58
+ const driverName = name || 'stripe'
59
+ const gateway = this.gateways.get(driverName)
60
+ if (!gateway) {
61
+ throw new Error(`Payment driver [${driverName}] is not registered`)
62
+ }
63
+ return gateway
64
+ }
65
+
66
+ getGateway(name: string): MockPaymentGateway {
67
+ return this.gateways.get(name) as MockPaymentGateway
68
+ }
69
+ }
70
+
71
+ describe('Payment UseCases', () => {
72
+ describe('ProcessPayment', () => {
73
+ it('應該能成功建立支付意向', async () => {
74
+ const manager = new MockPaymentManager()
75
+ const useCase = new ProcessPayment(manager as any)
76
+
77
+ const result = await useCase.execute({
78
+ orderId: 'ord-123',
79
+ amount: 1000,
80
+ currency: 'TWD',
81
+ gateway: 'stripe',
82
+ })
83
+
84
+ expect(result.gatewayTransactionId).toBeDefined()
85
+ expect(result.clientSecret).toBeDefined()
86
+ expect(result.status).toBe('requires_action')
87
+ })
88
+
89
+ it('應該能設置自定義 metadata', async () => {
90
+ const manager = new MockPaymentManager()
91
+ const useCase = new ProcessPayment(manager as any)
92
+
93
+ const result = await useCase.execute({
94
+ orderId: 'ord-123',
95
+ amount: 1000,
96
+ currency: 'TWD',
97
+ gateway: 'stripe',
98
+ metadata: {
99
+ lockId: 'lock-xyz',
100
+ source: 'web',
101
+ },
102
+ })
103
+
104
+ expect(result.gatewayTransactionId).toBeDefined()
105
+ })
106
+
107
+ it('應該使用預設驅動器當未指定時', async () => {
108
+ const manager = new MockPaymentManager()
109
+ const useCase = new ProcessPayment(manager as any)
110
+
111
+ const result = await useCase.execute({
112
+ orderId: 'ord-123',
113
+ amount: 1000,
114
+ currency: 'TWD',
115
+ })
116
+
117
+ expect(result.gatewayTransactionId).toBeDefined()
118
+ })
119
+
120
+ it('應該在不同幣別間工作', async () => {
121
+ const manager = new MockPaymentManager()
122
+ const useCase = new ProcessPayment(manager as any)
123
+
124
+ const twdResult = await useCase.execute({
125
+ orderId: 'ord-1',
126
+ amount: 1000,
127
+ currency: 'TWD',
128
+ })
129
+
130
+ const usdResult = await useCase.execute({
131
+ orderId: 'ord-2',
132
+ amount: 30,
133
+ currency: 'USD',
134
+ })
135
+
136
+ expect(twdResult.gatewayTransactionId).toBeDefined()
137
+ expect(usdResult.gatewayTransactionId).toBeDefined()
138
+ })
139
+
140
+ it('應該支援多個支付閘道', async () => {
141
+ const manager = new MockPaymentManager()
142
+ const useCase = new ProcessPayment(manager as any)
143
+
144
+ const stripeResult = await useCase.execute({
145
+ orderId: 'ord-1',
146
+ amount: 1000,
147
+ currency: 'TWD',
148
+ gateway: 'stripe',
149
+ })
150
+
151
+ const mockResult = await useCase.execute({
152
+ orderId: 'ord-2',
153
+ amount: 2000,
154
+ currency: 'TWD',
155
+ gateway: 'mock',
156
+ })
157
+
158
+ expect(stripeResult.gatewayTransactionId).toBeDefined()
159
+ expect(mockResult.gatewayTransactionId).toBeDefined()
160
+ })
161
+
162
+ it('當驅動器不存在時應拋出錯誤', async () => {
163
+ const manager = new MockPaymentManager()
164
+ const useCase = new ProcessPayment(manager as any)
165
+
166
+ try {
167
+ await useCase.execute({
168
+ orderId: 'ord-123',
169
+ amount: 1000,
170
+ currency: 'TWD',
171
+ gateway: 'nonexistent',
172
+ })
173
+ expect.unreachable('應該拋出錯誤')
174
+ } catch (error: any) {
175
+ expect(error.message).toContain('not registered')
176
+ }
177
+ })
178
+ })
179
+
180
+ describe('RefundPayment', () => {
181
+ it('應該能成功退款', async () => {
182
+ const gateway = new MockPaymentGateway()
183
+
184
+ // 先建立一筆交易
185
+ const tx = Transaction.create('tx-1', {
186
+ orderId: 'ord-123',
187
+ amount: 1000,
188
+ currency: 'TWD',
189
+ gateway: 'mock',
190
+ })
191
+
192
+ const intent = await gateway.createIntent(tx)
193
+
194
+ // 執行退款
195
+ const useCase = new RefundPayment(gateway)
196
+ const result = await useCase.execute({
197
+ gatewayTransactionId: intent.gatewayTransactionId,
198
+ })
199
+
200
+ expect(result).toBe(true)
201
+ })
202
+
203
+ it('應該能支援部分退款', async () => {
204
+ const gateway = new MockPaymentGateway()
205
+
206
+ const tx = Transaction.create('tx-1', {
207
+ orderId: 'ord-123',
208
+ amount: 1000,
209
+ currency: 'TWD',
210
+ gateway: 'mock',
211
+ })
212
+
213
+ const intent = await gateway.createIntent(tx)
214
+
215
+ const useCase = new RefundPayment(gateway)
216
+ const result = await useCase.execute({
217
+ gatewayTransactionId: intent.gatewayTransactionId,
218
+ amount: 500,
219
+ })
220
+
221
+ expect(result).toBe(true)
222
+ })
223
+
224
+ it('當交易不存在時應返回 false', async () => {
225
+ const gateway = new MockPaymentGateway()
226
+ const useCase = new RefundPayment(gateway)
227
+
228
+ const result = await useCase.execute({
229
+ gatewayTransactionId: 'nonexistent_tx',
230
+ })
231
+
232
+ expect(result).toBe(false)
233
+ })
234
+
235
+ it('應該能連續退款多筆交易', async () => {
236
+ const gateway = new MockPaymentGateway()
237
+
238
+ // 建立多筆交易
239
+ const tx1 = Transaction.create('tx-1', {
240
+ orderId: 'ord-1',
241
+ amount: 1000,
242
+ currency: 'TWD',
243
+ gateway: 'mock',
244
+ })
245
+ const tx2 = Transaction.create('tx-2', {
246
+ orderId: 'ord-2',
247
+ amount: 2000,
248
+ currency: 'TWD',
249
+ gateway: 'mock',
250
+ })
251
+
252
+ const intent1 = await gateway.createIntent(tx1)
253
+ const intent2 = await gateway.createIntent(tx2)
254
+
255
+ const useCase = new RefundPayment(gateway)
256
+
257
+ const result1 = await useCase.execute({
258
+ gatewayTransactionId: intent1.gatewayTransactionId,
259
+ })
260
+ const result2 = await useCase.execute({
261
+ gatewayTransactionId: intent2.gatewayTransactionId,
262
+ })
263
+
264
+ expect(result1).toBe(true)
265
+ expect(result2).toBe(true)
266
+ })
267
+ })
268
+
269
+ describe('Transaction State Machine with UseCases', () => {
270
+ it('應該能從 PENDING 經過 AUTHORIZED 到 CAPTURED', async () => {
271
+ const gateway = new MockPaymentGateway()
272
+
273
+ // 1. 建立交易(ProcessPayment)
274
+ const tx = Transaction.create('tx-1', {
275
+ orderId: 'ord-123',
276
+ amount: 1000,
277
+ currency: 'TWD',
278
+ gateway: 'mock',
279
+ })
280
+
281
+ expect(tx.status).toBe(TransactionStatus.PENDING)
282
+
283
+ // 2. 建立支付意向
284
+ const intent = await gateway.createIntent(tx)
285
+ tx.authorize(intent.gatewayTransactionId)
286
+
287
+ expect(tx.status).toBe(TransactionStatus.AUTHORIZED)
288
+
289
+ // 3. 清算
290
+ await gateway.capture(intent.gatewayTransactionId)
291
+ tx.capture()
292
+
293
+ expect(tx.status).toBe(TransactionStatus.CAPTURED)
294
+ })
295
+
296
+ it('應該能在已清算狀態退款', async () => {
297
+ const gateway = new MockPaymentGateway()
298
+
299
+ // 建立並處理交易
300
+ const tx = Transaction.create('tx-1', {
301
+ orderId: 'ord-123',
302
+ amount: 1000,
303
+ currency: 'TWD',
304
+ gateway: 'mock',
305
+ })
306
+
307
+ const intent = await gateway.createIntent(tx)
308
+ tx.authorize(intent.gatewayTransactionId)
309
+ tx.capture()
310
+
311
+ // 執行退款
312
+ const useCase = new RefundPayment(gateway)
313
+ const result = await useCase.execute({
314
+ gatewayTransactionId: intent.gatewayTransactionId,
315
+ })
316
+
317
+ tx.refund()
318
+
319
+ expect(result).toBe(true)
320
+ expect(tx.status).toBe(TransactionStatus.REFUNDED)
321
+ })
322
+
323
+ it('應該防止無效的狀態轉移', async () => {
324
+ const tx = Transaction.create('tx-1', {
325
+ orderId: 'ord-123',
326
+ amount: 1000,
327
+ currency: 'TWD',
328
+ gateway: 'mock',
329
+ })
330
+
331
+ // 不能在 PENDING 狀態捕獲
332
+ expect(() => tx.capture()).toThrow('Only authorized transactions can be captured')
333
+
334
+ tx.authorize('gateway_id')
335
+
336
+ // 不能在 AUTHORIZED 狀態退款
337
+ expect(() => tx.refund()).toThrow('Only captured transactions can be refunded')
338
+ })
339
+ })
340
+
341
+ describe('Edge Cases', () => {
342
+ it('應該能處理大金額交易', async () => {
343
+ const manager = new MockPaymentManager()
344
+ const useCase = new ProcessPayment(manager as any)
345
+
346
+ const result = await useCase.execute({
347
+ orderId: 'ord-999999',
348
+ amount: 999999999,
349
+ currency: 'TWD',
350
+ })
351
+
352
+ expect(result.gatewayTransactionId).toBeDefined()
353
+ })
354
+
355
+ it('應該能處理極小金額交易', async () => {
356
+ const manager = new MockPaymentManager()
357
+ const useCase = new ProcessPayment(manager as any)
358
+
359
+ const result = await useCase.execute({
360
+ orderId: 'ord-1',
361
+ amount: 1,
362
+ currency: 'TWD',
363
+ })
364
+
365
+ expect(result.gatewayTransactionId).toBeDefined()
366
+ })
367
+
368
+ it('應該能處理 0 金額(測試場景)', async () => {
369
+ const manager = new MockPaymentManager()
370
+ const useCase = new ProcessPayment(manager as any)
371
+
372
+ const result = await useCase.execute({
373
+ orderId: 'ord-free',
374
+ amount: 0,
375
+ currency: 'TWD',
376
+ })
377
+
378
+ expect(result.gatewayTransactionId).toBeDefined()
379
+ })
380
+ })
381
+ })
package/tsconfig.json CHANGED
@@ -1,26 +1,14 @@
1
1
  {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "baseUrl": ".",
6
- "paths": {
7
- "@gravito/core": [
8
- "../../packages/core/src/index.ts"
9
- ],
10
- "@gravito/*": [
11
- "../../packages/*/src/index.ts"
12
- ]
13
- },
14
- "types": [
15
- "bun-types"
16
- ]
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "baseUrl": ".",
6
+ "paths": {
7
+ "@gravito/core": ["../../packages/core/src/index.ts"],
8
+ "@gravito/*": ["../../packages/*/src/index.ts"]
17
9
  },
18
- "include": [
19
- "src/**/*"
20
- ],
21
- "exclude": [
22
- "node_modules",
23
- "dist",
24
- "**/*.test.ts"
25
- ]
26
- }
10
+ "types": ["bun-types"]
11
+ },
12
+ "include": ["src/**/*"],
13
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
14
+ }
package/dist/index.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import { ServiceProvider, Container } from '@gravito/core';
2
-
3
- declare class PaymentServiceProvider extends ServiceProvider {
4
- register(container: Container): void;
5
- boot(): void;
6
- }
7
-
8
- export { PaymentServiceProvider };