diff --git a/docs/stripe.md b/docs/stripe.md index e15b2fa3..b5a74f20 100644 --- a/docs/stripe.md +++ b/docs/stripe.md @@ -58,31 +58,37 @@ We need to provide Postgres with the credentials to connect to Stripe, and any a The Stripe Wrapper supports data read and modify from Stripe API. -| Object | Select | Insert | Update | Delete | Truncate | -| ----------- | :----: | :----: | :----: | :----: | :----: | -| [Accounts](https://stripe.com/docs/api/accounts/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Balance](https://stripe.com/docs/api/balance) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Balance Transactions](https://stripe.com/docs/api/balance_transactions/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Charges](https://stripe.com/docs/api/charges/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Checkout Sessions](https://stripe.com/docs/api/checkout/sessions/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Customers](https://stripe.com/docs/api/customers/list) | :white_check_mark:| :white_check_mark:| :white_check_mark:| :white_check_mark:| :x: | -| [Disputes](https://stripe.com/docs/api/disputes/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Events](https://stripe.com/docs/api/events/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Files](https://stripe.com/docs/api/files/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [File Links](https://stripe.com/docs/api/file_links/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Invoices](https://stripe.com/docs/api/invoices/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Mandates](https://stripe.com/docs/api/mandates) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [PaymentIntents](https://stripe.com/docs/api/payment_intents/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Payouts](https://stripe.com/docs/api/payouts/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Prices](https://stripe.com/docs/api/prices/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Products](https://stripe.com/docs/api/products/list) | :white_check_mark:| :white_check_mark:| :white_check_mark:| :white_check_mark:| :x: | -| [Refunds](https://stripe.com/docs/api/refunds/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [SetupAttempts](https://stripe.com/docs/api/setup_attempts/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [SetupIntents](https://stripe.com/docs/api/setup_intents/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Subscriptions](https://stripe.com/docs/api/subscriptions/list) | :white_check_mark:| :white_check_mark:| :white_check_mark:| :white_check_mark:| :x: | -| [Tokens](https://stripe.com/docs/api/tokens) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Topups](https://stripe.com/docs/api/topups/list) | :white_check_mark:| :x: | :x: | :x: | :x: | -| [Transfers](https://stripe.com/docs/api/transfers/list) | :white_check_mark:| :x: | :x: | :x: | :x: | +| Object | Select | Insert | Update | Delete | Truncate | +|-------------------------------------------------------------------------------|-:-:----------------|-:-:----------------|-:-:----------------|-:-:----------------|-:-:------| +| [Accounts](https://stripe.com/docs/api/accounts/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Balance](https://stripe.com/docs/api/balance) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Balance Transactions](https://stripe.com/docs/api/balance_transactions/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Charges](https://stripe.com/docs/api/charges/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Checkout Sessions](https://stripe.com/docs/api/checkout/sessions/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Coupons](https://stripe.com/docs/api/coupons/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Customers](https://stripe.com/docs/api/customers/list) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [Disputes](https://stripe.com/docs/api/disputes/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Events](https://stripe.com/docs/api/events/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Files](https://stripe.com/docs/api/files/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [File Links](https://stripe.com/docs/api/file_links/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Invoices](https://stripe.com/docs/api/invoices/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Mandates](https://stripe.com/docs/api/mandates) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [PaymentIntents](https://stripe.com/docs/api/payment_intents/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Payouts](https://stripe.com/docs/api/payouts/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Prices](https://stripe.com/docs/api/prices/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Products](https://stripe.com/docs/api/products/list) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [Promotion Codes](https://stripe.com/docs/api/promotion_codes/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Refunds](https://stripe.com/docs/api/refunds/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [SetupAttempts](https://stripe.com/docs/api/setup_attempts/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [SetupIntents](https://stripe.com/docs/api/setup_intents/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Shipping Rates](https://stripe.com/docs/api/shipping_rates/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Subscriptions](https://stripe.com/docs/api/subscriptions/list) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [Tax Codes](https://stripe.com/docs/api/tax_codes/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Tax Rates](https://stripe.com/docs/api/tax_rates/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Tokens](https://stripe.com/docs/api/tokens) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Topups](https://stripe.com/docs/api/topups/list) | :white_check_mark: | :x: | :x: | :x: | :x: | +| [Transfers](https://stripe.com/docs/api/transfers/list) | :white_check_mark: | :x: | :x: | :x: | :x: | + The Stripe foreign tables mirror Stripe's API. We can create a schema to hold all the Stripe tables. @@ -249,6 +255,8 @@ While any column is allowed in a where clause, it is most efficient to filter by - payment_intent - subscription +// TODO: Add coupons docs + ### Customers *read and modify* @@ -564,6 +572,8 @@ While any column is allowed in a where clause, it is most efficient to filter by - id - active +// TODO: Add promotion_codes docs + ### Refunds *read only* @@ -657,6 +667,8 @@ While any column is allowed in a where clause, it is most efficient to filter by - customer - payment_method +// TODO: Add shipping_rates docs + ### Subscriptions *read and modify* @@ -688,6 +700,10 @@ While any column is allowed in a where clause, it is most efficient to filter by - price - status +// TODO: Add tax_codes docs + +// TODO: Add tax_rates docs + ### Tokens *read only* @@ -766,6 +782,14 @@ While any column is allowed in a where clause, it is most efficient to filter by - id - destination +## Removing foreign tables + +If you would like to remove a foreign table because you do not use it anymore this can be done easily with the following command + +```sql +drop foreign table stripe.customers, stripe.invoices, stripe.subscriptions; +``` + ## Examples Some examples on how to use Stripe foreign tables. diff --git a/wrappers/src/fdw/stripe_fdw/README.md b/wrappers/src/fdw/stripe_fdw/README.md index 16d3a169..5d4bf486 100644 --- a/wrappers/src/fdw/stripe_fdw/README.md +++ b/wrappers/src/fdw/stripe_fdw/README.md @@ -8,13 +8,14 @@ This is a foreign data wrapper for [Stripe](https://stripe.com/) developed using ## Changelog -| Version | Date | Notes | -| ------- | ---------- | ---------------------------------------------------- | -| 0.1.7 | 2023-07-13 | Added fdw stats collection | -| 0.1.6 | 2023-05-30 | Added Checkout Session object | -| 0.1.5 | 2023-05-01 | Added 'prices' object and empty result improvement | -| 0.1.4 | 2023-02-21 | Added Connect objects | -| 0.1.3 | 2022-12-21 | Added more core objects | -| 0.1.2 | 2022-12-04 | Added 'products' objects support | -| 0.1.1 | 2022-12-03 | Added quals pushdown support | -| 0.1.0 | 2022-12-01 | Initial version | +| Version | Date | Notes | +| ------- | ---------- | ---------------------------------------------------------------------------------------------------- | +| 0.1.8 | 2023-09-13 | Added objects for Coupons, Promotion Codes, Tax codes, Tax rates and Shipping rates | +| 0.1.7 | 2023-07-13 | Added fdw stats collection | +| 0.1.6 | 2023-05-30 | Added Checkout Session object | +| 0.1.5 | 2023-05-01 | Added 'prices' object and empty result improvement | +| 0.1.4 | 2023-02-21 | Added Connect objects | +| 0.1.3 | 2022-12-21 | Added more core objects | +| 0.1.2 | 2022-12-04 | Added 'products' objects support | +| 0.1.1 | 2022-12-03 | Added quals pushdown support | +| 0.1.0 | 2022-12-01 | Initial version | diff --git a/wrappers/src/fdw/stripe_fdw/stripe_fdw.rs b/wrappers/src/fdw/stripe_fdw/stripe_fdw.rs index 59972834..e189b8f2 100644 --- a/wrappers/src/fdw/stripe_fdw/stripe_fdw.rs +++ b/wrappers/src/fdw/stripe_fdw/stripe_fdw.rs @@ -97,6 +97,8 @@ fn body_to_rows( .and_then(|v| match *col_type { "bool" => v.as_bool().map(Cell::Bool), "i64" => v.as_i64().map(Cell::I64), + "f64" => v.as_f64().map(Cell::F64), + "json" => v.as_i64().map(Cell::Json), "string" => v.as_str().map(|a| Cell::String(a.to_owned())), "timestamp" => v.as_i64().map(|a| { let ts = to_timestamp(a as f64); @@ -147,6 +149,9 @@ fn row_to_body(row: &Row) -> JsonValue { Cell::I64(v) => { map.insert(col_name, JsonValue::Number(Number::from(*v))); } + Cell::F64(v) => { + map.insert(col_name, JsonValue::Number(Decimal::from(*v))); + } Cell::String(v) => { map.insert(col_name, JsonValue::String(v.to_string())); } @@ -155,6 +160,8 @@ fn row_to_body(row: &Row) -> JsonValue { if let Some(m) = v.0.clone().as_object_mut() { map.append(m) } + } else { + map.insert(col_name, v); } } _ => { @@ -254,7 +261,7 @@ macro_rules! report_request_error { } #[wrappers_fdw( - version = "0.1.7", + version = "0.1.8", author = "Supabase", website = "https://github.com/supabase/wrappers/tree/main/wrappers/src/fdw/stripe_fdw", error_type = "StripeFdwError" @@ -287,6 +294,8 @@ impl StripeFdw { "balance" => vec![], "balance_transactions" => vec!["type"], "charges" => vec!["customer"], + "checkout/sessions" => vec!["customer", "payment_intent", "subscription"], + "coupons" => vec![], "customers" => vec!["email"], "disputes" => vec!["charge", "payment_intent"], "events" => vec!["type"], @@ -298,14 +307,17 @@ impl StripeFdw { "payouts" => vec!["status"], "prices" => vec!["active", "currency", "product", "type"], "products" => vec!["active"], + "promotion_codes" => vec![], "refunds" => vec!["charge", "payment_intent"], "setup_attempts" => vec!["setup_intent"], "setup_intents" => vec!["customer", "payment_method"], + "shipping_rates" => vec!["active", "created", "currency"], "subscriptions" => vec!["customer", "price", "status"], + "tax_codes" => vec![], + "tax_rates" => vec!["active"], "tokens" => vec![], "topups" => vec!["status"], "transfers" => vec!["destination"], - "checkout/sessions" => vec!["customer", "payment_intent", "subscription"], _ => { report_error( PgSqlErrorCode::ERRCODE_FDW_TABLE_NOT_FOUND, @@ -378,6 +390,32 @@ impl StripeFdw { ], tgt_cols, ), + "checkout/sessions" => body_to_rows( + resp_body, + vec![ + ("id", "string"), + ("customer", "string"), + ("payment_intent", "string"), + ("subscription", "string"), + ("created", "timestamp"), + ], + tgt_cols, + ), + "coupons" => body_to_rows( + resp_body, + vec![ + ("id", "string"), + ("amount_off", "i64"), + ("currency", "string"), + ("duration", "string"), + ("duration_in_months", "i64"), + ("max_redemptions", "i64"), + ("name", "string"), + ("percent_off", "f64"), + ("created", "timestamp"), + ], + tgt_cols, + ), "customers" => body_to_rows( resp_body, vec![ @@ -516,6 +554,17 @@ impl StripeFdw { ], tgt_cols, ), + "promotion_codes" => body_to_rows( + resp_body, + vec![ + ("id", "string"), + ("code", "string"), + ("coupon", "string"), + ("active", "bool"), + ("created", "timestamp"), + ], + tgt_cols, + ), "refunds" => body_to_rows( resp_body, vec![ @@ -559,6 +608,18 @@ impl StripeFdw { ], tgt_cols, ), + "shipping_rates" => body_to_rows( + resp_body, + vec![ + ("id", "string"), + ("active", "bool"), + ("display_name", "string"), + ("amount", "string"), + ("type", "string"), + ("created", "timestamp"), + ], + tgt_cols, + ), "subscriptions" => body_to_rows( resp_body, vec![ @@ -570,6 +631,27 @@ impl StripeFdw { ], tgt_cols, ), + "tax_codes" => body_to_rows( + resp_body, + vec![ + ("id", "string"), + ("description", "string"), + ("name", "string"), + ], + tgt_cols, + ), + "tax_rates" => body_to_rows( + resp_body, + vec![ + ("id", "string"), + ("active", "bool"), + ("country", "string"), + ("description", "string"), + ("display_name", "string"), + ("inclusive", "bool"), + ("percentage", "f64"), + ("created", "timestamp"), + ), "tokens" => body_to_rows( resp_body, vec![ @@ -605,17 +687,6 @@ impl StripeFdw { ], tgt_cols, ), - "checkout/sessions" => body_to_rows( - resp_body, - vec![ - ("id", "string"), - ("customer", "string"), - ("payment_intent", "string"), - ("subscription", "string"), - ("created", "timestamp"), - ], - tgt_cols, - ), _ => { report_error( PgSqlErrorCode::ERRCODE_FDW_TABLE_NOT_FOUND, diff --git a/wrappers/src/fdw/stripe_fdw/tests.rs b/wrappers/src/fdw/stripe_fdw/tests.rs index bed205b2..2823b808 100644 --- a/wrappers/src/fdw/stripe_fdw/tests.rs +++ b/wrappers/src/fdw/stripe_fdw/tests.rs @@ -111,6 +111,28 @@ mod tests { ) .unwrap(); + c.update( + r#" + CREATE FOREIGN TABLE checkout_sessions ( + id text, + customer text, + payment_intent text, + subscription text, + attrs jsonb + ) + SERVER my_stripe_server + OPTIONS ( + object 'checkout/sessions', + rowid_column 'id' + ) + "#, + None, + None, + ) + .unwrap(); + + // TODO: Add coupons test setup + c.update( r#" CREATE FOREIGN TABLE stripe_customers ( @@ -242,6 +264,8 @@ mod tests { ) .unwrap(); + // TODO: Add mandates test setup + c.update( r#" CREATE FOREIGN TABLE stripe_payment_intents ( @@ -331,6 +355,8 @@ mod tests { ) .unwrap(); + // TODO: Add promotion_codes test setup + c.update( r#" CREATE FOREIGN TABLE stripe_refunds ( @@ -401,6 +427,8 @@ mod tests { ) .unwrap(); + // TODO: Add shipping_rates test setup + c.update( r#" CREATE FOREIGN TABLE stripe_subscriptions ( @@ -422,6 +450,12 @@ mod tests { ) .unwrap(); + // TODO: Add tax_codes test setup + + // TODO: Add tax_rates test setup + + // TODO: Check why there is no tokens test setup + c.update( r#" CREATE FOREIGN TABLE stripe_topups ( @@ -464,26 +498,6 @@ mod tests { ) .unwrap(); - c.update( - r#" - CREATE FOREIGN TABLE checkout_sessions ( - id text, - customer text, - payment_intent text, - subscription text, - attrs jsonb - ) - SERVER my_stripe_server - OPTIONS ( - object 'checkout/sessions', - rowid_column 'id' - ) - "#, - None, - None, - ) - .unwrap(); - let results = c .select("SELECT * FROM stripe_accounts", None, None) .unwrap() @@ -544,6 +558,22 @@ mod tests { .collect::>(); assert_eq!(results, vec![(((100, "usd"), "succeeded"))]); + // TODO: Add coupons test + + let results = c + .select( + "SELECT attrs->>'id' as id FROM checkout_sessions", + None, + None, + ) + .unwrap() + .filter_map(|r| r.get_by_name::<&str, _>("id").unwrap()) + .collect::>(); + assert_eq!( + results, + vec!["cs_test_a1DmlfbOPqmbKHfpwpFQ0RM3pVXmKoESZbJxnKrPdMsLDPPMGYtEBcHGPR"] + ); + let results = c .select("SELECT * FROM stripe_customers", None, None) .unwrap() @@ -572,20 +602,6 @@ mod tests { .collect::>(); assert_eq!(results, vec!["cus_MJiBgSUgeWFN0z"]); - let results = c - .select( - "SELECT attrs->>'id' as id FROM checkout_sessions", - None, - None, - ) - .unwrap() - .filter_map(|r| r.get_by_name::<&str, _>("id").unwrap()) - .collect::>(); - assert_eq!( - results, - vec!["cs_test_a1DmlfbOPqmbKHfpwpFQ0RM3pVXmKoESZbJxnKrPdMsLDPPMGYtEBcHGPR"] - ); - // Stripe mock service cannot return 404 error code correctly for // non-exists customer, so we have to disable this test case. // @@ -690,6 +706,8 @@ mod tests { vec![((("cus_MJiBgSUgeWFN0z", 1000), "usd"), "draft")] ); + // TODO: Add mandates test + let results = c .select("SELECT * FROM stripe_payment_intents", None, None) .unwrap() @@ -755,6 +773,8 @@ mod tests { vec![(("T-shirt", true), "Comfortable gray cotton t-shirt")] ); + // TODO: Add promotion_codes test + let results = c .select("SELECT * FROM stripe_refunds", None, None) .unwrap() @@ -806,6 +826,8 @@ mod tests { )] ); + // TODO: Add shipping_rates test + let results = c .select("SELECT * FROM stripe_subscriptions", None, None) .unwrap() @@ -831,6 +853,12 @@ mod tests { )] ); + // TODO: Add tax_codes test + + // TODO: Add tax_rates test + + // TODO: Check why there is no tokens test + let results = c .select("SELECT * FROM stripe_topups", None, None) .unwrap()