mirror of
				https://github.com/therootcompany/paypal-checkout.js.git
				synced 2025-10-30 12:52:46 +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. \ | ||||
| (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 | ||||
| npm install --save @root/paypal-checkout | ||||
| ``` | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| ```js | ||||
| "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 | ||||
| `constant`/`enum` representing an option you can pick from limited number of | ||||
| options. | ||||
|  | ||||
| @ -1,13 +1,34 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| let qs = require("querystring"); | ||||
| 
 | ||||
| let request = require("@root/request"); | ||||
| 
 | ||||
| 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.__baseUrl = PayPal.__sandboxUrl; | ||||
|   PayPal.__id = client_id; | ||||
|   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) { | ||||
|   let headers = {}; | ||||
| @ -24,6 +45,30 @@ PayPal.request = async function _paypalRequest(reqObj) { | ||||
|   }; | ||||
|   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) { | ||||
|   return resp.body; | ||||
| @ -52,6 +97,14 @@ function must201or200(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) { | ||||
| @ -127,15 +180,49 @@ Product.create = async function _createProduct({ | ||||
|     .then(must201or200) | ||||
|     .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({ | ||||
|     url: "/v1/catalogs/products?page_size=20&total_required=true", | ||||
|     url: `/v1/catalogs/products?${search}`, | ||||
|     json: true, | ||||
|   }) | ||||
|     .then(must201or200) | ||||
|     .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 = {}; | ||||
| Plan.intervals = { | ||||
|   DAY: "DAY", | ||||
| @ -209,16 +296,50 @@ Plan.create = async function _createPlan({ | ||||
|     .then(justBody); | ||||
| }; | ||||
| 
 | ||||
| Plan.list = async function _listPlans() { | ||||
|   // TODO paging
 | ||||
| Plan.list = async function _listPlans(query = {}) { | ||||
|   query = Object.assign({}, PayPal.__defaultQuery, query); | ||||
|   let search = qs.stringify(query); | ||||
|   return await PayPal.request({ | ||||
|     url: "/v1/billing/plans?page_size=20&total_required=true", | ||||
|     url: `/v1/billing/plans?${search}`, | ||||
|     json: true, | ||||
|   }) | ||||
|     .then(must201or200) | ||||
|     .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 = {}; | ||||
| Subscription.actions = { | ||||
|   CONTINUE: "CONTINUE", | ||||
| @ -298,7 +419,7 @@ Subscription.createRequest = async function _createSubscription({ | ||||
|     .then(justBody); | ||||
| }; | ||||
| 
 | ||||
| Subscription.get = async function _getSubscription(id) { | ||||
| Subscription.details = async function _getSubscription(id) { | ||||
|   return await PayPal.request({ | ||||
|     url: `/v1/billing/subscriptions/${id}`, | ||||
|     json: true, | ||||
| @ -307,6 +428,23 @@ Subscription.get = async function _getSubscription(id) { | ||||
|     .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.request = PayPal.request; | ||||
| module.exports.Plan = Plan; | ||||
|  | ||||
							
								
								
									
										2
									
								
								test.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								test.js
									
									
									
									
									
								
							| @ -141,7 +141,7 @@ async function test() { | ||||
|   }); | ||||
|   process.stdin.pause(); | ||||
| 
 | ||||
|   let s = await Subscription.get(subscription.id); | ||||
|   let s = await Subscription.details(subscription.id); | ||||
|   console.info("Subscription: (After Approval)"); | ||||
|   console.info(JSON.stringify(s, null, 2)); | ||||
|   console.info(); | ||||
|  | ||||
| @ -16,13 +16,33 @@ let { Plan, Product, Subscription } = PayPal; | ||||
| async function test() { | ||||
|   let products = await Product.list(); | ||||
|   console.info(); | ||||
|   console.info("Products:"); | ||||
|   console.info(JSON.stringify(products, null, 2)); | ||||
|   console.info("Products:", products.products.length); | ||||
|   //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(); | ||||
|   console.info(); | ||||
|   console.info("Plans:"); | ||||
|   console.info(JSON.stringify(plans, null, 2)); | ||||
|   console.info("Plans:", plans.plans.length); | ||||
|   //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(); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user