Skip to content
Open
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
8 changes: 4 additions & 4 deletions .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ jobs:
- uses: actions/checkout@v3

- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@0.28.0
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
with:
scan-type: "fs"
scan-ref: "${{ github.workspace }}"
trivy-config: "${{ github.workspace }}/trivy.yaml"
scan-type: 'fs'
scan-ref: '${{ github.workspace }}'
trivy-config: '${{ github.workspace }}/trivy.yaml'
7,477 changes: 3,483 additions & 3,994 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
],
"overrides": {
"semver": "^7.5.2",
"qs": "^6.14.1"
"qs": "^6.14.1",
"underscore": "^1.13.8",
"undici": "^6.24.0"
},
"config": {
"commitizen": {
Expand Down
4 changes: 2 additions & 2 deletions services/subscription-service/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import {
FeatureToggleBindings,
FeatureToggleServiceComponent,
} from '@sourceloop/feature-toggle-service';
import {BillingComponent} from 'loopback4-billing';
import {AuthenticationComponent} from 'loopback4-authentication';
import {
AuthorizationBindings,
AuthorizationComponent,
} from 'loopback4-authorization';
import {BillingComponent} from 'loopback4-billing';
import {
SubscriptionServiceBindings,
SYSTEM_USER,
Expand Down Expand Up @@ -202,7 +202,7 @@ export class SubscriptionServiceComponent implements Component {

// Mount authorization component for default sequence
this.application.bind(AuthorizationBindings.CONFIG).to({
allowAlwaysPaths: ['/explorer'],
allowAlwaysPaths: ['/explorer', '/billing', '/webhooks'],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there was a webhook component right ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes ma'am

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it not added there then ?

});
this.application.component(AuthorizationComponent);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {inject} from '@loopback/core';
import {get, param} from '@loopback/rest';
import {authorize} from 'loopback4-authorization';
import {authenticate, STRATEGY} from 'loopback4-authentication';
import {
BillingComponentBindings,
IService,
TInvoicePdf,
TInvoicePaymentDetails,
TPaymentIntent,
} from 'loopback4-billing';
import {
getModelSchemaRefSF,
STATUS_CODE,
OPERATION_SECURITY_SPEC,
} from '@sourceloop/core';
import {BillingPaymentStatusResponse, BillingErrorResponse} from '../models';
import {
InvoicePdfDto,
InvoicePaymentDetailsDto,
PaymentIntentDto,
} from '../models/dto';
import {PermissionKey} from '../permissions';

const BASE = '/billing';

export class BillingServiceController {
constructor(
@inject(BillingComponentBindings.SDKProvider)
private readonly billingService: IService,
) {}

/**
* Check whether an invoice has been paid.
*/
@authorize({permissions: [PermissionKey.ViewInvoice]})
@authenticate(STRATEGY.BEARER, {passReqToCallback: true})
@get(`${BASE}/invoices/{invoiceId}/payment-status`, {
security: OPERATION_SECURITY_SPEC,
summary: 'Check if an invoice is paid',
responses: {
[STATUS_CODE.OK]: {
description: 'Payment status',
content: {
'application/json': {
schema: {...getModelSchemaRefSF(BillingPaymentStatusResponse)},
},
},
},
},
})
async getPaymentStatus(
@param.path.string('invoiceId') invoiceId: string,
): Promise<BillingPaymentStatusResponse> {
const paid = await this.billingService.getPaymentStatus(invoiceId);
return {paid};
}

@authorize({permissions: [PermissionKey.ViewInvoice]})
@authenticate(STRATEGY.BEARER, {passReqToCallback: true})
@get(`${BASE}/invoices/{invoiceId}/pdf`, {
security: OPERATION_SECURITY_SPEC,
summary: 'Get PDF download URL for an invoice',
description:
'Retrieves a temporary URL to download the invoice PDF. ' +
'For Stripe, only finalized invoices have PDF URLs available. ' +
'For ChargeBee, most invoice states support PDF generation.',
responses: {
[STATUS_CODE.OK]: {
description: 'PDF information retrieved successfully',
content: {
'application/json': {
schema: getModelSchemaRefSF(InvoicePdfDto),
},
},
},
[STATUS_CODE.NOT_FOUND]: {
description: 'Invoice not found',
content: {
'application/json': {
schema: getModelSchemaRefSF(BillingErrorResponse),
},
},
},
[STATUS_CODE.BAD_REQUEST]: {
description: 'PDF URL not available',
content: {
'application/json': {
schema: getModelSchemaRefSF(BillingErrorResponse),
},
},
},
},
})
async getInvoicePdf(
@param.path.string('invoiceId') invoiceId: string,
): Promise<TInvoicePdf> {
const pdfInfo: TInvoicePdf =
await this.billingService.getInvoicePdf(invoiceId);
return pdfInfo;
}

@authorize({permissions: [PermissionKey.ViewInvoice]})
@authenticate(STRATEGY.BEARER, {passReqToCallback: true})
@get(`${BASE}/invoices/{invoiceId}/payment-details`, {
security: OPERATION_SECURITY_SPEC,
summary: 'Get payment details for an invoice',
description:
'Retrieves payment method details, payment amount, status, and ' +
'transaction information for a specific invoice.',
responses: {
[STATUS_CODE.OK]: {
description: 'Payment details retrieved successfully',
content: {
'application/json': {
schema: getModelSchemaRefSF(InvoicePaymentDetailsDto),
},
},
},
[STATUS_CODE.NOT_FOUND]: {
description: 'Invoice not found',
content: {
'application/json': {
schema: getModelSchemaRefSF(BillingErrorResponse),
},
},
},
[STATUS_CODE.BAD_REQUEST]: {
description: 'No payment details available',
content: {
'application/json': {
schema: getModelSchemaRefSF(BillingErrorResponse),
},
},
},
},
})
async getInvoicePaymentDetails(
@param.path.string('invoiceId') invoiceId: string,
): Promise<TInvoicePaymentDetails> {
return this.billingService.getInvoicePaymentDetails(invoiceId);
}

@authorize({permissions: [PermissionKey.ViewInvoice]})
@authenticate(STRATEGY.BEARER, {passReqToCallback: true})
@get(`${BASE}/payment-intents/{paymentIntentId}`, {
security: OPERATION_SECURITY_SPEC,
summary: 'Get payment intent details',
description:
'Retrieves detailed information about a payment intent including ' +
'status, payment method, amount, and transaction metadata. ' +
'Useful for payment tracking and webhook handling.',
responses: {
[STATUS_CODE.OK]: {
description: 'Payment intent retrieved successfully',
content: {
'application/json': {
schema: getModelSchemaRefSF(PaymentIntentDto),
},
},
},
[STATUS_CODE.NOT_FOUND]: {
description: 'Payment intent not found',
content: {
'application/json': {
schema: getModelSchemaRefSF(BillingErrorResponse),
},
},
},
},
})
async getPaymentIntent(
@param.path.string('paymentIntentId') paymentIntentId: string,
): Promise<TPaymentIntent> {
return this.billingService.getPaymentIntent(paymentIntentId);
}
}
Loading
Loading