mirror of
				https://github.com/therootcompany/paypal-checkout.js.git
				synced 2025-10-31 05:02:53 +00:00 
			
		
		
		
	feature!: more CRUD, also renamed .get to .details to match PayPal docs
This commit is contained in:
		
							parent
							
								
									82d225cafc
								
							
						
					
					
						commit
						0a9d277317
					
				
							
								
								
									
										54
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								README.md
									
									
									
									
									
								
							| @ -5,10 +5,26 @@ with lots of abstraction without much value - this is very little abstraction, | |||||||
| but specificially designed to be (mostly) idiomatic JavaScript / Node.js. \ | but specificially designed to be (mostly) idiomatic JavaScript / Node.js. \ | ||||||
| (excuse the `snake_case` - that's how the PayPal REST API is designed). | (excuse the `snake_case` - that's how the PayPal REST API is designed). | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | <img src="https://www.paypalobjects.com/webstatic/en_US/i/buttons/checkout-logo-large.png" alt="Check out with PayPal" /> | ||||||
|  | 
 | ||||||
|  | The Good Documentation™ for the PayPal API (a.k.a. PayPal Checkout SDK) is the | ||||||
|  | "REST API". See | ||||||
|  | 
 | ||||||
|  | - <https://developer.paypal.com/docs/api/orders/v2/> (one-time payments) | ||||||
|  | - <https://developer.paypal.com/docs/api/subscriptions/v1/> (recurring | ||||||
|  |   subscriptions) | ||||||
|  | - <https://www.paypal.com/webapps/mpp/logos-buttons> (the buttons) | ||||||
|  | 
 | ||||||
|  | # Install | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| npm install --save @root/paypal-checkout | npm install --save @root/paypal-checkout | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | # Usage | ||||||
|  | 
 | ||||||
| ```js | ```js | ||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| @ -25,15 +41,39 @@ PPC.Subscriptions.createRequest({ | |||||||
| }); | }); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | # API | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | PayPal.init(client_id, client_secret, 'sandbox|live', defaults); | ||||||
|  | PayPal.request({ method, url, headers, json }); | ||||||
|  | 
 | ||||||
|  | PayPal.Product.create({ ... });               // event_type: "CATALOG.PRODUCT.CREATED" | ||||||
|  | PayPal.Product.list(); | ||||||
|  | PayPal.Product.details(id); | ||||||
|  | PayPal.Product.update(id, { description });   // event_type: "CATALOG.PRODUCT.UPDATED" | ||||||
|  | 
 | ||||||
|  | PayPal.Plan.create({ ... });                  // event_type: "BILLING.PLAN.CREATED" | ||||||
|  | PayPal.Plan.list(); | ||||||
|  | PayPal.Plan.details(id); | ||||||
|  | PayPal.Plan.update(id, { description });      // event_type: "BILLING.PLAN.UPDATED" | ||||||
|  | 
 | ||||||
|  | PayPal.Subscription.create({ ... }); | ||||||
|  | PayPal.Subscription.details(id); | ||||||
|  | PayPal.Subscription.cancel(id, { reason }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Webhooks | ||||||
|  | 
 | ||||||
|  | Webhooks can be set up in the Application section of the Dashboard: | ||||||
|  | 
 | ||||||
|  | - <https://developer.paypal.com/developer/applications> | ||||||
|  | 
 | ||||||
|  | You'll see a list of applications. Click on one to access the webhooks. | ||||||
|  | 
 | ||||||
|  | # Notes | ||||||
|  | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| The Good Documentation™ for the PayPal API (a.k.a. PayPal Checkout SDK) is the |  | ||||||
| "REST API". See |  | ||||||
| 
 |  | ||||||
| - <https://developer.paypal.com/docs/api/orders/v2/> (one-time payments) |  | ||||||
| - <https://developer.paypal.com/docs/api/subscriptions/v1/> (recurring |  | ||||||
|   subscriptions) |  | ||||||
| 
 |  | ||||||
| Note: Just about everything in the PayPal SDK that uses `ALL_CAPS` is a | Note: Just about everything in the PayPal SDK that uses `ALL_CAPS` is a | ||||||
| `constant`/`enum` representing an option you can pick from limited number of | `constant`/`enum` representing an option you can pick from limited number of | ||||||
| options. | options. | ||||||
|  | |||||||
| @ -1,13 +1,34 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
|  | let qs = require("querystring"); | ||||||
|  | 
 | ||||||
| let request = require("@root/request"); | let request = require("@root/request"); | ||||||
| 
 | 
 | ||||||
| let PayPal = {}; | let PayPal = {}; | ||||||
| PayPal.init = function (client_id, client_secret) { | PayPal.init = function (client_id, client_secret, env, opts) { | ||||||
|  |   if (!opts) { | ||||||
|  |     opts = {}; | ||||||
|  |   } | ||||||
|  |   if (!("total_required" in opts)) { | ||||||
|  |     opts.total_required = true; | ||||||
|  |   } | ||||||
|  |   if (!opts.page_size) { | ||||||
|  |     opts.page_size = 20; | ||||||
|  |   } | ||||||
|  |   if (!opts.prefer) { | ||||||
|  |     opts.prefer = "return=representation"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   PayPal.__sandboxUrl = "https://api-m.sandbox.paypal.com"; |   PayPal.__sandboxUrl = "https://api-m.sandbox.paypal.com"; | ||||||
|   PayPal.__baseUrl = PayPal.__sandboxUrl; |   PayPal.__baseUrl = PayPal.__sandboxUrl; | ||||||
|   PayPal.__id = client_id; |   PayPal.__id = client_id; | ||||||
|   PayPal.__secret = client_secret; |   PayPal.__secret = client_secret; | ||||||
|  |   PayPal.__defaultQuery = { | ||||||
|  |     page_size: opts.page_size, | ||||||
|  |     total_required: opts.total_required, | ||||||
|  |     page: 1, | ||||||
|  |   }; | ||||||
|  |   PayPal.__prefer = opts.prefer; | ||||||
| }; | }; | ||||||
| PayPal.request = async function _paypalRequest(reqObj) { | PayPal.request = async function _paypalRequest(reqObj) { | ||||||
|   let headers = {}; |   let headers = {}; | ||||||
| @ -24,6 +45,30 @@ PayPal.request = async function _paypalRequest(reqObj) { | |||||||
|   }; |   }; | ||||||
|   return await request(reqObj).then(sanitize); |   return await request(reqObj).then(sanitize); | ||||||
| }; | }; | ||||||
|  | PayPal._patch = function (obj) { | ||||||
|  |   let ops = []; | ||||||
|  | 
 | ||||||
|  |   Object.keys(obj).forEach(function (k) { | ||||||
|  |     let val = obj[k]; | ||||||
|  |     if ("undefined" === typeof val) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let op = "replace"; | ||||||
|  |     if (null === val) { | ||||||
|  |       op = "delete"; | ||||||
|  |       val = undefined; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ops.push({ | ||||||
|  |       path: `/${k}`, | ||||||
|  |       op: op, | ||||||
|  |       value: val, | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return ops; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| function justBody(resp) { | function justBody(resp) { | ||||||
|   return resp.body; |   return resp.body; | ||||||
| @ -52,6 +97,14 @@ function must201or200(resp) { | |||||||
|   } |   } | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
|  | function must204or200(resp) { | ||||||
|  |   if (![200, 204].includes(resp.statusCode)) { | ||||||
|  |     let err = new Error("[@root/paypal-checkout] bad response"); | ||||||
|  |     err.response = resp; | ||||||
|  |     throw err; | ||||||
|  |   } | ||||||
|  |   return resp; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| function enumify(obj) { | function enumify(obj) { | ||||||
| @ -127,15 +180,49 @@ Product.create = async function _createProduct({ | |||||||
|     .then(must201or200) |     .then(must201or200) | ||||||
|     .then(justBody); |     .then(justBody); | ||||||
| }; | }; | ||||||
| Product.list = async function _listProducts() { | 
 | ||||||
|  | Product.list = async function _listProducts(query = {}) { | ||||||
|  |   query = Object.assign({}, PayPal.__defaultQuery, query); | ||||||
|  |   let search = qs.stringify(query); | ||||||
|   return await PayPal.request({ |   return await PayPal.request({ | ||||||
|     url: "/v1/catalogs/products?page_size=20&total_required=true", |     url: `/v1/catalogs/products?${search}`, | ||||||
|     json: true, |     json: true, | ||||||
|   }) |   }) | ||||||
|     .then(must201or200) |     .then(must201or200) | ||||||
|     .then(justBody); |     .then(justBody); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | Product.details = async function _showProductDetails(id) { | ||||||
|  |   return await PayPal.request({ | ||||||
|  |     url: `/v1/catalogs/products/${id}`, | ||||||
|  |     json: true, | ||||||
|  |   }) | ||||||
|  |     .then(must201or200) | ||||||
|  |     .then(justBody); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Update product info | ||||||
|  |  * @param id | ||||||
|  |  * @param {{ | ||||||
|  |  *   description: string, | ||||||
|  |  *   category: string, | ||||||
|  |  *   image_url: string, | ||||||
|  |  *   home_url: string, | ||||||
|  |  * }} | ||||||
|  |  */ | ||||||
|  | Product.update = async function _updateProduct( | ||||||
|  |   id, | ||||||
|  |   { description, category, image_url, home_url } | ||||||
|  | ) { | ||||||
|  |   let body = PayPal._patch({ description, category, image_url, home_url }); | ||||||
|  |   return await PayPal.request({ | ||||||
|  |     method: "PATCH", | ||||||
|  |     url: `/v1/catalogs/products/${id}`, | ||||||
|  |     json: body, | ||||||
|  |   }).then(must204or200); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| let Plan = {}; | let Plan = {}; | ||||||
| Plan.intervals = { | Plan.intervals = { | ||||||
|   DAY: "DAY", |   DAY: "DAY", | ||||||
| @ -209,16 +296,50 @@ Plan.create = async function _createPlan({ | |||||||
|     .then(justBody); |     .then(justBody); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Plan.list = async function _listPlans() { | Plan.list = async function _listPlans(query = {}) { | ||||||
|   // TODO paging
 |   query = Object.assign({}, PayPal.__defaultQuery, query); | ||||||
|  |   let search = qs.stringify(query); | ||||||
|   return await PayPal.request({ |   return await PayPal.request({ | ||||||
|     url: "/v1/billing/plans?page_size=20&total_required=true", |     url: `/v1/billing/plans?${search}`, | ||||||
|     json: true, |     json: true, | ||||||
|   }) |   }) | ||||||
|     .then(must201or200) |     .then(must201or200) | ||||||
|     .then(justBody); |     .then(justBody); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | Plan.details = async function _showPlanDetails(id) { | ||||||
|  |   return await PayPal.request({ | ||||||
|  |     url: `/v1/billing/plans/${id}`, | ||||||
|  |     json: true, | ||||||
|  |   }) | ||||||
|  |     .then(must201or200) | ||||||
|  |     .then(justBody); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Update plan info | ||||||
|  |  * @param id | ||||||
|  |  * @param {{ | ||||||
|  |  *   description: string, | ||||||
|  |  *   payment_preferences.auto_bill_outstandin: boolean, | ||||||
|  |  *   taxes.percentage: string, | ||||||
|  |  *   payment_preferences.payment_failure_threshold: number, | ||||||
|  |  *   payment_preferences.setup_fee: string, | ||||||
|  |  *   payment_preferences.setup_fee_failure_action: string, | ||||||
|  |  * }} | ||||||
|  |  */ | ||||||
|  | Plan.update = async function _updatePlan( | ||||||
|  |   id, | ||||||
|  |   // TODO handle nested keys (ex: 'taxes.percentage')
 | ||||||
|  |   { description /*, payment_preferences, taxes*/ } | ||||||
|  | ) { | ||||||
|  |   return await PayPal.request({ | ||||||
|  |     method: "PATCH", | ||||||
|  |     url: `/v1/billing/plans/${id}`, | ||||||
|  |     json: PayPal._patch({ description }), | ||||||
|  |   }).then(must204or200); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| let Subscription = {}; | let Subscription = {}; | ||||||
| Subscription.actions = { | Subscription.actions = { | ||||||
|   CONTINUE: "CONTINUE", |   CONTINUE: "CONTINUE", | ||||||
| @ -298,7 +419,7 @@ Subscription.createRequest = async function _createSubscription({ | |||||||
|     .then(justBody); |     .then(justBody); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Subscription.get = async function _getSubscription(id) { | Subscription.details = async function _getSubscription(id) { | ||||||
|   return await PayPal.request({ |   return await PayPal.request({ | ||||||
|     url: `/v1/billing/subscriptions/${id}`, |     url: `/v1/billing/subscriptions/${id}`, | ||||||
|     json: true, |     json: true, | ||||||
| @ -307,6 +428,23 @@ Subscription.get = async function _getSubscription(id) { | |||||||
|     .then(justBody); |     .then(justBody); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Cancel a subscription (prevent future auto billing) | ||||||
|  |  * @param id | ||||||
|  |  * @param {{ | ||||||
|  |  *   reason: string | ||||||
|  |  * }} | ||||||
|  |  */ | ||||||
|  | Subscription.cancel = async function _showProductDetails(id, { reason }) { | ||||||
|  |   return await PayPal.request({ | ||||||
|  |     method: "POST", | ||||||
|  |     url: `/v1/catalogs/products/${id}/cancel`, | ||||||
|  |     json: { reason }, | ||||||
|  |   }) | ||||||
|  |     .then(must201or200) | ||||||
|  |     .then(justBody); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| module.exports.init = PayPal.init; | module.exports.init = PayPal.init; | ||||||
| module.exports.request = PayPal.request; | module.exports.request = PayPal.request; | ||||||
| module.exports.Plan = Plan; | module.exports.Plan = Plan; | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								test.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								test.js
									
									
									
									
									
								
							| @ -141,7 +141,7 @@ async function test() { | |||||||
|   }); |   }); | ||||||
|   process.stdin.pause(); |   process.stdin.pause(); | ||||||
| 
 | 
 | ||||||
|   let s = await Subscription.get(subscription.id); |   let s = await Subscription.details(subscription.id); | ||||||
|   console.info("Subscription: (After Approval)"); |   console.info("Subscription: (After Approval)"); | ||||||
|   console.info(JSON.stringify(s, null, 2)); |   console.info(JSON.stringify(s, null, 2)); | ||||||
|   console.info(); |   console.info(); | ||||||
|  | |||||||
| @ -16,13 +16,33 @@ let { Plan, Product, Subscription } = PayPal; | |||||||
| async function test() { | async function test() { | ||||||
|   let products = await Product.list(); |   let products = await Product.list(); | ||||||
|   console.info(); |   console.info(); | ||||||
|   console.info("Products:"); |   console.info("Products:", products.products.length); | ||||||
|   console.info(JSON.stringify(products, null, 2)); |   //console.info(JSON.stringify(products, null, 2));
 | ||||||
|  | 
 | ||||||
|  |   if (products.products.length) { | ||||||
|  |     let product = await Product.details(products.products[0].id); | ||||||
|  |     console.info("Product 0:"); | ||||||
|  |     console.info(JSON.stringify(product, null, 2)); | ||||||
|  | 
 | ||||||
|  |     await Product.update(product.id, { | ||||||
|  |       description: `Product Description 10${Math.random()}`, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   let plans = await Plan.list(); |   let plans = await Plan.list(); | ||||||
|   console.info(); |   console.info(); | ||||||
|   console.info("Plans:"); |   console.info("Plans:", plans.plans.length); | ||||||
|   console.info(JSON.stringify(plans, null, 2)); |   //console.info(JSON.stringify(plans, null, 2));
 | ||||||
|  | 
 | ||||||
|  |   if (plans.plans.length) { | ||||||
|  |     let plan = await Plan.details(plans.plans[0].id); | ||||||
|  |     console.info("Plan 0:"); | ||||||
|  |     console.info(JSON.stringify(plan, null, 2)); | ||||||
|  | 
 | ||||||
|  |     await Plan.update(plan.id, { | ||||||
|  |       description: `Plan Description 20${Math.random()}`, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   console.info(); |   console.info(); | ||||||
| } | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user