Skip to content

Commit

Permalink
feature(apple-pay): add models for payloads received from client comm…
Browse files Browse the repository at this point in the history
…unication with apple pay
  • Loading branch information
lrosenfeldt committed Aug 9, 2024
1 parent d996f08 commit cd0e753
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/models/applepay/ApplePayPayment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ApplePayPaymentToken } from './ApplePayPaymentToken.js';
import type { ApplePayPaymentContact } from './ApplePayPaymentContact.js';

/**
* The result of authorizing a payment request that contains payment information.
*
* Data in ApplePayPaymentToken is encrypted. Billing and shipping contact data are not encrypted.
*/
export interface ApplePayPayment {
/**
* An object that contains the user's payment credentials.
*
*/
token?: ApplePayPaymentToken;

/**
* The shipping contact selected by the user for this transaction.
*
*/
billingContact?: ApplePayPaymentContact;

/**
* The billing contact selected by the user for this transaction.
*/
shippingContact?: ApplePayPaymentContact;
}
71 changes: 71 additions & 0 deletions src/models/applepay/ApplePayPaymentContact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* The result of authorizing a payment request that contains payment information.
*
* Data in ApplePayPaymentToken is encrypted. Billing and shipping contact data are not encrypted.
*/
export interface ApplePayPaymentContact {
/**
* @description A phone number for the contact.
*/
phoneNumber?: string;

/**
* @description An email address for the contact.
*/
emailAddress?: string;

/**
* @description The contact’s given name.
*/
givenName?: string;

/**
* @description The contact’s family name.
*/
familyName?: string;

/**
* @description The phonetic spelling of the contact’s given name.
*/
phoneticGivenName?: string;

/**
* @description The phonetic spelling of the contact’s family name.
*/
phoneticFamilyName?: string;

/**
* @description The street portion of the address for the contact.
*/
addressLines?: string[];

/**
* @description The city for the contact.
*/
locality?: string;

/**
* @description The zip code or postal code, where applicable, for the contact.
*/
postalCode?: string;

/**
* @description The state for the contact.
*/
administrativeArea?: string;

/**
* @description The subadministrative area (such as a county or other region) in a postal address.
*/
subAdministrativeArea?: string;

/**
* @description The name of the country or region for the contact.
*/
country?: string;

/**
* @description The contact’s two-letter ISO 3166 country code.
*/
countryCode?: string;
}
49 changes: 49 additions & 0 deletions src/models/applepay/ApplePayPaymentData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { ApplePayPaymentDataHeader } from './ApplePayPaymentDataHeader.js';
/**
* An object that contains the user's payment credentials.
* You access the payment token of an authorized payment request using the token property of the ApplePayPayment object.
*/
export interface ApplePayPaymentData {
/**
* A base64 encoded string that contains custom data with the following structure:
*
* Payment Data Keys:
* The decrypted payment data in the `data` value contains the following keys and values:
*
* - **applicationPrimaryAccountNumber** (string): Device-specific account number of the card that funds this transaction.
* - **applicationExpirationDate** (string, date as a string): Card expiration date in the format YYMMDD.
* - **currencyCode** (string): ISO 4217 numeric currency code, as a string to preserve leading zeros.
* - **transactionAmount** (number): Transaction amount.
* - **cardholderName** (string, optional): Cardholder name.
* - **deviceManufacturerIdentifier** (string): Hex-encoded device manufacturer identifier.
* - **paymentDataType** (string): Either "3DSecure" or "EMV".
* - **paymentData** (dictionary): Detailed payment data; see Detailed Payment Data Keys (3D Secure) and Detailed Payment Data Keys (EMV).
* - **authenticationResponses** (list): For a multitoken request, a list of submerchant responses that contain cryptograms.
* - **merchantTokenIdentifier** (string): For a merchant token request, the provisioned merchant token identifier from the payment network.
* - **merchantTokenMetadata** (MerchantTokenMetadata): For a merchant token request, this data contains card art and the token's last four digits and expiration date.
*
* The encoded string must be decoded and parsed according to the expected structure to access the individual values.
*
*/
data?: string;

/**
* Additional version-dependent information you use to decrypt and verify the payment
*/
header?: ApplePayPaymentDataHeader;

/**
* detached PKCS #7 signature, Base64 encoded as a string.
* Signature of the payment and header data.
* The signature includes the signing certificate, its intermediate CA certificate, and information about the signing algorithm.
*
*/
signature?: string;

/**
* Version information about the payment token
* The token uses EC_v1 for ECC-encrypted data and RSA_v1 for RSA-encrypted data.
*
*/
version?: string;
}
39 changes: 39 additions & 0 deletions src/models/applepay/ApplePayPaymentDataHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export interface ApplePayPaymentDataHeader {
/**
* Optional. Hash of the applicationData property of the original PKPaymentRequest object
* for transactions that initiate in apps. For transactions that initiate in Apple Pay on the Web,
* the value is the hash of applicationData in ApplePayPaymentRequest or of applicationData in ApplePayRequest.
* This key is omitted if the value of that property is nil.
*
* @description SHA-256 hash, hex encoded as a string
*/
applicationData?: string;

/**
* Ephemeral public key bytes.
*
* @description X.509 encoded key bytes, Base64 encoded as a string
*/
ephemeralPublicKey?: string;

/**
* The symmetric key wrapped using your RSA public key.
*
* @description A Base64-encoded string
*/
wrappedKey?: string;

/**
* Hash of the X.509 encoded public key bytes of the merchant's certificate.
*
* @description SHA-256 hash, Base64 encoded as a string
*/
publicKeyHash?: string;

/**
* Transaction identifier, generated on the device.
*
* @description A hexadecimal identifier, as a string
*/
transactionId?: string;
}
31 changes: 31 additions & 0 deletions src/models/applepay/ApplePayPaymentMethod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ApplePayPaymentMethodType } from './ApplePayPaymentMethodType.js';
import type { ApplePayPaymentContact } from './ApplePayPaymentContact.js';
/**
* Information about the card used in the transaction.
*/
export interface ApplePayPaymentMethod {
/**
* @description A string, suitable for display, that describes the card.
*/
displayName?: string;

/**
* @description A string, suitable for display, that is the name of the payment network backing the card.
*/
network?: string;

/**
* @description A string value representing the card's type of payment.
*/
type?: ApplePayPaymentMethodType;

/**
* @description string|null The payment pass object currently selected to complete the payment.
*/
paymentPass?: string;

/**
* @description The billing contact associated with the card.
*/
billingContact?: ApplePayPaymentContact;
}
11 changes: 11 additions & 0 deletions src/models/applepay/ApplePayPaymentMethodType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* A string that represents the type of the payment method.
*
* The type of card the customer uses to complete the transaction.
*/
export enum ApplePayPaymentMethodType {
DEBIT = 'debit',
CREDIT = 'credit',
PREPAID = 'prepaid',
STORE = 'store',
}
30 changes: 30 additions & 0 deletions src/models/applepay/ApplePayPaymentToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { ApplePayPaymentData } from './ApplePayPaymentData.js';
import type { ApplePayPaymentMethod } from './ApplePayPaymentMethod.js';

/**
* An object that contains the user's payment credentials.
*
* You access the payment token of an authorized payment request using the token property of the ApplePayPayment object.
*/
export interface ApplePayPaymentToken {
/**
*
* This data is used by your e-commerce back-end system, which decrypts it and submits it to your payment processor.
* For a full guide on this data, see:
* https://developer.apple.com/documentation/passkit_apple_pay_and_wallet/apple_pay/payment_token_format_reference
*
*/
paymentData?: ApplePayPaymentData;

/**
* @description An object that contains the user's payment credentials.
* You access the payment token of an authorized payment request using the token property of the ApplePayPayment object.
*/
paymentMethod?: ApplePayPaymentMethod;

/**
* @description A unique identifier for this payment.
* This identifier is suitable for use in a receipt.
*/
transactionIdentifier?: string;
}
66 changes: 66 additions & 0 deletions src/tests/transformer/applepay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, expect, test } from 'vitest';
import type { ApplePayPayment } from '../../models/applepay/ApplePayPayment.js';
import type { MobilePaymentMethodSpecificInput } from '../../models/MobilePaymentMethodSpecificInput.js';
import { ApplePayPaymentMethodType } from '../../models/applepay/ApplePayPaymentMethodType.js';
import { Network } from '../../models/Network.js';
import { applePayPaymentToMobilePaymentMethodSpecificInput } from '../../transformer/applepay.js';

describe('applepay transformer', () => {
describe('applePayPaymentToMobilePaymentMethodSpecificInput', () => {
test('converts a full ApplePayPayment correctly', () => {
const payment: ApplePayPayment = {
token: {
paymentData: {
data: 'data',
header: {
applicationData: undefined,
publicKeyHash: 'hashhashhash',
transactionId: 'transaction-101',
},
signature: undefined,
version: undefined,
},
paymentMethod: {
displayName: 'The name is...',
network: 'MasterCard',
type: ApplePayPaymentMethodType.CREDIT,
paymentPass: undefined,
billingContact: undefined,
},
transactionIdentifier: 'transaction-101-cc',
},
billingContact: {
phoneNumber: '+1239452324',
emailAddress: '[email protected]',
givenName: 'John',
familyName: 'Michell',
phoneticGivenName: '',
phoneticFamilyName: '',
addressLines: ['Alarichtstraße 12'],
locality: 'Berlin',
postalCode: '12105',
subAdministrativeArea: '',
},
shippingContact: undefined,
};

const expected: MobilePaymentMethodSpecificInput = {
paymentProductId: 302,
publicKeyHash: 'hashhashhash',
ephemeralKey: undefined,
paymentProduct302SpecificInput: {
network: Network.MASTERCARD,
token: {
signature: undefined,
header: {
transactionId: 'transaction-101',
applicationData: undefined,
},
},
},
};

expect(applePayPaymentToMobilePaymentMethodSpecificInput(payment)).toEqual(expected);
});
});
});
53 changes: 53 additions & 0 deletions src/transformer/applepay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ApplePayPayment } from '../models/applepay/ApplePayPayment.js';
import { ApplePaymentTokenVersion } from '../models/ApplePaymentTokenVersion.js';
import type { MobilePaymentMethodSpecificInput } from '../models/MobilePaymentMethodSpecificInput.js';
import { Network } from '../models/Network.js';

function networkFromString(value: string): Network {
switch (value.toUpperCase()) {
case Network.MASTERCARD:
return Network.MASTERCARD;
case Network.VISA:
return Network.VISA;
case Network.AMEX:
return Network.AMEX;
case Network.GIROCARD:
return Network.GIROCARD;
case Network.DISCOVER:
return Network.DISCOVER;
case Network.JCB:
return Network.JCB;
default:
throw new TypeError(`'${value}' can't represent a Network`);
}
}

function versionFromString(value: string): ApplePaymentTokenVersion {
switch (value) {
case ApplePaymentTokenVersion.EC_V1:
return ApplePaymentTokenVersion.EC_V1;
default:
throw new TypeError(`'${value}' can't represent an ApplePaymentTokenVersion`);
}
}

export function applePayPaymentToMobilePaymentMethodSpecificInput(payment: ApplePayPayment) {
return {
paymentProductId: 302,
publicKeyHash: payment.token?.paymentData?.header?.publicKeyHash,
ephemeralKey: payment.token?.paymentData?.header?.ephemeralPublicKey,
paymentProduct302SpecificInput: {
network: payment.token?.paymentMethod?.network
? networkFromString(payment.token.paymentMethod.network)
: undefined,
token: {
version: payment.token?.paymentData?.version ? versionFromString(payment.token.paymentData.version) : undefined,
signature: payment.token?.paymentData?.signature,
header: {
transactionId: payment.token?.paymentData?.header?.transactionId,
applicationData: payment.token?.paymentData?.header?.applicationData,
},
},
},
};
}

0 comments on commit cd0e753

Please sign in to comment.