Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion packages/ui-extensions/src/surfaces/point-of-sale/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ export type {

export type {OrderApiContent, OrderApi} from './render/api/order-api/order-api';

export type {PaymentDetailsApi} from './render/api/payment-details-api/payment-details-api';

export type {
PaymentApi,
PaymentApiContent,
PaymentAttemptResult,
PaymentAttemptOptions,
PaymentAttemptStatus,
PaymentResultCallback,
} from './render/api/payment-api/payment-api';

export type {
ProductApi,
ProductApiContent,
Expand Down Expand Up @@ -107,7 +118,12 @@ export type {

export type {TaxLine} from './types/tax-line';

export type {PaymentMethod, Payment} from './types/payment';
export type {
PaymentMethod,
Payment,
PaymentTerminal,
PaymentWithDetails,
} from './types/payment';

export type {MultipleResourceResult} from './types/multiple-resource-result';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Payment API

The Payment API provides functionality for handling payment attempts and notifications in point-of-sale extensions.

## Features

- **Payment Processing**: Initiate payment attempts with callbacks for results
- **Status Tracking**: Monitor payment attempt status in real-time
- **Terminal Management**: Discover and manage payment terminals
- **Result Notifications**: Notify about payment results from external processors

## Usage

### Basic Payment Attempt

```typescript
import type {
PaymentApi,
PaymentAttemptOptions,
} from '@shopify/ui-extensions/point-of-sale';

export default function PaymentExtension(root, {payment}: PaymentApi) {
async function processPayment() {
const options: PaymentAttemptOptions = {
payment: {
amount: 1500, // $15.00 in cents
currency: 'USD',
type: 'CreditCard',
description: 'Product purchase',
},
timeout: 30000, // 30 seconds
requireConfirmation: true,
};

const attemptId = await payment.attemptPayment(options, (result) => {
console.log('Payment result:', result);

if (result.status === 'completed') {
console.log(
'Payment successful!',
result.details?.transactionReference,
);
} else if (result.status === 'failed') {
console.error('Payment failed:', result.details?.errorMessage);
}
});

console.log('Payment attempt initiated:', attemptId);
}
}
```

### Terminal Management

```typescript
export default function TerminalExtension(root, {payment}: PaymentApi) {
async function setupTerminal() {
// Get available terminals
const terminals = await payment.getAvailableTerminals();
console.log('Available terminals:', terminals);

// Set preferred terminal
if (terminals.length > 0) {
await payment.setPreferredTerminal(terminals[0].id);
console.log('Preferred terminal set:', terminals[0].name);
}
}
}
```

### Payment Status Monitoring

```typescript
export default function MonitoringExtension(root, {payment}: PaymentApi) {
async function monitorPayment(attemptId: string) {
const status = await payment.getPaymentAttemptStatus(attemptId);
console.log('Current payment status:', status);

// Cancel if needed
if (status === 'pending') {
await payment.cancelPaymentAttempt(attemptId);
console.log('Payment cancelled');
}
}
}
```

### External Payment Processor Integration

```typescript
export default function ExternalProcessorExtension(
root,
{payment}: PaymentApi,
) {
function handleExternalPaymentResult(externalResult: any) {
// Notify the POS system about payment result from external processor
payment.notifyPaymentResult({
attemptId: externalResult.attemptId,
payment: {
amount: externalResult.amount,
currency: externalResult.currency,
type: 'StripeCreditCard',
terminal: {
id: externalResult.terminalId,
name: 'Stripe Terminal',
type: 'stripe_terminal',
isOnline: true,
},
},
status: externalResult.success ? 'completed' : 'failed',
details: {
transactionReference: externalResult.transactionId,
errorMessage: externalResult.errorMessage,
metadata: externalResult.metadata,
},
completedAt: new Date(),
});
}
}
```

## Types

### PaymentAttemptStatus

```typescript
type PaymentAttemptStatus =
| 'pending' // Payment is waiting to be processed
| 'processing' // Payment is currently being processed
| 'completed' // Payment completed successfully
| 'failed' // Payment failed
| 'cancelled' // Payment was cancelled
| 'timeout'; // Payment timed out
```

### PaymentAttemptResult

Contains the complete result of a payment attempt including status, payment details, and any error information.

### PaymentWithDetails

Extended payment information that includes terminal details, tip amounts, and customer-facing descriptions.

### PaymentTerminal

Information about payment terminals including their ID, name, type, and online status.

## Best Practices

1. **Error Handling**: Always handle payment failures gracefully and provide clear error messages
2. **Timeouts**: Set appropriate timeout values based on payment method and expected processing time
3. **Terminal Selection**: Check terminal availability before attempting payments
4. **Status Monitoring**: Monitor payment status for long-running transactions
5. **Security**: Never log sensitive payment information in production
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type {PaymentWithDetails, PaymentTerminal} from '../../../types/payment';

/**
* Represents the status of a payment attempt
*/
export type PaymentAttemptStatus =
| 'pending'
| 'processing'
| 'completed'
| 'failed'
| 'cancelled'
| 'timeout';

/**
* Represents the result of a payment attempt
*/
export interface PaymentAttemptResult {
/** Unique identifier for the payment attempt */
attemptId: string;
/** The payment information */
payment: PaymentWithDetails;
/** The status of the payment attempt */
status: PaymentAttemptStatus;
/** Additional details about the payment result */
details?: {
/** Error message if the payment failed */
errorMessage?: string;
/** Error code if the payment failed */
errorCode?: string;
/** Transaction reference from the payment processor */
transactionReference?: string;
/** Additional metadata from the payment processor */
metadata?: Record<string, any>;
};
/** Timestamp when the payment attempt was completed */
completedAt: Date;
}

/**
* Options for initiating a payment attempt
*/
export interface PaymentAttemptOptions {
/** The payment information */
payment: PaymentWithDetails;
/** Optional metadata to associate with the payment attempt */
metadata?: Record<string, any>;
/** Timeout in milliseconds for the payment attempt (default: 30000) */
timeout?: number;
/** Whether to require confirmation before processing */
requireConfirmation?: boolean;
}

/**
* Callback function for payment attempt results
*/
export type PaymentResultCallback = (result: PaymentAttemptResult) => void;

export interface PaymentApiContent {
/**
* Initiates a payment attempt and notifies the result via callback.
* @param options Payment attempt options including payment details
* @param callback Callback function to receive the payment result
* @returns A promise that resolves with the attempt ID
*/
attemptPayment: (
options: PaymentAttemptOptions,
callback: PaymentResultCallback,
) => Promise<string>;

/**
* Cancels a pending payment attempt.
* @param attemptId The ID of the payment attempt to cancel
* @returns A promise that resolves when the cancellation is processed
*/
cancelPaymentAttempt: (attemptId: string) => Promise<void>;

/**
* Gets the current status of a payment attempt.
* @param attemptId The ID of the payment attempt
* @returns A promise that resolves with the current status
*/
getPaymentAttemptStatus: (
attemptId: string,
) => Promise<PaymentAttemptStatus | undefined>;

/**
* Notifies about a payment attempt result. This is typically used
* when integrating with external payment processors.
* @param result The payment attempt result to notify
*/
notifyPaymentResult: (result: PaymentAttemptResult) => void;

/**
* Gets a list of available payment terminals.
* @returns A promise that resolves with available terminals
*/
getAvailableTerminals: () => Promise<PaymentTerminal[]>;

/**
* Sets the preferred terminal for payment processing.
* @param terminalId The ID of the terminal to use
* @returns A promise that resolves when the terminal is set
*/
setPreferredTerminal: (terminalId: string) => Promise<void>;
}

/**
* Payment API for handling payment attempts and notifications
*/
export interface PaymentApi {
payment: PaymentApiContent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {Payment} from '../../../types/payment';

export interface PaymentDetailsApi {
paymentDetails: Payment;
}
23 changes: 20 additions & 3 deletions packages/ui-extensions/src/surfaces/point-of-sale/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {
DraftOrderApi,
ProductApi,
OrderApi,
PaymentDetailsApi,
PaymentApi,
} from './api';
import type {RenderExtension} from './extension';
import type {Components} from './shared';
Expand Down Expand Up @@ -159,6 +161,21 @@ export interface ExtensionTargets {
CartLineItemApi,
BlockComponents
>;
'pos.checkout.payment-options.action.render': RenderExtension<
ActionTargetApi<'pos.checkout.payment-options.action.render'> &
CartApi &
PaymentDetailsApi &
PaymentApi,
BasicComponents
>;
'pos.checkout.payment-options.action.menu-item.render': RenderExtension<
StandardApi<'pos.checkout.payment-options.action.menu-item.render'> &
ActionApi &
CartApi &
PaymentDetailsApi &
PaymentApi,
ActionComponents
>;
'pos.receipt-footer.block.render': RenderExtension<
// NOTE: key/any type is cause of no arg useApi() that includes all target types.
// stop using useApi() with no args, instead specify the target type explicitly.
Expand All @@ -175,14 +192,14 @@ export type ExtensionForExtensionTarget<T extends ExtensionTarget> =

/**
* For a given extension target, returns the value that is expected to be
* returned by that extension targets callback type.
* returned by that extension target's callback type.
*/
export type ReturnTypeForExtension<ID extends keyof ExtensionTargets> =
ReturnType<ExtensionTargets[ID]>;

/**
* For a given extension target, returns the tuple of arguments that would
* be provided to that extension targets callback type.
* be provided to that extension target's callback type.
*/
export type ArgumentsForExtension<ID extends keyof ExtensionTargets> =
Parameters<ExtensionTargets[ID]>;
Expand All @@ -203,7 +220,7 @@ export type RenderExtensionTarget = {
}[keyof ExtensionTargets];

/**
* A mapping of each render extension name to its callback type.
* A mapping of each "render extension" name to its callback type.
*/
export type RenderExtensions = {
[ID in RenderExtensionTarget]: ExtensionTargets[ID];
Expand Down
30 changes: 30 additions & 0 deletions packages/ui-extensions/src/surfaces/point-of-sale/types/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,33 @@ export interface Payment {
currency: string;
type: PaymentMethod;
}

/**
* Information about the payment terminal or device used for processing
*/
export interface PaymentTerminal {
/** Unique identifier for the terminal */
id: string;
/** Human-readable name of the terminal */
name: string;
/** Type of terminal (e.g., 'stripe_terminal', 'square_terminal', etc.) */
type: string;
/** Whether the terminal is currently online and available */
isOnline: boolean;
/** Additional terminal-specific metadata */
metadata?: Record<string, any>;
}

/**
* Extended payment information with additional processing details
*/
export interface PaymentWithDetails extends Payment {
/** Optional payment terminal information */
terminal?: PaymentTerminal;
/** Optional tip amount */
tip?: number;
/** Whether the payment supports partial payments */
allowPartial?: boolean;
/** Customer-facing description of the payment */
description?: string;
}