net-tools/paypal-js

客户端JS库,用于管理简单的电子商务网站,包括购物车和PayPal

安装: 77

依赖项: 0

建议者: 0

安全性: 0

星级: 0

关注者: 2

分支: 0

开放性问题: 0

语言:JavaScript

1.1.28 2023-11-01 10:07 UTC

README

客户端JS库,用于管理简单的电子商务网站,包括购物车和PayPal

设置说明

要安装 net-tools/paypal-js 包,只需通过 composer 引入,并在 HEAD 部分插入一个脚本标签

<script src="/path_to_vendor/net-tools/paypal-js/src/js/client.js"></script>

请注意,net-tools/js-core 也需要安装

<script src="/path_to_vendor/net-tools/js-core/src/js-core.js.min"></script>

如何使用(简单过程)

如果不需要处理购物车,并且不需要客户数据,脚本相当简单

var shop = new NTPaypal.Shop('EUR');
shop.expressButtons('product title', 10.50, 'PHYSICAL_GOODS', '#selector_here')

expressButtons 方法期望一个标题、一个金额、一个类别('PHYSICAL_GOODS'、'DIGITAL_GOODS'、'DONATION')和一个选择器来绘制PayPal按钮。可以省略类别参数(null);它返回一个Promise对象,当支付完成时解析(否则,Promise被拒绝)。

下面是一个如何使用Promise的示例。

如何使用(完整过程,具有购物车支持和流畅API)

对于更复杂的情况,例如购买多个产品、处理运费等,我们必须处理其他对象。一种方法是使用程序化API(见下一章),通过在调用 paypalButtons 方法来初始化支付之前创建客户、购物车和订单对象。

另一种创建所需数据的方法是使用提供的流畅API。

设置PayPal脚本

首先,必须在脚本标签中包含PayPal库(将 XXXXXX 替换为您的paypal客户端ID)

<script src="https://www.paypal.com/sdk/js?currency=EUR&client-id=XXXXXX"></script>

使用流畅API

首先,我们创建一个带有所需货币的 Shop 对象;Shop对象作为工厂来构建其他所需对象

var shop = new NTPaypal.Shop('EUR');

然后,我们开始使用流畅API,从 sell 方法开始。它接受一个 Product 对象来表示购买的单个项目。如果购买不同的产品,可能每个产品的数量不同,则 sell 方法可以接受一个 ProductQuantity 对象数组(关联产品数量)。产品对象可以以编程方式创建,或者使用流畅API提供的 Shop.product 方法(具有标题、成本和类别参数)

shop.sell(shop.product('Product Label', 3.45, 'PHYSICAL_GOODS'))

然后,如果需要,我们使用产品流畅方法

shop.sell(
		shop.product('Product Label', 3.45, 'PHYSICAL_GOODS')
				.setTax(12)
				.withDescription('Fantastic product here')
				.setSku('EANXXXXXXX')
	)

为了设置产品的单位数量,我们使用 Shop.product 返回的产品对象上的流畅方法 setQuantity。但是,重要的是要注意,setQuantity 不返回产品对象,而是一个新的 ProductQuantity 类对象,从而防止进一步链式调用。换句话说,必须在 setTaxwithDescriptionsetSku 调用之前调用

shop.sell(
		shop.product('Product Label', 3.45, 'PHYSICAL_GOODS')
				.setTax(12)
				.withDescription('Fantastic product here')
				.setSku('EANXXXXXXX')
				.setQuantity(3)
	)

sell 方法返回一个 Sale 对象,提供了几个流畅方法(towithShippingwithDescriptionwithCustom_id),使得可以链式调用以指定客户、运费、描述和客户端ID。

shop.sell(
		shop.product('Product Label', 3.45, 'PHYSICAL_GOODS')
				.setTax(12)
				.withDescription('Fantastic product here')
				.setSku('EANXXXXXXX')
	)	
	.withShipping(32)
	.withDescription('Order received, thanks')
	.withCustom_id('Invoice nABC123')
	.to(
		shop.customer()
			.named('John', 'Doe'),
			.living('123, fifth Avenue')
			.in('75000', 'PARIS', 'FR')
			.withEmail('john.doe@gmail.com')
			.withPhone('0601020304', 'MOBILE')
	)

在这里,Sale对象已准备好;请注意,所有流畅API调用(除 sell 及其参数外)都是可选的。为了准备PayPal按钮的渲染,我们调用 payButtonsInside 方法(带有DOM树中的目标选择器),它准备一个Payment对象,然后我们调用其 execute 方法来显示按钮。

shop.sell(
		shop.product('Product Label', 3.45, 'PHYSICAL_GOODS')
				.setTax(12)
				.withDescription('Fantastic product here')
				.setSku('EANXXXXXXX')
	)	
	.withShipping(32)
	.withDescription('Order received, thanks')
	.to(
		shop.customer()
			.named('John', 'Doe'),
			.living('123, fifth Avenue')
			.in('75000', 'PARIS', 'FR')
			.withEmail('john.doe@gmail.com')
			.withPhone('0601020304', 'MOBILE')
	)
	.payButtonsInside('#div_paypal_here')
	.execute();

如果需要一些PayPal应用程序上下文参数,它们可以通过定义 setwithApplicationContext 方法的Payment对象来设置

shop.sell(
		shop.product('Product Label', 3.45, 'PHYSICAL_GOODS')
				.setTax(12)
				.withDescription('Fantastic product here')
				.setSku('EANXXXXXXX')
	)	
	.withShipping(32)
	.withDescription('Order received, thanks')
	.to(
		shop.customer()
			.named('John', 'Doe'),
			.living('123, fifth Avenue')
			.in('75000', 'PARIS', 'FR')
			.withEmail('john.doe@gmail.com')
			.withPhone('0601020304', 'MOBILE')
	)
	.payButtonsInside('#div_paypal_here')
	.set('shipping_preference', 'NO_SHIPPING')
	.execute();

sell 方法返回一个Promise,当支付完成时解析。

如何使用(完整流程,包含购物车支持,程序化API)

此库定义了几个JavaScript对象来管理产品、客户详情、购物车、订单,并使启动PayPal支付窗口成为可能。

设置PayPal脚本

首先,必须在脚本标签中包含PayPal库(将 XXXXXX 替换为您的paypal客户端ID)

<script src="https://www.paypal.com/sdk/js?currency=EUR&client-id=XXXXXX"></script>

如果需要,可以将PayPal库的加载推迟到显示PayPal "购买"按钮(见下文)。

创建对象

首先,我们创建一个带有所需货币的 Shop 对象;Shop对象作为工厂来构建其他所需对象

var shop = new NTPaypal.Shop('EUR');

然后我们创建客户和产品对象。请注意,必填值作为常规参数传递给构造函数,而非必填项通过一个other对象字面量参数传递。

var cust = shop.newCustomer('John', {
	surname : 'doe',
	address1 : '1 Kensington Avenue', 
	address2 : 'Building B', 
	zipcode : '75000', 
	city : 'PARIS', 
	countrycode : 'FR'
	email : 'john.doe@gmail.com', 
	phone : '0601020304',
	phone_type : 'MOBILE'
});

var p1 = shop.newProduct('Product 1', 12.50, 'PHYSICAL_GOODS', {
		sku : 'EAN123456',
 		tax : 1.12, 
		description : 'Great product 1 here'
	});

我们创建一个购物车,里面装满了商品(在这里,一个包含5个单位的商品)和适当的数量。

var cart = shop.newCart([shop.newProductQuantity(p1, 5)]);

最后,我们创建一个订单,将这些对象绑定在一起

var order = shop.newOrder(cart, cust);

到目前为止,还没有显示PayPal按钮。我们仍然可以使用相关的购物车对象方法(addremovesetQuantitycontainscountempty)来更新购物车。

然后,要显示PayPal "立即购买"按钮,我们调用Shop对象中的适当函数

shop.paypalButtons(order, '#paypal_buttons_container');

第二个参数是一个选择器,用于标识将包含PayPal按钮的DOM标签(通常是一个DIV标签);将#paypal_buttons_container替换为任何相关的选择器。在方法调用完成后,PayPal按钮将在容器中渲染,用户可以开始支付。

还有一个第三个参数,这使得可以传递application_context参数到PayPal API。主要用途是将它设置为{ shipping_preference : 'NO_SHIPPING' },从而移除一些不必要的客户地址文本字段(仅在点击“通过卡支付”黑色按钮时)。

数据捕获

上面的JavaScript行使客户端可以进行支付,但我们还没有回调来捕获任何相关数据(如交易ID)或发送“感谢”电子邮件。

paypalButton返回一个Promise,当解析时,将传递有关交易的数据

shop.paypalButtons(order, '#paypal_buttons_container').then(
	function(data){
		alert('Transaction is OK, with ID ' + data.purchase_units[0].payments.captures[0].id);
	}
);

保存/恢复购物车和购物车项目到浏览器存储

由于用户可能浏览多个页面来创建订单,将多个产品放入购物车,因此需要在页面加载、重新加载等之间保存当前购物车内容。

NTPaypal客户端库提供了3种存储策略:cookies(浏览器关闭时购物车内容丢失)、localStorage(购物车内容永远不会丢失)、sessionStorage(页面关闭时购物车内容丢失,但浏览器没有关闭)。

要将购物车内容保存到浏览器存储,必须创建一个具有适当参数的Session对象;然后调用save方法。要恢复数据,应调用restore方法。

// creating session
// save/restore will be done through CookiesStorage interface (please note that you mustn't create a CookiesStorage object with new keyword, just pass the constructor reference)
// create new Session object, with storage interface constructor, shop/cart/store objects
var s = new NTPaypal.Session(NTPaypal.CookiesStorage, shop, cart);

// saving a previously created Cart object
s.save();

// ...... some time later
var cart = s.restore();

管理库存和购物车

加载脚本

与商店相关的函数在一个单独的javascript文件中

<script src="/path_to_vendor/net-tools/paypal-js/src/js/store.js"></script>

创建商店

对于简单的支付流程,像上面示例中那样从头创建购物车就足够了。然而,当创建在线商店时,处理产品库存是强制性的;它防止用户将比商店中可用的更多单位的商品放入购物车。

创建一个包含产品和其数量的商店对象,与创建购物车非常相似

// creating 2 products with respectively 20 and 10 units
var p1 = new NTPaypal.ProductQuantity(new NTPaypal.Product('store_product_1', 11.11, 'PHYSICAL_GOODS', 'EUR', { sku : 'prd_1' }), 20);
var p2 = new NTPaypal.ProductQuantity(new NTPaypal.Product('store_product_2', 22.22, 'PHYSICAL_GOODS', 'EUR', { sku : 'prd_2' }), 10);
		
var store = new NTPaypal.Store([p1, p2]);

由于每种产品的数量可能来自服务器端的数据库,因此可以使用Json数据创建javascript商店对象

var store = NTPaypal.Store.fromJson('{
   "inventory": {
      "items": [
         {
            "product": {
               "title": "store_product_1",
               "price": 11.11,
               "category": "PHYSICAL_GOODS",
               "tax": 0,
               "withDescription": "",
               "sku": "prd_1",
               "currency_code": "EUR"
            },
            "quantity": 20
         },
         {
            "product": {
               "title": "store_product_2",
               "price": 22.22,
               "category": "PHYSICAL_GOODS",
               "tax": 0,
               "withDescription": "",
               "sku": "prd_2",
               "currency_code": "EUR"
            },
            "quantity": 10
         }
      ]
   }
}');

将商品添加到购物车

要将商品从商店对象移动到之前创建的购物车对象中

store.addToCart('prd_1', cart);

如果给定的SKU产品不存在,则抛出一个错误异常。如果没有更多单位可用在商店中,我们不能将其移动到购物车中,方法addToCart返回false。

将购物车中的商品移回商店

要将购物车中某种产品的所有单位移除并放回商店

store.removeFromCart('prd_1', cart);

处理购物车中的数量

要更新购物车中产品的数量,有两种方法:一种用于设置数量,另一种用于从当前购物车中的数量添加/移除单位。

// set the amount to 5
store.setCartQuantity('prd_1', 5, cart);

// set the amount to the current amount in the cart minus 1
store.updateCartQuantity('prd_2', -1, cart);

使用库存管理从存储中加载购物车

如果管理库存数量,则在从存储中加载购物车时必须更新商店,以确保商店中的可用数量与总数量减去购物车中的数量相匹配。

为此,必须使用带有商店对象参数的会话对象,并将实现处理特定情况回调的SessionStoreInterface对象传递给恢复方法。

// creating session
var s = new NTPaypal.Session(NTPaypal.CookiesStorage, shop, cart, store);

// ...... some time later
var cart = s.restore(new NTPaypal.SessionStoreInterface(
		function(product)
		{
			// deal with lower quantity available issues
		},
		
		function(product)
		{
			// deal with product unavailable issues
		}
	));

使用UI

基础知识

提供了一个简单的JavaScript购物车/配送/确认UI。在3个连续的屏幕中,它可以

  • 显示购物车内容(增加/减少购物车中项目的数量)
  • 询问配送详情
  • 提示用户选择承运人(非强制),要求销售条款协议并显示PayPal按钮

默认UI语言为英语,但可以轻松实现翻译(请参阅提供的i18n-fr.js文件中的法语翻译)。

尽管UI脚本相当大(在单独的ui.js文件中),但使用相对简单,只需要少量强制参数。

var ui = new NTPaypal.UI("#container", session, {
		
	// get a list of countries available for user shipping ; returns an array of object litterals (country, code, isDefault)
	onCountries : function(cart, customer){
		return [ {country:'USA', code:'US', isDefault:true}, {country:'United Kingdom', code:'GB'} ];
	},


	// do some stuff when payment done
	onPaymentReceived : function(cart, customer, paypalData){
		alert("Payment done with paypal transaction id " + paypalData.purchase_units[0].payments.captures[0].id);
	},
	
	
	// do some stuff after 'payment done' called, during process cleaning
	onPaymentCompleted : function()
	{
		alert('Thanks you, now redirecting you to home page');
		document.location = '/';
	},


	// return a float with shipping cost for cart/customer
	onShippingCost : function(cart, customer){
		return 12.32;
	},


	// return a custom message displayed above buttons
	onCustomMessage : function(cart, customer){
		return "This is a custom message";
	},
	
	
	// display a link to Terms of Sales (not mandatory)
	TOSLink : { url : 'https://XXXXXXX', title:'Terms Of Sales : click here' }
});


ui.show();

高级使用:询问用户选择的承运人

在上面的示例中,onShippingCost回调返回一个带有定义好的配送费用的浮点数(可能是0);我们可以要求用户选择其承运人。承运人由名称、成本、配送时间、图片定义;最后两个参数不是强制性的。

// returning an array of carriers, ie object litterals (carrier, cost, shipping_time, image)
onShippingCost : function(cart, customer){
	return [ {carrier:"DHL", cost:24.50, shipping_time:"2 days"}, {carrier:"UPS", cost:20.89, shipping_time:"1 day"} ];
},

使用上述代码,用户将通过列表框选择承运人。

如果用户更喜欢,可以通过点击与承运人图标链接的单选按钮来选择承运人。

// returning an array of carriers, ie object litterals (carrier, cost, shipping_time, image)
onShippingCost : function(cart, customer){
	return [ {carrier:"DHL", cost:24.50, image:"url"}, {carrier:"UPS", cost:20.89, image:"data:image/png;base64,xxxxxxx"} ];
},

要使用单选按钮和图片替换列表框,我们添加一个image属性(可以链接到URL或可以是base64编码的数据字符串)。

carriers.json文件中提供了一些base64编码的图片;建议将所需的图片复制/粘贴到您的代码中,没有必要包含包含多个承运人图标的庞大文件,而您可能只需要一个或两个图标。

高级使用:实现异步onPaymentReceived处理器

在上面的示例中,调用、执行onPaymentReceived处理器,并在其完成后,控制权返回到ui.js脚本,该脚本将进行购物车和会话清理。

如果在onPaymentReceived中必须在客户端执行某些异步任务,在完成此异步任务之前清理购物车和会话可能是不正确的。

在这种情况下,onPaymentReceived必须返回一个Promise,当ui.js可以在清理时链接(换句话说,当客户端任务完成时)时将解决。例如,如果在onPaymentReceived中使用Fetch API调用某些服务器端脚本,我们可以在链接fetch()时将Promise构造函数的resolve方法的then参数。

onPaymentReceived : function(cart, customer, paypal){
		return new Promise(function(resolve, reject){
			console.log(paypal); alert('Payment done, calling server-side script with Fetch API !');

			fetch('/path/to/script.php', {paypal_id : paypal.purchase_units[0].payments.captures[0].id})
				.then(resolve)
				.catch(reject);
		});
	},