Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | import { PaymentProvider } from '../payment-provider.interface';
import Stripe from 'stripe';
import { Logger } from '@nestjs/common';
export class StripeProvider implements PaymentProvider {
name = 'Stripe';
private stripe: Stripe;
private readonly logger = new Logger(StripeProvider.name);
private webhookSecret: string;
constructor(apiKey: string, webhookSecret: string = '') {
this.stripe = new Stripe(apiKey);
this.webhookSecret = webhookSecret;
}
async createCheckoutLink(params: {
tenantId: string;
amount: number;
currency: string;
invoiceId: string;
callbackUrl: string;
}): Promise<{ checkoutUrl: string; providerIntentId: string }> {
try {
const session = await this.stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: params.currency.toLowerCase(),
product_data: {
name: `Invoice ${params.invoiceId}`,
},
unit_amount: params.amount,
},
quantity: 1,
},
],
mode: 'payment',
success_url: `${params.callbackUrl}?session_id={CHECKOUT_SESSION_ID}&status=success`,
cancel_url: `${params.callbackUrl}?status=cancel`,
client_reference_id: params.tenantId,
metadata: {
invoiceId: params.invoiceId,
tenantId: params.tenantId,
},
});
return {
checkoutUrl: session.url as string,
providerIntentId: session.id,
};
} catch (error: any) {
this.logger.error(`Stripe create checkout failed: ${error.message}`);
throw error;
}
}
async verifyWebhook(
payload: any,
signature: string,
): Promise<{
success: boolean;
transactionId: string;
amountPaid: number;
status: 'SUCCESS' | 'FAILED';
}> {
let event: Stripe.Event;
// Ideally, payload should be the raw body buffer.
// If we have the webhook secret, we use constructEvent.
if (this.webhookSecret && typeof payload === 'string') {
try {
event = this.stripe.webhooks.constructEvent(
payload,
signature,
this.webhookSecret,
);
} catch (err: any) {
this.logger.error(
`Webhook signature verification failed: ${err.message}`,
);
return {
success: false,
transactionId: '',
amountPaid: 0,
status: 'FAILED',
};
}
} else {
// Fallback for when raw body is not passed or no secret provided (testing environments)
event = typeof payload === 'string' ? JSON.parse(payload) : payload;
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
return {
success: true,
transactionId: (session.payment_intent as string) || session.id,
amountPaid: session.amount_total || 0,
status: 'SUCCESS',
};
}
return {
success: true,
transactionId: '',
amountPaid: 0,
status: 'FAILED',
};
}
async refund(
transactionId: string,
amount: number,
): Promise<{ success: boolean; refundId: string }> {
try {
const refund = await this.stripe.refunds.create({
payment_intent: transactionId,
amount,
});
return { success: true, refundId: refund.id };
} catch (error: any) {
this.logger.error(`Stripe refund failed: ${error.message}`);
return { success: false, refundId: '' };
}
}
}
|