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
43 changes: 43 additions & 0 deletions firestore-stripe-payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,49 @@ Then, in the [Stripe Dashboard](https://dashboard.stripe.com):

- Create a new [restricted key](https://stripe.com/docs/keys#limit-access) with write access for the "Customers", "Checkout Sessions" and "Customer portal" resources, and read-only access for the "Subscriptions" and "Prices" resources.

#### Installing via Firebase CLI

When installing via the CLI, be sure to pin the version.

```
firebase ext:install invertase/firestore-stripe-payments --project=projectId_or_alias
Alternatively for local source:
firebase ext:install . --project=projectId_or_alias
```

The current version can be found in [extension.yaml](extension.yaml).

#### Using webhooks locally

If you wish to test the webhooks **locally**, use the following command to configure the extension:

```
firebase ext:configure firestore-stripe-payments --local
```

Be sure to configure your test mode [API Key](https://stripe.com/docs/keys) and webhook [signing secret](https://stripe.com/docs/webhooks/signatures#:~:text=Before%20you%20can%20verify%20signatures,secret%20key%20for%20each%20endpoint.) when prompted.

Start the firebase emulator with:

```
firebase emulators:start --project=projectId_or_alias
```

Find the functions path associated with the stripe extension, typically it looks like this:

- `http://192.0.0.1:5001/{projectId}/{region}/ext-firestore-stripe-payments-handleWebhookEvents`

- You can tunnel your local endpoint using a tool like [ngrok](https://ngrok.com/). In this case you will tunnel the localhost domain and port `http://127.0.01:5001`. Replace `127.0.0.1:5001` with your tunnel url. The end result would look something like:

```
https://1234-1234-1234.ngrok.io/{projectId}/{region}/ext-firestore-stripe-payments-handleWebhookEvents
```

- Configure your test mode stripe [webhook endpoint](https://stripe.com/docs/webhooks) with the url you just constructed.

- Your local webhooks are now set up.


#### Billing

This extension uses the following Firebase services which may have associated charges:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,52 @@ describe('createCheckoutSession', () => {
expect(success_url).toBe('http://test.com/success');
});

test('should setup a future payment with mode:setup', async () => {
const collection = firestore.collection('customers');

const customer: DocumentData = await waitForDocumentToExistInCollection(
collection,
'email',
user.email
);

/** Define params */
const client = 'web';
const mode = 'setup';
const success_url = 'http://test.com/success';
const cancel_url = 'http://test.com/cancel';
const payment_method_types = ['card'];

const checkoutSessionCollection = collection
.doc(customer.doc.id)
.collection('checkout_sessions');

const checkoutSessionDocument: DocumentReference =
await checkoutSessionCollection.add({
client: 'web',
mode: 'setup',
success_url: 'http://test.com/success',
cancel_url: 'http://test.com/cancel',
payment_method_types,
});

const customerDoc = await waitForDocumentToExistWithField(
checkoutSessionDocument,
'created'
);

const result = customerDoc.data();

expect(result.client).toBe(client);
expect(result.mode).toBe(mode);
expect(result.success_url).toBe(success_url);
expect(result.cancel_url).toBe(cancel_url);
expect(result.payment_method_types).toEqual(payment_method_types);
expect(result.sessionId).toBeDefined();
expect(url).toBeDefined();
expect(result.created).toBeDefined();
});

test.skip('throws an error when success_url has not been provided', async () => {});

test.skip('throws an error when cancel_url has not been provided', async () => {});
Expand Down
34 changes: 25 additions & 9 deletions firestore-stripe-payments/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ exports.createCustomer = functions.auth
});
});

function generateLineItems(
line_items: Stripe.Checkout.SessionCreateParams.LineItem[],
price: string,
quantity: number
): Stripe.Checkout.SessionCreateParams.LineItem[] {
if (line_items) {
return line_items;
}

if (price && quantity)
return [
{
price,
quantity,
},
];

return [];
}

/**
* Create a CheckoutSession or PaymentIntent based on which client is being used.
*/
Expand Down Expand Up @@ -185,14 +205,7 @@ exports.createCheckoutSession = functions
shipping_rates,
customer,
customer_update,
line_items: line_items
? line_items
: [
{
price,
quantity,
},
],
line_items: generateLineItems(line_items, price, quantity),
mode,
success_url,
cancel_url,
Expand All @@ -205,6 +218,7 @@ exports.createCheckoutSession = functions
if (payment_method_types) {
sessionCreateParams.payment_method_types = payment_method_types;
}

if (mode === 'subscription') {
sessionCreateParams.payment_method_collection =
payment_method_collection;
Expand Down Expand Up @@ -244,15 +258,17 @@ exports.createCheckoutSession = functions
}
if (promotion_code) {
sessionCreateParams.discounts = [{ promotion_code }];
} else {
} else if (mode !== 'setup') {
sessionCreateParams.allow_promotion_codes = allow_promotion_codes;
}
if (client_reference_id)
sessionCreateParams.client_reference_id = client_reference_id;

const session = await stripe.checkout.sessions.create(
sessionCreateParams,
{ idempotencyKey: context.params.id }
);

await snap.ref.set(
{
client,
Expand Down