Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/api/src/__tests__/PaymentIntent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ListHelper } from '../utils/ListHelper';
import {
PaymentIntent,
PaymentIntentAmountDetailsLineItem,
QueryOperators,
INCOMPLETE_PAYMENT_INTENT_STATUSES,
} from '@zoneless/shared-types';
import {
CreateMockDatabase,
Expand Down Expand Up @@ -654,6 +656,7 @@ describe('PaymentIntentModule', () => {
limit: 25,
customer: 'cus_z_1',
customer_account: 'acct_z_customer',
status: 'succeeded',
});

expect(listSpy).toHaveBeenCalledWith(
Expand All @@ -663,6 +666,35 @@ describe('PaymentIntentModule', () => {
filters: expect.objectContaining({
customer: 'cus_z_1',
customer_account: 'acct_z_customer',
status: 'succeeded',
}),
})
);
listSpy.mockRestore();
});

it('should expand incomplete status into an in-filter', async () => {
const listSpy = jest
.spyOn(ListHelper.prototype, 'List')
.mockResolvedValue({
object: 'list',
data: [],
has_more: false,
url: '/v1/payment_intents',
});

await module.ListPaymentIntents({
account: 'acct_z_platform',
status: 'incomplete',
});

expect(listSpy).toHaveBeenCalledWith(
expect.objectContaining({
filters: expect.objectContaining({
status: {
operator: QueryOperators['in'],
value: INCOMPLETE_PAYMENT_INTENT_STATUSES,
},
}),
})
);
Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/modules/PaymentIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PaymentIntentAmountDetails,
PaymentIntentAmountDetailsLineItem,
QueryOperators,
INCOMPLETE_PAYMENT_INTENT_STATUSES,
} from '@zoneless/shared-types';
import { ValidateUpdate } from './Util';
import { ExtractChangedFields } from './Event';
Expand Down Expand Up @@ -763,13 +764,21 @@ export class PaymentIntentModule {
async ListPaymentIntents(
options: ListOptions & ListPaymentIntentsFiltersInput
): Promise<ListResult<PaymentIntentType>> {
const { customer, customer_account, ...listOptions } = options;
const { customer, customer_account, status, ...listOptions } = options;

const filters: Record<string, unknown> = {};
if (customer !== undefined) filters.customer = customer;
if (customer_account !== undefined) {
filters.customer_account = customer_account;
}
if (status === 'incomplete') {
filters.status = {
operator: QueryOperators['in'],
value: INCOMPLETE_PAYMENT_INTENT_STATUSES,
};
} else if (status !== undefined) {
filters.status = status;
}

return this.listHelper.List({
...listOptions,
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/routes/paymentIntents.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CreatePaymentIntentSchema,
UpdatePaymentIntentSchema,
CancelPaymentIntentSchema,
ListPaymentIntentsFiltersInput,
} from '@zoneless/shared-schemas';
import { PaymentIntent } from '@zoneless/shared-types';
import { ApplyExpand, RegisterExpansions } from '../utils/Expand';
Expand Down Expand Up @@ -231,6 +232,9 @@ router.get(

const customer = req.query.customer as string | undefined;
const customerAccount = req.query.customer_account as string | undefined;
const status = req.query.status as
| ListPaymentIntentsFiltersInput['status']
| undefined;

const result = await paymentIntentModule.ListPaymentIntents({
account: platformAccountId,
Expand All @@ -240,6 +244,7 @@ router.get(
created,
customer,
customer_account: customerAccount,
status,
});

Logger.info('PaymentIntents listed successfully', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@angular/core';
import { DatePipe } from '@angular/common';
import type { Customer, PaymentIntent } from '@zoneless/shared-types';
import { GetPaymentIntentListStatus } from '@zoneless/shared-types';
import { CustomerService } from '../../../../../data';
import { CustomerActionsService } from '../../services/customer-actions.service';
import { ActivatedRoute, Router } from '@angular/router';
Expand Down Expand Up @@ -107,11 +108,14 @@ export class CustomerDetailComponent implements OnInit, OnDestroy {
header: 'Status',
field: 'status',
type: 'status',
formatter: (item: unknown) =>
GetPaymentIntentListStatus((item as PaymentIntent).status),
},
{
header: 'Description',
field: 'description',
type: 'text',
dimmed: true,
formatter: (item: unknown) => {
const paymentIntent = item as PaymentIntent;
return paymentIntent.description ?? '—';
Expand All @@ -121,6 +125,7 @@ export class CustomerDetailComponent implements OnInit, OnDestroy {
header: 'Date',
field: 'created',
type: 'date',
dimmed: true,
},
];
this.paymentQueryParams.set({ customer: customerId });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,142 @@
<div class="header-wrapper">
<h1>Transactions</h1>
</div>

<div class="tab-bar">
<button
type="button"
class="tab-button"
[class.tab-active]="transactionsTab() === 'payments'"
(click)="SetTransactionsTab('payments')"
>
Payments
</button>
<button
type="button"
class="tab-button"
[class.tab-active]="transactionsTab() === 'payouts'"
(click)="SetTransactionsTab('payouts')"
>
Payouts
</button>
<button
type="button"
class="tab-button"
[class.tab-active]="transactionsTab() === 'topups'"
(click)="SetTransactionsTab('topups')"
>
Top-ups
</button>
<button
type="button"
class="tab-button"
[class.tab-active]="transactionsTab() === 'transfers'"
(click)="SetTransactionsTab('transfers')"
>
Transfers
</button>
<button
type="button"
class="tab-button"
[class.tab-active]="transactionsTab() === 'all'"
(click)="SetTransactionsTab('all')"
>
All activity
</button>
</div>

@if (transactionsTab() === 'payments') {
<div class="field">
<div class="field-pill">
<div
class="field-pill-option"
role="button"
tabindex="0"
[class.field-pill-option-selected]="paymentsStatusTab() === 'all'"
(click)="SetPaymentsStatusTab('all')"
(keydown.enter)="SetPaymentsStatusTab('all')"
(keydown.space)="SetPaymentsStatusTab('all')"
>
<span>All</span>
</div>
<div
class="field-pill-option"
role="button"
tabindex="0"
[class.field-pill-option-selected]="paymentsStatusTab() === 'succeeded'"
(click)="SetPaymentsStatusTab('succeeded')"
(keydown.enter)="SetPaymentsStatusTab('succeeded')"
(keydown.space)="SetPaymentsStatusTab('succeeded')"
>
<span>Succeeded</span>
</div>
<div
class="field-pill-option"
role="button"
tabindex="0"
[class.field-pill-option-selected]="paymentsStatusTab() === 'incomplete'"
(click)="SetPaymentsStatusTab('incomplete')"
(keydown.enter)="SetPaymentsStatusTab('incomplete')"
(keydown.space)="SetPaymentsStatusTab('incomplete')"
>
<span>Incomplete</span>
</div>
<div
class="field-pill-option"
role="button"
tabindex="0"
[class.field-pill-option-selected]="paymentsStatusTab() === 'canceled'"
(click)="SetPaymentsStatusTab('canceled')"
(keydown.enter)="SetPaymentsStatusTab('canceled')"
(keydown.space)="SetPaymentsStatusTab('canceled')"
>
<span>Canceled</span>
</div>
<div
class="field-pill-option"
role="button"
tabindex="0"
[class.field-pill-option-selected]="paymentsStatusTab() === 'uncaptured'"
(click)="SetPaymentsStatusTab('uncaptured')"
(keydown.enter)="SetPaymentsStatusTab('uncaptured')"
(keydown.space)="SetPaymentsStatusTab('uncaptured')"
>
<span>Uncaptured</span>
</div>
</div>
</div>

<app-paginated-list
endpoint="payment_intents"
[columns]="paymentIntentColumns"
[limit]="10"
[limit]="20"
[queryParams]="paymentIntentsQueryParams()"
[expand]="paymentIntentsExpand()"
(rowClick)="OnPaymentIntentClick($event)"
></app-paginated-list>
} @if (transactionsTab() === 'payouts') {
<app-transaction-list
[transactionColumns]="activityColumns"
[limit]="20"
[queryParams]="payoutQueryParams"
></app-transaction-list>
} @if (transactionsTab() === 'topups') {
<app-transaction-list
[transactionColumns]="activityColumns"
[limit]="20"
[queryParams]="topupQueryParams"
></app-transaction-list>
} @if (transactionsTab() === 'transfers') {
<app-transaction-list
[transactionColumns]="activityColumns"
[limit]="20"
[queryParams]="transferQueryParams"
></app-transaction-list>
} @if (transactionsTab() === 'all') {
<app-transaction-list
[transactionColumns]="activityColumns"
[limit]="20"
></app-transaction-list>
}

<app-payment-intent-actions-host></app-payment-intent-actions-host>
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@use '../../../../../styles/account.scss' as *;
@use '../../../../../styles/buttons.scss' as *;
@use '../../../../../styles/forms.scss' as *;
Loading
Loading