as

Settings
Sign out
Notifications
Alexa
亚马逊应用商店
Ring
AWS
文档
Support
Contact Us
My Cases

as

开发
测试
应用发布
盈利
用户参与
设备规格
资源

IAP v2.0示例网页应用

IAP v2.0示例网页应用

您可以从此处下载示例IAP网页应用:

要访问该示例,请在您的本地计算机上启动HTTP服务器。如果已安装python,则可以使用python HTTP服务器:

  1. 解压下载的文件。
  2. 在终端窗口中,导航至新解压的文件夹(IAPv2-web-app-sample)。
  3. 输入python -m SimpleHTTPServer以启动Python服务器。

有关其他服务器选项,请查看此Mozilla文章

如何运行该示例:

  1. 在浏览器中打开地址http://localhost:8000。​
  2. 按照页面上的说明使用该应用

示例描述

示例中的index.html文件包含以下内容:

  1. 包含Amazon Services库和测试库的脚本标签。Amazon Services库提供了IAP v2.0功能。
  2. 包含buttonclicker.js文件的脚本标签。
  3. 三个按钮的HTML。Button Clicker JavaScript文件会将方法联接到按钮操作。

Button Clicker JavaScript文件包含以下内容:


// 这是一个简单的模型-视图-控制器设置,用于应对
// “可以随时调用onPurchaseResponse”这一
// 事实。
//
// 在使用API时,知道“响应可能随时出现”
// 这一事实非常重要。例如,在传送收据之前,
// 您的网页应用可能已关闭。
// 在这种情况下,在下次运行应用时,
//将在初始化API后传送收据。

//模型数据
var state = {
    clicksLeft: 10,
    activeButton: "",
    hasRedButton: false,
    hasGreenButton: false,
    hasBlueButton: false,
    lastPurchaseCheckTime: null
}

// 使视图(HTML)看起来像模型

function refreshPageState() {
    $("#clicksLeft").text(state.clicksLeft);

    var button = $("#theButton");
    var redButton = $("#redButton");
    var greenButton = $("#greenButton");
    var blueButton = $("#blueButton");

    setClass(redButton, "locked", !state.hasRedButton);
    setClass(greenButton, "locked", !state.hasGreenButton);
    setClass(blueButton, "locked", !state.hasBlueButton);

    setClass(redButton, "active", state.activeButton == "red");
    setClass(greenButton, "active", state.activeButton == "green");
    setClass(blueButton, "active", state.activeButton == "blue");

    if (state.activeButton != "") {
        button.css("background-color", state.activeButton);
    } else {
        button.css("background-color", "gray");
    }
    persistPageState();
}

// 将状态保存到localStorage,以使应用
// 在下次运行时状态与前次相同。最重要的
// 是记住它的IAP状态,包括传递给getPurchaseUpdates的
// lastPurchaseCheckTime。
function persistPageState() {
    localStorage.setItem("state", JSON.stringify(state));
}

// 从localStorage还原状态
function loadPageState() {
    if ("state" in localStorage) {
        // 如果这是首次运行,则使用
        // 之前设置的默认值。
        state = JSON.parse(localStorage.getItem("state"));
    }
}

function setClass(element, className, condition) {
    if (condition) {
        element.addClass(className);
    } else {
        element.removeClass(className);
    }
}

// 控制器

// 使用消费品
function buttonPressed() {
    if (state.clicksLeft > 0) {
        state.clicksLeft--;
    } else {
        buyClicks();
    }
    refreshPageState();
}

// 行使权利
function redButtonPressed() {
    if (state.hasRedButton) {
        state.activeButton = "red";
    } else {
        buyButton("sample.redbutton");
    }
    refreshPageState();
}

// 行使权利
function greenButtonPressed() {
    if (state.hasGreenButton) {
        state.activeButton = "green";
    } else {
        buyButton("sample.greenbutton");
    }
    refreshPageState();
}

// 使用订阅
function blueButtonPressed() {
    if (state.hasBlueButton) {
        state.activeButton = "blue";
    } else {
        // 您需要购买特定订阅(添加了期限)
        buyButton("sample.bluebutton.subscription.1mo");
    }
    refreshPageState();
}

function buyClicks() {
    if (window.AmazonIapV2 == null) {
        alert(您的点击次数已用完,不过亚马逊应用内购买仅适用于亚马逊应用商店中的应用。);
    } else if (confirm(购买更多点击次数?)) {
        //window.AmazonIapV2.purchase('sample.clicks');
        var requestOptions = {
            sku: 'sample.clicks'
        };
        window.AmazonIapV2.purchase(
            function(operationResponse) {
                console.debug(operationResponse.requestId);
            },
            function(errorResponse) {
                console.debug(errorResponse);
            },
            [requestOptions]
        );
    }
}

function buyButton(buttonName) {
    if (window.AmazonIapV2 == null) {
        alert(您无法购买此按钮,亚马逊应用内购买仅适用于亚马逊应用商店中的应用。);
    } else {
        //window.AmazonIapV2.purchase(buttonName);
        var requestOptions = {
            sku: buttonName
        };
        window.AmazonIapV2.purchase(
            function(operationResponse) {
                console.debug(operationResponse.requestId);
            },
            function(errorResponse) {
                console.debug(errorResponse);
            },
            [requestOptions]
        );
    }
}

// 从回调中调用的处理程序函数

// purchaseItem将导致包含一个收据的购买响应
function onPurchaseResponse(e) {
    var response = e.response;
    if (response.status === 'SUCCESSFUL') {
        handleReceipt(response.purchaseReceipt);
    } else if (response.status === 'ALREADY_PURCHASED') {
        // 不知何故,我们没有与服务器同步。
        // 从起点刷新试试吧。
        var requestOptions = {
            reset: true
        };
        window.AmazonIapV2.getPurchaseUpdates(
            function(operationResponse) {
                // 处理操作响应
                var requestId = operationResponse.requestId;
                console.debug(requestId);
            },
            function(errorResponse) {
                // 处理错误响应
                console.debug(errorResponse);
            },
            [requestOptions]
        );
    } else if (response.status === 'FAILED') {
        alert('购买请求已中断。请稍后再试。');
    } else if (response.status === 'INVALID_SKU') {
        alert('SKU无效。请确保SKU配置。');
    }

    refreshPageState();
}

// getPurchaseUpdates将返回一个收据数组
function onPurchaseUpdatesResponse(e) {
    var response = e.response;
    for (var i = 0; i < response.receipts.length; i++) {
        handleReceipt(response.receipts[i]);
    }
    state.lastPurchaseCheckTime = response.offset;
    refreshPageState();
    if (response.hasMore) {
        // 如果还有更新未跟随
        // 此响应发送,请确保
        // 我们获得了其余的更新。
        var requestOptions = {
            reset: false
        };
        window.AmazonIapV2.getPurchaseUpdates(
            function(operationResponse) {
                // 处理操作响应
                var requestId = operationResponse.requestId;
                console.debug(requestId);
            },
            function(errorResponse) {
                // 处理错误响应
                console.debug(errorResponse);
            },
            [requestOptions]
        );
    }
}

// 在任一情况下,都以相同方式处理收据内容
function handleReceipt(receipt) {
    if (receipt.sku == "sample.redbutton") {
        // 权利
        state.hasRedButton = true;
    } else if (receipt.sku == "sample.greenbutton") {
        // 权利
        state.hasGreenButton = true;
    } else if (receipt.sku.substring(0, 30) == "sample.bluebutton.subscription") {
        // 订阅有时会返回父级ID,因此我们会与父级ID进行
        // 比较
        if (receipt.cancelDate == null) {
            // 如果我们在当前期间,则cancelDate为null
            state.hasBlueButton = true;
        }

    } else if (receipt.sku == "sample.clicks") {
        // 消费品
        state.clicksLeft += 10;
    }
    //如果尚未履行,则履行收据。
    //存储receiptId以跟踪已履行的商品,
    //然后对状态为FULFILLED的receiptId调用notifyFulfillment()。
    //如果由于该商品仅适用于之前的游戏状态
    //或游戏不支持该商品而无法进行而无法履行,则
    //
    notifyFulfillment(receipt.receiptId);
}

/**
* @function notifyFulfillment
* @description NotifyFulfillment通知亚马逊关于购买履行的情况。
* @param {String} receiptId receipt id
*/
function notifyFulfillment(receiptId) {
    //fulfillmentResult的状态不能为FULFILLED或UNAVAILABLE
    var requestOptions = {
        'receiptId': receiptId,
        'fulfillmentResult': 'FULFILLED'
    };
    window.AmazonIapV2.notifyFulfillment(
        function(operationResponse) {
            // 处理操作响应
            console.debug(operationResponse);
        },
        function(errorResponse) {
            // 处理错误响应
            console.debug(errorResponse);
        },
        [requestOptions]
    );

}

// 设置

function initialize() {
    loadPageState();
    amzn_wa.enableApiTester(amzn_wa_tester);
    refreshPageState();

    // “设置”按钮按下处理程序
    $("#theButton").click(function() { buttonPressed(); });
    $("#redButton").click(function() { redButtonPressed(); });
    $("#greenButton").click(function() { greenButtonPressed(); });
    $("#blueButton").click(function() { blueButtonPressed(); });

    document.addEventListener('amazonPlatformReady', function() {
        // 确保我们可以调用IAP API
        if (window.AmazonIapV2 === null) {
            console.debug('亚马逊应用内购买只适用于亚马逊应用商店中的应用');
        } else {
            window.AmazonIapV2.addListener('getUserDataResponse', function(resp) {});
            window.AmazonIapV2.addListener('getProductDataResponse', function(data) {});
            window.AmazonIapV2.addListener('purchaseResponse', this.onPurchaseResponse);
            window.AmazonIapV2.addListener('getPurchaseUpdatesResponse', this.onPurchaseUpdatesResponse);
        }
    }.bind(this));
}

$(function() {
    initialize();
});


").insertBefore(this); } }); }); $(document).ready(function() { if ($("#jumpoffset").length) { var scrollhash = document.getElementById('jumpoffset'); scrollhash.scrollIntoView(true); if (location.hash) { location.href = location.hash; } else { var scrollbody = document.querySelector('.mainColumn'); scrollbody.scrollIntoView(true); window.scroll(0, -500); } } }); $(document).ready(function() { if ($("#jump").length) { var scrollold = document.getElementById('jump'); scrollold.scrollIntoView(true); window.scroll(0, -100); if (location.hash) { location.href = location.hash; } } }); //resize container on local builds $(document).ready(function() { if (window.location.href.indexOf("/127.0.0.1:4000/") > -1) { document.querySelector('.container-fluid').style.position = 'fixed'; } }); //add underline to headers $(document).ready(function() { if ($(".productTitle").is(':contains("Alexa Skills Kit")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Skills Kit")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($(".productTitle").is(':contains("Alexa Presentation Language (APL)")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Skills Kit")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($(".productTitle").is(':contains("Alexa Connect Kit")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Connect Kit")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($(".productTitle").is(':contains("A/B Testing")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Skills Kit")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($(".productTitle").is(':contains("Alexa Smart Properties")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Smart Properties")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($(".productTitle").is(':contains("Alexa Conversations Developer Guide")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Skills Kit")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($(".productTitle").is(':contains("Alexa Design Guide")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Design Guide")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Design Guide")')) { $(this).css("padding", "0"); } }); } if ($(".productTitle").is(':contains("Alexa Gadgets Toolkit")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Gadgets Toolkit")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Gadgets Toolkit")')) { $(this).css("padding", "0"); } }); } if ($(".productTitle").is(':contains("Alexa Voice Service")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Alexa Voice Service")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($("h1").is(':contains("Alexa Reference Content")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Reference")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($("h1").is(':contains("Alexa Videos")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Videos")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($("h1").is(':contains("Alexa Code Samples")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Code Samples")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } if ($("h1").is(':contains("Documentation Home")')) { $(".dppnItem a").each(function() { if ($(this).is(':contains("Home")')) { $(this).css("border-bottom", "1.3px solid #FFFFFF"); } }); } }); $(document).ready(function () { const myScrollspy = $("#myScrollspy"); if (myScrollspy.length) { const containsList2orList3 = myScrollspy.find(".list2, .list3").length > 0; if (containsList2orList3) { myScrollspy.css("display", "block"); $(".fa.fa-info-circle").show(); } else { myScrollspy.css("display", "none"); $(".fa.fa-info-circle").hide(); } } }); /** * This function moves the 'page-not-translated' section from its original position * to a new position after the 'atd-categories-header' and ensures it is displayed * only in the new location. This is achieved by hiding the section initially, * moving it, and then displaying it again once it is in the correct position. */ $(document).ready(function() { const $pageNotTranslatedMessage = $('#page-not-translated'); const $desktopCategoriesHeader = $('.navigation-header.atd-categories-header'); const $mobileToggleHeader = $('.mobile-toggle'); if ($pageNotTranslatedMessage.length) { if ($mobileToggleHeader.length) { $pageNotTranslatedMessage.insertAfter($mobileToggleHeader); } else if ($desktopCategoriesHeader.length) { $pageNotTranslatedMessage.insertAfter($desktopCategoriesHeader); } $pageNotTranslatedMessage.show(); } }); // Function to handle smooth scrolling to target elements when links are clicked $(document).ready(function () { $('.customspy a, .mainColumn a').on('click', function (event) { const targetId = $(this).attr('href'); if (targetId && targetId.startsWith('#')) { event.preventDefault(); const targetElement = document.querySelector(`[id="${targetId.replace('#', '')}"]`); if (targetElement) { const hash = targetId.substring(1); window.history.pushState(null, '', `#${hash}`); targetElement.scrollIntoView(true); window.scroll(0, -500); } } }); }); window.addEventListener('DOMContentLoaded', function () { const targetId = window.location.hash; if (targetId) { const targetElement = document.querySelector(targetId); if (targetElement) { setTimeout(() => { targetElement.scrollIntoView(true); window.scrollBy(0, -500); }, 10); } } }); function activateContentBasedOnHash() { const currentHash = window.location.hash.substring(1); if (currentHash) { const contentElement = document.getElementById(currentHash); if (contentElement) { activateTabAndContent(contentElement); } } } function activateTabAndContent(contentElement) { const tabSection = contentElement.closest('.tab-pane'); if (tabSection) { const parentTabLink = document.querySelector(`a[href='#${tabSection.id}']`); if (parentTabLink) { const parentNavTabs = parentTabLink.closest('.nav-tabs'); if (parentNavTabs) { parentNavTabs.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active', 'show'); }); parentTabLink.classList.add('active', 'show'); } const parentTabContent = tabSection.closest('.tab-content'); if (parentTabContent) { parentTabContent.querySelectorAll('.tab-pane').forEach(tab => { tab.classList.remove('active', 'show'); }); tabSection.classList.add('active', 'show'); } } } } function scrollToHashWithOffset() { const tabHash = window.location.hash; const targetTabLink = document.querySelector(`.nav-tabs a[href="${tabHash}"]`); const mainContentColumn = document.querySelector('.inline'); if (targetTabLink && mainContentColumn) { const totalHeaderHeight = getTotalHeaderHeight() + 40; targetTabLink.scrollIntoView({behavior: 'smooth', block: 'start'}); setTimeout(() => { mainContentColumn.scrollBy(0, -totalHeaderHeight); }, 20); } } function getTotalHeaderHeight() { return [...document.querySelectorAll('.atd-services-header, .atd-categories-header')] .reduce((total, header) => total + (header ? header.offsetHeight : 0), 0); } function handleClick(event) { event.preventDefault(); const link = event.currentTarget; const hash = link.getAttribute('href').substring(1); const contentElement = document.getElementById(hash); if (contentElement) { activateTabAndContent(contentElement); scrollToHashWithOffset(); } } function handleBackToTopClick(event) { event.preventDefault(); const mainContentColumn = document.querySelector('.inline'); if (mainContentColumn) { mainContentColumn.scrollTo({top: 0, behavior: 'smooth'}); } window.scrollTo({top: 0, behavior: 'smooth'}); } document.addEventListener('DOMContentLoaded', () => { activateContentBasedOnHash(); scrollToHashWithOffset(); const myScrollspy = document.getElementById('myScrollspy'); if (myScrollspy) { myScrollspy.querySelectorAll('a[href^="#"]').forEach(link => { link.addEventListener('click', handleClick); }); } document.querySelectorAll('.dpfBackToTopLink, .backToTop').forEach(link => { link.addEventListener('click', handleBackToTopClick); }); }); // Show tab link as selected when clicked $(document).ready(function() { $('.nav-tabs > li > a').on('click', function(e) { e.preventDefault(); $(this).tab('show'); $(this).addClass('active show'); }); }); // Fix for when version dropdown is on the same page as nav tab dropdown // closes other dropdown menu to avoid overlap/usability issues $(document).ready(function() { // Version selector dropdown $('.versionsButton').click(function(e) { // Close the nav tab dropdown if open $('.nav-link.dropdown-toggle').siblings('.dropdown-menu').removeClass('show'); }); // Nav tab selector dropdown $('.nav-link.dropdown-toggle').click(function(e) { // Close the version dropdown if open $('.versionsButton').siblings('.dropdown-menu').removeClass('show'); }); }); // Handle active state management for dropdown menu in nav-tabs // Fixes edge case issues for when version selector dropdown and // nav-tab dropdown appear on the same page $(document).ready(function() { // Function to deactivate all tab panes and reset dropdown items function deactivateAllTabs() { $('.position .nav-item.dropdown .dropdown-menu .dropdown-item').each(function() { const targetId = $(this).attr('href'); $(targetId).removeClass('active show'); $(this).removeClass('active show'); }); } // Store the original pushState function const originalPushState = history.pushState; // Create a custom event for pushState const pushStateEvent = new Event('pushState'); // Override pushState history.pushState = function() { originalPushState.apply(this, arguments); // Dispatch the custom event window.dispatchEvent(pushStateEvent); }; // Add pushState listener window.addEventListener('pushState', function() { const params = new URLSearchParams(window.location.search); const vValue = params.get('v'); // Handle query parameter change from version selector if (vValue) { // Remove active classes from all tabs deactivateAllTabs(); $('.position').each(function() { var firstTabPane = $(this).find('.tab-pane').first(); if (firstTabPane.length > 0) { var tabId = firstTabPane.attr('id'); var dropdownItem = $('.dropdown-menu .dropdown-item[href="#' + tabId + '"]'); // Make first tab in each version active dropdownItem.addClass('active show'); firstTabPane.addClass('active show'); } }); } }); // Handle dropdown item clicks $('.position .nav-item.dropdown .dropdown-menu .dropdown-item').click(function(e) { e.preventDefault(); // Remove active classes from all tabs deactivateAllTabs(); // Add active class to clicked item $(this).addClass('active show'); // Show corresponding tab content const targetId = $(this).attr('href'); $(targetId).addClass('active show'); }); // Handle initial load from URL hash if (window.location.hash) { const tabId = window.location.hash; if ($(tabId).length) { // Deactivate all tabs first deactivateAllTabs(); // Activate the correct tab and content $(`.position .nav-item.dropdown .dropdown-menu .dropdown-item[href="${tabId}"]`).addClass('active show'); $(tabId).addClass('active show'); } } }); // Fix issue when anchor linking into a tab with a dropdown menu $(document).ready(function() { // Handle initial load from URL hash if (window.location.hash) { const tabId = window.location.hash; if ($(tabId).length) { // Deactivate active links in dropdown $('.nav-item.dropdown .dropdown-menu .dropdown-item').each(function() { $(this).removeClass('active show'); }); } } }); // Remove show class from caption elements to preserve table-caption display // (fixes issue where the show class, which forces the display style to block, // caused the table caption to appear inside the table, rather than above it) $(document).ready(function() { if (window.location.hash) { const anchorId = window.location.hash.substring(1); const targetElement = document.getElementById(anchorId); if (targetElement) { const captionParent = $(targetElement).closest('caption'); if (captionParent.length) { captionParent.removeClass('show'); } } } });