Browse Source

feat(core): Allow public & private Payment metadata

Closes #476

BREAKING CHANGE: The `Payment.metadata` field is not private by default, meaning that it can only be read via the Admin API. Data required in the Shop API can be accessed by putting it in a field named `public`. Example: `Payment.metadata.public.redirectUrl`
Michael Bromley 5 years ago
parent
commit
3f72311635

+ 10 - 1
docs/content/docs/developer-guide/payment-integrations/index.md

@@ -57,7 +57,16 @@ const myPaymentIntegration = new PaymentMethodHandler({
         amount: order.total,
         state: 'Authorized' as const,
         transactionId: result.id.toString(),
-        metadata: result.outcome,
+        metadata: {
+          cardInfo: result.cardInfo,
+          // Any metadata in the `public` field
+          // will be available in the Shop API,
+          // All other metadata is private and 
+          // only available in the Admin API.
+          public: {
+            referenceCode: result.publicId,
+          }
+        },
       };
     } catch (err) {
       return {

+ 22 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -4396,6 +4396,20 @@ export type GetUiStateQuery = { uiState: (
     & Pick<UiState, 'language'>
   ) };
 
+export type GetClientStateQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type GetClientStateQuery = { networkStatus: (
+    { __typename?: 'NetworkStatus' }
+    & Pick<NetworkStatus, 'inFlightRequests'>
+  ), userStatus: (
+    { __typename?: 'UserStatus' }
+    & UserStatusFragment
+  ), uiState: (
+    { __typename?: 'UiState' }
+    & Pick<UiState, 'language'>
+  ) };
+
 export type SetActiveChannelMutationVariables = Exact<{
   channelId: Scalars['ID'];
 }>;
@@ -7077,6 +7091,14 @@ export namespace GetUiState {
   export type UiState = (NonNullable<GetUiStateQuery['uiState']>);
 }
 
+export namespace GetClientState {
+  export type Variables = GetClientStateQueryVariables;
+  export type Query = GetClientStateQuery;
+  export type NetworkStatus = (NonNullable<GetClientStateQuery['networkStatus']>);
+  export type UserStatus = (NonNullable<GetClientStateQuery['userStatus']>);
+  export type UiState = (NonNullable<GetClientStateQuery['uiState']>);
+}
+
 export namespace SetActiveChannel {
   export type Variables = SetActiveChannelMutationVariables;
   export type Mutation = SetActiveChannelMutation;

+ 15 - 4
packages/core/e2e/fixtures/test-payment-methods.ts

@@ -11,7 +11,7 @@ export const testSuccessfulPaymentMethod = new PaymentMethodHandler({
             amount: order.total,
             state: 'Settled',
             transactionId: '12345',
-            metadata,
+            metadata: { public: metadata },
         };
     },
     settlePayment: order => ({
@@ -32,7 +32,7 @@ export const twoStagePaymentMethod = new PaymentMethodHandler({
             amount: order.total,
             state: 'Authorized',
             transactionId: '12345',
-            metadata,
+            metadata: { public: metadata },
         };
     },
     settlePayment: () => {
@@ -87,13 +87,24 @@ export const failsToSettlePaymentMethod = new PaymentMethodHandler({
             amount: order.total,
             state: 'Authorized',
             transactionId: '12345',
-            metadata,
+            metadata: {
+                privateCreatePaymentData: 'secret',
+                public: {
+                    publicCreatePaymentData: 'public',
+                },
+            },
         };
     },
     settlePayment: () => {
         return {
             success: false,
             errorMessage: 'Something went horribly wrong',
+            metadata: {
+                privateSettlePaymentData: 'secret',
+                public: {
+                    publicSettlePaymentData: 'public',
+                },
+            },
         };
     },
 });
@@ -106,7 +117,7 @@ export const testFailingPaymentMethod = new PaymentMethodHandler({
             amount: order.total,
             state: 'Declined',
             errorMessage: 'Insufficient funds',
-            metadata,
+            metadata: { public: metadata },
         };
     },
     settlePayment: order => ({

+ 256 - 237
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -5035,125 +5035,6 @@ export type GetPromoProductsQuery = {
     };
 };
 
-export type SettlePaymentMutationVariables = Exact<{
-    id: Scalars['ID'];
-}>;
-
-export type SettlePaymentMutation = {
-    settlePayment:
-        | PaymentFragment
-        | Pick<SettlePaymentError, 'errorCode' | 'message' | 'paymentErrorMessage'>
-        | Pick<PaymentStateTransitionError, 'errorCode' | 'message'>
-        | Pick<OrderStateTransitionError, 'errorCode' | 'message'>;
-};
-
-export type PaymentFragment = Pick<Payment, 'id' | 'state' | 'metadata'>;
-
-export type GetOrderListFulfillmentsQueryVariables = Exact<{ [key: string]: never }>;
-
-export type GetOrderListFulfillmentsQuery = {
-    orders: {
-        items: Array<
-            Pick<Order, 'id' | 'state'> & {
-                fulfillments?: Maybe<Array<Pick<Fulfillment, 'id' | 'state' | 'nextStates' | 'method'>>>;
-            }
-        >;
-    };
-};
-
-export type GetOrderFulfillmentItemsQueryVariables = Exact<{
-    id: Scalars['ID'];
-}>;
-
-export type GetOrderFulfillmentItemsQuery = {
-    order?: Maybe<Pick<Order, 'id' | 'state'> & { fulfillments?: Maybe<Array<FulfillmentFragment>> }>;
-};
-
-export type CancelOrderMutationVariables = Exact<{
-    input: CancelOrderInput;
-}>;
-
-export type CancelOrderMutation = {
-    cancelOrder:
-        | CanceledOrderFragment
-        | Pick<EmptyOrderLineSelectionError, 'errorCode' | 'message'>
-        | Pick<QuantityTooGreatError, 'errorCode' | 'message'>
-        | Pick<MultipleOrderError, 'errorCode' | 'message'>
-        | Pick<CancelActiveOrderError, 'errorCode' | 'message'>
-        | Pick<OrderStateTransitionError, 'errorCode' | 'message'>;
-};
-
-export type CanceledOrderFragment = Pick<Order, 'id'> & {
-    lines: Array<Pick<OrderLine, 'quantity'> & { items: Array<Pick<OrderItem, 'id' | 'cancelled'>> }>;
-};
-
-export type RefundFragment = Pick<
-    Refund,
-    'id' | 'state' | 'items' | 'transactionId' | 'shipping' | 'total' | 'metadata'
->;
-
-export type RefundOrderMutationVariables = Exact<{
-    input: RefundOrderInput;
-}>;
-
-export type RefundOrderMutation = {
-    refundOrder:
-        | RefundFragment
-        | Pick<QuantityTooGreatError, 'errorCode' | 'message'>
-        | Pick<NothingToRefundError, 'errorCode' | 'message'>
-        | Pick<OrderStateTransitionError, 'errorCode' | 'message'>
-        | Pick<MultipleOrderError, 'errorCode' | 'message'>
-        | Pick<PaymentOrderMismatchError, 'errorCode' | 'message'>
-        | Pick<RefundOrderStateError, 'errorCode' | 'message'>
-        | Pick<AlreadyRefundedError, 'errorCode' | 'message'>
-        | Pick<RefundStateTransitionError, 'errorCode' | 'message'>;
-};
-
-export type SettleRefundMutationVariables = Exact<{
-    input: SettleRefundInput;
-}>;
-
-export type SettleRefundMutation = {
-    settleRefund: RefundFragment | Pick<RefundStateTransitionError, 'errorCode' | 'message'>;
-};
-
-export type GetOrderHistoryQueryVariables = Exact<{
-    id: Scalars['ID'];
-    options?: Maybe<HistoryEntryListOptions>;
-}>;
-
-export type GetOrderHistoryQuery = {
-    order?: Maybe<
-        Pick<Order, 'id'> & {
-            history: Pick<HistoryEntryList, 'totalItems'> & {
-                items: Array<
-                    Pick<HistoryEntry, 'id' | 'type' | 'data'> & {
-                        administrator?: Maybe<Pick<Administrator, 'id'>>;
-                    }
-                >;
-            };
-        }
-    >;
-};
-
-export type AddNoteToOrderMutationVariables = Exact<{
-    input: AddNoteToOrderInput;
-}>;
-
-export type AddNoteToOrderMutation = { addNoteToOrder: Pick<Order, 'id'> };
-
-export type UpdateOrderNoteMutationVariables = Exact<{
-    input: UpdateOrderNoteInput;
-}>;
-
-export type UpdateOrderNoteMutation = { updateOrderNote: Pick<HistoryEntry, 'id' | 'data' | 'isPublic'> };
-
-export type DeleteOrderNoteMutationVariables = Exact<{
-    id: Scalars['ID'];
-}>;
-
-export type DeleteOrderNoteMutation = { deleteOrderNote: Pick<DeletionResponse, 'result' | 'message'> };
-
 export type ProductOptionGroupFragment = Pick<ProductOptionGroup, 'id' | 'code' | 'name'> & {
     options: Array<Pick<ProductOption, 'id' | 'code' | 'name'>>;
     translations: Array<Pick<ProductOptionGroupTranslation, 'id' | 'languageCode' | 'name'>>;
@@ -5501,6 +5382,135 @@ export type DeleteTaxRateMutationVariables = Exact<{
 
 export type DeleteTaxRateMutation = { deleteTaxRate: Pick<DeletionResponse, 'result' | 'message'> };
 
+export type SettlePaymentMutationVariables = Exact<{
+    id: Scalars['ID'];
+}>;
+
+export type SettlePaymentMutation = {
+    settlePayment:
+        | PaymentFragment
+        | Pick<SettlePaymentError, 'errorCode' | 'message' | 'paymentErrorMessage'>
+        | Pick<PaymentStateTransitionError, 'errorCode' | 'message'>
+        | Pick<OrderStateTransitionError, 'errorCode' | 'message'>;
+};
+
+export type PaymentFragment = Pick<Payment, 'id' | 'state' | 'metadata'>;
+
+export type GetOrderListFulfillmentsQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetOrderListFulfillmentsQuery = {
+    orders: {
+        items: Array<
+            Pick<Order, 'id' | 'state'> & {
+                fulfillments?: Maybe<Array<Pick<Fulfillment, 'id' | 'state' | 'nextStates' | 'method'>>>;
+            }
+        >;
+    };
+};
+
+export type GetOrderFulfillmentItemsQueryVariables = Exact<{
+    id: Scalars['ID'];
+}>;
+
+export type GetOrderFulfillmentItemsQuery = {
+    order?: Maybe<Pick<Order, 'id' | 'state'> & { fulfillments?: Maybe<Array<FulfillmentFragment>> }>;
+};
+
+export type CancelOrderMutationVariables = Exact<{
+    input: CancelOrderInput;
+}>;
+
+export type CancelOrderMutation = {
+    cancelOrder:
+        | CanceledOrderFragment
+        | Pick<EmptyOrderLineSelectionError, 'errorCode' | 'message'>
+        | Pick<QuantityTooGreatError, 'errorCode' | 'message'>
+        | Pick<MultipleOrderError, 'errorCode' | 'message'>
+        | Pick<CancelActiveOrderError, 'errorCode' | 'message'>
+        | Pick<OrderStateTransitionError, 'errorCode' | 'message'>;
+};
+
+export type CanceledOrderFragment = Pick<Order, 'id'> & {
+    lines: Array<Pick<OrderLine, 'quantity'> & { items: Array<Pick<OrderItem, 'id' | 'cancelled'>> }>;
+};
+
+export type RefundFragment = Pick<
+    Refund,
+    'id' | 'state' | 'items' | 'transactionId' | 'shipping' | 'total' | 'metadata'
+>;
+
+export type RefundOrderMutationVariables = Exact<{
+    input: RefundOrderInput;
+}>;
+
+export type RefundOrderMutation = {
+    refundOrder:
+        | RefundFragment
+        | Pick<QuantityTooGreatError, 'errorCode' | 'message'>
+        | Pick<NothingToRefundError, 'errorCode' | 'message'>
+        | Pick<OrderStateTransitionError, 'errorCode' | 'message'>
+        | Pick<MultipleOrderError, 'errorCode' | 'message'>
+        | Pick<PaymentOrderMismatchError, 'errorCode' | 'message'>
+        | Pick<RefundOrderStateError, 'errorCode' | 'message'>
+        | Pick<AlreadyRefundedError, 'errorCode' | 'message'>
+        | Pick<RefundStateTransitionError, 'errorCode' | 'message'>;
+};
+
+export type SettleRefundMutationVariables = Exact<{
+    input: SettleRefundInput;
+}>;
+
+export type SettleRefundMutation = {
+    settleRefund: RefundFragment | Pick<RefundStateTransitionError, 'errorCode' | 'message'>;
+};
+
+export type GetOrderHistoryQueryVariables = Exact<{
+    id: Scalars['ID'];
+    options?: Maybe<HistoryEntryListOptions>;
+}>;
+
+export type GetOrderHistoryQuery = {
+    order?: Maybe<
+        Pick<Order, 'id'> & {
+            history: Pick<HistoryEntryList, 'totalItems'> & {
+                items: Array<
+                    Pick<HistoryEntry, 'id' | 'type' | 'data'> & {
+                        administrator?: Maybe<Pick<Administrator, 'id'>>;
+                    }
+                >;
+            };
+        }
+    >;
+};
+
+export type AddNoteToOrderMutationVariables = Exact<{
+    input: AddNoteToOrderInput;
+}>;
+
+export type AddNoteToOrderMutation = { addNoteToOrder: Pick<Order, 'id'> };
+
+export type UpdateOrderNoteMutationVariables = Exact<{
+    input: UpdateOrderNoteInput;
+}>;
+
+export type UpdateOrderNoteMutation = { updateOrderNote: Pick<HistoryEntry, 'id' | 'data' | 'isPublic'> };
+
+export type DeleteOrderNoteMutationVariables = Exact<{
+    id: Scalars['ID'];
+}>;
+
+export type DeleteOrderNoteMutation = { deleteOrderNote: Pick<DeletionResponse, 'result' | 'message'> };
+
+export type GetOrderWithPaymentsQueryVariables = Exact<{
+    id: Scalars['ID'];
+}>;
+
+export type GetOrderWithPaymentsQuery = {
+    order?: Maybe<
+        Pick<Order, 'id'> & { payments?: Maybe<Array<Pick<Payment, 'id' | 'errorMessage' | 'metadata'>>> }
+    >;
+};
+
 export type DeleteZoneMutationVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -6840,124 +6850,6 @@ export namespace GetPromoProducts {
     >;
 }
 
-export namespace SettlePayment {
-    export type Variables = SettlePaymentMutationVariables;
-    export type Mutation = SettlePaymentMutation;
-    export type SettlePayment = NonNullable<SettlePaymentMutation['settlePayment']>;
-    export type ErrorResultInlineFragment = DiscriminateUnion<
-        NonNullable<SettlePaymentMutation['settlePayment']>,
-        { __typename?: 'ErrorResult' }
-    >;
-    export type SettlePaymentErrorInlineFragment = DiscriminateUnion<
-        NonNullable<SettlePaymentMutation['settlePayment']>,
-        { __typename?: 'SettlePaymentError' }
-    >;
-}
-
-export namespace Payment {
-    export type Fragment = PaymentFragment;
-}
-
-export namespace GetOrderListFulfillments {
-    export type Variables = GetOrderListFulfillmentsQueryVariables;
-    export type Query = GetOrderListFulfillmentsQuery;
-    export type Orders = NonNullable<GetOrderListFulfillmentsQuery['orders']>;
-    export type Items = NonNullable<
-        NonNullable<NonNullable<GetOrderListFulfillmentsQuery['orders']>['items']>[number]
-    >;
-    export type Fulfillments = NonNullable<
-        NonNullable<
-            NonNullable<
-                NonNullable<NonNullable<GetOrderListFulfillmentsQuery['orders']>['items']>[number]
-            >['fulfillments']
-        >[number]
-    >;
-}
-
-export namespace GetOrderFulfillmentItems {
-    export type Variables = GetOrderFulfillmentItemsQueryVariables;
-    export type Query = GetOrderFulfillmentItemsQuery;
-    export type Order = NonNullable<GetOrderFulfillmentItemsQuery['order']>;
-    export type Fulfillments = NonNullable<
-        NonNullable<NonNullable<GetOrderFulfillmentItemsQuery['order']>['fulfillments']>[number]
-    >;
-}
-
-export namespace CancelOrder {
-    export type Variables = CancelOrderMutationVariables;
-    export type Mutation = CancelOrderMutation;
-    export type CancelOrder = NonNullable<CancelOrderMutation['cancelOrder']>;
-    export type ErrorResultInlineFragment = DiscriminateUnion<
-        NonNullable<CancelOrderMutation['cancelOrder']>,
-        { __typename?: 'ErrorResult' }
-    >;
-}
-
-export namespace CanceledOrder {
-    export type Fragment = CanceledOrderFragment;
-    export type Lines = NonNullable<NonNullable<CanceledOrderFragment['lines']>[number]>;
-    export type Items = NonNullable<
-        NonNullable<NonNullable<NonNullable<CanceledOrderFragment['lines']>[number]>['items']>[number]
-    >;
-}
-
-export namespace Refund {
-    export type Fragment = RefundFragment;
-}
-
-export namespace RefundOrder {
-    export type Variables = RefundOrderMutationVariables;
-    export type Mutation = RefundOrderMutation;
-    export type RefundOrder = NonNullable<RefundOrderMutation['refundOrder']>;
-    export type ErrorResultInlineFragment = DiscriminateUnion<
-        NonNullable<RefundOrderMutation['refundOrder']>,
-        { __typename?: 'ErrorResult' }
-    >;
-}
-
-export namespace SettleRefund {
-    export type Variables = SettleRefundMutationVariables;
-    export type Mutation = SettleRefundMutation;
-    export type SettleRefund = NonNullable<SettleRefundMutation['settleRefund']>;
-    export type ErrorResultInlineFragment = DiscriminateUnion<
-        NonNullable<SettleRefundMutation['settleRefund']>,
-        { __typename?: 'ErrorResult' }
-    >;
-}
-
-export namespace GetOrderHistory {
-    export type Variables = GetOrderHistoryQueryVariables;
-    export type Query = GetOrderHistoryQuery;
-    export type Order = NonNullable<GetOrderHistoryQuery['order']>;
-    export type History = NonNullable<NonNullable<GetOrderHistoryQuery['order']>['history']>;
-    export type Items = NonNullable<
-        NonNullable<NonNullable<NonNullable<GetOrderHistoryQuery['order']>['history']>['items']>[number]
-    >;
-    export type Administrator = NonNullable<
-        NonNullable<
-            NonNullable<NonNullable<NonNullable<GetOrderHistoryQuery['order']>['history']>['items']>[number]
-        >['administrator']
-    >;
-}
-
-export namespace AddNoteToOrder {
-    export type Variables = AddNoteToOrderMutationVariables;
-    export type Mutation = AddNoteToOrderMutation;
-    export type AddNoteToOrder = NonNullable<AddNoteToOrderMutation['addNoteToOrder']>;
-}
-
-export namespace UpdateOrderNote {
-    export type Variables = UpdateOrderNoteMutationVariables;
-    export type Mutation = UpdateOrderNoteMutation;
-    export type UpdateOrderNote = NonNullable<UpdateOrderNoteMutation['updateOrderNote']>;
-}
-
-export namespace DeleteOrderNote {
-    export type Variables = DeleteOrderNoteMutationVariables;
-    export type Mutation = DeleteOrderNoteMutation;
-    export type DeleteOrderNote = NonNullable<DeleteOrderNoteMutation['deleteOrderNote']>;
-}
-
 export namespace ProductOptionGroup {
     export type Fragment = ProductOptionGroupFragment;
     export type Options = NonNullable<NonNullable<ProductOptionGroupFragment['options']>[number]>;
@@ -7365,6 +7257,133 @@ export namespace DeleteTaxRate {
     export type DeleteTaxRate = NonNullable<DeleteTaxRateMutation['deleteTaxRate']>;
 }
 
+export namespace SettlePayment {
+    export type Variables = SettlePaymentMutationVariables;
+    export type Mutation = SettlePaymentMutation;
+    export type SettlePayment = NonNullable<SettlePaymentMutation['settlePayment']>;
+    export type ErrorResultInlineFragment = DiscriminateUnion<
+        NonNullable<SettlePaymentMutation['settlePayment']>,
+        { __typename?: 'ErrorResult' }
+    >;
+    export type SettlePaymentErrorInlineFragment = DiscriminateUnion<
+        NonNullable<SettlePaymentMutation['settlePayment']>,
+        { __typename?: 'SettlePaymentError' }
+    >;
+}
+
+export namespace Payment {
+    export type Fragment = PaymentFragment;
+}
+
+export namespace GetOrderListFulfillments {
+    export type Variables = GetOrderListFulfillmentsQueryVariables;
+    export type Query = GetOrderListFulfillmentsQuery;
+    export type Orders = NonNullable<GetOrderListFulfillmentsQuery['orders']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<GetOrderListFulfillmentsQuery['orders']>['items']>[number]
+    >;
+    export type Fulfillments = NonNullable<
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<GetOrderListFulfillmentsQuery['orders']>['items']>[number]
+            >['fulfillments']
+        >[number]
+    >;
+}
+
+export namespace GetOrderFulfillmentItems {
+    export type Variables = GetOrderFulfillmentItemsQueryVariables;
+    export type Query = GetOrderFulfillmentItemsQuery;
+    export type Order = NonNullable<GetOrderFulfillmentItemsQuery['order']>;
+    export type Fulfillments = NonNullable<
+        NonNullable<NonNullable<GetOrderFulfillmentItemsQuery['order']>['fulfillments']>[number]
+    >;
+}
+
+export namespace CancelOrder {
+    export type Variables = CancelOrderMutationVariables;
+    export type Mutation = CancelOrderMutation;
+    export type CancelOrder = NonNullable<CancelOrderMutation['cancelOrder']>;
+    export type ErrorResultInlineFragment = DiscriminateUnion<
+        NonNullable<CancelOrderMutation['cancelOrder']>,
+        { __typename?: 'ErrorResult' }
+    >;
+}
+
+export namespace CanceledOrder {
+    export type Fragment = CanceledOrderFragment;
+    export type Lines = NonNullable<NonNullable<CanceledOrderFragment['lines']>[number]>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<NonNullable<CanceledOrderFragment['lines']>[number]>['items']>[number]
+    >;
+}
+
+export namespace Refund {
+    export type Fragment = RefundFragment;
+}
+
+export namespace RefundOrder {
+    export type Variables = RefundOrderMutationVariables;
+    export type Mutation = RefundOrderMutation;
+    export type RefundOrder = NonNullable<RefundOrderMutation['refundOrder']>;
+    export type ErrorResultInlineFragment = DiscriminateUnion<
+        NonNullable<RefundOrderMutation['refundOrder']>,
+        { __typename?: 'ErrorResult' }
+    >;
+}
+
+export namespace SettleRefund {
+    export type Variables = SettleRefundMutationVariables;
+    export type Mutation = SettleRefundMutation;
+    export type SettleRefund = NonNullable<SettleRefundMutation['settleRefund']>;
+    export type ErrorResultInlineFragment = DiscriminateUnion<
+        NonNullable<SettleRefundMutation['settleRefund']>,
+        { __typename?: 'ErrorResult' }
+    >;
+}
+
+export namespace GetOrderHistory {
+    export type Variables = GetOrderHistoryQueryVariables;
+    export type Query = GetOrderHistoryQuery;
+    export type Order = NonNullable<GetOrderHistoryQuery['order']>;
+    export type History = NonNullable<NonNullable<GetOrderHistoryQuery['order']>['history']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<NonNullable<GetOrderHistoryQuery['order']>['history']>['items']>[number]
+    >;
+    export type Administrator = NonNullable<
+        NonNullable<
+            NonNullable<NonNullable<NonNullable<GetOrderHistoryQuery['order']>['history']>['items']>[number]
+        >['administrator']
+    >;
+}
+
+export namespace AddNoteToOrder {
+    export type Variables = AddNoteToOrderMutationVariables;
+    export type Mutation = AddNoteToOrderMutation;
+    export type AddNoteToOrder = NonNullable<AddNoteToOrderMutation['addNoteToOrder']>;
+}
+
+export namespace UpdateOrderNote {
+    export type Variables = UpdateOrderNoteMutationVariables;
+    export type Mutation = UpdateOrderNoteMutation;
+    export type UpdateOrderNote = NonNullable<UpdateOrderNoteMutation['updateOrderNote']>;
+}
+
+export namespace DeleteOrderNote {
+    export type Variables = DeleteOrderNoteMutationVariables;
+    export type Mutation = DeleteOrderNoteMutation;
+    export type DeleteOrderNote = NonNullable<DeleteOrderNoteMutation['deleteOrderNote']>;
+}
+
+export namespace GetOrderWithPayments {
+    export type Variables = GetOrderWithPaymentsQueryVariables;
+    export type Query = GetOrderWithPaymentsQuery;
+    export type Order = NonNullable<GetOrderWithPaymentsQuery['order']>;
+    export type Payments = NonNullable<
+        NonNullable<NonNullable<GetOrderWithPaymentsQuery['order']>['payments']>[number]
+    >;
+}
+
 export namespace DeleteZone {
     export type Variables = DeleteZoneMutationVariables;
     export type Mutation = DeleteZoneMutation;

+ 12 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -2925,6 +2925,12 @@ export type GetActiveOrderPaymentsQuery = {
     >;
 };
 
+export type GetOrderByCodeWithPaymentsQueryVariables = Exact<{
+    code: Scalars['String'];
+}>;
+
+export type GetOrderByCodeWithPaymentsQuery = { orderByCode?: Maybe<TestOrderWithPaymentsFragment> };
+
 export type GetNextOrderStatesQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetNextOrderStatesQuery = Pick<Query, 'nextOrderStates'>;
@@ -3336,6 +3342,12 @@ export namespace GetActiveOrderPayments {
     >;
 }
 
+export namespace GetOrderByCodeWithPayments {
+    export type Variables = GetOrderByCodeWithPaymentsQueryVariables;
+    export type Query = GetOrderByCodeWithPaymentsQuery;
+    export type OrderByCode = NonNullable<GetOrderByCodeWithPaymentsQuery['orderByCode']>;
+}
+
 export namespace GetNextOrderStates {
     export type Variables = GetNextOrderStatesQueryVariables;
     export type Query = GetNextOrderStatesQuery;

+ 9 - 0
packages/core/e2e/graphql/shop-definitions.ts

@@ -510,6 +510,15 @@ export const GET_ACTIVE_ORDER_PAYMENTS = gql`
     }
 `;
 
+export const GET_ORDER_BY_CODE_WITH_PAYMENTS = gql`
+    query GetOrderByCodeWithPayments($code: String!) {
+        orderByCode(code: $code) {
+            ...TestOrderWithPayments
+        }
+    }
+    ${TEST_ORDER_WITH_PAYMENTS_FRAGMENT}
+`;
+
 export const GET_NEXT_STATES = gql`
     query GetNextOrderStates {
         nextOrderStates

+ 63 - 2
packages/core/e2e/order.e2e-spec.ts

@@ -34,6 +34,7 @@ import {
     GetOrderHistory,
     GetOrderList,
     GetOrderListFulfillments,
+    GetOrderWithPayments,
     GetProductWithVariants,
     GetStockMovement,
     HistoryEntryType,
@@ -52,6 +53,9 @@ import {
     AddItemToOrder,
     DeletionResult,
     GetActiveOrder,
+    GetActiveOrderWithPayments,
+    GetOrderByCode,
+    GetOrderByCodeWithPayments,
     TestOrderFragmentFragment,
     UpdatedOrder,
 } from './graphql/generated-e2e-shop-types';
@@ -66,7 +70,14 @@ import {
     TRANSIT_FULFILLMENT,
     UPDATE_PRODUCT_VARIANTS,
 } from './graphql/shared-definitions';
-import { ADD_ITEM_TO_ORDER, GET_ACTIVE_ORDER } from './graphql/shop-definitions';
+import {
+    ADD_ITEM_TO_ORDER,
+    GET_ACTIVE_ORDER,
+    GET_ACTIVE_ORDER_WITH_PAYMENTS,
+    GET_ORDER_BY_CODE,
+    GET_ORDER_BY_CODE_WITH_PAYMENTS,
+    TEST_ORDER_WITH_PAYMENTS_FRAGMENT,
+} from './graphql/shop-definitions';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
 
@@ -159,6 +170,9 @@ describe('Orders resolver', () => {
     });
 
     describe('payments', () => {
+        let firstOrderCode: string;
+        let firstOrderId: string;
+
         it('settlePayment fails', async () => {
             await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
             await proceedToArrangingPayment(shopClient);
@@ -185,6 +199,38 @@ describe('Orders resolver', () => {
             });
 
             expect(result.order!.state).toBe('PaymentAuthorized');
+            firstOrderCode = order.code;
+            firstOrderId = order.id;
+        });
+
+        it('public payment metadata available in Shop API', async () => {
+            const { orderByCode } = await shopClient.query<
+                GetOrderByCodeWithPayments.Query,
+                GetOrderByCodeWithPayments.Variables
+            >(GET_ORDER_BY_CODE_WITH_PAYMENTS, { code: firstOrderCode });
+
+            expect(orderByCode?.payments?.[0].metadata).toEqual({
+                public: {
+                    publicCreatePaymentData: 'public',
+                    publicSettlePaymentData: 'public',
+                },
+            });
+        });
+
+        it('public and private payment metadata available in Admin API', async () => {
+            const { order } = await adminClient.query<
+                GetOrderWithPayments.Query,
+                GetOrderWithPayments.Variables
+            >(GET_ORDER_WITH_PAYMENTS, { id: firstOrderId });
+
+            expect(order?.payments?.[0].metadata).toEqual({
+                privateCreatePaymentData: 'secret',
+                privateSettlePaymentData: 'secret',
+                public: {
+                    publicCreatePaymentData: 'public',
+                    publicSettlePaymentData: 'public',
+                },
+            });
         });
 
         it('settlePayment succeeds, onStateTransitionStart called', async () => {
@@ -212,8 +258,10 @@ describe('Orders resolver', () => {
             expect(settlePayment!.state).toBe('Settled');
             // further metadata is combined into existing object
             expect(settlePayment!.metadata).toEqual({
-                baz: 'quux',
                 moreData: 42,
+                public: {
+                    baz: 'quux',
+                },
             });
             expect(onTransitionSpy).toHaveBeenCalledTimes(2);
             expect(onTransitionSpy.mock.calls[1][0]).toBe('Authorized');
@@ -1613,3 +1661,16 @@ export const DELETE_ORDER_NOTE = gql`
         }
     }
 `;
+
+const GET_ORDER_WITH_PAYMENTS = gql`
+    query GetOrderWithPayments($id: ID!) {
+        order(id: $id) {
+            id
+            payments {
+                id
+                errorMessage
+                metadata
+            }
+        }
+    }
+`;

+ 2 - 2
packages/core/e2e/shop-order.e2e-spec.ts

@@ -943,7 +943,7 @@ describe('Shop orders', () => {
                 expect(payment.state).toBe('Declined');
                 expect(payment.transactionId).toBe(null);
                 expect(payment.metadata).toEqual({
-                    foo: 'bar',
+                    public: { foo: 'bar' },
                 });
             });
 
@@ -997,7 +997,7 @@ describe('Shop orders', () => {
                 expect(payment.state).toBe('Settled');
                 expect(payment.transactionId).toBe('12345');
                 expect(payment.metadata).toEqual({
-                    baz: 'quux',
+                    public: { baz: 'quux' },
                 });
             });
 

+ 2 - 1
packages/core/e2e/utils/test-order-utils.ts

@@ -8,6 +8,7 @@ import {
     GetShippingMethods,
     SetShippingAddress,
     SetShippingMethod,
+    TestOrderFragmentFragment,
     TransitionToState,
 } from '../graphql/generated-e2e-shop-types';
 import {
@@ -42,7 +43,7 @@ export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient)
         TransitionToState.Variables
     >(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
 
-    return transitionOrderToState!.id;
+    return (transitionOrderToState as TestOrderFragmentFragment)!.id;
 }
 
 export async function addPaymentToOrder(

+ 9 - 0
packages/core/src/api/resolvers/entity/payment-entity.resolver.ts

@@ -1,9 +1,13 @@
 import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
+import { pick } from '@vendure/common/lib/pick';
 
+import { PaymentMetadata } from '../../../common/types/common-types';
 import { Payment } from '../../../entity/payment/payment.entity';
 import { Refund } from '../../../entity/refund/refund.entity';
 import { OrderService } from '../../../service/services/order.service';
+import { ApiType } from '../../common/get-api-type';
 import { RequestContext } from '../../common/request-context';
+import { Api } from '../../decorators/api.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Payment')
@@ -18,4 +22,9 @@ export class PaymentEntityResolver {
             return this.orderService.getPaymentRefunds(ctx, payment.id);
         }
     }
+
+    @ResolveField()
+    metadata(@Api() apiType: ApiType, @Parent() payment: Payment): PaymentMetadata {
+        return apiType === 'admin' ? payment.metadata : pick(payment.metadata, ['public']);
+    }
 }

+ 3 - 0
packages/core/src/common/index.ts

@@ -6,4 +6,7 @@ export * from './error/error-result';
 export * from './error/generated-graphql-admin-errors';
 export * from './injector';
 export * from './ttl-cache';
+export * from './types/common-types';
+export * from './types/injectable-strategy';
+export * from './types/locale-types';
 export * from './utils';

+ 6 - 0
packages/core/src/common/types/common-types.ts

@@ -113,3 +113,9 @@ export interface DateOperators {
     after?: Date;
     between?: DateRange;
 }
+
+export type PaymentMetadata = {
+    [prop: string]: any;
+} & {
+    public?: any;
+};

+ 1 - 3
packages/core/src/config/payment-method/example-payment-method-handler.ts

@@ -9,9 +9,7 @@ const gripeSDK = {
     charges: {
         create: (options: any) => {
             return Promise.resolve({
-                id: Math.random()
-                    .toString(36)
-                    .substr(3),
+                id: Math.random().toString(36).substr(3),
             });
         },
         capture: (transactionId: string) => {

+ 28 - 3
packages/core/src/config/payment-method/payment-method-handler.ts

@@ -7,8 +7,9 @@ import {
     ConfigurableOperationDefOptions,
 } from '../../common/configurable-operation';
 import { OnTransitionStartFn, StateMachineConfig } from '../../common/finite-state-machine/types';
+import { PaymentMetadata } from '../../common/types/common-types';
 import { Order } from '../../entity/order/order.entity';
-import { Payment, PaymentMetadata } from '../../entity/payment/payment.entity';
+import { Payment } from '../../entity/payment/payment.entity';
 import {
     PaymentState,
     PaymentTransitionData,
@@ -27,8 +28,21 @@ export type OnPaymentTransitionStartReturnType = ReturnType<
  * @docsPage Payment Method Types
  */
 export interface CreatePaymentResult {
+    /**
+     * @description
+     * The amount (as an integer - i.e. $10 = `1000`) that this payment is for.
+     * Typically this should equal the Order total, unless multiple payment methods
+     * are being used for the order.
+     */
     amount: number;
-    state: Exclude<PaymentState, 'Refunded' | 'Error'>;
+    /**
+     * @description
+     * The {@link PaymentState} of the resulting Payment.
+     *
+     * In a single-step payment flow, this should be set to `'Settled'`.
+     * In a two-step flow, this should be set to `'Authorized'`.
+     */
+    state: Exclude<PaymentState, 'Error'>;
     /**
      * @description
      * The unique payment reference code typically assigned by
@@ -45,7 +59,13 @@ export interface CreatePaymentResult {
     /**
      * @description
      * This field can be used to store other relevant data which is often
-     * provided by the payment provider.
+     * provided by the payment provider, such as security data related to
+     * the payment method or data used in troubleshooting or debugging.
+     *
+     * Any data stored in the optional `public` property will be available
+     * via the Shop API. This is useful for certain checkout flows such as
+     * external gateways, where the payment provider returns a unique
+     * url which must then be passed to the storefront app.
      */
     metadata?: PaymentMetadata;
 }
@@ -95,6 +115,8 @@ export interface SettlePaymentResult {
  * @description
  * This function contains the logic for creating a payment. See {@link PaymentMethodHandler} for an example.
  *
+ * Returns a {@link CreatePaymentResult}.
+ *
  * @docsCategory payment
  * @docsPage Payment Method Types
  */
@@ -177,6 +199,9 @@ export interface PaymentMethodConfigOptions<T extends ConfigArgs> extends Config
  * third-party payment gateway before the Payment is created and can also define actions to fire
  * when the state of the payment is changed.
  *
+ * PaymentMethodHandlers are instantiated using a {@link PaymentMethodConfigOptions} object, which
+ * configures the business logic used to create, settle and refund payments.
+ *
  * @example
  * ```ts
  * import { PaymentMethodHandler, CreatePaymentResult, SettlePaymentResult, LanguageCode } from '\@vendure/core';

+ 0 - 4
packages/core/src/entity/payment-method/payment-method.entity.ts

@@ -2,11 +2,7 @@ import { ConfigArg } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity } from 'typeorm';
 
-import { UserInputError } from '../../common/error/errors';
-import { getConfig } from '../../config/config-helpers';
 import { VendureEntity } from '../base/base.entity';
-import { Order } from '../order/order.entity';
-import { Payment, PaymentMetadata } from '../payment/payment.entity';
 
 /**
  * @description

+ 3 - 4
packages/core/src/entity/payment/payment.entity.ts

@@ -1,13 +1,12 @@
 import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
+import { PaymentMetadata } from '../../common/types/common-types';
 import { PaymentState } from '../../service/helpers/payment-state-machine/payment-state';
 import { VendureEntity } from '../base/base.entity';
 import { Order } from '../order/order.entity';
 import { Refund } from '../refund/refund.entity';
 
-export type PaymentMetadata = any;
-
 /**
  * @description
  * A Payment represents a single payment transaction and exists in a well-defined state
@@ -27,8 +26,8 @@ export class Payment extends VendureEntity {
 
     @Column('varchar') state: PaymentState;
 
-    @Column({ nullable: true })
-    errorMessage: string;
+    @Column({ type: 'varchar', nullable: true })
+    errorMessage: string | undefined;
 
     @Column({ nullable: true })
     transactionId: string;

+ 2 - 1
packages/core/src/entity/refund/refund.entity.ts

@@ -1,11 +1,12 @@
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinColumn, JoinTable, ManyToOne, OneToMany } from 'typeorm';
 
+import { PaymentMetadata } from '../../common/types/common-types';
 import { RefundState } from '../../service/helpers/refund-state-machine/refund-state';
 import { VendureEntity } from '../base/base.entity';
 import { EntityId } from '../entity-id.decorator';
 import { OrderItem } from '../order-item/order-item.entity';
-import { Payment, PaymentMetadata } from '../payment/payment.entity';
+import { Payment } from '../payment/payment.entity';
 
 @Entity()
 export class Refund extends VendureEntity {

+ 18 - 4
packages/core/src/service/services/order.service.ts

@@ -60,7 +60,7 @@ import {
     PaymentDeclinedError,
     PaymentFailedError,
 } from '../../common/error/generated-graphql-shop-errors';
-import { ListQueryOptions } from '../../common/types/common-types';
+import { ListQueryOptions, PaymentMetadata } from '../../common/types/common-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
 import { Customer } from '../../entity/customer/customer.entity';
@@ -609,10 +609,10 @@ export class OrderService {
         await this.connection.getRepository(ctx, Order).save(order, { reload: false });
 
         if (payment.state === 'Error') {
-            return new PaymentFailedError(payment.errorMessage);
+            return new PaymentFailedError(payment.errorMessage || '');
         }
         if (payment.state === 'Declined') {
-            return new PaymentDeclinedError(payment.errorMessage);
+            return new PaymentDeclinedError(payment.errorMessage || '');
         }
 
         if (orderTotalIsCovered(order, 'Settled')) {
@@ -645,7 +645,7 @@ export class OrderService {
                 const transitionError = ctx.translate(e.message, { fromState, toState });
                 return new PaymentStateTransitionError(transitionError, fromState, toState);
             }
-            payment.metadata = { ...payment.metadata, ...settlePaymentResult.metadata };
+            payment.metadata = this.mergePaymentMetadata(payment.metadata, settlePaymentResult.metadata);
             await this.connection.getRepository(ctx, Payment).save(payment, { reload: false });
             this.eventBus.publish(
                 new PaymentStateTransitionEvent(fromState, toState, ctx, payment, payment.order),
@@ -665,6 +665,9 @@ export class OrderService {
                 }
             }
         } else {
+            payment.errorMessage = settlePaymentResult.errorMessage;
+            payment.metadata = this.mergePaymentMetadata(payment.metadata, settlePaymentResult.metadata);
+            await this.connection.getRepository(ctx, Payment).save(payment, { reload: false });
             return new SettlePaymentError(settlePaymentResult.errorMessage || '');
         }
         return payment;
@@ -1107,4 +1110,15 @@ export class OrderService {
             items: Array.from(items.values()),
         };
     }
+
+    private mergePaymentMetadata(m1: PaymentMetadata, m2?: PaymentMetadata): PaymentMetadata {
+        if (!m2) {
+            return m1;
+        }
+        const merged = { ...m1, ...m2 };
+        if (m1.public && m1.public) {
+            merged.public = { ...m1.public, ...m2.public };
+        }
+        return merged;
+    }
 }

+ 2 - 7
packages/core/src/service/services/payment-method.service.ts

@@ -18,7 +18,7 @@ import { PaymentMethodHandler } from '../../config/payment-method/payment-method
 import { OrderItem } from '../../entity/order-item/order-item.entity';
 import { Order } from '../../entity/order/order.entity';
 import { PaymentMethod } from '../../entity/payment-method/payment-method.entity';
-import { Payment, PaymentMetadata } from '../../entity/payment/payment.entity';
+import { Payment } from '../../entity/payment/payment.entity';
 import { Refund } from '../../entity/refund/refund.entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { PaymentStateTransitionEvent } from '../../event-bus/events/payment-state-transition-event';
@@ -78,12 +78,7 @@ export class PaymentMethodService {
         return this.connection.getRepository(ctx, PaymentMethod).save(updatedPaymentMethod);
     }
 
-    async createPayment(
-        ctx: RequestContext,
-        order: Order,
-        method: string,
-        metadata: PaymentMetadata,
-    ): Promise<Payment> {
+    async createPayment(ctx: RequestContext, order: Order, method: string, metadata: any): Promise<Payment> {
         const { paymentMethod, handler } = await this.getMethodAndHandler(ctx, method);
         const result = await handler.createPayment(order, paymentMethod.configArgs, metadata || {});
         const initialState = 'Created';